概述
RBAC : 基于角色的權限訪問控制(Role-Based Access Control)希停,通過角色綁定權限下隧,然后給用戶劃分角色咨演。在web應用中辰企,可以將權限理解為url风纠,一個權限對應一個url。
基于角色的訪問控制方法(RBAC)的顯著的兩大特征是:
1.由于角色/權限之間的變化比角色/用戶關系之間的變化相對要慢得多蟆豫,減小了授權管理的復雜性议忽,降低管理開銷。
2.靈活地支持企業(yè)的安全策略十减,并對企業(yè)的變化有很大的伸縮性。
實現(xiàn)步驟
1 創(chuàng)建項目愤估,包含兩個應用app01
帮辟,rbac
2 setting中配置:
INSTALLED_APPS DATABASES STATICFILES_DIRS
3 設計表關系
基于上述分析,在設計表關系時玩焰,起碼要有5張表:用戶由驹,角色,權限,權限組蔓榄,菜單:
- 用戶可以綁定多個角色并炮,從而實現(xiàn)靈活的權限組合 :用戶和角色,多對多關系
- 每個角色下甥郑,綁定多個權限逃魄,一個權限也可以屬于多個角色:角色和權限,多對多關系
- 多個權限附屬在一個權限組下澜搅,一個權限組下可以有多個權限:權限和權限組伍俘,多對一關系
- 一個菜單包含多個權限組:權限組和菜單,多對一關系
- 一個菜單下可能有多個子菜單勉躺,也可能有一個父菜單:菜單和菜單是自引用關系
在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 錄入數(shù)據(jù)
models.Role.objects.create(name='CEO')
models.Role.objects.create(name='總監(jiān)')
models.Role.objects.create(name='經(jīng)理')
models.Role.objects.create(name='業(yè)務員')
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='總監(jiān)').permissions.add(1,2,5,6)
models.Role.objects.get(name='經(jīng)理').permissions.add(1,5)
models.Role.objects.get(name='業(yè)務員').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
用來執(zhí)行初始化權限的操作:用戶登錄后妨退,取出其權限及所屬菜單信息,寫入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數(shù)據(jù)結構如下
{
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 數(shù)據(jù)結構如下
[
{'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': '菜單二'}
]
"""
#初步處理數(shù)據(jù)碧注,在自定義標簽中生成左側菜單數(shù)據(jù)
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
逝变,在其中實現(xiàn)檢查用戶權限,控制訪問:
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作展開顯示弥臼,其余菜單折疊宴咧;
- 第三,菜單的層級是不確定的(而且径缅,后面要實現(xiàn)權限的后臺管理掺栅,允許管理員添加菜單和權限);
在rabc應用的目錄下新建templatetags
目錄纳猪,寫一個腳本my_tags.py
氧卧,寫一個函數(shù)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 數(shù)據(jù)結構
[
{'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 數(shù)據(jù)結構
{
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數(shù)據(jù)結構:
{
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 # 錄入數(shù)據(jù)
- 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.*',
]