Django基于RBAC的权限组件实战:从模型设计到中间件拦截
本文详细解析Django中基于角色的访问控制(RBAC)组件实现。涵盖五类七张数据表的模型设计,登录时Session权限初始化逻辑,中间件对URL白名单及权限匹配的处理,以及利用Inclusion Tag动态生成左侧菜单的完整流程。
RBAC权限管理模型概述
1.基于rbac的权限管理
RBAC(Role-Based Access Control,基于角色的访问控制)是一种将用户与权限通过角色进行关联的机制。简而言之,一个用户拥有多个角色,而一个角色又包含多项权限,从而构建出“用户-角色-权限”的授权模型。在此模型中,用户与角色之间、角色与权限之间均呈现多对多的关系。
简单的模型图示如下:

2.Rbac组件的基本目录结构:

3.按照写的流程,来讲解rbac组件中的各个部分,以及功能,
数据库模型与表结构设计
3.1 models数据库表设计(models.py)。
为了实现前端页面的双重控制,需额外引入菜单(menu)和分组(group)两个表。控制目标包括:1. 页面功能按钮的显示与隐藏,如添加、编辑、删除等按钮;2. 左侧菜单栏的动态生成。因此,整体模型共包含5个类,涉及7张数据表,具体Model定义见下方代码。
models.py
# models.py
from django.db import models
class Menu(models.Model):
'''页面中的菜单名'''
title = models.CharField(max_length=32)
class Group(models.Model):
'''权限url所属的组'''
caption = models.CharField(verbose_name='组名称',max_length=32)
menu =models.ForeignKey(verbose_name='组所属菜单',to='Menu',default=1) # 组所在的菜单
class Meta:
verbose_name_plural = 'Group组表'
def __str__(self):
return self.caption
class User(models.Model):
"""
用户表
"""
username = models.CharField(verbose_name='用户名',max_length=32)
password = models.CharField(verbose_name='密码',max_length=64)
email = models.CharField(verbose_name='邮箱',max_length=32)
roles = models.ManyToManyField(verbose_name='具有的所有角色',to="Role",blank=True)
class Meta:
verbose_name_plural = "用户表"
def __str__(self):
return self.username
class Role(models.Model):
"""
角色表
"""
title = models.CharField(max_length=32)
permissions = models.ManyToManyField(verbose_name='具有的所有权限',to='Permission',blank=True)
class Meta:
verbose_name_plural = "角色表"
def __str__(self):
return self.title
class Permission(models.Model):
"""
权限表
"""
title = models.CharField(verbose_name='标题',max_length=32)
url = models.CharField(verbose_name="含正则URL",max_length=64)
is_menu = models.BooleanField(verbose_name="是否是菜单")
code = models.CharField(verbose_name='url代码',max_length=32,default=0) # 路径对应的描述名称
group = models.ForeignKey(verbose_name='所属组',to='Group',null=True,blank=True) # 所属组
class Meta:
verbose_name_plural = "权限表"
def __str__(self):
return self.titlemodel
权限初始化逻辑实现
3.2 service中的init_permission.py
功能:在用户登录成功时,向Session中写入两项关键信息:1. 获取当前用户的权限URL(即code信息);2. 获取当前用户可访问的菜单URL信息。
详细代码如下:
初始化权限
def init_permission(user, request):
'''
前端页面调用,把当前登录用户的权限放到session中,request参数指前端传入的当前当前login请求时的request
:param user: 当前登录用户
:param request: 当前请求
:return: None
'''
# 拿到当前用户的权限信息
permission_url_list = user.roles.values('permissions__group_id',
'permissions__code',
'permissions__url',
'permissions__group__menu__id', # 菜单需要
'permissions__group__menu__title', # 菜单需要
'permissions__title', # 菜单需要
'permissions__url', # 菜单需要
'permissions__is_menu', # 菜单需要
).distinct()
# 页面显示权限相关,用到了权限的分组,
dest_dic = {}
for each in permission_url_list:
if each['permissions__group_id'] in dest_dic:
dest_dic[each['permissions__group_id']]['code'].append(each['permissions__code'])
dest_dic[each['permissions__group_id']]['per_url'].append(each['permissions__url'])
else:
# 刚循环,先创建需要的结构,并把第一次的值放进去。
dest_dic[each['permissions__group_id']] = {'code': [each['permissions__code'], ],
'per_url': [each['permissions__url'], ]}
request.session['permission_url_list'] = dest_dic
# 页面菜单相关
# 1.去掉不做菜单的url,拿到的结果是menu_list,列表中的元素是字典
menu_list = []
for item_dic in permission_url_list:
if item_dic['permissions__is_menu']:
temp = {'menu_id':item_dic['permissions__group__menu__id'],
'menu_title':item_dic['permissions__group__menu__title'],
'permission__title': item_dic['permissions__title'],
'permission_url':item_dic['permissions__url'],
'permissions__is_menu':item_dic['permissions__is_menu'],
'active':False, # 用于页面是否被选中,
}
# temp 其实只是给key重新起名字,之前的名字太长了。。。。
menu_list.append(temp)
# 执行完成之后是如下的数据,用来做菜单。
request.session['permission_menu_list'] = menu_list
中间件权限拦截机制
3.3 中间件md
功能:1. 白名单验证;
2. 验证Session中是否已写入权限信息,即判断用户是否已登录;
3. 将当前访问的URL与用户权限URL进行匹配验证,并在request对象中写入code信息;
详细代码如下:
中间件
import re
from django.shortcuts import render,redirect,HttpResponse
from django.conf import settings
class MiddlewareMixin(object):
def __init__(self, get_response=None):
self.get_response = get_response
super(MiddlewareMixin, self).__init__()
def __call__(self, request):
response = None
if hasattr(self, 'process_request'):
response = self.process_request(request)
if not response:
response = self.get_response(request)
if hasattr(self, 'process_response'):
response = self.process_response(request, response)
return response
class M1(MiddlewareMixin):
'''
判断用户有无此url的权限的中间件
'''
def process_request(self,request):
current_url = request.path_info
# 1.白名单验证
valid_url = settings.VALID_URL
for each in valid_url:
if re.match(each, current_url):
return None
# 2.验证是否已经写入session,即:是否已经登录
permission_dic = request.session.get('permission_url_list')
if not permission_dic:
return redirect('/login/')
# 3.与当前访问的url与权限url进行匹配验证,并在request中写入code信息,
flag = False
for group_id,code_urls in permission_dic.items():
for url in code_urls['per_url']:
regax = '^{0}$'.format(url)
if re.match(regax,current_url):
flag = True
request.permission_code_list = code_urls['code'] # 在session中增加code的信息,用于在页面判断在当前页面的权限,
break
if flag:
break
if not flag:
return HttpResponse('无权访问')
def process_response(self,request,response):
return response
动态左侧菜单生成方案
3.4 左侧菜单的生成templatetags目录下的rbac.py
功能:利用inclusion_tag标签生成页面中的左侧菜单。
运用:只需在需要展示菜单的模板文件中引用,即可生成对应的菜单内容。
需要用到的模板文件中:
{% load rbac %}
{% menu_html request %} 这部分就会变成用inclusion_tag生成的menu_html
详细代码如下:
inclusion_tag生成左侧菜单
import re
from django.template import Library
register = Library()
# inclusion_tag的结果是:把menu_html函数的返回值,放到menu_html中做渲染,生成一个渲染之后的大字符串,
# 在前端需要显示这个字符串的地方,只要调用menu_html就可以,如果有菜单需要传参数,这里是request,前端模板本来就有request,
@register.inclusion_tag('menu.html')
def menu_html(request):
current_url = request.path_info
# 结构化在页面显示的menu数据
menu_list = request.session.get('permission_menu_list')
menu_show_dic = {}
for item in menu_list:
# 先跟当前url进行匹配,如果当前的url在权限URl中,则需要修改当前的active,用于在前端页面的显示。
url = item['permission_url']
reg = '^{0}$'.format(url)
if re.match(reg, current_url):
print('匹配到了')
item['active'] = True
if item['menu_id'] in menu_show_dic:
menu_show_dic[item['menu_id']]['children'].append(
{'permission__title': item['permission__title'], 'permission_url': item['permission_url'],
'active': item['active']})
if item['active']:
menu_show_dic[item['menu_id']]['active'] = True
else:
menu_show_dic[item['menu_id']] = {'menu_id': item['menu_id'],
'menu_title': item['menu_title'],
'active': False,
'children': [{'permission__title': item['permission__title'],
'permission_url': item['permission_url'],
'active': item['active']}, ]
}
if item['active']:
menu_show_dic[item['menu_id']]['active'] = True
return {'menu_dic':menu_show_dic}
需要的模板文件templates下的menu.html
menu.html
# menu.html
<div class="menu">
{% for k,menu in menu_dic.items %}
{# 一级菜单 #}
<div class="menu_first">{{ menu.menu_title }}</div>
{# 二级菜单(就是一级菜单下边的内容) #}
{% if menu.active %}
<ul class="">
{% else %}
<ul class="hide">
{% endif %}
{% for child in menu.children %}
{% if child.active %}
<li class="menu_second active"><a href="{{ child.permission_url }}" rel="external nofollow" rel="external nofollow" >{{ child.permission__title }}</a></li>
{% else %}
<li class="menu_second"><a href="{{ child.permission_url }}" rel="external nofollow" rel="external nofollow" >{{ child.permission__title }}</a></li>
{% endif %}
{% endfor %}
</ul>
{% endfor %}
</div>
使用inclusion_tag的文件示例:
inclusion_tag的使用模板文件
# 这个是django的模板文件
{% load rbac %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}模板{% endblock %}</title>
<link rel="stylesheet" href="{% static 'rbac/bootstrap-3.3.7/css/bootstrap.min.css' %}" rel="external nofollow" >
<link rel="stylesheet" href="{% static 'rbac/menu.css' %}" rel="external nofollow" >
{% block css %} {% endblock css %}
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="col-md-2 menu">
{% block menu %}
{% menu_html request %} {# 用inclusion_tag生成的menu_html #}
{% endblock menu %}
</div>
<div class="col-md-9">
{% block content %}
content
{% endblock %}
</div>
</div>
</div>
以上就是django中基于rbac实现的权限组件
这篇基于Django框架的权限组件rbac实例讲解就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持本站。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









