- A+
概述
RBAC : 基于角色的权限访问控制(Role-Based Access Control),通过角色绑定权限,然后给用户划分角色。在web应用中,可以将权限理解为url,一个权限对应一个url。
基于角色的访问控制方法(RBAC)的显著的两大特征是:
1.由于角色/权限之间的变化比角色/用户关系之间的变化相对要慢得多,减小了授权管理的复杂性,降低管理开销。
2.灵活地支持企业的安全策略,并对企业的变化有很大的伸缩性。
实现步骤
1 创建项目,包含两个应用app01
,rbac
2 setting中配置:
INSTALLED_APPS DATABASES STATICFILES_DIRS
3 设计表关系
基于上述分析,在设计表关系时,起码要有5张表:用户,角色,权限,权限组,菜单:
-
用户可以绑定多个角色,从而实现灵活的权限组合 :用户和角色,多对多关系
-
每个角色下,绑定多个权限,一个权限也可以属于多个角色:角色和权限,多对多关系
-
多个权限附属在一个权限组下,一个权限组下可以有多个权限:权限和权限组,多对一关系
-
一个菜单包含多个权限组:权限组和菜单,多对一关系
-
一个菜单下可能有多个子菜单,也可能有一个父菜单:菜单和菜单是自引用关系
在rbac的models中定义这几张表:
from django.db import models# Create your models here.class User(models.Model): """ 用户表 """ username = models.CharField(max_length=32,verbose_name='用户名') password = models.CharField(max_length=32,verbose_name='密码') roles = models.ManyToManyField('Role',verbose_name='与角色多对多绑定')class Role(models.Model): """ 角色表,多对多绑定权限 """ name = models.CharField(max_length=32,verbose_name='角色名称') Permissions = models.ManyToManyField('Permission',verbose_name='与权限多对多绑定')class Permission(models.Model): """ 权限表 """ name = models.CharField(max_length=32,verbose_name='权限名称') url = models.CharField(max_length=32,verbose_name='对应路径') code = models.CharField(max_length=32,verbose_name='别名') menu_group = models.ForeignKey(to='Permission',related_name='xxx',null=True,blank=True,default=None,verbose_name='所属菜单组') PermissionGroup = models.ForeignKey('PermissionGroup',null=True,verbose_name='所属权限组')class PermissionGroup(models.Model): """ 权限分组 """ name = models.CharField(max_length=32,verbose_name='权限组名称') Menu = models.ForeignKey('Menu',null=True,verbose_name='所属菜单')class Menu(models.Model): """ 菜单 """ name = models.CharField(max_length=32,verbose_name='菜单名称')
4 录入数据
models.Role.objects.create(name='CEO') models.Role.objects.create(name='总监') models.Role.objects.create(name='经理') models.Role.objects.create(name='业务员') models.User.objects.create(username='番禺',password=123) models.User.objects.create(username='鲁宁',password=123) models.User.objects.create(username='肾松',password=123) models.User.objects.create(username='文飞',password=123) models.User.objects.create(username='成栋',password=123) models.Menu.objects.create(name='菜单一') models.Menu.objects.create(name='菜单二') models.PermissionGroup.objects.create(name='用户组',menu_id=1) models.PermissionGroup.objects.create(name='订单组',menu_id=2) models.Permission.objects.create(url='/userinfo/',name='用户列表',permissionGroup_id=1,code='list') models.Permission.objects.create(url='/userinfo/add/',name='添加用户',permissionGroup_id=1,code='add') models.Permission.objects.create(url='/userinfo/edit/(\d+)/',name='编辑用户',permissionGroup_id=1,code='edit') models.Permission.objects.create(url='/userinfo/del/(\d+)/',name='删除用户',permissionGroup_id=1,code='del') models.Permission.objects.create(url='/order/', name='订单列表',permissionGroup_id=2,code='list') models.Permission.objects.create(url='/order/add/', name='添加订单',permissionGroup_id=2,code='add') models.Permission.objects.create(url='/order/edit/(\d+)/', name='编辑订单',permissionGroup_id=2,code='edit') models.Permission.objects.create(url='/order/del/(\d+)/', name='删除订单',permissionGroup_id=2,code='del') models.Role.objects.get(name='CEO').permissions.add(1,2,3,4,5,6,7,8) models.Role.objects.get(name='总监').permissions.add(1,2,5,6) models.Role.objects.get(name='经理').permissions.add(1,5) models.Role.objects.get(name='业务员').permissions.add(5) models.User.objects.get(username='番禺').roles.add(1) models.User.objects.get(username='鲁宁').roles.add(2) models.User.objects.get(username='肾松').roles.add(3,4) models.User.objects.get(username='文飞').roles.add(4) models.User.objects.get(username='成栋').roles.add(4)
5 views.py 登录
from rbac.service.init_permission import init_permissiondef login(request): if request.method == 'GET': return render(request,'login.html') else: username = request.POST.get('username') password = request.POST.get('password') user = models.User.objects.filter(username=username,password=password).first() if user: init_permission(request,user) return redirect('/index/') return render(request, 'login.html',{'msg':'用户名或密码错误'})
6 提取用户权限信息,写入session
在rbac应用下新建一个文件夹service
,写一个脚本init_permission.py
用来执行初始化权限的操作:用户登录后,取出其权限及所属菜单信息,写入session中
from collections import defaultdictfrom django.conf import settingsdef init_permission(request,user): #查询登录用户的权限等信息 current_url = request.path_info userdata = user.roles.values('permissions__id' # 权限ID , 'permissions__name' # 权限名称 , 'permissions__code' # 别名 , 'permissions__url' # 权限路径 , 'permissions__menu_group_id' # 组内菜单ID,Null表示是菜单 , 'permissions__permissionGroup_id' # 权限所属组ID , 'permissions__permissionGroup__menu_id'# 菜单ID , 'permissions__permissionGroup__menu__name' # 菜单名称 ).distinct() #权限相关 """ permission_url_dict数据结构如下 { 1: { 'codes': ['list', 'add', 'edit', 'del'], 'urls': ['/userinfo/', '/userinfo/add/', '/userinfo/edit/(\\d+)/', '/userinfo/del/(\\d+)/'] }, 2: { 'codes': ['list', 'add', 'edit', 'del'], 'urls': ['/order/', '/order/add/', '/order/edit/(\\d+)/', '/order/del/(\\d+)/'] } } """ permission_url_dict = defaultdict(lambda :{'codes':[],'urls':[]}) for item in userdata: permission_url_dict[item['permissions__permissionGroup_id']]['codes'].append(item['permissions__code']) permission_url_dict[item['permissions__permissionGroup_id']]['urls'].append(item['permissions__url']) request.session[settings.PERMISSION_URL_KEY] = permission_url_dict#用户的权限信息保存到session中 #菜单相关 """ permission_menu_list 数据结构如下 [ {'id': 1, 'title': '用户列表', 'url': '/userinfo/', 'menu_gp_id': None, 'menu_id': 1, 'menu_title': '菜单一'}, {'id': 2, 'title': '添加用户', 'url': '/userinfo/add/', 'menu_gp_id': 1, 'menu_id': 1, 'menu_title': '菜单一'}, {'id': 3, 'title': '编辑用户', 'url': '/userinfo/edit/(\\d+)/', 'menu_gp_id': 1, 'menu_id': 1, 'menu_title': '菜单一'}, {'id': 4, 'title': '删除用户', 'url': '/userinfo/del/(\\d+)/', 'menu_gp_id': 1, 'menu_id': 1, 'menu_title':'菜单一'}, {'id': 5, 'title': '订单列表', 'url': '/order/', 'menu_gp_id': None, 'menu_id': 2, 'menu_title': '菜单二'}, {'id': 6, 'title': '添加订单', 'url': '/order/add/', 'menu_gp_id': 5, 'menu_id': 2, 'menu_title': '菜单二'}, {'id': 7, 'title': '编辑订单', 'url': '/order/edit/(\\d+)/', 'menu_gp_id': 5, 'menu_id': 2, 'menu_title': '菜单二'}, {'id': 8, 'title': '删除订单', 'url': '/order/del/(\\d+)/', 'menu_gp_id': 5, 'menu_id': 2, 'menu_title': '菜单二'} ] """ #初步处理数据,在自定义标签中生成左侧菜单数据 permission_menu_list = [] for item in userdata: tpl = { 'id': item['permissions__id'], 'title': item['permissions__name'], 'url': item['permissions__url'], 'menu_gp_id': item['permissions__menu_group_id'], 'menu_id': item['permissions__permissionGroup__menu_id'], 'menu_title': item['permissions__permissionGroup__menu__name'], } permission_menu_list.append(tpl) request.session[settings.PERMISSION_MENU_KEY] = permission_menu_list #用户的菜单信息保存到session中
7 登陆成功后跳转到index页面,运用模板继承
8 定义中间件,处理所有请求。
在rbac应用下新建一个目录middleware
,用来存放自定义中间件,新建rbac.py
,在其中实现检查用户权限,控制访问:
import refrom django.conf import settingsfrom django.shortcuts import HttpResponse,render,redirectclass 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 responseclass RbacMiddleware(MiddlewareMixin): def process_request(self,request): current_url = request.path_info # 白名单处理 for url in settings.VALID_URLS: if re.match(url, current_url): return None # 当前用户的权限列表 permission_dict = request.session.get(settings.PERMISSION_URL_KEY) if not permission_dict: return redirect('/login/') flag = False for group_id,codes_urls in permission_dict.items(): for permission_url in codes_urls['urls']: regex = "^{0}$".format(permission_url) if re.match(regex,current_url): request.permission_code_list = codes_urls['codes'] flag = True break if flag: break if not flag: return HttpResponse('无权访问') MIDDLEWARE = ['rbac.middlewares.rbac.RbacMiddleware',]
9 自定义标签 - 左侧菜单
显示菜单要处理三个问题:
-
第一,只显示用户权限对应的菜单,因此不同用户看到的菜单可能是不一样的
-
第二,对用户当前访问的菜单下的url作展开显示,其余菜单折叠;
-
第三,菜单的层级是不确定的(而且,后面要实现权限的后台管理,允许管理员添加菜单和权限);
在rabc应用的目录下新建templatetags
目录,写一个脚本my_tags.py
,写一个函数menu_html
,并加上自定义标签的装饰器:
import refrom django.template import Libraryfrom django.conf import settings register = Library()@register.inclusion_tag('siderbar_menu.html')def menu_html(request): menu_list = request.session.get(settings.PERMISSION_MENU_KEY) current_url = request.path_info """ menu_list 数据结构 [ {'id': 1, 'title': '用户列表', 'url': '/userinfo/', 'menu_gp_id': None, 'menu_id': 1, 'menu_title': '菜单一', 'active': True}, {'id': 2, 'title': '添加用户', 'url': '/userinfo/add/', 'menu_gp_id': 1, 'menu_id': 1, 'menu_title': '菜单一'}, {'id': 3, 'title': '编辑用户', 'url': '/userinfo/edit/(\\d+)/', 'menu_gp_id': 1, 'menu_id': 1, 'menu_title': '菜单一'}, {'id': 4, 'title': '删除用户', 'url': '/userinfo/del/(\\d+)/', 'menu_gp_id': 1, 'menu_id': 1, 'menu_title': '菜单一'}, {'id': 5, 'title': '订单列表', 'url': '/order/', 'menu_gp_id': None, 'menu_id': 2, 'menu_title': '菜单二'}, {'id': 6, 'title': '添加订单', 'url': '/order/add/', 'menu_gp_id': 5, 'menu_id': 2, 'menu_title': '菜单二'}, {'id': 7, 'title': '编辑订单', 'url': '/order/edit/(\\d+)/', 'menu_gp_id': 5, 'menu_id': 2, 'menu_title': '菜单二'}, {'id': 8, 'title': '删除订单', 'url': '/order/del/(\\d+)/', 'menu_gp_id': 5, 'menu_id': 2,'menu_title': '菜单二'} ] """ """ menu_dict 数据结构 { 1: {'id': 1, 'title': '用户列表', 'url': '/userinfo/', 'menu_gp_id': None, 'menu_id': 1, 'menu_title': '菜单一', 'active': True}, 5: {'id': 5, 'title': '订单列表', 'url': '/order/', 'menu_gp_id': None, 'menu_id': 2, 'menu_title': '菜单二'} } """ menu_dict = {} for item in menu_list: if not item['menu_gp_id']: menu_dict[item['id']] = item for item in menu_list: regex = "^{0}$".format(item['url']) if re.match(regex,current_url): if not item['menu_gp_id']: menu_dict[item['id']]['active'] = True else: menu_dict[item['menu_gp_id']]['active'] = True """ result数据结构: { 1: { 'menu_id': 1, 'menu_title': '菜单一', 'active': True, 'children': [ {'title': '用户列表', 'url': '/userinfo/', 'active': True} ] }, 2: { 'menu_id': 2, 'menu_title': '菜单二', 'active': None, 'children': [ {'title': '订单列表','url': '/order/', 'active': None} ] } } """ result = {} for item in menu_dict.values(): active = item.get('active') menu_id = item['menu_id'] if item['menu_id'] not in result: result[menu_id] = { 'menu_id': menu_id, 'menu_title': item['menu_title'], 'active': active, 'children': [ {'title': item['title'], 'url': item['url'], 'active': active}, ] } else: result[menu_id]['children'].append({'title': item['title'], 'url': item['url'], 'active': active}) if active: result[menu_id]['active'] = True return {'menu_dict': result}
需要渲染的标签页面:siderbar_menu.html
{% for k,item in menu_dict.items %} <div class="item"> <div class="item-title">{{ item.menu_title }}</div> {% if item.active %} <div class="item-permission"> {% else %} <div class="item-permission hide"> {% endif %} {% for v in item.children %} {% if v.active %} <a href="{{ v.url }}" class="active">{{ v.title }}</a> {% else %} <a href="{{ v.url }}">{{ v.title }}</a> {% endif %} {% endfor %} </div> </div>{% endfor %}
10 页面的按钮按权限显示 -- 面向对象
class BasePagePermission(object): def __init__(self,code_list): self.code_list = code_list def has_add(self): if 'add' in self.code_list: return True def has_edit(self): if 'edit' in self.code_list: return True def has_del(self): if 'del' in self.code_list: return True
def userinfo(request): print(request.permission_code_list) #['list', 'add', 'edit', 'del'] pagePermission = BasePagePermission(request.permission_code_list) user_list = models.User.objects.all() return render(request,'userinfo.html',{'user_list':user_list,'pagePermission':pagePermission})
{% extends 'base.html' %} {% block content %} {% if pagePermission.has_add %} <a href="/userinfo/add">添加用户</a> {% endif %} <table> {% for user in user_list %} <tr> <td>{{ user.id }}</td> <td>{{ user.username }}</td> {% if pagePermission.has_edit %} <td><a href="">编辑</a></td> {% endif %} {% if pagePermission.has_del %} <td><a href="">编辑</a></td> {% endif %} </tr> {% endfor %} </table>{% endblock %}
总结
RBAC中的代码
- models.py
- admin.py # 录入数据
- service.init_permission.py
- middlewares.rbac.py
- templatetags.my_tags.py
- static
配置文件
# 权限相关PERMISSION_URL_KEY = 'aaaa'PERMISSION_MENU_KEY = 'bbbbb'VALID_URLS = [ '/login/', '/logout/', '/index/', '/test/', 'admin.*', 'rbac.*', ]
FROM:https://www.jianshu.com/p/f45b54768aa9
- 我的微信
- 这是我的微信扫一扫
- 我的微信公众号
- 我的微信公众号扫一扫