Python权限安全案例如何保障权限安全

wen python案例 28

本文目录导读:

Python权限安全案例如何保障权限安全

  1. 基于角色的访问控制(RBAC)案例
  2. 文件系统权限安全案例
  3. 数据库查询权限安全案例
  4. 系统命令执行权限(最危险的场景)
  5. 权限安全黄金法则

在Python中保障权限安全,通常不是指Python语言本身的权限控制(Python没有像Java那样的SecurityManager),而是指应用程序层面的权限管理(用户角色控制、文件系统权限、API端点保护等)。

下面的案例从多维度出发,展示如何在实际项目中保障权限安全,核心思路是:认证(你是谁) + 授权(你能做什么) + 数据隔离(你能看什么)


基于角色的访问控制(RBAC)案例

这是最常见的权限模型,核心是:用户 -> 角色 -> 权限。

# 核心代码:权限检查装饰器
import functools
from flask import Flask, request, jsonify, session, abort
from typing import List, Set
app = Flask(__name__)
app.secret_key = 'your-secret-key-change-this'
# ---------- 模拟数据结构 ----------
# 权限定义(扁平化、易于检查)
PERMISSIONS = {
    "admin": {"create_user", "delete_user", "view_reports", "manage_settings"},
    "editor": {"create_user", "view_reports"},
    "viewer": {"view_reports"}
}
# 模拟用户数据库
USERS_DB = {
    "alice": {"password": "pass1", "role": "admin"},
    "bob": {"password": "pass2", "role": "editor"},
    "charlie": {"password": "pass3", "role": "viewer"}
}
# ---------- 权限检查装饰器 ----------
def require_permission(required_perm: str):
    """装饰器:检查当前用户是否拥有指定权限"""
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            # 1. 获取当前用户角色
            user_role = session.get('role')
            if not user_role:
                abort(401, description="未登录或会话已过期")
            # 2. 获取该角色拥有的所有权限
            user_perms: Set[str] = PERMISSIONS.get(user_role, set())
            # 3. 检查是否拥有所需权限
            if required_perm not in user_perms:
                abort(403, description="权限不足")
            # 4. 放行
            return func(*args, **kwargs)
        return wrapper
    return decorator
# ---------- API 端点 ----------
@app.route('/reports')
@require_permission('view_reports')
def view_reports():
    return jsonify({"message": "查看报告", "data": [1,2,3]})
@app.route('/admin/settings')
@require_permission('manage_settings')
def manage_settings():
    return jsonify({"message": "管理设置成功"})
@app.route('/users', methods=['POST'])
@require_permission('create_user')
def create_user():
    return jsonify({"message": "创建用户成功"})
# ---------- 登录(认证) ----------
@app.route('/login', methods=['POST'])
def login():
    data = request.get_json()
    username = data.get('username')
    password = data.get('password')
    user = USERS_DB.get(username)
    if not user or user['password'] != password:
        return jsonify({"error": "账号或密码错误"}), 401
    # 存储用户的角色到会话中
    session['username'] = username
    session['role'] = user['role']
    return jsonify({"message": "登录成功", "role": user['role']})
if __name__ == '__main__':
    app.run(debug=True)

保障要点:

  • 最小权限原则:每个角色只赋予完成工作所需的最小权限集。
  • 显式检查:使用装饰器强制每个受保护端点都必须检查权限。
  • 凭据不硬编码:密码不应明文存储(此处为演示简化),应使用哈希。

文件系统权限安全案例

当Python脚本需要读写文件时,权限控制不足可能导致敏感数据泄露或被恶意篡改。

