百万年薪python之路 -- RBAC角色权限设计
RBAC(Role-Based Access Control,基于角色的访问控制),就是用户通过角色与权限进行关联。简单地说,一个用户拥有若干角色,每一个角色拥有若干权限。这样,就构造成“用户-角色-权限”的授权模型。
在这种模型中,用户与角色之间,角色与权限之间,一般都是多对多的关系。
角色是什么?可以理解为一定数量的权限的集合,权限的载体。例如:一个论坛系统,“超级管理员”、“版主”都是角色。版主可管理版内的帖子、可管理版内的用户等,这些是权限。要给某个用户授予这些权限,不需要直接将权限授予用户,可将“版主”这个角色赋予该用户。
当用户的数量非常大时,要给系统每个用户逐一授权(授角色),是件非常烦琐的事情。这时,就需要给用户分组,每个用户组内有多个用户。除了可给用户授权外,还可以给用户组授权。这样一来,用户拥有的所有权限,就是用户个人拥有的权限与该用户所在用户组拥有的权限之和。
在应用系统中,权限表现成什么?对功能模块的操作,对上传文件的删改,菜单的访问,甚至页面上某个按钮、某个图片的可见性控制,都可属于权限的范畴。有些权限设计,会把功能操作作为一类,而把文件、菜单、页面元素等作为另一类,这样构成“用户-角色-权限-资源”的授权模型。而在做数据表建模时,可把功能操作和资源统一管理,也就是都直接与权限表进行关联,这样可能更具便捷性和易扩展性。
RBAC权限设计:
一张图搞定RBAC用户权限设计:
权限设计的大致步骤:
# 1 权限表设计(model)
# 2 权限分配(数据分配)
# 3 查询权限并注入权限(封装的session功能)
# 4 权限验证(中间件)
# 5 动态生成左侧菜单
CRM的权限设计
1. 权限表设计(model)
# 用户表
class UserInfo(models.Model):
username = models.CharField(max_length=32) # 用户名
password = models.CharField(max_length=32) # 密码 最好在注册时把密码加密,在这里没有
roles = models.ManyToManyField('Role') # 关联到角色表,在数据库中会生成第三张表rbac_userinfo_roles表
def __str__(self):
return self.username
# 角色表
class Role(models.Model):
name = models.CharField(max_length=16) # 角色名
permissions = models.ManyToManyField('Permission') # 关联到权限表
def __str__(self):
return self.name
# 权限
class Permission(models.Model):
title = models.CharField(max_length=32) # 权限名,如 账单管理
url = models.CharField(max_length=32) # 权限的url
menus = models.ForeignKey('Menu',null=True,blank=True)
# 关联Permission表,用于 非菜单权限 关联 二级菜单权限
parent = models.ForeignKey('self',null=True,blank=True)
# url_alias_name 存储url的别名
url_alias_name = models.CharField(max_length=32,null=True,blank=True)
def __str__(self):
return self.title
# 一级菜单数据表
class Menu(models.Model):
name = models.CharField(max_length=32) # 一级菜单名
icon = models.CharField(max_length=32, null=True, blank=True) # 一级菜单所用的font-awesome图标值
weight = models.IntegerField(default=100) # 控制菜单排序的,权重值越大,菜单展示越靠前
def __str__(self):
return self.name
# 一级菜单的核心思想
"""
一级菜单
id name icon
1 业务系统
2 教务系统
权限表
id title url menu_id
1 客户展示 /list/ 1
2 客户添加 /add/ None
3 跟进记录展示 /plist/ 1
4 课程记录 /course/ 2
5 课程记录添加 /add/course/ None
"""
2. 权限分配(数据分配)
rbac_permission表
rbac_role表
rbac_role_permissions表
rbac_userinfo表
rbac_userinfo_roles表
rbac_menu表
3. 查询权限并注入权限(封装的session功能)
from rbac import models
# 权限注入到session中
def init_permission(request, user_obj):
# 登录成功之后,将该用户的所有权限(url)全部加入到session中
permission_list = models.Role.objects.filter(
userinfo__username=user_obj.username
).values(
'permissions__url', # 需要设置权限的url
'permissions__title', # 权限的名称(即二级菜单名)
'permissions__pk', # 权限表里对应的主键值(id)
'permissions__menus__pk', # 一级菜单的主键值(id)
'permissions__menus__name', # 一级菜单名
'permissions__menus__icon', # 一级菜单的图标的font-awesome值
'permissions__menus__weight',# 一级菜单的权重(用来对多个一级菜单的排序)
'permissions__parent_id', # 权限的id(即二级菜单的id值)
'permissions__url_alias_name'# 权限的url的别名
).distinct() # 相同的权限去重
# queryset对象不能通过Json进行可序列化,所以转化成List对象
# # Object of type 'QuerySet' is not JSON serializable
# request.session['permission_list'] = list(permission_list)
permission_dict = {}
url_alias_name = [] # 存放所有的权限url别名
# 筛选菜单权限
menu_dict = {}
for i in permission_list:
permission_dict[i.get('permissions__pk')] = i
url_alias_name.append(i.get('permissions__url_alias_name'))
if i.get('permissions__menus__pk'):
if i.get('permissions__menus__pk') in menu_dict:
menu_dict[i.get('permissions__menus__pk')]['children'].append(
{
'title': i.get('permissions__title'),
'url': i.get('permissions__url'),
'second_menu_id': i.get('permissions__pk'),
}
)
else:
menu_dict[i.get('permissions__menus__pk')] = {
'name': i.get('permissions__menus__name'),
'icon': i.get('permissions__menus__icon'),
'weight': i.get('permissions__menus__weight'),
'children': [
{
'title': i.get('permissions__title'),
'url': i.get('permissions__url'),
'second_menu_id': i.get('permissions__pk'),
}
]
}
# 将菜单权限注入到session
request.session['menu_dict'] = menu_dict
request.session['url_alias_name'] = url_alias_name
request.session['permission_dict'] = permission_dict
# menu_dict形成以下的数据结构
'''
{
1: {
'name': '业务系统',
'icon': 'fa fa-home fa-fw',
'weight': 100,
'children': [{
'title': '客户管理',
'url': '/customer/list/',
'second_menu_id': None,
}]
},
2: {
'name': '财务系统',
'icon': 'fa fa-jpy fa-fw',
'weight': 200,
'children': [{
'title': '账单管理',
'url': '/payment/list/',
'second_menu_id': None,
}]
}
}
'''
"""
# permission_dict 形成以下的数据结构
{
1: {
'permissions__url': '/customer/list/',
'permissions__title': '客户管理',
'permissions__pk': 1,
'permissions__menus__pk': 2,
'permissions__menus__name': '业务系统',
'permissions__menus__icon': 'fafa-homefa-fw',
'permissions__menus__weight': 200,
'permissions__parent_id': None,
'permissions__url_alias_name': 'customer_list'
},
2: {
'permissions__url': '/customer/add/',
'permissions__title': '添加客户',
'permissions__pk': 2,
'permissions__menus__pk': None,
'permissions__menus__name': None,
'permissions__menus__icon': None,
'permissions__menus__weight': None,
'permissions__parent_id': 1,
'permissions__url_alias_name': 'customer_add'
},
3: {
'permissions__url': '/customer/edit/(?P<cid>\\d+)/',
'permissions__title': '编辑客户',
'permissions__pk': 3,
'permissions__menus__pk': None,
'permissions__menus__name': None,
'permissions__menus__icon': None,
'permissions__menus__weight': None,
'permissions__parent_id': 1,
'permissions__url_alias_name': 'customer_edit'
},
4: {
'permissions__url': '/customer/del/(?P<cid>\\d+)/',
'permissions__title': '删除客户',
'permissions__pk': 4,
'permissions__menus__pk': None,
'permissions__menus__name': None,
'permissions__menus__icon': None,
'permissions__menus__weight': None,
'permissions__parent_id': 1,
'permissions__url_alias_name': 'customer_del'
},
5: {
'permissions__url': '/payment/list/',
'permissions__title': '账单管理',
'permissions__pk': 5,
'permissions__menus__pk': 1,
'permissions__menus__name': '财务系统',
'permissions__menus__icon': 'fafa-rmbfa-fw',
'permissions__menus__weight': 100,
'permissions__parent_id': None,
'permissions__url_alias_name': 'payment_list'
},
6: {
'permissions__url': '/payment/add/',
'permissions__title': '添加缴费',
'permissions__pk': 6,
'permissions__menus__pk': None,
'permissions__menus__name': None,
'permissions__menus__icon': None,
'permissions__menus__weight': None,
'permissions__parent_id': 5,
'permissions__url_alias_name': 'payment_add'
},
7: {
'permissions__url': '/payment/edit/(?P<pid>\\d+)/',
'permissions__title': '编辑缴费',
'permissions__pk': 7,
'permissions__menus__pk': None,
'permissions__menus__name': None,
'permissions__menus__icon': None,
'permissions__menus__weight': None,
'permissions__parent_id': 5,
'permissions__url_alias_name': 'payment_edit'
},
8: {
'permissions__url': '/payment/del/(?P<pid>\\d+)/',
'permissions__title': '删除缴费',
'permissions__pk': 8,
'permissions__menus__pk': None,
'permissions__menus__name': None,
'permissions__menus__icon': None,
'permissions__menus__weight': None,
'permissions__parent_id': 5,
'permissions__url_alias_name': 'payment_del'
},
9: {
'permissions__url': '/nashui/',
'permissions__title': '纳税管理',
'permissions__pk': 9,
'permissions__menus__pk': 1,
'permissions__menus__name': '财务系统',
'permissions__menus__icon': 'fafa-rmbfa-fw',
'permissions__menus__weight': 100,
'permissions__parent_id': None,
'permissions__url_alias_name': 'nashui'
}
}
"""
4. 权限验证(中间件)
在rbac应用里新建一个middlewares文件夹(文件夹名随意),然后新建一个mymiddleware.py文件(py文件名字随意)
记得在settings文件的MIDDLEWARE里加入中间件
import re
from django.utils.deprecation import MiddlewareMixin
from django.urls import reverse
from django.shortcuts import redirect, HttpResponse, render
class Auth(MiddlewareMixin):
def process_request(self, request):
# 登录认证白名单
white_list = [reverse('login'), reverse('logout'), ]
# 权限认证白名单
permission_white_list = [reverse('index'), '/admin/*']
request.pid = None
bread_crumb = [
{'url': reverse('index'), 'title': '首页'}
]
request.bread_crumb = bread_crumb # 把生成面包屑的数据放入到request对象中,在42行处加入注入数据
# 登录认证
path = request.path
if path not in white_list:
is_login = request.session.get('is_login')
if not is_login:
return redirect('login')
# 权限认证
permission_dict = request.session.get('permission_dict')
for white_path in permission_white_list:
if re.match(white_path, path):
break
else:
for i in permission_dict.values():
reg = r"^%s$" % i['permissions__url']
if re.match(reg, path):
pid = i.get('permissions__parent_id')
if pid: # 如果这个不是菜单权限,就执行
# 父级二级菜单路径信息
request.bread_crumb.append(
{
'url': permission_dict[str(pid)]['permissions__url'], # 面包屑的父级二级菜单url
'title': permission_dict[str(pid)]['permissions__title'] # 面包屑的父级二级菜单名字
}
)
# 子权限的路径信息 #/payment/add/
request.bread_crumb.append(
{ # 面包屑的当前权限url
'url': i.get('permissions__url'),
# 面包屑的当前权限名字
'title': i.get('permissions__title')
}
)
request.pid = pid # 把二级菜单的id注入到request里
else:
# 二级菜单路径信息
request.bread_crumb.append(
{
'url': i.get('permissions__url'),
'title': i.get('permissions__title')
}
)
request.pid = i.get('permissions__pk')
break
else:
return HttpResponse('你权限不足!!!')
5. 动态生成左侧菜单
采用自定义标签来完成
- 现在rbac里新建一个templatetags文件夹(文件夹名字必须叫这个)
- 在文件夹里新建一个mytags.py文件(py文件名字任意)
- 在py文件里注册 "注册器",如下
from django import template
register = template.Library()
- 在函数上添加 @register.inclusion_tag('HTML文件名')
mytags文件:
import re
from collections import OrderedDict
from django import template
register = template.Library() # 注册 注册器register
@register.inclusion_tag('menu.html')
def menu(request):
menu_dict = request.session.get('menu_dict')
menu_order_key = sorted(menu_dict, key=lambda x: menu_dict[x]['weight'], reverse=True) # 对menu_dict里字典数据排序,
menu_order_dict = OrderedDict() # 生成有序字典,python3.6以上字典默认顺序为加入字典时的顺序,可不用OrderedDict
for key in menu_order_key:
menu_order_dict[key] = menu_dict[key]
# path = request.path
for k, v in menu_order_dict.items():
v['class'] = 'hidden'
for i in v['children']:
# if re.match(i['url'], path):
if request.pid == i['second_menu_id']:
v['class'] = ''
i['class'] = 'active'
menu_data = {'menu_data': menu_order_dict}
return menu_data
生成左侧菜单的HTML文件
layout.html的核心部分 :
#### HTML中生成左侧菜单的两行代码
{% load mytags %}
{% menu request %}
##### 面包屑的代码
<div>
<ol class="breadcrumb no-radius no-margin" style="border-bottom: 1px solid #ddd;">
{# <li><a href="#">首页</a></li>#}
{# <li class="active">客户管理</li>#}
{% for crumb in request.bread_crumb %}
{% if forloop.last %}
<li class="active">{{ crumb.title }}</li>
{% else %}
<li><a href="{{ crumb.url }}">{{ crumb.title }}</a></li>
{% endif %}
{% endfor %}
</ol>
</div>
百万年薪python之路 -- RBAC角色权限设计的更多相关文章
- RBAC角色权限设计思路
1 设计思路 为了设计一套具有较强可扩展性的用户认证管理,需要建立用户.角色和权限等数据库表,并且建立之间的关系,具体实现如下. 1.1 用户 用户仅仅是纯粹的用户,用来记录用户相关信息,如用户名.密 ...
- 百万年薪python之路 -- MySQL数据库之 用户权限
MySQL用户授权 (来自于https://www.cnblogs.com/dong-/p/9667787.html) 一. 对新用户的增删改 1. 增加用户 : ①. 指定某一个用户使用某一个ip登 ...
- 百万年薪python之路 -- 数据库初始
一. 数据库初始 1. 为什么要有数据库? 先来一个场景: 假设现在你已经是某大型互联网公司的高级程序员,让你写一个火车票购票系统,来hold住十一期间全国的购票需求,你怎么写? 由于在同一时 ...
- 百万年薪python之路 -- 并发编程之 协程
协程 一. 协程的引入 本节的主题是基于单线程来实现并发,即只用一个主线程(很明显可利用的cpu只有一个)情况下实现并发,为此我们需要先回顾下并发的本质:切换+保存状态 cpu正在运行一个任务,会在两 ...
- 百万年薪python之路 -- 面向对象之继承
面向对象之继承 1.什么是面向对象的继承 继承(英语:inheritance)是面向对象软件技术当中的一个概念. 通俗易懂的理解是:子承父业,合法继承家产 专业的理解是:子类可以完全使用父类的方法和属 ...
- 百万年薪python之路 -- re模块
re模块 re模块是python用来描述正则表达式的一个模块. 正则表达式本身也和python没有什么关系,就是匹配字符串内容的一种规则. 官方定义:正则表达式是对字符串操作的一种逻辑公式,就是用事先 ...
- 百万年薪python之路 -- JS基础介绍及数据类型
JS代码的引入 方式1: <script> alert('兽人永不为奴!') </script> 方式2:外部文件引入 src属性值为js文件路径 <script src ...
- 百万年薪python之路 -- 前端CSS样式
CSS样式 控制高度和宽度 width宽度 height高度 块级标签能设置高度和宽度,而内联标签不能设置高度和宽度,内联标签的高度宽度由标签内部的内容来决定. 示例: <!DOCTYPE ht ...
- 百万年薪python之路 -- MySQL数据库之 Navicat工具和pymysql模块
一. IDE工具介绍(Navicat) 生产环境还是推荐使用mysql命令行,但为了方便我们测试,可以使用IDE工具,我们使用Navicat工具,这个工具本质上就是一个socket客户端,可视化的连接 ...
随机推荐
- 多事之秋-最近在阿里云上遇到的问题:负载均衡失灵、服务器 CPU 100%、被 DDoS 攻击
昨天 22:00~22:30 左右与 23:30~00:30 左右,有1台服役多年的阿里云负载均衡突然失灵,造成通过这台负载均衡访问博客站点的用户遭遇 502, 503, 504 ,由此给您带来麻烦, ...
- springboot项目启动报错 url' attribute is not specified and no embedded datasource could be configured
报错相关信息: 2019-07-22 17:12:48.971 ERROR 8312 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter : **** ...
- 数据结构与算法(C/C++版)【数组】
第五章<数组> 一.概念 根据数组中存储的数据元素之间的逻辑关系,可以将数组分为 : 一维数组.二维数组.….n维数组.n维数组中,维数 n 的判断依据是:根据数组中为确定元素所在位置使用 ...
- Python学习笔记整理总结【ORM(SQLAlchemy)】
一.介绍SQLAlchemy是Python编程语言下的一款ORM框架,该框架建立在数据库API之上,使用关系对象映射进行数据库操作,简言之便是:将对象转换成SQL,然后使用数据API执行SQL并获取执 ...
- Spring MVC 梳理 - handlerMapping和handlerAdapter分析
参考图片 综上所述我们来猜测一下spring mvc 中根据URL找到处理器Controller中相应方法的流程 ①:获取Request的URL ②:从UrlLookup这个map中找到相应的requ ...
- 深入集合类系列——ArrayList和Vector的区别
区别: 1)Vector的方法都是同步的(Synchronized),是线程安全的(thread-safe),而ArrayList的方法不是,由于线程的同步必然要影响性能,因此,ArrayList的性 ...
- 【ADO.NET--MVC】初学MVC(MVC入门)(1)
最近一直在学MVC,本来今天想开始做项目了,但是一下手才发现还有好多好多都不懂,虽然想照搬别人的模板,但是还是觉得很虚,这也不懂哪也不懂.看来学习一门技术断不是那么简单,只要随便套套模板,看看别人代码 ...
- 最近太多人问Protobuf的问题了,把这个重新搬出来!
pb杀手 我先让pbkiller做个自我介绍 pbkiller:我是一位专业的争对 protobuf 问题训练有素的杀手,我可以为您轻松搞定 protobuf 在 Cocos Creaotr 开发中的 ...
- SpringBoot自定义异常,优雅解决业务逻辑中的错误
概要 你是不是在为业务逻辑中出现的异常弄的焦头烂额,常常在后台报错,前端却无法提示错误内容,导致用户体验极差?比如下单失败,前端只能提示下单失败,但是却不知道为什么失败,是库存不足,还是余额不足,亦或 ...
- Python3 GUI开发(PyQt)安装和配置
Python3 GUI开发(PyQt5)安装和配置: 下载安装好Miniconda3, 并且安装好jupyter 注意:最好关闭360杀毒软件或者把cmd加入信任,否则运行activate会有问题. ...