權限管理 RBAC
權限管理
1. 為什么要有權限?
2. 開發一套權限的組件。為什么要開發組件?
3. 權限是什么?
web 開發中 URL 約等於 權限
4. 表結構的設計
權限表
ID URL
1 /customer/list/
2 /customer/add/
用戶表
ID name pwd
1 ward 123
用戶和權限的關系表(多對多)
ID user_id permission_id
1 1 1
1 1 2
5. 寫代碼
1. 查詢出用戶的權限寫入session
2. 讀取權限信息,判斷是否有權限
最初版的權限管理梳理流程
表結構
from django.db import models
class Permission(models.Model):
"""
權限表
"""
title = models.CharField(max_length=32, verbose_name='標題')
url = models.CharField(max_length=32, verbose_name='權限')
class Meta:
verbose_name_plural = '權限表'
verbose_name = '權限表'
def __str__(self):
return self.title
class Role(models.Model):
name = models.CharField(max_length=32, verbose_name='角色名稱')
permissions = models.ManyToManyField(to='Permission', verbose_name='角色所擁有的權限', blank=True)
def __str__(self):
return self.name
class User(models.Model):
"""
用戶表
"""
name = models.CharField(max_length=32, verbose_name='用戶名')
password = models.CharField(max_length=32, verbose_name='密碼')
roles = models.ManyToManyField(to='Role', verbose_name='用戶所擁有的角色', blank=True)
def __str__(self):
return self.name
settings文件配置
# ###### 權限相關的配置 ######
PERMISSION_SESSION_KEY = 'permissions'
WHITE_URL_LIST = [
r'^/login/$',
r'^/logout/$',
r'^/reg/$',
r'^/admin/.*',
]其實權限就是用戶能夠訪問那些url,不能訪問那些url,我們所做的就是將每個不同身份的人
分配不同的url
在最初用戶登錄的時候就查詢出用戶的權限。並將此次權限存入到session中
為什么要存入session中啊,為了不重復讀取數據庫,存到session中
我們可以配置session然后將session存到緩存中(非關系型數據庫中)
這樣讀取的速度回很快
登錄成功后如何查看當前用戶的權限並將其寫入到session中
from django.shortcuts import render, HttpResponse, redirect, reverse
from rbac import models
from django.conf import settings
...
user = models.User.objects.filter(name=username, password=pwd).first()
# 登錄成功
# 將權限信息寫入到session
# 1. 查當前登錄用戶擁有的權限
permission_list = user.roles.filter(permissions__url__isnull=False).values_list(
'permissions__url').distinct()
# for i in permission_list:
# print(i)
# 2. 將權限信息寫入到session # 這里的鍵值我們做了全局配置
request.session[settings.PERMISSION_SESSION_KEY] = list(permission_list)
# 得到的permission_list是一個QuerySet的元組對象,因為session的存儲是有數據類型限制所以轉換為列表(列表中套元組)然后,該用戶能夠訪問那些,不能訪問那些,這時,我們可以將這個邏輯寫在中間件這里
from django.utils.deprecation import MiddlewareMixin
from django.conf import settings
from django.shortcuts import HttpResponse
import re
class PermissionMiddleware(MiddlewareMixin):
# 每一個請求來,都會走這個鈎子函數
def process_request(self, request):
# 對權限進行校驗
# 1. 當前訪問的URL
current_url = request.path_info
# 白名單的判斷我們這里將白名單設置在了settings中,往settings中加就ok
for i in settings.WHITE_URL_LIST:
if re.match(i, current_url):
return
# 2. 獲取當前用戶的所有權限信息
permission_list = request.session.get(settings.PERMISSION_SESSION_KEY)
# 3. 權限的校驗
print(current_url) # Django的session做了轉換將元組轉換成為一個列表
for item in permission_list:
url = item[0]
if re.match("^{}$".format(url), current_url):
return
else:
return HttpResponse('沒有權限')升級版
動態生成一級菜單
表結構的設計
from django.db import models
class Permission(models.Model):
"""
權限表
"""
title = models.CharField(max_length=32, verbose_name='標題')
url = models.CharField(max_length=32, verbose_name='權限')
# 用來判斷哪些url是菜單,哪些不是菜單
is_menu = models.BooleanField(default=False, verbose_name='是否是菜單')
# 記錄該菜單對應的圖標信息(這里是屬性樣式類)
icon = models.CharField(max_length=32, verbose_name='圖標', null=True, blank=True)
class Meta:
verbose_name_plural = '權限表'
verbose_name = '權限表'
def __str__(self):
return self.title
class Role(models.Model):
name = models.CharField(max_length=32, verbose_name='角色名稱')
permissions = models.ManyToManyField(to='Permission', verbose_name='角色所擁有的權限', blank=True)
def __str__(self):
return self.name
class User(models.Model):
"""
用戶表
"""
name = models.CharField(max_length=32, verbose_name='用戶名')
password = models.CharField(max_length=32, verbose_name='密碼')
roles = models.ManyToManyField(to='Role', verbose_name='用戶所擁有的角色', blank=True)
def __str__(self):
return self.name
注冊層成功之后:
user = models.User.objects.filter(name=username, password=pwd).first()
# 將權限信息寫入到session中
init_permission(request, user)def init_permission(request, user):
# 1. 查當前登錄用戶擁有的權限
permission_query = user.roles.filter(permissions__url__isnull=False).values(
'permissions__url',
'permissions__is_menu',
'permissions__icon',
'permissions__title'
).distinct()
print('permission_query', permission_query)
# 存放權限信息
permission_list = []
# 存放菜單信息
menu_list = []
for item in permission_query:
permission_list.append({'url': item['permissions__url']})
if item.get('permissions__is_menu'): # 如若菜單這個字段為True
# 將這個菜單的信息先存入一個字典,然后存入session
menu_list.append({
'url': item['permissions__url'], # 權限信息
'icon': item['permissions__icon'], # 圖標(Bootstrap的類樣式)
'title': item['permissions__title'], # 標題
})
# 2. 將權限信息寫入到session
request.session[settings.PERMISSION_SESSION_KEY] = permission_list
# 將菜單的信息寫入到session中
request.session[settings.MENU_SESSION_KEY] = menu_list母版中的菜單(一級菜單)
在母版中合適的位置導入這個include_tag
{% load rbac %}
{% menu request %}在templatetags下的rbac.py文件中寫(自定義過濾器)
import re
from django import template
from django.conf import settings
register = template.Library()
在templates下的rbac文件夾下創建enum.html
<div class="static-menu">
{% for item in menu_list %}
<a href="{{ item.url }}" class="{{ item.class }}">
<span class="icon-wrap"><i class="fa {{ item.icon }}"></i></span>{{ item.title }}</a>
{% endfor %}
</div>
<--這個代碼的樣式可以放到該app文件夾下的static下的css中建立一個menu.css-->因為將數據存入了session中,所以我們可以通過request.session.來獲取數據
.left-menu .menu-body .static-menu {
}
.left-menu .menu-body .static-menu .icon-wrap {
width: 20px;
display: inline-block;
text-align: center;
}
.left-menu .menu-body .static-menu a {
text-decoration: none;
padding: 8px 15px;
border-bottom: 1px solid #ccc;
color: #333;
display: block;
background: #efefef;
background:settings的配置
# ###### 權限相關的配置 ######
PERMISSION_SESSION_KEY = 'permissions'
MENU_SESSION_KEY = 'menus'
WHITE_URL_LIST = [
r'^/login/$',
r'^/logout/$',
r'^/reg/$',
r'^/admin/.*',
]
中間件的配置
在middlewares目錄(中間件目錄中)創建rbac.py文件
from django.utils.deprecation import MiddlewareMixin
from django.conf import settings
from django.shortcuts import HttpResponse
import re
class PermissionMiddleware(MiddlewareMixin):
def process_request(self, request):
# 對權限進行校驗
# 1. 當前訪問的URL
current_url = request.path_info
# 白名單的判斷(settings中配置好了)
for i in settings.WHITE_URL_LIST:
if re.match(i, current_url):
return
# 2. 獲取當前用戶的所有權限信息
permission_list = request.session.get(settings.PERMISSION_SESSION_KEY)
# 3. 權限的校驗
for item in permission_list:
url = item['url']
if re.match("^{}$".format(url), current_url):
return
else:
return HttpResponse('沒有權限')
應用rbac組件
1、拷貝rbac組件到新的項目中並注冊APP
2、配置權限的相關信息
# ###### 權限相關的配置 ######
PERMISSION_SESSION_KEY = 'permissions'
MENU_SESSION_KEY = 'menus'
WHITE_URL_LIST = [
r'^/login/$',
r'^/logout/$',
r'^/reg/$',
r'^/admin/.*',
]3、創建跟權限相關的表
執行命令
python3 manage.py makemigrations
python3 manage.py migrate
4、錄入權限信息
創建超級用戶
錄入所有權限信息
創建角色 給角色分權限
創建用戶 給用戶分角色
5、在登錄成功之后 寫入權限和菜單的信息到session中
6、配置上中間件,進行權限的校驗
7、使用動態菜單
<!-導入靜態文件-->
<link rel="stylesheet" href="{% static 'css/menu.css' %}">
使用inclusion_tag
<div class="left-menu">
<div class="menu-body">
{% load rbac %}
{% menu request %}
</div>
</div>
母版中的菜單(動態生成二級菜單)
信息管理
客戶列表
財務管理
繳費列表
User name pwd
Role name permissions(FK) 2user
Permission title(二) url menu(FK) 2role
Menu title(一)
Models.py
from django.db import models
class Menu(models.Model):
"""
一級菜單
"""
title = models.CharField(max_length=32, unique=True) # 一級菜單的名字
icon = models.CharField(max_length=32, verbose_name='圖標', null=True, blank=True)
class Meta:
verbose_name_plural = '菜單表'
verbose_name = '菜單表'
def __str__(self):
return self.title
class Permission(models.Model):
"""
權限表
有關聯Menu的二級菜單
沒有關聯Menu的不是二級菜單,是不可以做菜單的權限
"""
title = models.CharField(max_length=32, verbose_name='標題')
url = models.CharField(max_length=32, verbose_name='權限')
menu = models.ForeignKey('Menu', null=True, blank=True)
class Meta:
verbose_name_plural = '權限表'
verbose_name = '權限表'
def __str__(self):
return self.title
class Role(models.Model):
name = models.CharField(max_length=32, verbose_name='角色名稱')
permissions = models.ManyToManyField(to='Permission', verbose_name='角色所擁有的權限', blank=True)
def __str__(self):
return self.name
class User(models.Model):
"""
用戶表
"""
name = models.CharField(max_length=32, verbose_name='用戶名')
password = models.CharField(max_length=32, verbose_name='密碼')
roles = models.ManyToManyField(to='Role', verbose_name='用戶所擁有的角色', blank=True)
def __str__(self):
return self.name
登錄
from django.shortcuts import render, HttpResponse, redirect, reverse
from rbac import models
from django.conf import settings
import copy
from rbac.server.init_permission import init_permission
def login(request):
if request.method == 'POST':
username = request.POST.get('username')
pwd = request.POST.get('pwd')
user = models.User.objects.filter(name=username, password=pwd).first()
if not user:
err_msg = '用戶名或密碼錯誤'
return render(request, 'login.html', {'err_msg': err_msg})
# 登錄成功
# 將權限信息寫入到session
init_permission(request, user)
return redirect(reverse('customer'))
return render(request, 'login.html')
def init_permission(request, user):
# 1. 查當前登錄用戶擁有的權限
permission_query = user.roles.filter(permissions__url__isnull=False).values(
'permissions__url',
'permissions__title',
'permissions__menu_id',
'permissions__menu__title',
'permissions__menu__icon',
).distinct()
print(permission_query)
# 存放權限信息
permission_list = []
# 存放菜單信息
menu_dict = {}
for item in permission_query:
permission_list.append({'url': item['permissions__url']})
menu_id = item.get('permissions__menu_id')
if not menu_id:
continue
if menu_id not in menu_dict:
menu_dict[menu_id] = {
'title': item['permissions__menu__title'],
'icon': item['permissions__menu__icon'],
'children': [
{
'title': item['permissions__title'],
'url': item['permissions__url']}
]
}
else:
menu_dict[menu_id]['children'].append(
{'title': item['permissions__title'], 'url': item['permissions__url']})
# 2. 將權限信息寫入到session
request.session[settings.PERMISSION_SESSION_KEY] = permission_list
# 將菜單的信息寫入到session中
request.session[settings.MENU_SESSION_KEY] = menu_dict
將拿到的數據存入session
寫在一個自定義inclusion_tag
母版
{% load rbac %}
{% menu request %}rbac.py
import re
from django import template
from django.conf import settings
register = template.Library()
@register.inclusion_tag('rbac/menu.html')
def menu(request):
menu_list = request.session.get(settings.MENU_SESSION_KEY)
return {"menu_list": menu_list}
menu.html
<div class="multi-menu">
{% for item in menu_list.values %}
<div class="item">
<div class="title"><i class="fa {{ item.icon }}"></i> {{ item.title }}</div>
<div class="body hide">
{% for child in item.children %}
<a href="{{ child.url }}">{{ child.title }}</a>
{% endfor %}
</div>
</div>
{% endfor %}
</div>menu.css0
.static-menu .icon-wrap {
width: 20px;
display: inline-block;
text-align: center;
}
.static-menu a {
text-decoration: none;
padding: 8px 15px;
border-bottom: 1px solid #ccc;
color: #333;
display: block;
background: #efefef;
background: