#权限管理:rbac:Role Based access control(角色访问控制(RBAC,Role-Based Access Control))
# 一 根据用户获取权限, session中 中间件实现权限控制
# 二 菜单管理
#默认展开
#只显示当前用户菜单
一、权限用户表
a:SQL表结构
from django.db import modelsmodels.py
# Create your models here.
class User(models.Model):
"""
用户表
"""
username = models.CharField(verbose_name='用户名', max_length=32)
password = models.CharField(verbose_name='密码', max_length=64)
email = models.EmailField(verbose_name='邮箱')
def __str__(self):
return self.username
class Role(models.Model):
"""
角色表
"""
caption = models.CharField(verbose_name='角色', max_length=32)
def __str__(self):
return self.caption
class User2Role(models.Model):
"""
用户角色关系表
"""
user = models.ForeignKey(User, verbose_name='用户', related_name='roles')
role = models.ForeignKey(Role, verbose_name='角色', related_name='users')
def __str__(self):
return '%s-%s' % (self.user.username, self.role.caption,)
class Menu(models.Model):
"""
菜单表
"""
caption = models.CharField(verbose_name='菜单名称', max_length=32)
parent = models.ForeignKey('self', verbose_name='父菜单', related_name='p', null=True, blank=True)
def __str__(self):
prev = ""
parent = self.parent
while True:
if parent:
prev = prev + '-' + str(parent.caption)
parent = parent.parent
else:
break
return '%s-%s' % (prev, self.caption,)
class Permission(models.Model):
"""
权限
"""
caption = models.CharField(verbose_name='权限', max_length=32)
url = models.CharField(verbose_name='URL正则', max_length=128)
menu = models.ForeignKey(Menu, verbose_name='所属菜单', related_name='permissions',null=True,blank=True)
def __str__(self):
return "%s-%s" % (self.caption, self.url,)
class Action(models.Model):
"""
操作:增删改查
"""
caption = models.CharField(verbose_name='操作标题', max_length=32)
code = models.CharField(verbose_name='方法', max_length=32)
def __str__(self):
return self.caption
class Permission2Action2Role(models.Model):
"""
权限操作关系表
"""
permission = models.ForeignKey(Permission, verbose_name='权限URL', related_name='actions')
action = models.ForeignKey(Action, verbose_name='操作', related_name='permissions')
role = models.ForeignKey(Role, verbose_name='角色', related_name='p2as')
class Meta:
unique_together = (
('permission', 'action', 'role'),
)
def __str__(self):
return "%s-%s-%s" % (self.permission, self.action, self.role,)
models.py
b.输出菜单
urlpatterns = [urls.py
url(r'^auth-menu.html$',view2.menu),
]
from django.contrib import adminapp02/admin.py
# Register your models here.
from app02 import models
admin.site.register(models.User)
admin.site.register(models.Role)
admin.site.register(models.User2Role)
admin.site.register(models.Menu)
admin.site.register(models.Permission)
admin.site.register(models.Action)
admin.site.register(models.Permission2Action2Role)
def menu(request):app02/views.py
"""
需要用户名或用户ID,产出:用户关联所有菜单
:param request:
:return:
"""
# 所有菜单:处理成当前用关联的菜单
all_menu_list = models.Menu.objects.all().values('id', 'caption', 'parent_id')
"""
[
{'id':1, 'caption':'菜单1', parent_id:None},
{'id':2, 'caption':'菜单2', parent_id:None},
{'id':3, 'caption':'菜单3', parent_id:None},
{'id':4, 'caption':'菜单1-1', parent_id:1},
]
{
1:{'id':1, 'caption':'菜单1', parent_id:None,status:False,opened:False,child:[]},
2:{'id':2, 'caption':'菜单2', parent_id:None,status:False,opened:False,child:[]},
3:{'id':3, 'caption':'菜单3', parent_id:None,status:False,opened:False,child:[]},
5:{'id':4, 'caption':'菜单1-1', parent_id:1,status:False,opened:False,child:[]},
}
"""
user = models.User.objects.filter(username='alex').first()
role_list = models.Role.objects.filter(users__user=user)
permission_list = models.Permission2Action2Role.objects.filter(role__in=role_list).values('permission__id',
'permission__url',
'permission__menu_id',
'permission__caption').distinct()
"""
[
{'permission__url':'/order.html','permission__caption': '订单管理','permission__menu_id': 1 },
{'permission__url':'/order.html','permission__caption': '订单管理','permission__menu_id': 2 },
{'permission__url':'/order.html','permission__caption': '订单管理','permission__menu_id': 3 },
{'permission__url':'/order.html','permission__caption': '订单管理','permission__menu_id': 4 },
]
"""
##### 将权限挂靠到菜单上 ########
all_menu_dict = {}
for row in all_menu_list:
row['child'] = [] # 添加孩子
row['status'] = False # 是否显示菜单
row['opened'] = False # 是否默认打开
all_menu_dict[row['id']] = row
for per in permission_list:
if not per['permission__menu_id']:
continue
item = {
'id': per['permission__id'],
'caption': per['permission__caption'],
'parent_id': per['permission__menu_id'],
'url': per['permission__url'],
'status': True,
'opened': False
}
# print(item["url"])
if re.match(per['permission__url'],request.path_info):
# if re.match(per['permission__url'], "/orders.html"):
item['opened'] = True
pid = item['parent_id']
all_menu_dict[pid]['child'].append(item)
# 将当前权限前辈status=True
temp = pid # 1.父亲ID
while not all_menu_dict[temp]['status']:
all_menu_dict[temp]['status'] = True
temp = all_menu_dict[temp]['parent_id']
if not temp:
break
# 将当前权限前辈opened=True
if item['opened']:
temp1 = pid # 1.父亲ID
while not all_menu_dict[temp1]['opened']:
all_menu_dict[temp1]['opened'] = True
temp1 = all_menu_dict[temp1]['parent_id']
if not temp1:
break
# ############ 处理菜单和菜单之间的等级关系 ############
"""
all_menu_dict = {
1:{'id':1, 'caption':'菜单1', parent_id:None,status:False,opened:False,child:[{'permission__url':'/order.html','permission__caption': '订单管理','permission__menu_id': 1 },]},
2:{'id':2, 'caption':'菜单2', parent_id:None,status:False,opened:False,child:[]},
3:{'id':3, 'caption':'菜单3', parent_id:None,status:False,opened:False,child:[]},
5:{'id':4, 'caption':'菜单1-1', parent_id:1,status:False,opened:False,child:[]},
}
all_menu_list= [
{'id':1, 'caption':'菜单1', parent_id:None,status:False,opened:False,child:[{'permission__url':'/order.html','permission__caption': '订单管理','permission__menu_id': 1 }, {'id':4, 'caption':'菜单1-1', parent_id:1,status:False,opened:False,child:[]},]},
{'id':2, 'caption':'菜单2', parent_id:None,status:False,opened:False,child:[]},
{'id':3, 'caption':'菜单3', parent_id:None,status:False,opened:False,child:[]},
]
"""
result = []
for row in all_menu_list:
pid = row['parent_id']
if pid:
all_menu_dict[pid]['child'].append(row)
else:
result.append(row)
##################### 结构化处理结果 #####################
# print(result)
# for row in result:
# # print(row['caption'], row['status'], row['opened'], )
# print(row)
##################### 通过结构化处理结果,生成菜单开始 #####################
def menu_tree(menu_list):
tpl1 = """
<div class='menu-item'>
<div class='menu-header'>{0}</div>
<div class='menu-body {2}'>{1}</div>
</div>
"""
tpl2 = """
<a href='{0}' class='{1}'>{2}</a>
"""
menu_str = ""
for menu in menu_list:
if not menu['status']:
continue
# menu: 菜单,权限(url)
if menu.get('url'):
# 权限
menu_str += tpl2.format(menu['url'],'active' if menu['opened'] else "",menu['caption'])
else:
# 菜单
if menu['child']:
child_html = menu_tree(menu['child'])
else:
child_html = ""
menu_str += tpl1.format(menu['caption'], child_html,"" if menu['opened'] else 'hide')
return menu_str
menu_html = menu_tree(result)
return render(request, "menu_html.html", {"menu_html":menu_html})
<!DOCTYPE html>menu_html
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.menu-body{
margin-left: 20px;
}
.menu-body a{
display: block;
}
.menu-body a.active{
color: red;
}
.hide{
display: none;
}
</style>
</head>
<body>
{{ menu_html|safe }}
<script src="/static/jquery-3.2.1.js"></script>
<script>
$(function () {
$(".menu-header").click(function () {
$(this).next().removeClass("hide").parent().siblings().find(".menu-body").addClass("hide")
})
})
</script>
</body>
</html>
c.result 结果递归 debug调试
result =
[
{'opened': True, 'parent_id': None, 'caption': '菜单一', 'id': 1, 'status': True,
'child': [
{'opened': False, 'parent_id': 1, 'caption': '帅哥管理', 'id': 5, 'status': True, 'url': '/shuaige.html'},
{'opened': True, 'parent_id': 1, 'caption': '菜单一 一', 'id': 4, 'status': True,
'child': [
{'opened': True, 'parent_id': 4, 'caption': '用户管理', 'id': 1, 'status': True, 'url': '/users.html'},
{'opened': False, 'parent_id': 4, 'caption': '订单管理', 'id': 2, 'status': True, 'url': '/orders.html'}
]
},
{'opened': False, 'parent_id': 1, 'caption': '菜单一 二', 'id': 5, 'status': False, 'child': []},
{'opened': False, 'parent_id': 1, 'caption': '菜单一 三', 'id': 6, 'status': False, 'child': []}
]
},
{'opened': False, 'parent_id': None, 'caption': '菜单二', 'id': 2, 'status': False, 'child': []},
{'opened': False, 'parent_id': None, 'caption': '菜单三', 'id': 3, 'status': False, 'child': []}
]
result = [{'opened': True, 'parent_id': None, 'caption': '菜单一', 'id': 1, 'status': True, 'child': [{'opened': False, 'parent_id': 1, 'caption': '帅哥管理', 'id': 5, 'status': True, 'url': '/shuaige.html'}, {'opened': True, 'parent_id': 1, 'caption': '菜单一 一', 'id': 4, 'status': True, 'child': [{'opened': True, 'parent_id': 4, 'caption': '用户管理', 'id': 1, 'status': True, 'url': '/users.html'}, {'opened': False, 'parent_id': 4, 'caption': '订单管理', 'id': 2, 'status': True, 'url': '/orders.html'}]}, {'opened': False, 'parent_id': 1, 'caption': '菜单一 二', 'id': 5, 'status': False, 'child': []}, {'opened': False, 'parent_id': 1, 'caption': '菜单一 三', 'id': 6, 'status': False, 'child': []}]}, {'opened': False, 'parent_id': None, 'caption': '菜单二', 'id': 2, 'status': False, 'child': []}, {'opened': False, 'parent_id': None, 'caption': '菜单三', 'id': 3, 'status': False, 'child': []}]
def menu_tree(menu_list):
tpl1 = """
<div class='menu-item'>
<div class='menu-header'>{0}</div>
<div class='menu-body {2}'>{1}</div>
</div>
"""
tpl2 = """
<a href='{0}' class='{1}'>{2}</a>
"""
menu_str = ""
for menu in menu_list:
if not menu['status']:
continue
# menu: 菜单,权限(url)
if menu.get('url'):
# 权限
menu_str += tpl2.format(menu['url'], 'active' if menu['opened'] else "", menu['caption'])
print("***", menu_str)
else:
# 菜单
if menu['child']:
child_html = menu_tree(menu['child'])
print("----", child_html)
else:
child_html = ""
print("111")
menu_str += tpl1.format(menu['caption'], child_html, "" if menu['opened'] else 'hide')
print("AAAAA", menu_str)
print(123)
return menu_str
menu_html = menu_tree(result)
print("xx", menu_html)
二、组件
生成公共app
- 权限限制
- 生成菜单
python3 manage.py startapp rbac
配置文件:
复制代码config.py
#白名单url, 不验证
VALID_URL = [
'/app01/.*',
'/app02/.*'
'/login.html'
'/logout.html'
]
rbac models:
from django.db import modelsmodels.py
class User(models.Model):
"""
用户表
"""
username = models.CharField(verbose_name='用户名', max_length=32)
password = models.CharField(verbose_name='密码', max_length=64)
email = models.EmailField(verbose_name='邮箱')
def __str__(self):
return self.username
class Role(models.Model):
"""
角色表
"""
caption = models.CharField(verbose_name='角色', max_length=32)
def __str__(self):
return self.caption
class User2Role(models.Model):
"""
用户角色关系表
"""
user = models.ForeignKey(User, verbose_name='用户', related_name='roles')
role = models.ForeignKey(Role, verbose_name='角色', related_name='users')
def __str__(self):
return '%s-%s' % (self.user.username, self.role.caption,)
class Menu(models.Model):
"""
菜单表
"""
caption = models.CharField(verbose_name='菜单名称', max_length=32)
parent = models.ForeignKey('self', verbose_name='父菜单', related_name='p', null=True, blank=True)
def __str__(self):
prev = ""
parent = self.parent
while True:
if parent:
prev = prev + '-' + str(parent.caption)
parent = parent.parent
else:
break
return '%s-%s' % (prev, self.caption,)
class Permission(models.Model):
"""
权限
"""
caption = models.CharField(verbose_name='权限', max_length=32)
url = models.CharField(verbose_name='URL正则', max_length=128)
menu = models.ForeignKey(Menu, verbose_name='所属菜单', related_name='permissions',null=True,blank=True)
def __str__(self):
return "%s-%s" % (self.caption, self.url,)
class Action(models.Model):
"""
操作:增删改查
"""
caption = models.CharField(verbose_name='操作标题', max_length=32)
code = models.CharField(verbose_name='方法', max_length=32)
def __str__(self):
return self.caption
class Permission2Action2Role(models.Model):
"""
权限操作关系表
"""
permission = models.ForeignKey(Permission, verbose_name='权限URL', related_name='actions')
action = models.ForeignKey(Action, verbose_name='操作', related_name='permissions')
role = models.ForeignKey(Role, verbose_name='角色', related_name='p2as')
class Meta:
unique_together = (
('permission', 'action', 'role'),
)
def __str__(self):
return "%s-%s-%s" % (self.permission, self.action, self.role,)
中间件认证
#验证中间件/middleware/md.py
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse
from rbac import config
import re
class RbacMiddleware(MiddlewareMixin):
def process_request(self,request,*args,**kwargs):
for pattern in config.VALID_URL:
if re.match(pattern,request.path_info):
return None
action = request.GET.get('md') # GET
user_permission_dict = request.session.get('user_permission_dict')
if not user_permission_dict:
return HttpResponse('无权限')
# action_list = user_permission_dict.get(request.path_info)
flag = False
for k,v in user_permission_dict.items():
if re.match(k,request.path_info):
if action in v:
flag = True
break
if not flag:
return HttpResponse('无权限')
后端代码
import reservice.py
from rbac import models
from django.utils.safestring import mark_safe
def permission_session(user_id,request):
"""
:param user_id: rbac中的user表中一条数据id
:param request:
:return:
"""
# obj = models.User.objects.filter(username='杨明').first()
#
# # x = models.User2Role.objects.filter(user_id=obj.id)
# # [User2Role,User2Role,User2Role]
#
# role_list = models.Role.objects.filter(users__user_id=obj.id)
# # [Role,]
# from django.db.models import Count
# # permission_list = models.Permission2Action2Role.objects.filter(role__in=role_list).values('permission__url','action__code').annotate(c=Count('id'))
# permission_list = models.Permission2Action2Role.objects.filter(role__in=role_list).values('permission__url','action__code').distinct()
"""
[
{permission_url: '/index.html', action_code:'GET'},
{permission_url: '/index.html', action_code:'POST'},
{permission_url: '/index.html', action_code:'DEL'},
{permission_url: '/index.html', action_code:'Edit'},
{permission_url: '/order.html', action_code:'GET'},
{permission_url: '/order.html', action_code:'POST'},
{permission_url: '/order.html', action_code:'DEL'},
{permission_url: '/order.html', action_code:'Edit'},
]
放在Session中
/index.html?md=GET
{
'/index.html': [GET,POST,DEL,Edit],
'/order.html': [GET,POST,DEL,Edit],
}
"""
user_permission_dict = {
'/ah-index.html': ["GET","POST","DEL","Edit"],
'/order.html': ["GET","POST","DEL","Edit"],
'/index-(\d+).html': ["GET","POST","DEL","Edit"],
}
request.session['user_permission_dict'] = user_permission_dict
def menu(user_id,current_url):
"""
根据用户ID,当前URL:获取用户所有菜单以及权限,是否显示,是否打开
:param user_id:
:param current_url:
:return:
"""
# 所有菜单:处理成当前用关联的菜单
all_menu_list = models.Menu.objects.all().values('id','caption','parent_id')
user = models.User.objects.filter(id=user_id).first()
role_list = models.Role.objects.filter(users__user=user)
permission_list = models.Permission2Action2Role.objects.filter(role__in=role_list).values('permission__id','permission__url','permission__menu_id','permission__caption').distinct()
##### 将权限挂靠到菜单上 ########
all_menu_dict = {}
for row in all_menu_list:
row['child'] = [] # 添加孩子
row['status'] = False # 是否显示菜单
row['opened'] = False # 是否默认打开
all_menu_dict[row['id']] = row
for per in permission_list:
if not per['permission__menu_id']:
continue
item = {
'id':per['permission__id'],
'caption':per['permission__caption'],
'parent_id':per['permission__menu_id'],
'url': per['permission__url'],
'status': True,
'opened': False
}
if re.match(per['permission__url'],current_url):
item['opened'] = True
pid = item['parent_id']
all_menu_dict[pid]['child'].append(item)
# 将当前权限前辈status=True
temp = pid # 1.父亲ID
while not all_menu_dict[temp]['status']:
all_menu_dict[temp]['status'] = True
temp = all_menu_dict[temp]['parent_id']
if not temp:
break
# 将当前权限前辈opened=True
if item['opened']:
temp1 = pid # 1.父亲ID
while not all_menu_dict[temp1]['opened']:
all_menu_dict[temp1]['opened'] = True
temp1 = all_menu_dict[temp1]['parent_id']
if not temp1:
break
# ############ 处理菜单和菜单之间的等级关系 ############
result = []
for row in all_menu_list:
pid = row['parent_id']
if pid:
all_menu_dict[pid]['child'].append(row)
else:
result.append(row)
##################### 结构化处理结果 #####################
for row in result:
print(row['caption'],row['status'],row['opened'],row)
def menu_tree(menu_list):
tpl1 = """
<div class='menu-item'>
<div class='menu-header'>{0}</div>
<div class='menu-body {2}'>{1}</div>
</div>
"""
tpl2 = """
<a href='{0}' class='{1}'>{2}</a>
"""
menu_str = ""
for menu in menu_list:
if not menu['status']:
continue
# menu: 菜单,权限(url)
if menu.get('url'):
# 权限
menu_str += tpl2.format(menu['url'],'active' if menu['opened'] else "",menu['caption'])
else:
# 菜单
if menu['child']:
child_html = menu_tree(menu['child'])
else:
child_html = ""
menu_str += tpl1.format(menu['caption'], child_html,"" if menu['opened'] else 'hide')
return menu_str
menu_html = menu_tree(result)
return menu_html
# simple_tag
def css():
v = """
<style>
.hide{
display: none;
}
.menu-body{
margin-left: 20px;
}
.menu-body a{
display: block;
}
.menu-body a.active{
color: red;
}
</style>
"""
return v
# simple_tag
def js():
v = """
<script>
$(function(){
$('.menu-header').click(function(){
$(this).next().removeClass('hide').parent().siblings().find('.menu-body').addClass('hide');
})
})
</script>
"""
return v
a:封装以后调用
加入中间件
#自定义的中间件加入到settingsettings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'rbac.middleware.md.RbacMiddleware',
]
#后端
from rbac import service
1. 用户登录后,拿到用户的ID,调用permission_session()函数(函数代码还没写)
函数获取用户角色的权限,格式如:
“”“
user_permission_dict = {
'/ah-index.html': ["GET","POST","DEL","Edit"],
'/order.html': ["GET","POST","DEL","Edit"],
'/index-(\d+).html': ["GET","POST","DEL","Edit"],
}
”“”
def login():
permission_session(用户ID,request)
return .....
2.setting中加入中间件,如上
3.#获取菜单
current_url= request.pathinfo
menu_list = service.menu(用户ID,current_url)
4.尽量用simple_tag
css = servicr.css()
js = servicr.js()
5.前端
{{ css|safe }}
{{ menu_list|safe }}
{{ js|safe }}
a. RBAC原理:
原理:RBAC(基于角色的权限管理原理及其使用)
1. 简单管理
角色单一,无需使用权限管理
2. 角色多管理(权限)
a. 登录
session放置用户信息(检测是否已经登录)
session放置权限信息(检测是否有权访问)
{
'/index.html':[GET,EDIT],
'/order.html':[GET,EDIT],
'/xxx.html':[GET,EDIT...],
'/xxx.html':[GET,EDIT...],
'/xxx.html':[GET,EDIT...],
'/xxx.html':[GET,EDIT...],
'/xxx.html':[GET,EDIT...],
'/xxx.html':[GET,EDIT...],
'/xxx.html':[GET,EDIT...],
'/xxx.html':[GET,EDIT...],
'/xxx.html':[GET,EDIT...],
'/xxx.html':[GET,EDIT...],
'/xxx.html':[GET,EDIT...],
}
session放置菜单权限信息(用于生成动态多级菜单)
b. 访问网站其他功能: http://www.baiuc.om/xxx.hmtl
- 获取当前访问的URL, request.path_info
-
/xxx.hmtl?md=get
匹配1
/xxx.hmtl
session放置权限信息(检测是否有权访问)
{
'/index.html':[GET,EDIT],
'/order.html':[GET,EDIT],
'/xxx.html':[GET,EDIT...],
}
匹配2
/xxx.hmtl
session放置权限信息(检测是否有权访问)
{
'/index.html':[GET,EDIT],
'/order.html':[GET,EDIT],
'/xxx.html':[GET,EDIT...],
}
request.permission_code = "EDIT"
request.permission_code_list = [GET,EDIT...]
PS: 中间件实现
c. 视图函数
def xxx(request):
request.permission_code = "EDIT" # 业务逻辑的编写
request.permission_code_list = [GET,EDIT...] # 前端显示功能按钮
d. 模板
e. 创建动态菜单【多级菜单】
session中获取菜单权限信息(用于生成动态多级菜单)
- 当前用户权限
- 所有菜单
1. 权限挂到菜单上
2. 菜单父子关系处理
3. 递归生成菜单
辅助:
css
js
推荐:simple_tag
使用:【详细见README】
1. 导入rbac
- 表
- 中间件
- service
- simple_tag
2. 注册app
3. 用户登录
初始化权限信息: service.initail_permission(request,user_id)
4. settings中配置中间件
5. {% rbac_menu reqeust %}
其他配置:
...
具体功能:b. 应用实例
临时工:
报障单 trouble.html
查看列表 look
查看详细 detail
普通员工:
报障单 trouble.html
创建 post
删除 del
修改 edit
查看列表 look
查看详细 detail
运维人员:
报账单
解决报障单 trouble-kill.html
总监:
报障单
解决报障单
报表 report.html