后台系统设计——角色权限

一、前言

不论是哪种后台管理系统,“人员权限”始终是绕不开的话题。无论是移动端,PC端产品,登陆都需要一个账号。只是对于C端的产品,大多都是用户自己注册即可。

而对于后台产品而言,是需要公司内部人员去创建账号的。每个使用系统的用户都有一个独一无二的账号,每个账号都有自己对应的权限。

多数情况下,除了超级管理员外,我们会对大多数的账号的权限做一些限制,以此来管理不同用户的使用权限问题。

譬如,做企业使用类软件,不同部门、不同职位的人的权限是不同的;再例如一款收费产品的收费用户和免费用户权限也是迥然不同的。

如果每个用户都单独做权限控制的话,当系统用户体量非常大的时候,就会发现以下问题:

很多账号权限都是一样的,但每次都要再配一次;

当某类权限用户的权限需要修改时,无法批量修改,只能一个个去修改非常耗时;

二、经典模型——RBAC

这时候,聪明的产品先人就创建了“角色”的概念,通过对权限集的抽象,创立了角色,通过修改角色的权限,来控制拥有该角色的人员账号的权限。

1、RBAC——基于角色的访问控制(Role-Based Access Control )

其基本思想是,对系统操作的各种权限不是直接授予具体的用户,而是在用户集合与权限集合之间建立一个角色集合。每一种角色对应一组相应的权限。一旦用户被分配了适当的角色后,该用户就拥有此角色的所有操作权限。

这样做的好处是,不必在每次创建用户时都进行分配权限的操作,只要分配用户相应的角色即可,而且角色的权限变更比用户的权限变更要少得多,这样将简化用户的权限管理,减少系统的开销。

按照百度百科对RBAC的定义,我们可以理解为此模型是通过角色关联用户,角色关联权限的方式,间接赋予用户权限。

2.基于 Django 的后台管理平台,采用 RBAC 权限管理机制

  • 用户表、角色表、权限表、资源列表
    • 用户表
    • 角色表
    • 资源列表(路径正则):资源名称(项目模块名称),资源路径(后台路由)
      • 资源分类:商品模块、订单模块、营销模块、权限模块、内容模块、其他模块
    • 权限表:对某一个路由的增删改查权限
    • 20201221200059924

3. RBAC角色权限管理机制实现思路

# 面向资源编程
https://www.shiyanlou.com/v1/books/        # 请求后端 books书籍表中数据
	get
    post
# 用户表
# 角色表
# 权限表
get/post/put/delete 对应关系
  • 所有权限的本质是对数据库中表中数据增删改查的操作
  • 而这些增删改查的操作是通过前端不同路由,通过get、post、put、delete方法操作数据库的
  • 对权限的控制,最简单的方法就是判断当前用户是否可以对指定路由请求操作的权限
  • 把角色和这个角色能够访问的 url 和 请求方式进行关联(因为正是的业务逻辑用户权限划分力度可能非常细致)
  • 再简单的业务逻辑中这一张表就是权限表
路由 资源(可能对应的是后端路由的 name名称,可以通过name名称解析出对应路由) 请求方式 说明
https://www.shiyanlou.com/v1/books/ get 判断用户是否可以查询books表中数据
https://www.shiyanlou.com/v1/books/ post 判断用户是否可以添加books表中数据
https://www.shiyanlou.com/v1/books/ put 判断用户是否可以更新books表中数据
https://www.shiyanlou.com/v1/books/ delete 判断用户是否可以删除books表中数据
  • 后端如何判断用户权限

4. 代码实现

表的设计

2020122120045577
from django.contrib.auth.models import AbstractUser
from django.db import models
from utils.basemodel import Base


# Create your models here.
# 角色表
class Role(models.Model):
    name = models.CharField(max_length=32)

    class Meta:
        db_table = '角色'


