Django框架的权限组件--rbac
1,基于rbac的权限管理
RBAC(Role-Based Access Control,基于角色的访问控制),就是用户通过角色与权限进行关联.简单的说,一个用户拥有若干角色,一个角色拥有若干权限.这样就构造成"用户-角色-权限"的授权模型.在这种模式中,用户与角色之间,角色与权限之间都是多对多的关系.
简单模型示意图:

2; rbac组件的基本目录结构:

3;rbac组件中的各个部分,以及功能.
3.1models数据库表设计(models.py)
为了在前端页面实现2方面的控制,还需要引入两个表菜单menu和分组group:1.在一个页面,当前用户的权限,例如是否显示添加按钮、编辑、删除等按钮;2.左侧菜单栏的创建。所以一共是5个类,7张表,详细model请看下边代码。
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.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>
浙公网安备 33010602011771号