使用 Django 实现细粒度权限控制
1. 定义权限映射表与访问白名单
为实现不同用户角色对页面功能的差异化访问,首先在
settings.py 中配置无需登录即可访问的路径白名单:
WHITE_URLS = [
'/web/login/',
'/web/sms_login/',
'/web/sms_send/',
'/web/logout/'
]
接着定义角色权限结构。每个角色(如管理员、老板、普通客户)拥有可访问的 URL 别名及其元信息,包括显示文本和父级菜单项,用于构建导航路径。
ROLE_PERMISSIONS = {
"ADMIN": {
"home": {"label": "首页", "parent": None},
"order": {"label": "订单管理", "parent": None},
"level_list": {"label": "等级列表", "parent": None},
"level_add": {"label": "添加等级", "parent": "level_list"},
"customer_list": {"label": "客户列表", "parent": None}
},
"BOSS": {
"home": {"label": "首页", "parent": None},
"order": {"label": "订单管理", "parent": None},
"order_add": {"label": "创建订单", "parent": "order"},
"level_list": {"label": "等级列表", "parent": None},
"level_add": {"label": "添加等级", "parent": "level_list"},
"level_edit": {"label": "编辑等级", "parent": "level_list"},
"level_del": {"label": "删除等级", "parent": "level_list"},
"customer_list": {"label": "客户列表", "parent": None},
"customer_add": {"label": "新增客户", "parent": "customer_list"},
"customer_edit": {"label": "编辑客户", "parent": "customer_list"},
"customer_del": {"label": "删除客户", "parent": "customer_list"}
},
"CUSTOMER": {
"home": {"label": "首页", "parent": None}
}
}
2. 用户登录时存储角色信息至会话
用户成功认证后,从数据库获取其角色级别,并将用户名和角色写入 session,供后续权限校验使用。
user = Customer.objects.filter(username=username, password=password).first()
if not user:
form.add_error("password", "用户名或密码错误")
return render(request, 'login.html', {'form': form})
request.session['user_info'] = {
'username': user.username,
'role': user.get_role_level_display() # 显示 CHOICES 字段对应的中文标签
}
return redirect('/home/')
对应模型中定义了角色字段:
class Customer(ActiveBaseModel):
ROLE_CHOICES = (
(30, "BOSS"),
(20, "ADMIN"),
(1, "CUSTOMER")
)
username = models.CharField(max_length=20, verbose_name="用户名")
password = models.CharField(max_length=20, verbose_name="密码")
role_level = models.SmallIntegerField(choices=ROLE_CHOICES, default=1, verbose_name="角色")
level = models.ForeignKey("Level", on_delete=models.CASCADE, verbose_name="会员等级", default=1)
create_date = models.DateTimeField(auto_now_add=True, verbose_name="注册时间")
3. 中间件实现权限拦截与路径追踪
创建中间件,在请求处理流程中完成身份验证和权限检查。
首先定义一个封装类,用于组织用户相关信息:
class UserInfo:
def __init__(self, username, role):
self.username = username
self.role = role
self.permissions = settings.ROLE_PERMISSIONS.get(role, {})
self.breadcrumb = []
中间件逻辑如下:
- process_request:检查当前路径是否在白名单中;若不在,则验证 session 是否存在用户信息,否则跳转至登录页。
- process_view:根据当前视图的 URL 名称(
url_name)判断该角色是否具备访问权限。若无权限,返回"禁止访问"响应。同时构建面包屑导航路径。
from django.utils.deprecation import MiddlewareMixin
from django.http import HttpResponse
from django.urls import resolve
class PermissionMiddleware(MiddlewareMixin):
def process_request(self, request):
if request.path_info in settings.WHITE_URLS:
return None
user_info = request.session.get('user_info')
if not user_info:
return HttpResponse("请先登录", status=401)
request.user_info = UserInfo(**user_info)
def process_view(self, request, view_func, args, kwargs):
if not hasattr(request, 'user_info'):
return None
resolver_match = resolve(request.path_info)
url_name = resolver_match.url_name
if url_name not in request.user_info.permissions:
return HttpResponse("您没有访问此页面的权限", status=403)
# 构建面包屑路径
path_labels = []
current = url_name
while current:
node = request.user_info.permissions[current]
path_labels.append(node["label"])
current = node["parent"]
request.user_info.breadcrumb = list(reversed(path_labels))
4. 自定义模板标签增强前端控制
为了在前端动态渲染按钮和列,编写自定义模板标签提升用户体验。
注册标签文件
templatetags/access_tags.py:
from django import template
from django.urls import reverse
from django.utils.safestring import mark_safe
register = template.Library()
添加操作按钮: 根据权限决定是否生成"新增"链接。
@register.simple_tag(takes_context=True)
def create_button(context, name, *args, **kwargs):
request = context['request']
url = reverse(name, args=args, kwargs=kwargs)
if name not in request.user_info.permissions:
return ''
btn_html = f'<a href="{url}" class="btn btn-success">新增</a>'
return mark_safe(btn_html)
编辑与删除按钮: 支持传入对象主键。
@register.simple_tag(takes_context=True)
def edit_link(context, name, pk):
request = context['request']
url = reverse(name, kwargs={'pk': pk})
if name not in request.user_info.permissions:
return ''
html = f'<a href="{url}" class="btn btn-primary">编辑</a>'
return mark_safe(html)
@register.simple_tag(takes_context=True)
def delete_link(context, name, pk):
request = context['request']
url = reverse(name, kwargs={'pk': pk})
if name not in request.user_info.permissions:
return ''
html = f'<a href="{url}" class="btn btn-danger">删除</a>'
return mark_safe(html)
权限过滤器: 判断是否有任意一项权限,用于控制整列显示。
@register.filter
def can_access(request, names):
name_list = [n.strip() for n in names.split(',')]
return any(name in request.user_info.permissions for name in name_list)
5. 模板中应用权限控制
在 HTML 模板中引入标签库并使用自定义标签进行条件渲染。
{% extends "base.html" %}
{% load access_tags %}
{% block content %}
<div>
{% create_button "level_add" %}
</div>
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>标题</th>
<th>折扣</th>
{% if request|can_access:"level_edit,level_del" %}
<th>操作</th>
{% endif %}
</tr>
</thead>
<tbody>
{% for item in object_list %}
<tr>
<td>{{ item.id }}</td>
<td>{{ item.title }}</td>
<td>{{ item.discount }}%</td>
{% if request|can_access:"level_edit,level_del" %}
<td>
{% edit_link "level_edit" item.id %}
{% delete_link "level_del" item.id %}
</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}