权限
权限¶
SRF 的权限类用于控制 ViewSet 访问。视图级检查在 handler 前由check_permissions() 执行;
对象级检查由 get_object() 调用 check_object_permissions()(覆盖 retrieve / update / destroy,以及调用 get_object() 的自定义 action)。
内置权限类¶
from srf.permission.permission import (
AllowAny,
BasePermission,
IsAuthenticated,
IsRoleAdminUser,
IsSafeMethodOnly,
)
AllowAny:始终允许(settings.DEFAULT_PERMISSION_CLASSES默认值)。IsAuthenticated:request.ctx.user存在,且用户的is_active为真。IsRoleAdminUser:当前用户关联角色的name等于"admin"。IsSafeMethodOnly:只允许 GET、HEAD、OPTIONS。
在 ViewSet 上声明权限类:
from srf.permission.permission import IsAuthenticated
from srf.views import BaseViewSet
class OrderViewSet(BaseViewSet):
permission_classes = (IsAuthenticated,)
BaseViewSet.check_permissions() 会按顺序在类上调用
has_permission(request, view) 同步和异步返回值都受支持。任一结果为假时抛出
403 Forbidden。认证中间件因 Token 缺失或无效而拒绝请求时则是 401。
权限方法是 @staticmethod:推荐继承 BasePermission,但不强制;只要提供同名静态方法即可(鸭式)。
自定义视图级权限¶
from srf.permission.permission import BasePermission
class IsEditor(BasePermission):
@staticmethod
def has_permission(request, view=None):
user = getattr(request.ctx, "user", None)
role = getattr(user, "role", None)
return role is not None and role.name == "editor"
view 为可选参数;BaseViewSet 会传入当前 View 实例。
异步检查同样可用:
class HasActiveSubscription(BasePermission):
@staticmethod
async def has_permission(request, view=None):
user = getattr(request.ctx, "user", None)
return user is not None and await Subscription.filter(
user_id=user.id, active=True
).exists()
按请求方法设置权限¶
当前框架没有 self.action、get_permissions() 或 action 级权限参数。需要
按操作区分时,可重写 check_permissions():
import asyncio
from sanic.exceptions import Forbidden
from srf.permission.permission import IsAuthenticated, IsRoleAdminUser
from srf.views import BaseViewSet
class ProductViewSet(BaseViewSet):
async def check_permissions(self, request):
if request.method.upper() in ("GET", "HEAD", "OPTIONS"):
return
classes = (
(IsAuthenticated,)
if request.method.upper() == "POST"
else (IsAuthenticated, IsRoleAdminUser)
)
for permission_class in classes:
result = permission_class.has_permission(request, self)
if asyncio.iscoroutine(result):
result = await result
if not result:
raise Forbidden(message="Forbidden")
对象级权限¶
BasePermission.has_object_permission(request, view, obj) 定义对象级接口。
get_object() 会调用 check_object_permissions(),后者在类上调用
对象级检查不会逐条应用于列表结果。列表接口应在 queryset 中限制数据。
基本用法¶
class IsOwner(BasePermission):
"""对象级权限:检查是否是所有者"""
@staticmethod
def has_object_permission(request, view=None, obj=None):
# 用户只能查看、修改、删除自己的对象
return obj.owner_id == request.ctx.user.id
class OrderViewSet(BaseViewSet):
permission_classes = (IsAuthenticated, IsOwner)
列表侧通过 queryset 限制可见数据:
class OrderViewSet(BaseViewSet):
@property
def queryset(self):
user = self.get_current_user(self.request)
role = getattr(user, "role", None)
if role is not None and role.name == "admin":
return Order.all()
return Order.filter(user_id=user.id)
self.request 由 BaseViewSet.as_view() 在处理请求前设置。
自定义 action 的权限¶
所有由 SanicRouter 注册的 action 都会先执行 ViewSet 的
check_permissions()。如需额外限制,在 action 内执行附加检查:
from sanic.exceptions import Forbidden
from srf.permission.permission import IsRoleAdminUser
from srf.views.decorators import action
@action(methods=["post"], detail=True, url_path="approve")
async def approve(self, request, pk):
if not IsRoleAdminUser.has_permission(request, self):
raise Forbidden(message="需要管理员权限")
product = await self.get_object(request, pk)
product.is_approved = True
await product.save()
return json({"message": "审核通过"})
最佳实践¶
- 最小权限原则:默认拒绝访问,明确授予必要权限
- 分离关注点:将权限逻辑独立到权限类中
- 组合权限:使用多个简单权限类组合实现复杂权限
- 对象级权限:对敏感资源使用对象级权限检查
- 异步支持:在需要数据库查询时使用异步方法
- 清晰的错误消息:提供友好的权限错误提示
- 测试权限:为权限类编写单元测试