一個項目能夠有多個應用;一個應用能夠在多個項目下;前提:應用是組件。html
一個包含正則表達式的url就是一個權限。python
能夠理解爲以下方程式:正則表達式
who what how ---------->True or Flase
1)用戶表UserInfor數據庫
id 用戶名 操做級別 1 admin 5 2 user 1
2)權限表Permissiondjango
id 操做 操做級別 1 select 1 2 add 2 3 del 3
1)用戶表bash
name pwd egon 123 alex 456
2)權限表session
id url title 1 "/users/" "查看用戶" 2 "/users/add/" "添加用戶" 3 "/customer/add" "添加客戶"
3)用戶與權限關係表(多對多)app
id user_id permission_id 1 1 1 2 1 2 3 2 2
def users(request): user_id = request.session.get("user_id") # 基於對象的跨表查詢 obj = UserInfor.objects.filter(pk=user_id).first() # obj.permission.all() 拿到這個用戶全部的關聯權限 obj.permission.all().valuelist("url") return HttpResponse("users...")
能夠看到egon擁有查看用戶權限,所以能夠正常訪問http://127.0.0.1:8000/users/。函數
這種方式是給人定權限,每每每一個人擁有多個權限,那每一個人都要在用戶與權限關係表擁有多條權限記錄,現實中每每不少人有相同的職責和權限,則權限記錄還要根據人數翻倍,數據庫存儲的數據量過大。網站
所以應該給角色定權限,直接給人分配角色。
1)userInfor用戶表
name pwd egon 123 alex 456
2)Role角色表
id title 1 銷售員
3)UserInfo2Role用戶角色關係表(多對多)
id user_id role_id 1 1 1
4)Permission權限表
id url title 1 "/users/" "查看用戶" 2 "/users/add/" "添加用戶" 3 "/customer/add" "添加客戶"
5)Role2Permission角色權限關係表(多對多)
id role_id permission_id 1 1 1 2 1 2 3 1 3
以上就設計了一個簡單的rbac數據表。
rbac就是role-based access control,也就是基於角色的訪問控制。
因爲要建立的權限組件是一個獨立組件,新建一個rbac應用:
(1)運行Tools——》Run manage.py Task...
(2)在manage命令框執行:startapp rbac
manage.py@learn_rbac > startapp rbac bash -cl "/Users/hqs/venv/bin/python /Applications/PyCharm.app/Contents/helpers/pycharm/django_manage.py startapp rbac /Users/hqs/PycharmProjects/learn_rbac"
(3)在settings.py的INSTALLED_APPS中添加這個應用:
INSTALLED_APPS = [ ... 'django.contrib.staticfiles', 'app01.apps.App01Config', 'rbac.apps.RbacConfig', ]
這個沒有添加會致使models裏的內容找不到等問題。
/rbac/models.py:
class User(models.Model): name = models.CharField(max_length=32) pwd = models.CharField(max_length=32) roles = models.ManyToManyField(to="Role") def __str__(self): return self.name class Role(models.Model): title = models.CharField(max_length=32) url = models.CharField(max_length=32) permissions = models.ManyToManyField(to="Permission") def __str__(self): return self.title class Permission(models.Model): title = models.CharField(max_length=32) url = models.CharField(max_length=32) def __str__(self): return self.title
接着進行數據遷移:
$ python3 manage.py makemigrations $ python3 manage.py migrate
(venv)MacBook-Pro:learn_rbac hqs$ python3 manage.py createsuperuser Username (leave blank to use 'hqs'): yuan Email address: Password: Password (again): Superuser created successfully.
建立好新的超級用戶的帳號密碼:yuan/yuan1234,訪問並登陸http://127.0.0.1:8000/admin
/rbac/admin.py:
from django.contrib import admin # Register your models here. from .models import * admin.site.register(User) admin.site.register(Role) admin.site.register(Permission)
註冊完成頁面顯示以下:
建立了CEO、銷售、保潔三種角色:CEO具有全部權限、銷售具有查看和添加用戶權限、保潔只有查看用戶權限。
建立三名用戶,yuan分配CEO角色、egon分配保潔、alex分配保潔和銷售角色。
from django.contrib import admin from django.urls import path from app01 import views urlpatterns = [ path('admin/', admin.site.urls), path('users/', views.users), path('users/add/', views.add_user), path('roles/', views.roles), path('login/', views.login), ]
from django.shortcuts import render, HttpResponse # Create your views here. from rbac.models import * def users(request): user_list = User.objects.all() return render(request, "users.html", locals()) def add_user(request): return HttpResponse("add user....") def roles(request): role_list = Role.objects.all() return render(request, "roles.html", locals())
learn_rbac/app01/views.py:
def login(request): if request.method == "POST": user = request.POST.get("user") pwd = request.POST.get("pwd") user = User.objects.filter(name=user, pwd=pwd).first() if user: # 保存登陸狀態,request.session ############### 在session中註冊用戶id ################ request.session["user_id"] = user.pk ############### 在sessions中註冊權限列表 ############# # 登陸成功 # 查詢當前登陸用戶的全部角色 ret = user.roles.all() print(ret) # <QuerySet [<Role: 保潔>, <Role: 銷售>]> # 查看當前用戶全部的權限 # 一、用values()來遍歷QuerySet; 二、跨表查詢 三、distinct去重 permissions = user.roles.all().values("permissions__url").distinct() print(permissions) # <QuerySet [{'permissions__url': '/users/'}, {'permissions__url': '/users/add'}]> permission_list = [] for item in permissions: permission_list.append(item["permissions__url"]) print(permission_list) # ['/users/', '/users/add'] # ret = user.roles.all().values("title", "permissions__url") # print(ret) # <QuerySet [{'title': '保潔', 'permissions__url': '/users/'}, {'title': '銷售', 'permissions__url': '/users/'}, {'title': '銷售', 'permissions__url': '/users/add'}]> request.session["permission_list"] = permission_list return HttpResponse("登陸成功!") return render(request, "login.html")
注意:
(1)用request.session保存登陸狀態,在session中註冊用戶ID:
request.session["user_id"] = user.pk
在sessions中註冊權限列表:
request.session["permission_list"] = permission_list
(2)user.roles.all()查詢到當前登陸用戶的全部角色:<QuerySet [<Role: 保潔>, <Role: 銷售>]>
user.roles.all().values("permissions__url")查詢到當前用戶全部權限,這裏須要注意三點:1.用values()來遍歷QuerySet; 2.跨表查詢 3.values不會去重,須要用distinct去重。
(3)針對vlues的解析:
ret = user.roles.all().values("title", "permissions__url") print(ret) # <QuerySet [{'title': '保潔', 'permissions__url': '/users/'}, {'title': '銷售', 'permissions__url': '/users/'}, {'title': '銷售', 'permissions__url': '/users/add'}]> """上面的代碼能夠解釋爲以下步驟: temp = [] for role in user.roles.all(): temp.append({ "title": role.title, "permissions_url": role.permissions.url, }) """
from rbac.models import * import re def add_user(request): permission_list = request.session["permission_list"] # ['/users/', '/users/add', '/users/delete/(\\d+)', '/users/edit/(\\d+)'] current_path = request.path_info # 當前路徑的屬性 flag = False for permission in permission_list: permission = "^%s$" % permission ret = re.match(permission, current_path) # 第一個參數是匹配規則,第二個參數是匹配項 if ret: flag = True break if not flag: return HttpResponse("沒有訪問權限!") def roles(request): permission_list = request.session["permission_list"] current_path = request.path_info # 當前路徑的屬性 flag = False for permission in permission_list: permission = "^%s$" % permission ret = re.match(permission, current_path) # 第一個參數是匹配規則,第二個參數是匹配項 if ret: flag = True break if not flag: return HttpResponse("沒有訪問權限!") role_list = Role.objects.all() return render(request, "roles.html", locals())
注意:
(1)利用re.match讓匹配項去匹配匹配規則,確認用戶是否有對應的url即權限。
import re ret = re.match('/users/', "/users/delete/9") print(ret) # 匹配成功:<_sre.SRE_Match object; span=(0, 7), match='/users/'> # 這兩個字段一個是查詢、一個是刪除權限,應該是不匹配成功的 ret = re.match('^/users/$', "/users/delete/9") print(ret) # 匹配失敗:None
第一個參數是匹配規則,第二個參數是匹配項,須要注意給匹配規則添加^和$來確保匹配規則正常生效。
(2)因爲刪除路徑通常是"/user/delete/數字"的形式,所以在添加編輯、刪除等權限是使用的url以下所示:
(3)驗證這條url是否在權限組中方式:
l = ['/users/', '/users/add', '/users/delete/(\d+)', '/users/edit/(\d+)'] c_path = "/users/delete/9" flag = False for permission in l: permission = "^%s$" % permission ret = re.match(permission, c_path) if ret: # 匹配成功有一個對象 flag = True if flag: print("success")
(4)能夠看到這樣須要在每一個視圖函數中添加一樣的代碼來實現權限控制,所以仍是須要基於中間件來實現權限校驗。
import re from django.utils.deprecation import MiddlewareMixin from django.shortcuts import HttpResponse, redirect class ValidPermission(MiddlewareMixin): def process_request(self, request): # 當前訪問路徑 current_path = request.path_info # 當前路徑的屬性 ########### 檢查是否屬於白名單 ############# valid_url_list = ['/login/', '/reg/', '/admin/.*'] for valid_url in valid_url_list: ret = re.match(valid_url, current_path) if ret: return # 等同於return none ############### 檢驗是否登陸 ############## user_id = request.session.get("user_id") if not user_id: return redirect("/login/") ################ 校驗權限 ################# permission_list = request.session.get("permission_list") flag = False for permission in permission_list: permission = "^%s$" % permission ret = re.match(permission, current_path) # 第一個參數是匹配規則,第二個參數是匹配項 if ret: flag = True break if not flag: # 若是沒有訪問權限 return HttpResponse("沒有訪問權限!")
注意:
(1)在這裏建立了/rbac/service子目錄,並在目錄下建立rbac.py來自定義一箇中間件。
(2)在settings中添加自定義中間件:
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', ... 'rbac.service.rbac.ValidPermission', ]
(3)白名單,不須要任何權限局能訪問的url
valid_url_list = ['/login/', '/reg/', '/admin/.*'] for valid_url in valid_url_list: ret = re.match(valid_url, current_path) if ret: return # 等同於return none
views.py:
from rbac.service.permissions import * def login(request): if request.method == "POST": user = request.POST.get("user") pwd = request.POST.get("pwd") user = User.objects.filter(name=user, pwd=pwd).first() if user: # 保存登陸狀態,request.session ############### 在session中註冊用戶id ################ request.session["user_id"] = user.pk ############### 在sessions中註冊權限列表 ############# initial_session(user, request) return HttpResponse("登陸成功!") return render(request, "login.html")
/rbac/service/permissions.py:
def initial_session(user,request): """ 查看當前用戶全部的權限 :param user: :param request: :return: """ permissions = user.roles.all().values("permissions__url").distinct() print(permissions) # <QuerySet [{'permissions__url': '/users/'}, {'permissions__url': '/users/add'}]> permission_list = [] for item in permissions: permission_list.append(item["permissions__url"]) print(permission_list) request.session["permission_list"] = permission_list