import os
import stat
class SecureFileHandler:
    """安全文件处理器:确保操作在允许范围内,并检查文件权限"""
    ALLOWED_BASE_DIR = "/secure_data"
    def __init__(self, user_role: str):
        self.user_role = user_role
    def _validate_path(self, filepath: str) -> str:
        """路径验证:防止路径遍历攻击 (Path Traversal)"""
        # 1. 规范化路径(消除 '..' 和 '.')
        normalized_path = os.path.normpath(filepath)
        # 2. 获取绝对路径
        abs_path = os.path.abspath(normalized_path)
        # 3. 检查是否在允许的基目录下
        allowed_abs = os.path.abspath(self.ALLOWED_BASE_DIR)
        if not abs_path.startswith(allowed_abs):
            raise PermissionError(f"不允许访问路径: {filepath}")
        return abs_path
    def read_file(self, filepath: str) -> str:
        """安全读取文件"""
        full_path = self._validate_path(filepath)
        # 检查文件权限(Unix/Linux)
        if os.name != 'nt':  # Windows 文件权限模型不同
            st = os.stat(full_path)
            file_mode = stat.S_IMODE(st.st_mode)
            # 示例:如果用户不是文件所有者,不允许读
            # 更复杂的逻辑可以根据实际需求(如组权限)扩展
            owner_uid = st.st_uid
            current_uid = os.getuid()
            if owner_uid != current_uid:
                raise PermissionError("文件不属于当前用户,拒绝访问")
        with open(full_path, 'r', encoding='utf-8') as f:
            return f.read()
    def write_file(self, filepath: str, content: str) -> bool:
        """安全写入文件"""
        full_path = self._validate_path(filepath)
        # 检查文件权限(Unix/Linux)
        if os.name != 'nt':
            st = os.stat(full_path)
            file_mode = stat.S_IMODE(st.st_mode)
            owner_uid = st.st_uid
            current_uid = os.getuid()
            if owner_uid != current_uid:
                raise PermissionError("文件不属于当前用户,拒绝写入")
        with open(full_path, 'w', encoding='utf-8') as f:
            f.write(content)
        return True
# 使用示例
try:
    handler = SecureFileHandler('user1')
    content = handler.read_file("/secure_data/report.txt")
    print("文件内容:", content)
except PermissionError as e:
    print(f"权限错误: {e}")
except FileNotFoundError:
    print("文件不存在")

保障要点:

  • 路径规范化:防止 ../../etc/passwd 这种路径穿越攻击。
  • 基目录限制:所有文件操作只能发生在 ALLOWED_BASE_DIR 下。
  • 操作系统权限检查:在Unix/Linux下,额外检查文件UID与当前进程UID是否匹配(可以根据需求扩展为组权限检查)。

数据库查询权限安全案例

防止SQL注入是权限安全的基础,同时也要限制不同数据库用户能访问的数据范围。

import sqlite3
from contextlib import contextmanager
class SecureDatabaseAccess:
    """安全数据库访问:使用参数化查询 + 行级权限过滤"""
    def __init__(self, db_path: str, user_id: str, user_role: str):
        self.db_path = db_path
        self.user_id = user_id
        self.user_role = user_role
    @contextmanager
    def get_connection(self):
        """获取数据库连接(上下文管理器自动关闭)"""
        conn = sqlite3.connect(self.db_path)
        try:
            yield conn
        finally:
            conn.close()
    def get_user_data(self) -> list:
        """获取用户自己的数据(行级权限过滤)"""
        query = """
            SELECT id, name, email, owner_id
            FROM sensitive_data
            WHERE owner_id = ?  -- 关键:只能查自己所属的数据
        """
        with self.get_connection() as conn:
            cursor = conn.cursor()
            cursor.execute(query, (self.user_id,))  # 参数化查询
            return cursor.fetchall()
    def admin_get_all_data(self) -> list:
        """管理员可以查看所有数据"""
        if self.user_role != 'admin':
            raise PermissionError("只有管理员才能查看全部数据")
        query = "SELECT id, name, email, owner_id FROM sensitive_data"
        with self.get_connection() as conn:
            cursor = conn.cursor()
            cursor.execute(query)
            return cursor.fetchall()
    def update_own_email(self, new_email: str) -> bool:
        """更新自己的邮箱(再次验证owner_id)"""
        # 先查询该记录的owner_id是否等于自己
        query_check = "SELECT owner_id FROM sensitive_data WHERE id = ?"
        query_update = "UPDATE sensitive_data SET email = ? WHERE id = ? AND owner_id = ?"
        record_id = 123  # 假设这是要更新的记录ID
        with self.get_connection() as conn:
            cursor = conn.cursor()
            cursor.execute(query_check, (record_id,))
            row = cursor.fetchone()
            if row is None:
                raise ValueError("记录不存在")
            if row[0] != self.user_id:
                raise PermissionError("不能修改其他用户的数据")
            cursor.execute(query_update, (new_email, record_id, self.user_id))
            conn.commit()
            return cursor.rowcount > 0
