Django 依用户权限与数据值动态生成queryset及防越权最佳实践咨询
适配当前场景的Django落地方案
动态生成权限匹配的QuerySet的最佳实践
你现在的场景本质是基于角色+业务关联关系的行级权限控制,完全不需要把判断逻辑散落在每个视图的if-else里,最简洁可维护的方式是把所有和权限相关的取数逻辑封装成可复用的方法,和业务视图解耦:
- 先梳理你已有的表关联关系,确保关联链路打通:自定义用户模型要存好角色标识、关联的租户外键;楼层表绑定所属租户;AV设备表绑定所属楼层;用户-设备授权表直接关联用户和对应可访问的设备。
- 在项目的公共工具文件里封装统一的取数方法,所有视图复用,举可直接套用的代码示例:
def get_user_allowed_floors(user): # 超管直接返回全部楼层 if user.is_superuser: return Floor.objects.all() # 租户管理员返回所属租户下的所有楼层 if user.role == "tenant_admin": return Floor.objects.filter(tenant=user.tenant) # 普通租户用户返回所有被分配过访问权限的楼层,去重避免重复记录 if user.role == "tenant_user": return Floor.objects.filter(av_access_links__user=user).distinct()
- 同理封装设备维度的取数方法,避免重复写过滤逻辑:
def get_user_allowed_devices(user, floor_id=None): # 先拿到用户有权限访问的楼层ID范围 allowed_floor_ids = get_user_allowed_floors(user).values_list("id", flat=True) device_qs = AVDevice.objects.filter(floor_id__in=allowed_floor_ids) # 如果请求指定了楼层ID,就过滤对应楼层的设备 if floor_id: device_qs = device_qs.filter(floor_id=floor_id) # 普通租户用户要额外过滤他被实际授权的设备,不能暴露同楼层的其他设备 if user.role == "tenant_user": device_qs = device_qs.filter(access_links__user=user).distinct() return device_qs
这种实现的好处是后续调整权限规则,只需要改这两个方法的逻辑,不用挨个改所有视图的代码,维护成本很低。
越权访问的防范方案
你担心的篡改URL参数、越权访问未授权资源的问题,只需要两层校验就能完全覆盖,不需要引入复杂的权限框架:
- 第一层:所有涉及权限判断的视图,强制加登录校验。直接用Django自带的
@login_required装饰器,或者类视图继承LoginRequiredMixin,直接拦截未登录的匿名用户访问。 - 第二层:所有单对象操作的视图(比如设备详情页、设备控制页、权限配置页),不要直接拿URL里的对象ID不校验就查询对象,要基于上面封装的授权QuerySet做查询范围限制:
- 比如写通用类视图的时候,重写视图的
get_queryset方法,直接返回当前用户有权限的设备QuerySet,这种情况下如果用户篡改URL里的设备ID,因为不在查询范围内,Django会直接返回404,不会暴露未授权的资源,示例写法:
from django.contrib.auth.mixins import LoginRequiredMixin from django.views.generic import DetailView from .models import AVDevice from .utils import get_user_allowed_devices class DeviceControlView(LoginRequiredMixin, DetailView): model = AVDevice # 直接用授权范围作为查询集,自动过滤未授权对象 def get_queryset(self): return get_user_allowed_devices(self.request.user)- 针对POST类的请求(比如修改设备状态、提交权限配置),要在请求处理逻辑里手动校验提交的对象ID是否在用户的授权范围里,不能只依赖前端传参:
def handle_device_control(request): device_id = request.POST.get("target_device_id") allowed_device_ids = get_user_allowed_devices(request.user).values_list("id", flat=True) if int(device_id) not in allowed_device_ids: return HttpResponseForbidden("无权操作该设备") # 后续正常处理业务逻辑 - 比如写通用类视图的时候,重写视图的
这里完全不需要硬套Django内置的权限体系——你现在的权限逻辑是基于租户、业务关联关系的行级控制,硬套内置权限反而需要额外建很多关联表,和你已经搭好的表结构不兼容,维护成本更高。
单模板适配三类角色的实现技巧
你最初设想的单个模板复用的方案完全可行,不需要写三套独立模板:
- 视图层在取数的时候,把当前用户有权限的楼层列表、设备列表传到模板上下文里,不要在模板里写数据库查询逻辑。
- 模板里根据当前用户的
role字段做差异化的UI展示:比如超管和租户管理员可以看到对应楼层全部设备的操作按钮,普通租户用户只展示他被授权访问的设备的操作入口就行,不需要额外判断权限。
内容的提问来源于stack exchange,提问作者jez
相关产品推荐
相关产品推荐

