初识rbac
一、权限组件
1、项目与应用
一个项目可以有多个应用;一个应用可以在多个项目下;前提:应用是组件。
2、什么是权限?
一个包含正则表达式的url就是一个权限。
可以理解为如下方程式:
who what how ---------->True or Flase
3、以路飞网站为例,哪些人拥有哪些权限如何设计表
(1)初始设计
1)用户表UserInfor
id 用户名 操作级别 1 admin 5 2 user 1
2)权限表Permission
id 操作 操作级别 1 select 1 2 add 2 3 del 3
(2)根据url就是一个权限将表关系做如下修改
1)用户表
name pwd egon 123 alex 456
2)权限表
id url title 1 "/users/" "查看用户" 2 "/users/add/" "添加用户" 3 "/customer/add" "添加客户"
3)用户与权限关系表(多对多)
id user_id permission_id 1 1 1 2 1 2 3 2 2
(3)以登录人egon为例访问url:http://127.0.0.1:8000/users/
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/。
(4)这种实现方式存在的问题?
这种方式是给人定权限,往往每个人拥有多个权限,那每个人都要在用户与权限关系表拥有多条权限记录,现实中往往很多人有相同的职责和权限,则权限记录还要根据人数翻倍,数据库存储的数据量过大。
因此应该给角色定权限,直接给人分配角色。
4、给角色定权限
(1)设计表和表关系
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数据表。
(2)什么是rbac?
rbac就是role-based access control,也就是基于角色的访问控制。
二、构建一个独立的权限管理组件(随时插拔使用)
1、创建一个django项目,建立应用app01和rbac
由于要创建的权限组件是一个独立组件,新建一个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里的内容找不到等问题。
2、构建models.py
/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
3、运用Django的admin组件进行后台数据管理
(1)创建超级用户来登录和使用admin组件
(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
(2)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)
注册完成页面显示如下:
(3)admin页面添加权限、角色、用户
创建了CEO、销售、保洁三种角色:CEO具备所有权限、销售具备查看和添加用户权限、保洁只有查看用户权限。
创建三名用户,yuan分配CEO角色、egon分配保洁、alex分配保洁和销售角色。
(4)编辑/learn_rbac/learn_rbac/urls.py
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), ]
(5)编辑/learn_rbac/app01/views.py
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())
4、登录验证(session permission_list)
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: 销售>]> # 查看当前用户所有的权限 # 1、用values()来遍历QuerySet; 2、跨表查询 3、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, }) """
5、权限校验
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)可以看到这样需要在每个视图函数中添加同样的代码来实现权限控制,因此还是需要基于中间件来实现权限校验。
6、基于中间件的权限校验
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
8、分拆login视图函数,将查看当前用户所有的权限取出作为一个模块引用使用
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