基于角色的访问控制也就是RBAC,是很多Web项目常用的权限管理模型,核心思路是将权限关联到角色,再把角色分配给用户,用户通过所属角色获得对应权限,避免直接给用户分配权限带来的管理复杂度。在Django框架中,我们可以基于自带的用户系统扩展实现这套逻辑。

核心模型设计
首先需要扩展Django的默认用户模型,新增角色表和权限关联表,这里我们使用自定义用户模型的方式实现,首先需要定义角色模型:
from django.db import models
from django.contrib.auth.models import AbstractUser
# 角色模型,存储不同角色信息
class Role(models.Model):
name = models.CharField(max_length=50, unique=True, verbose_name="角色名称")
# 角色对应的权限列表,这里用逗号分隔的权限标识存储,也可以单独建权限表关联
permissions = models.TextField(verbose_name="权限列表", blank=True)
class Meta:
verbose_name = "角色"
verbose_name_plural = "角色"
def __str__(self):
return self.name
# 自定义用户模型,关联角色
class CustomUser(AbstractUser):
# 一个用户可以拥有多个角色,多对多关联
roles = models.ManyToManyField(Role, verbose_name="用户角色", blank=True)
class Meta:
verbose_name = "自定义用户"
verbose_name_plural = "自定义用户"
def get_permissions(self):
# 获取用户所有角色的权限,去重后返回
permissions = set()
for role in self.roles.all():
if role.permissions:
# 假设权限是按逗号分隔的字符串存储,比如"view_article,edit_article"
perms = role.permissions.split(",")
permissions.update(perms)
return permissions
权限校验实现方式
方式一:使用装饰器校验权限
我们可以自定义一个装饰器,在视图函数执行前校验当前用户是否拥有对应权限,适用于单个视图的权限控制:
from functools import wraps
from django.http import HttpResponseForbidden
from django.contrib.auth.decorators import login_required
def permission_required(perm):
"""
权限校验装饰器,参数perm为需要的权限标识
"""
def decorator(view_func):
@wraps(view_func)
@login_required # 先校验用户是否登录
def wrapper(request, *args, **kwargs):
user = request.user
# 获取用户所有权限
user_perms = user.get_permissions()
if perm in user_perms:
return view_func(request, *args, **kwargs)
else:
return HttpResponseForbidden("你没有访问该资源的权限")
return wrapper
return decorator
使用这个装饰器的示例如下,比如编辑文章的视图需要edit_article权限:
from django.shortcuts import render
from .decorators import permission_required
@permission_required("edit_article")
def edit_article_view(request):
return render(request, "edit_article.html")
方式二:使用中间件全局校验
如果需要全局校验某些路径的访问权限,可以自定义中间件,在请求到达视图前统一处理:
from django.http import HttpResponseForbidden
from django.urls import resolve
class RbacMiddleware:
def __init__(self, get_response):
self.get_response = get_response
# 定义不需要校验的路径,比如登录页、静态资源路径
self.exempt_urls = ["/login/", "/admin/"]
def __call__(self, request):
# 如果用户未登录,直接跳过校验,由login_required处理
if not request.user.is_authenticated:
return self.get_response(request)
# 获取当前请求的路径
current_path = request.path
# 如果路径在豁免列表中,直接放行
if any(current_path.startswith(url) for url in self.exempt_urls):
return self.get_response(request)
# 解析当前路径对应的视图函数,获取需要的权限(这里可以提前定义路由和权限的映射)
# 简单示例:假设路径包含edit的都需要edit_article权限
if "edit" in current_path and "edit_article" not in request.user.get_permissions():
return HttpResponseForbidden("你没有访问该资源的权限")
return self.get_response(request)
定义好中间件后,需要在Django的settings.py中注册:
MIDDLEWARE = [
# 其他中间件
"your_app.middleware.RbacMiddleware",
]
# 替换默认用户模型
AUTH_USER_MODEL = "your_app.CustomUser"
角色分配与管理
我们可以写一个管理视图,给管理员提供分配角色的功能,比如给指定用户添加角色:
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import permission_required
from .models import CustomUser, Role
@permission_required("manage_user")
def assign_role_view(request, user_id):
user = CustomUser.objects.get(id=user_id)
if request.method == "POST":
# 获取前端提交的角色ID列表
role_ids = request.POST.getlist("roles")
# 清空用户原有角色
user.roles.clear()
# 添加新选中的角色
for role_id in role_ids:
role = Role.objects.get(id=role_id)
user.roles.add(role)
return redirect("user_list")
# 获取所有角色
roles = Role.objects.all()
return render(request, "assign_role.html", {"user": user, "roles": roles})
注意事项
- 权限标识建议统一规范,比如使用资源名加操作的形式,如view_user、edit_user、delete_user,便于管理
- 如果权限较多,建议单独建立权限表,和角色做多对多关联,避免角色表的permissions字段过长
- 中间件校验适合全局通用的权限规则,单个视图的特殊权限建议使用装饰器实现,更灵活
- 自定义的权限校验逻辑需要做好异常处理,避免因为权限查询异常导致服务不可用