动态权限控制需继承basepermission并重写has_permission,通过request、view上下文实现细粒度鉴权,返回布尔值,避免硬编码与重复查询,合理组合权限类并记录日志。

权限类需要继承 BasePermission 并重写 has_permission
动态权限控制的核心不是靠硬编码的 is_authenticated 或 is_staff,而是让权限逻辑能感知当前请求上下文(比如 URL 路径、HTTP 方法、用户角色、甚至查询参数)。Django REST Framework 的 BasePermission 类提供了 has_permission(self, request, view) 和 has_object_permission(self, request, view, obj) 两个钩子。前者决定能否进入视图,后者决定能否操作具体对象。
常见错误是直接在视图里用 if not request.user.is_admin: 做判断——这会让权限逻辑散落在各处,无法复用、难以测试、也不符合 DRF 的设计意图。
- 必须返回布尔值:
True表示放行,False表示拒绝(会触发PermissionDenied异常) -
view参数可用来获取view.action(如"list"、"retrieve")、view.kwargs、甚至自定义的view.permission_required属性 - 不要在
has_permission中做数据库查询;高频调用下易成性能瓶颈,应提前缓存或改用has_object_permission阶段处理
用 view.action + 用户角色表实现 CRUD 级细粒度控制
Django REST Framework 的 ViewSet 会自动设置 view.action(如 "create"、"update"),结合用户所属角色(存在 UserRole 或 Group 模型中),就能实现「管理员可删,编辑可改,游客仅读」这类规则。
假设你有 RolePermission 模型记录角色对某个视图动作的授权状态:
class RolePermission(models.Model):
role = models.ForeignKey(Group, on_delete=models.CASCADE)
view_name = models.CharField(max_length=100) # 如 "article-viewset"
action = models.CharField(max_length=20) # 如 "destroy"
allowed = models.BooleanField(default=True)
对应权限类可这样写:
图片提示词生成器?不止如此。 马甲系统 —— 把脑海中的画面,翻译成AI能理解的专业表达。 用得越多,它越懂你:首次需要多问几句确认方向,用久了几乎一说就懂。 用得越多,它越快:缓存机制让后续对话越来越省。 RAG进化:成功案例持续入库,越跑越聪明。 输入「新手指南」查看完整功能介绍
class DynamicRolePermission(BasePermission):
def has_permission(self, request, view):
if not request.user.is_authenticated:
return False
# 获取视图唯一标识(推荐用 view.basename 或自定义 view.permission_key)
view_key = getattr(view, 'permission_key', f"{view.__class__.__name__.lower()}")
action = view.action
return RolePermission.objects.filter(
role__user=request.user,
view_name=view_key,
action=action,
allowed=True
).exists()
-
view.basename在路由中显式声明时才可靠;更稳妥的是在 ViewSet 类上加permission_key = "article" - 避免每次查库:可用
cache.get_or_set缓存(user_id, view_key, action)三元组结果,超时设为 60 秒 - 注意
view.action在函数视图(@api_view)中为None,需退化到检查request.method
URL 路径和查询参数也能参与权限判定
有些场景权限取决于请求路径结构或参数,比如「只允许访问 /api/v1/users/me/,禁止访问 /api/v1/users/123/」,或「带 ?scope=internal 才能看敏感字段」。这时不能只依赖 view.action,得解析 request.path 或 request.query_params。
例如限制只能访问当前用户资源:
class OwnResourcePermission(BasePermission):
def has_permission(self, request, view):
if not request.user.is_authenticated:
return False
# 匹配 /api/v1/users/{id}/ 这类路径
import re
match = re.match(r'^/api/v1/users/(\d+)/$', request.path)
if match:
target_id = int(match.group(1))
return target_id == request.user.id
return True # 其他路径不限制
- 正则匹配路径比解析
view.kwargs更底层、更可控,但要注意 Django 的 URL resolver 可能已处理过 path prefix(如API_PREFIX = "/api/v1/") - 查询参数判断要谨慎:
request.query_params.get("scope") == "internal"可用于临时调试开关,但不应作为长期权限依据——它不防篡改,且绕过认证流程 - 若需参数级鉴权,建议改用签名 token 或后端透传的 header(如
X-Request-Scope),再配合中间件预校验
多个权限类组合使用时的执行顺序和短路逻辑
DRF 的 permission_classes 是列表,按顺序逐个执行 has_permission,**任一返回 False 即终止并拒绝请求**。这意味着顺序很重要:开销小的判断(如是否登录)应放在前面,开销大的(如查库、远程调用)放后面。
- 典型组合:
[IsAuthenticated, DynamicRolePermission, OwnResourcePermission]—— 先拦未登录,再查角色,最后校验资源归属 - 不要把
AllowAny放在列表中间;它永远返回True,会导致后续权限类完全失效 - 如果需要「满足任一条件即可」,得自己写一个组合类(如
AnyOf([A, B])),而不是依赖列表顺序 - 调试时可在每个权限类里加
print(f"[{self.__class__.__name__}] {request.path} → {result}"),但上线前务必删除或改用logging
最易被忽略的是:权限类里的异常不会自动记录日志,也看不到具体被哪个条件拦截。上线前至少加一行 logger.info("Permission denied for %s on %s", request.user, request.path),否则排查线上 403 会非常被动。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










