Django框架的权限组件rbac
1.基于rbac的权限管理
RBAC(Role-Based Access Control,基于角色的访问控制),就是用户通过角色与权限进行关联。简单地说,一个用户拥有若干角色,一个角色拥有若干权限。这样,就构造成“用户-角色-权限”的授权模型。在这种模型中,用户与角色之间,角色与权限之间都是多对多的关系。
简单的模型图示如下:
2.Rbac组件的基本目录结构:
3.按照写的流程,来讲解rbac组件中的各个部分,以及功能,
3.1 models数据库表设计(models.py)。
为了在前端页面实现2方面的控制,还需要引入两个表菜单menu和分组group:1.在一个页面,当前用户的权限,例如是否显示添加按钮、编辑、删除等按钮;2.左侧菜单栏的创建。所以一共是5个类,7张表,详细model请看下边代码。
1 # models.py 2 3 from django.db import models 4 5 6 class Menu(models.Model): 7 \'\'\'页面中的菜单名\'\'\' 8 title = models.CharField(max_length=32) 9 10 class Group(models.Model): 11 \'\'\'权限url所属的组\'\'\' 12 caption = models.CharField(verbose_name=\'组名称\',max_length=32) 13 menu =models.ForeignKey(verbose_name=\'组所属菜单\',to=\'Menu\',default=1) # 组所在的菜单 14 15 class Meta: 16 verbose_name_plural = \'Group组表\' 17 18 def __str__(self): 19 return self.caption 20 21 class User(models.Model): 22 """ 23 用户表 24 """ 25 username = models.CharField(verbose_name=\'用户名\',max_length=32) 26 password = models.CharField(verbose_name=\'密码\',max_length=64) 27 email = models.CharField(verbose_name=\'邮箱\',max_length=32) 28 29 roles = models.ManyToManyField(verbose_name=\'具有的所有角色\',to="Role",blank=True) 30 class Meta: 31 verbose_name_plural = "用户表" 32 33 def __str__(self): 34 return self.username 35 36 class Role(models.Model): 37 """ 38 角色表 39 """ 40 title = models.CharField(max_length=32) 41 permissions = models.ManyToManyField(verbose_name=\'具有的所有权限\',to=\'Permission\',blank=True) 42 class Meta: 43 verbose_name_plural = "角色表" 44 45 def __str__(self): 46 return self.title 47 48 49 class Permission(models.Model): 50 """ 51 权限表 52 """ 53 title = models.CharField(verbose_name=\'标题\',max_length=32) 54 url = models.CharField(verbose_name="含正则URL",max_length=64) 55 is_menu = models.BooleanField(verbose_name="是否是菜单") 56 57 code = models.CharField(verbose_name=\'url代码\',max_length=32,default=0) # 路径对应的描述名称 58 group = models.ForeignKey(verbose_name=\'所属组\',to=\'Group\',null=True,blank=True) # 所属组 59 60 class Meta: 61 verbose_name_plural = "权限表" 62 63 def __str__(self): 64 return self.titlemodel
3.2 service中的init_permission.py
功能:在用户登录成功的时候,在session中写入两个内容:1.拿到当前用户的权限url(code信息);2.拿到当前用户的可以做菜单的url信息。
详细代码如下:
1 def init_permission(user, request): 2 \'\'\' 3 前端页面调用,把当前登录用户的权限放到session中,request参数指前端传入的当前当前login请求时的request 4 :param user: 当前登录用户 5 :param request: 当前请求 6 :return: None 7 \'\'\' 8 # 拿到当前用户的权限信息 9 permission_url_list = user.roles.values(\'permissions__group_id\', 10 \'permissions__code\', 11 \'permissions__url\', 12 \'permissions__group__menu__id\', # 菜单需要 13 \'permissions__group__menu__title\', # 菜单需要 14 \'permissions__title\', # 菜单需要 15 \'permissions__url\', # 菜单需要 16 \'permissions__is_menu\', # 菜单需要 17 ).distinct() 18 19 20 # 页面显示权限相关,用到了权限的分组, 21 dest_dic = {} 22 for each in permission_url_list: 23 if each[\'permissions__group_id\'] in dest_dic: 24 dest_dic[each[\'permissions__group_id\']][\'code\'].append(each[\'permissions__code\']) 25 dest_dic[each[\'permissions__group_id\']][\'per_url\'].append(each[\'permissions__url\']) 26 else: 27 # 刚循环,先创建需要的结构,并把第一次的值放进去。 28 dest_dic[each[\'permissions__group_id\']] = {\'code\': [each[\'permissions__code\'], ], 29 \'per_url\': [each[\'permissions__url\'], ]} 30 31 request.session[\'permission_url_list\'] = dest_dic 32 33 # 页面菜单相关 34 # 1.去掉不做菜单的url,拿到的结果是menu_list,列表中的元素是字典 35 menu_list = [] 36 for item_dic in permission_url_list: 37 if item_dic[\'permissions__is_menu\']: 38 temp = {\'menu_id\':item_dic[\'permissions__group__menu__id\'], 39 \'menu_title\':item_dic[\'permissions__group__menu__title\'], 40 \'permission__title\': item_dic[\'permissions__title\'], 41 \'permission_url\':item_dic[\'permissions__url\'], 42 \'permissions__is_menu\':item_dic[\'permissions__is_menu\'], 43 \'active\':False, # 用于页面是否被选中, 44 } 45 # temp 其实只是给key重新起名字,之前的名字太长了。。。。 46 menu_list.append(temp) 47 # 执行完成之后是如下的数据,用来做菜单。 48 49 request.session[\'permission_menu_list\'] = menu_list
3.3 中间件md
功能:1.白名单验证;
2.验证是否已经写入session,即:是否已经登录;
3.当前访问的url与当前用户的权限url进行匹配验证,并在request中写入code信息,
详细代码如下:
1 import re 2 from django.shortcuts import render,redirect,HttpResponse 3 from django.conf import settings 4 5 class MiddlewareMixin(object): 6 def __init__(self, get_response=None): 7 self.get_response = get_response 8 super(MiddlewareMixin, self).__init__() 9 10 def __call__(self, request): 11 response = None 12 if hasattr(self, \'process_request\'): 13 response = self.process_request(request) 14 if not response: 15 response = self.get_response(request) 16 if hasattr(self, \'process_response\'): 17 response = self.process_response(request, response) 18 return response 19 20 class M1(MiddlewareMixin): 21 \'\'\' 22 判断用户有无此url的权限的中间件 23 \'\'\' 24 def process_request(self,request): 25 current_url = request.path_info 26 27 # 1.白名单验证 28 valid_url = settings.VALID_URL 29 for each in valid_url: 30 if re.match(each, current_url): 31 return None 32 33 # 2.验证是否已经写入session,即:是否已经登录 34 permission_dic = request.session.get(\'permission_url_list\') 35 if not permission_dic: 36 return redirect(\'/login/\') 37 38 # 3.与当前访问的url与权限url进行匹配验证,并在request中写入code信息, 39 flag = False 40 for group_id,code_urls in permission_dic.items(): 41 for url in code_urls[\'per_url\']: 42 regax = \'^{0}$\'.format(url) 43 if re.match(regax,current_url): 44 flag = True 45 request.permission_code_list = code_urls[\'code\'] # 在session中增加code的信息,用于在页面判断在当前页面的权限, 46 break 47 if flag: 48 break 49 50 if not flag: 51 return HttpResponse(\'无权访问\') 52 53 54 def process_response(self,request,response): 55 return response
3.4 左侧菜单的生成templatetags目录下的rbac.py
功能;生成页面中的左侧菜单用inclusion_tag标签
运用:我们只需要在需要用到的文件中引用就可以生成这个菜单部分的内容。
需要用到的模板文件中:
{% load rbac %}
{% menu_html request %} 这部分就会变成用inclusion_tag生成的menu_html
详细代码如下:
1 import re 2 3 from django.template import Library 4 5 register = Library() 6 7 # inclusion_tag的结果是:把menu_html函数的返回值,放到menu_html中做渲染,生成一个渲染之后的大字符串, 8 # 在前端需要显示这个字符串的地方,只要调用menu_html就可以,如果有菜单需要传参数,这里是request,前端模板本来就有request, 9 @register.inclusion_tag(\'menu.html\') 10 def menu_html(request): 11 current_url = request.path_info 12 13 # 结构化在页面显示的menu数据 14 menu_list = request.session.get(\'permission_menu_list\') 15 16 menu_show_dic = {} 17 for item in menu_list: 18 # 先跟当前url进行匹配,如果当前的url在权限URl中,则需要修改当前的active,用于在前端页面的显示。 19 url = item[\'permission_url\'] 20 reg = \'^{0}$\'.format(url) 21 if re.match(reg, current_url): 22 print(\'匹配到了\') 23 item[\'active\'] = True 24 25 if item[\'menu_id\'] in menu_show_dic: 26 menu_show_dic[item[\'menu_id\']][\'children\'].append( 27 {\'permission__title\': item[\'permission__title\'], \'permission_url\': item[\'permission_url\'], 28 \'active\': item[\'active\']}) 29 if item[\'active\']: 30 menu_show_dic[item[\'menu_id\']][\'active\'] = True 31 else: 32 menu_show_dic[item[\'menu_id\']] = {\'menu_id\': item[\'menu_id\'], 33 \'menu_title\': item[\'menu_title\'], 34 \'active\': False, 35 \'children\': [{\'permission__title\': item[\'permission__title\'], 36 \'permission_url\': item[\'permission_url\'], 37 \'active\': item[\'active\']}, ] 38 } 39 if item[\'active\']: 40 menu_show_dic[item[\'menu_id\']][\'active\'] = True 41 42 43 return {\'menu_dic\':menu_show_dic}
需要的模板文件templates下的menu.html
1 # menu.html 2 3 <div class="menu"> 4 {% for k,menu in menu_dic.items %} 5 {# 一级菜单 #} 6 <div class="menu_first">{{ menu.menu_title }}</div> 7 8 {# 二级菜单(就是一级菜单下边的内容) #} 9 {% if menu.active %} 10 <ul class=""> 11 {% else %} 12 <ul class="hide"> 13 {% endif %} 14 15 {% for child in menu.children %} 16 {% if child.active %} 17 <li class="menu_second active"><a href="{{ child.permission_url }}">{{ child.permission__title }}</a></li> 18 {% else %} 19 <li class="menu_second"><a href="{{ child.permission_url }}">{{ child.permission__title }}</a></li> 20 {% endif %} 21 {% endfor %} 22 </ul> 23 {% endfor %} 24 </div>
使用inclusion_tag的文件示例:
1 # 这个是django的模板文件 2 {% load rbac %} 3 4 <!DOCTYPE html> 5 <html lang="en"> 6 <head> 7 <meta charset="UTF-8"> 8 <title>{% block title %}模板{% endblock %}</title> 9 <link rel="stylesheet" href="{% static \'rbac/bootstrap-3.3.7/css/bootstrap.min.css\' %}"> 10 <link rel="stylesheet" href="{% static \'rbac/menu.css\' %}"> 11 {% block css %} {% endblock css %} 12 13 </head> 14 <body> 15 <div class="container-fluid"> 16 <div class="row"> 17 <div class="col-md-2 menu"> 18 {% block menu %} 19 {% menu_html request %} {# 用inclusion_tag生成的menu_html #} 20 {% endblock menu %} 21 </div> 22 <div class="col-md-9"> 23 {% block content %} 24 content 25 {% endblock %} 26 </div> 27 </div> 28 </div>
以上就是django中基于rbac实现的权限组件