# 用户表
class User(AbstractUser):
    username = models.CharField(max_length=32, unique=True)
    password = models.CharField(max_length=132)
    email = models.CharField(max_length=32, null=True, blank=True)
    per = models.CharField(max_length=32, null=True, blank=True)
    role = models.ForeignKey(Role, on_delete=models.CASCADE, null=True, blank=True)

    class Meta:
        db_table = "用户"

    def __str__(self):
        return self.username



# 节点表
class Node(models.Model):
    name = models.CharField(max_length=32)

    class Meta:
        db_table = '节点表'


# 权限表
class Access(models.Model):
    role = models.ForeignKey(Role, on_delete=models.CASCADE)
    node = models.ForeignKey(Node, on_delete=models.CASCADE)

    class Meta:
        db_table = '权限表'

在自定义中间件 添加判断权限

我们在响应视图前添加如下判断

我们在访问/user/blacklist/时进行判断

获取token解码获取当前用户信息 进行关联查询

from rest_framework_jwt.utils import jwt_decode_handler
from django.utils.deprecation import MiddlewareMixin

# 判断用户属于的角色是否有权限
class JurisdictionMyMiddleware(MiddlewareMixin):
    # redis
    def process_view(self, request, view_func, view_args, view_kwargs):
        if request.path == "/user/blacklist/":
            try:
                # 获取token
                token = request.META.get('HTTP_AUTHORIZATION')[4:]
                # 获取userid
                user_id = jwt_decode_handler(token).get("user_id")
                # 获取用户id角色
                role_id = User.objects.filter(pk=user_id).first().role_id
                # 获取角色的权限(路由)
                path = Access.objects.filter(role_id=role_id)
                i = [i.node.name for i in path if request.path == i.node.name]
                # 判断当前用户有没有权限
                if not i:
                    return HttpResponse("No")
            except Exception as e:
                print(e)
                return

三、位运算实现权限管理

1. 位运算

程序中的所有数在计算机内存中都是以二进制的形式储存的。位运算就是直接对整数在内存中的二进制位进行操作。比如,and运算本来是一个逻辑运算符,但整数与整数之间也可以进行and运算。举个例子,6的二进制是110,11的二进制是1011,那么6 and 11的结果就是2,它是二进制对应位进行逻辑运算的结果(0表示False,1表示True,空位都当0处理)。

20201221202731845

二进制

print(0b001)
print(0b011)
print(0b100)
print(0b110)
print(0b111)


# 打印结果
1
3
4
6
7

2. 简单demo事例

user = 0b110
permisssion = {'usermange': 0b100, 'blacklist': 0b010, 'login': 0b001}


def my_decorator(func):
    def wrapper(*args):
        if user & permisssion[args[0]]:
            func(*args)
        else:
            print("没权限")

    return wrapper


@my_decorator
def view(name):
    print('进入视图')
    pass


view("usermange")
view("blacklist")
view("login")

# 打印结果
进入视图
进入视图
没权限

3. 代码实现

在中间件加入判断 我们的permission也可以创建相应的表

# 使用位运算来判断权限
class JurisdictionMyMiddleware(MiddlewareMixin):
    # redis
    def process_view(self, request, view_func, view_args, view_kwargs):
        # 权限对应表
        permission = {'/user/blacklist/': 0b100, '/user/user/': 0b010, '/user/user//': 0b100}
        if request.path == "/user/blacklist/":
            try:
                # 获取当前用户的权限
                per = User.objects.get(
                pk=jwt_decode_handler(token=request.META.get('HTTP_AUTHORIZATION')[4:]).get("user_id")).per
                # print(not bin(int(per)))   # False
                # print(permission[request.path]) # 4
                # 判断当前用户的权限 是否在 权限对应表中 有相应的权限
                if not int(per) & permission[request.path]:
                    print(123)
                    return HttpResponse("No")
            except Exception as e:
                print("utils/mymidder--142", e)
                return

在实际开发中,往往一个类对象拥有多种权限,每种权限有两个状态即有和无,正常情况下,有多少个权限,就需要多少个字段保存相应状态,如果权限过多,那么这种方式显得极其笨重,最近学习了一种用位运算权限管理的方式,方便快捷,实现原理简单,大大简化操作,简单实现权限的管理