关于RBAC
RBAC是基于角色的访问控制(Role-Based Access Control )在 RBAC 中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。这样管理都是层级相互依赖的,权限赋予给角色,而把角色又赋予用户,这样的权限设计很清楚,管理起来很方便。
RBAC实际上也就是 Who 、What、How之间的关系,也就是Who对What进行How的操作。
Who:是权限的拥有者或者主体
What:是具体的操作或对象(operation,object)
How:具体的权限(privilege,正向授权或负向授权)
关于权限
- 权限,如果一个网站没有权限管理的话,那么这个网站只要别人带着url即可访问的话,那就乱套了。
- 所以就有了权限管理。权限不一定就是rbac,但是rbac有其的优点
- 普通的权限
user表
id name pwd
1 张三 123
2 李四 456
3 王五 789
permission表
id title url
1 查看用户 /user/
2 添加用户 /user/add/
3 删除用户 /delete/user/(\d+)/
user_permission表
id user_id permission_id
1 1 1
2 1 2
3 1 3
4 2 1
5 2 2
6 2 3
7 3 1
8 3 2
9 3 3
按照普通的设计的话,光是这几个url的权限就够繁琐的了,下面来看看RBAC的好处
user表
id name pwd
1 张三 123
2 李四 456
3 王五 789
permission表
id title url
1 查看用户 /user/
2 添加用户 /user/add/
3 删除用户 /delete/user/(\d+)/
role表
id title
1 CEO
2 前台
3 IT
role_permission表
id role_id permission_id
1 1 1
2 1 2
3 1 3
1 2 1
2 2 2
1 3 1
2 3 2
3 3 3
user_role
id user_id role_id
1 1 1
2 2 2
3 3 3
不知道你们有没看懂这个的好处。
RBAC开端
- 新建一个Django项目(rbac_test):
- 创建一个名字为app01 的app,用于当做普通的项目
- 再创建一个名字为rbac的app,这个用于存放权限相关的东东,这就是rbac组件了。
- 数据库的话我用django 自带的sql3了
目录结构如图:
rbac的models.py的代码:
from django.db import models
# Create your models here.
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 Permission(models.Model):
title = models.CharField(max_length=32)
url = models.CharField(max_length=128)
roles = models.ManyToManyField(to='Role')
def __str__(self):
return self.title
class Role(models.Model):
title = models.CharField(max_length=32)
def __str__(self):
return self.title
上面rbac的表格设计好了,使用makemigrations 和migrate生成表格,然后我们需要在我们项目里面添加一些url,也就需要添加一些视图。
在app01的views里面加入:
from django.shortcuts import render, HttpResponse, redirect
# Create your views here.
from rbac.models import *
def login(request):
if request.method == "POST":
user = request.POST.get('name')
pwd = request.POST.get('pwd')
user_obj = User.objects.filter(name=user, pwd=pwd).first()
if user_obj:
return redirect('/user/')
return render(request, 'login.html')
return render(request, 'login.html')
def user(request):
return render(request, 'user.html')
def add_user(request):
return HttpResponse('添加用户成功!!!')
def role(request):
return render(request, 'role.html')
login.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>login</title>
</head>
<body>
<form action="" method="post">
{% csrf_token %}
用户名:<input type="text" name="name">
密码:<input type="password" name="pwd">
<button type="submit">登录</button>
{# <input type="submit">#}
</form>
</body>
</html>
为了测试登录功能,我们先使用createsuperuser 创建一个admin超级用户,密码为:admin1234,创建后就把我们创建的表添加进admin里面去
在rabc/admin.py添加这些代码即可:
from django.contrib import admin
from .models import *
# Register your models here.
admin.site.register(User)
admin.site.register(Role)
admin.site.register(Permission)
运行项目,你会发现,逐个用url打开,发现根本不需要权限即可访问,这样是不行的。
所以登录admin往里面添加我们需要的role, permission,user:
permission:
role:
添加完这些权限和对应的角色之后,就可以添加用户进行测试了:
user:
这些操作完成后就该进行逻辑代码的编写了;
首先我们要在 app01/views.py下的login函数:
def login(request):
if request.method == "POST":
user = request.POST.get('name')
pwd = request.POST.get('pwd')
user_obj = User.objects.filter(name=user, pwd=pwd).first()
if user_obj:
# 把 user的id放入session
request.session['user_id'] = user_obj.pk
# 我们先把用户对应的拥有的url添加取出放到session中备用(通过user_obj跨表到role再到permission拿到url)
role_obj = user_obj.roles.all()
url_list = role_obj.values(
'permissions__url') # 取出的url是queryset对象 <QuerySet [{'permissions__url': '/user/'}, {'permissions__url': '/add/user/'}, {'permissions__url': '/role/'}]>
permission_list = []
for i in url_list:
permission_list.append(i['permissions__url'])
print(permission_list)
request.session['permissions_list'] = permission_list
return redirect('/user/')
return render(request, 'login.html')
return render(request, 'login.html')
这里面就进行了每个登录用户拥有的url权限进行记录保存了。
然后我们要判断用户是否有权限访问了:
app01/views.py下面的add_user:
def add_user(request):
import re
# 从session取出url列表
permissions_list = request.session.get('permissions_list')
print(permissions_list)
# 取得当前的url用于和sesion中的进行比对
current_url = request.path_info
# 状态转换阀
trance = False
for i in permissions_list:
# 进行前后限定。使其匹配准确度为百分百
i = '^{0}$'.format(i)
match = re.match(i, current_url)
# 如果匹配成功,状态阀改为True,并跳出匹配
if match:
trance = True
break
# 根据状态阀来判断当前用户是否有访问权限
if not trance:
return HttpResponse('你没有权限访问!!!')
return HttpResponse('添加用户成功!!!')
然后我们分别来尝试一下权限是否好用,首先使用001用户进行登录,访问,发现CEO的权限完全没问题,可以访问到添加用户页面,但是使用002 用户就不行咯
每写一个视图函数就需要重复这个代码,你可能会说使用装饰器呀,但是还是要每个函数添加一行代码呀,作为一个组件就是要够通用呢,我们还有一个很好的地方可以使用,那就是中间件,添加到中间件只需一次添加即可全局使用。
所以我们需要把我们的代码添加到中间件。
我们写中间件参照session的中间件来写:
具体的中间件使用我就不解释了,改天再写一期中间件专场吧,毕竟中间件可是太强大了。这个设计。
回到我们代码:
我们首先在:rbac目录下创建rbac/my_code/permission.py里面编写:
# -*- coding: utf-8 -*-
# @Time : 2019/12/16 下午 04:09
# @Author : lh
# @Email : [email protected]
# @File : permission.py
# @Software: PyCharm
from django.shortcuts import HttpResponse
from django.utils.deprecation import MiddlewareMixin
class PermissionMiddleware(MiddlewareMixin):
# 我们这边只用到process_requst未使用到process_response.所以我就没写response这个函数了,但是也不影响
def process_request(self, request):
import re
# 从session取出url列表
permissions_list = request.session.get('permissions_list', [])
print(permissions_list)
# 取得当前的url用于和sesion中的进行比对
current_url = request.path_info
# 状态转换阀
trance = False
for i in permissions_list:
# 进行前后限定。使其匹配准确度为百分百
i = '^{0}$'.format(i)
match = re.match(i, current_url)
# 如果匹配成功,状态阀改为True,并跳出匹配
if match:
trance = True
break
# 根据状态阀来判断当前用户是否有访问权限
if not trance:
return HttpResponse('你没有权限访问!!!')
在settings.py中添加我们写好的中间件:
这个中间件还有位置要求,因为我们中间件使用了session,所以他的位置必须在’django.contrib.sessions.middleware.SessionMiddleware’,的后面,但是我直接把它放在最后了。也没问题的。
但是还有一bug,那就是如果我们有一些页面是不需要权限就可以访问的,比如注册登录,还有admin一系列页面。所以我们还需要添加一个白名单才可以呢!
所以对rbac/my_code/permission.py进行更改:
# -*- coding: utf-8 -*-
# @Time : 2019/12/16 下午 04:09
# @Author : lh
# @Email : [email protected]
# @File : permission.py
# @Software: PyCharm
import re
from django.shortcuts import HttpResponse
from django.utils.deprecation import MiddlewareMixin
class PermissionMiddleware(MiddlewareMixin):
# 我们这边只用到process_requst未使用到process_response.所以我就没写response这个函数了,但是也不影响
def process_request(self, request):
# 取得当前的url用于和sesion和白名单的进行比对
current_url = request.path_info
# 进行url白名单筛选
white_url = ['/login/', '/register/', '/admin/.*']
for url in white_url:
my_url = re.match(url, current_url)
if my_url:
# 返回空就不会继续走后面的代码了
return None
# 从session取出url列表
permissions_list = request.session.get('permissions_list', [])
print(permissions_list)
# 状态转换阀
trance = False
for i in permissions_list:
# 进行前后限定。使其匹配准确度为百分百
i = '^{0}$'.format(i)
match = re.match(i, current_url)
# 如果匹配成功,状态阀改为True,并跳出匹配
if match:
trance = True
break
# 根据状态阀来判断当前用户是否有访问权限
if not trance:
return HttpResponse('你没有权限访问!!!')
这样你想哪里url不需要权限添加进白名单即可了。
还有一个问题,那就是如果访问用户只是没有登录,你就不能直接说他们没有权限访问,而是要友好的跳转到登录页面。
所以在前面的代码继续添加几行即可:
# -*- coding: utf-8 -*-
# @Time : 2019/12/16 下午 04:09
# @Author : lh
# @Email : [email protected]
# @File : permission.py
# @Software: PyCharm
import re
from django.shortcuts import HttpResponse, redirect
from django.utils.deprecation import MiddlewareMixin
class PermissionMiddleware(MiddlewareMixin):
# 我们这边只用到process_requst未使用到process_response.所以我就没写response这个函数了,但是也不影响
def process_request(self, request):
# 取得当前的url用于和sesion和白名单的进行比对
current_url = request.path_info
# 进行url白名单筛选
white_url = ['/login/', '/register/', '/admin/.*']
for url in white_url:
my_url = re.match(url, current_url)
if my_url:
# 返回空就不会继续走后面的代码了
return None
# 判断用户是否登录使用前面session中的user_id
user_id = request.session.get('user_id')
if not user_id:
return redirect('/login/')
# 从session取出url列表
permissions_list = request.session.get('permissions_list', [])
# 状态转换阀
trance = False
for i in permissions_list:
# 进行前后限定。使其匹配准确度为百分百
i = '^{0}$'.format(i)
match = re.match(i, current_url)
# 如果匹配成功,状态阀改为True,并跳出匹配
if match:
trance = True
break
# 根据状态阀来判断当前用户是否有访问权限
if not trance:
return HttpResponse('你没有权限访问!!!')
既然是组件,我们需要把通用的代码给解耦处理才行
所以我们把它独立放在rbac/my_code/permission_url.py下面的initial_permission(request, user_obj)函数中,这样就独立出来。
这样一个基础的rbac组件就成型了。