一. 權限概述
1. 認識權限
為什么要有權限? 因為權限讓不同的用戶擁有不同的功能. 權限可以對功能進行划分.
生活中處處有權限. 比如, 騰訊視頻會員才有觀看某個最新電影的權限, 你有房間鑰匙就有了進入這個房間的權限. 同樣, 程序開發過程中也有權限, 我們今天所說的權限指的是web網站權限, 對於不同用戶訪問web服務時應該有不同的功能. 例如: 一個公司有CEO, 銷售主管, 銷售等等, 不同的用戶能訪問的服務也不是完全相同的. 處於這樣的需求下, 我們就需要權限控制了.
2. 為什么要開發權限組件?
組件可以減少代碼的重復, 能提高我們的開發效率--開發一次組件, 那么在以后的項目中可以一直使用.
3. web開發中權限是指什么?
首先我們要認識到, web程序是通過 url 的切換來查看不同的頁面(功能)的, 所以權限指的其實就是一個含有正則表達式的URL, 對url控制就是對權限的控制.
結論: 一個人有多少權限就取決於他有多少個URL的訪問權限.
二. 權限表結構設計
1.版本一
按照生活中的實際情況來看, 一個用戶有多個權限, 一個權限也可以分配給多個用戶, 所以我們可以設計出下面的三張表:
分析: 設計完該表結構之后, 我們又考慮到這種情況: 如果我們再新增10個用戶, 每個用戶都有權限表中的2個權限, 這意味着我們就要在"權限和用戶關聯表"中新增40條記錄. 如果再拓展到我們的實際生活中, 又會出現怎樣的情況呢? 生活中一個web網站肯定不止2個url, 這就意味着每新增一個客戶, 我們就要在某些表中增加若干條記錄. 這樣做不僅加重的程序員工作任務, 而且造成了數據庫空間的嚴重浪費. 很顯然, 此表有待改進!
2. 版本二
觀察生活中的實際情況, 我們發現, 某些網站有普通用戶和VIP用戶, 部分網站的VIP用戶也分了等級. 此時我們思考: 這樣的做法實際上是給用戶分了類, 把我們的用戶分為了不同的角色. 是的, 此時我們再進一步聯想: 我們是否也可以對用戶進行角色划分, 然后再對不同的角色進行權限的分配呢?
- 分析:
- 一個人可以有多個角色
- 一個角色可以有多個人
- 一個角色可以有多個權限
- 一個權限可以分配給多個角色
以下是我們的表結構設計:
調整以后的表結構, 由原來的基於用戶的權限控制轉換成基於角色的權限控制(RBAC), 以后再進行權限分配時只需要給指定角色分配一次權限, 給眾多用戶分配指定角色即可.
三. Django項目中進行權限設置
步驟概述:
- 1.數據庫配置(使用Django自帶數據庫即可)
- 2.models.py文件: 創建三張表, 分別是用戶表, 角色表, 權限表
- 3.使用Django自帶的admin來對各個表進行數據添加
- 4.路由匹配(urls.py文件)
- 5.視圖函數(views.py文件)
- 6.自定義中間件
Django生命周期的回顧
在進行Django權限設置之前, 我們首先要回顧一下Django的生命周期:
1. models.py文件配置
- 創建項目
MyLuffyPermission
, app名稱為rbac
. rbac/models.py
: 我們進行權限表結構設計時, 共有5張表, 其中2張是關系表, 所以models.py文件中只有3個model, 分別是權限表, 角色表和用戶表.- 數據庫遷移指令
python manage.py makemigrations
python manage.py migrate
2. 使用Django自帶的admin對表進行數據添加
(1) 創建超級用戶
(2) 對rabc/admin.py
文件進行配置
from django.contrib import admin
from rbac import models
class PermissionModelAdmin(admin.ModelAdmin):
list_display = ['title', 'url'] # 展示的字段
list_editable = ['url'] # 編輯的字段
admin.site.register(models.Permission, PermissionModelAdmin)
admin.site.register(models.Role)
admin.site.register(models.User)
(3)啟動Django項目, 瀏覽器輸入127.0.0.1:8000/admin/
, 回車. 對數據庫進行相關信息的添加與修改.
3. rbac組件的開發
在rabc應用中創建一個middlewares的文件夾, 在該文件夾中新建rabc.py
, 它就是我們自定義的一個中間件, 作用是權限校驗.
rabc/middlewares/rabc.py
: 該中間件的編寫分三步走:- (1)獲取當前訪問的url
- (2)獲取當前用戶權限信息
- (3)權限校驗
- 具體代碼如下所示:
from django.utils.deprecation import MiddlewareMixin
from django.conf import settings
from django.shortcuts import HttpResponse
import re
class RabcMiddleware(MiddlewareMixin):
def process_request(self, request):
# 1.獲取當前訪問的URL
url = request.path_info
print(111111)
# 如果當前的url是白名單中的內容,return None,即允許該url通過校驗
for i in settings.WHITE_LIST:
if re.match(i, url): # 如果匹配成功
return # 通過校驗
# 2.獲取當前用戶的權限信息 注意session中的鍵值對是在登錄的時候(web/views/account)設置的
permission_list = request.session.get(settings.PERMISSION_SESSION_KEY)
print(222222)
# 3.權限的校驗
for i in permission_list:
# URL權限 匹配成功
if re.match(r"^{}$".format(i['permissions__url']), url):
print(333333)
return
# 校驗失敗,拒絕訪問
return HttpResponse('沒有訪問權限')
4. 路由匹配, 視圖函數
(1) MyluffyPermission/urls.py
(2) 創建一個名叫web
的app應用, 並在該應用中創建urls.py
文件
# 創建app
python manage.py startapp app的名稱
# 注冊app
找到MyluffyPermission/settings.py文件下的INSTALLED_APPS項,然后注冊app
(3) 在web/urls.py文件中寫我們的路由匹配
(4) 在web應用下創建一個名為views
的python包
, 對視圖函數進行分類, 每個角色的功能單獨用一個.py
文件來寫.
5. 登錄功能
在成功配置完我們的rbac組件之后(主要包括rbac/middlewares/rbac.py
和rbac/migrations/middles.py
), 我們現在開始進行對登錄功能的編寫.
(1)路由匹配: web/urls.py
from django.conf.urls import url
from web.views import account
urlpatterns = [
# 用戶登錄
url(r'^login/$', account.login, name='login'),
]
(2)視圖函數: web/views/account.py
from django.shortcuts import HttpResponse, redirect, render, reverse
from rbac import models
from django.conf import settings
def login(request):
if request.method == 'POST':
# 獲取提交的數據
user = request.POST.get('user')
pwd = request.POST.get('pwd')
# 數據庫匹配管理對象
obj = models.User.objects.filter(name=user, password=pwd).first()
if not obj: # 錯誤優先
return render(request, 'login.html', {'error_msg': '用戶名或密碼錯誤'})
# 登錄成功,保存用戶權限信息:
ret = obj.roles.all().filter(permissions__url__isnull=False).values(
'permissions__url',
'permissions__title',).distinct()
request.session[settings.PERMISSION_SESSION_KEY] = list(ret)
return HttpResponse("登錄成功")
return render(request, 'login.html')
對account.py
文件中其中兩行代碼的解釋:
ret = obj.roles.all().filter(permissions__url__isnull=False).values(
'permissions__url',
'permissions__title',).distinct()
"""
obj -- User對象
.roles.all() -- 通過User對象拿到該用戶的所有角色對象(roles)
.filter(permissions__url__isnull=False)
-- permissions__url ==> 角色對象(roles)跨表查詢Permission表的 URL權限(url)信息
-- isnull=False ==> isnull表示空,那么isnull=False表示不為空,也就是說 整個ORM語句篩選出了"URL權限不為空的角色對象"
.values('permissions__url','permissions__title')
-- 拿到角色的 URL權限和權限標題 組成的字典
.distinct() -- 去重
"""
request.session[settings.PERMISSION_SESSION_KEY] = list(ret)
# request.session[key] = value表示設置session數據
# 由ret=xxx.values(xxx)可知:ret是一個QuerySet對象.
# 由於request.session設置session時,會把數據轉換為json格式,而ret卻是一個QuerySet,因此我們把ret轉換為列表后,就可以設置session了
# PERMISSION_SESSION_KEY是我們自定義的一個配置信息,我們把它寫在settings.py文件里. 配置它的目的是:今后我們再次設置session時,它的key就是一個動態的可變的,如果需要修改key,我們只需要在settings.py文件里重新配置即可,而不再需要找到對應的函數,重復修改.從而達到簡化我們代碼修改的工作量.
(3)模板文件: web/templates/login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="post">
{% csrf_token %}
<p>
用戶名:<input type="text" name="user">
</p>
<p>
密碼:<input type="password" name="pwd">
</p>
<p style="color: red">{{ error_msg }}</p>
<button>登錄</button>
</form>
</body>
</html>