当前位置:首页 > 技术 > 正文内容

Django 中基于角色的权限管理实现

访客 技术 2026年5月30日 1

使用 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 %}

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。