# 使用示例
db = SecureDatabaseAccess("mydb.db", user_id="user123", user_role="user")
try:
    data = db.get_user_data()
    print("我的数据:", data)
except PermissionError as e:
    print(f"权限错误: {e}")

保障要点:

  • 参数化查询:永远不要拼接SQL字符串,防止SQL注入。
  • 行级安全WHERE owner_id = ? 确保用户只能操作属于自己的数据。
  • 二次验证:在修改操作中,先查询再修改,确保条件匹配。

系统命令执行权限(最危险的场景)

在Python中执行系统命令(如 os.systemsubprocess)是最高风险操作,必须严格限制。

import subprocess
import shlex
import re
class SecureCommandExecutor:
    """安全命令执行器:白名单模式"""
    # 允许执行的命令白名单(命令名 + 参数模式)
    ALLOWED_COMMANDS = {
        "ls": {
            "args_pattern": r"^-la$|^-l$|^-a$",  # 只允许特定的参数组合
            "allow_dir": "/tmp"
        },
        "df": {
            "args_pattern": r"^-h$|^-k$",
            "allow_dir": None  # 无目录限制
        }
    }
    def execute(self, command_str: str) -> str:
        """安全执行命令"""
        # 1. 解析命令字符串为列表(防止shell注入)
        try:
            parts = shlex.split(command_str)
        except ValueError as e:
            raise ValueError(f"命令格式错误: {e}")
        if not parts:
            raise ValueError("空命令")
        cmd = parts[0]  # 命令名
        args = parts[1:]  # 参数
        # 2. 检查命令是否在白名单中
        if cmd not in self.ALLOWED_COMMANDS:
            raise PermissionError(f"命令 '{cmd}' 不允许执行")
        cmd_config = self.ALLOWED_COMMANDS[cmd]
        # 3. 校验参数(使用正则匹配)
        args_str = " ".join(args)
        if cmd_config["args_pattern"]:
            if not re.fullmatch(cmd_config["args_pattern"], args_str):
                raise PermissionError(f"参数 '{args_str}' 不被允许")
        # 4. 如果有目录限制,确保路径在允许范围内
        if cmd_config.get("allow_dir"):
            # 假设最后一个参数是目录(视具体命令而定)
            for arg in args:
                if not arg.startswith(cmd_config["allow_dir"]):
                    raise PermissionError(f"不允许访问路径: {arg}")
        # 5. 执行命令(使用列表形式避免shell)
        result = subprocess.run(parts, capture_output=True, text=True, timeout=10)
        if result.returncode != 0:
            raise RuntimeError(f"命令执行失败: {result.stderr}")
        return result.stdout
# 使用示例
executor = SecureCommandExecutor()
try:
    output = executor.execute("ls -la /tmp")
    print(output)
except PermissionError as e:
    print(f"权限错误: {e}")

保障要点:

  • 白名单机制:只允许预定义的命令和参数模式。
  • 禁用shell=True:始终使用列表形式调用 subprocess.run,避免shell注入。
  • 参数过滤:使用正则表达式严格匹配允许的参数格式。
  • 超时控制:防止恶意命令无限运行。

权限安全黄金法则

风险场景 防护措施 代码示例
API/Web端点 RBAC装饰器、JWT令牌、会话验证 Flask装饰器 + session
文件系统 路径规范化、基目录限制、OS权限检查 os.path.abspath + startswith
数据库 参数化查询、行级安全、最小数据库用户权限 参数化查询 + owner_id过滤
系统命令 白名单、禁用shell、参数正则校验 shlex.split + subprocess.run(list)
配置/密钥 环境变量、加密存储、避免硬编码 os.getenv、使用密钥管理服务

最后提醒: 权限安全是一个系统级问题,仅靠Python代码是不够的,还需要:

  • 操作系统层:文件权限、用户隔离、容器化
  • 网络层:防火墙、TLS/SSL
  • DevOps层:最小化运行权限、密钥管理、审计日志

抱歉,评论功能暂时关闭!