概述
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_permission
def 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 defaultdict
from django.conf import settings
def 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 re
from django.conf import settings
from django.shortcuts import HttpResponse,render,redirect
class 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 response
class 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 re
from django.template import Library
from 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.*',
]
网友评论