基于Django REST framework的 接口级别权限 角色访问控制
- 自动权限类生成
- 自动权限表数据生成
- 自动权限校验
- 只需着重于权限的分配
- User - 用户表
- Role - 角色表 (可作为岗位)
- Group - 分组表 (可作为部门)
- Permissions - 权限表
- UserRole - 用户角色多对多中间表
- UserPermissions - 用户权限多对多中间表
- RolePermissions - 角色权限多对多中间表
- 表关联关系如下:
使用 db_constraint=False 无物理外键
“ 如果一个角色有些像鸭子、且有和鸭子一样游泳的权限、和鸭子一样叫的权限、和鸭子一样的所有权限,那么这个角色就是鸭子。”
- 角色(Role)在访问控制逻辑中不参与任何逻辑判断,只作为有同类权限用户的合集,方便同角色用户的权限继承
-
e.g.
客服角色拥有<访问查看客户接口>的权限
那么所有角色为客服的用户 就都继承有<访问查看客户接口>的权限
如果用户本身拥有<访问查看客户接口>的权限,那么当前用户在访问控制逻辑中就可以视为客服
-
- 只适用于 访问控制逻辑 不适用于 业务逻辑, 如接口内容为查看 角色为客服的所有用户 ,则无法获取到 拥有与客服完全相同权限 用户的数据
- 权限(Permission)为访问控制的 唯一核心 ,无论什么身份,只要用户拥有接口所需的权限,即可访问该接口,否则 访问会被拦截
- 用户(User)可通过计算属性 get_permissions 来获取由自己的权限 (UserRole) 和继承自角色(Role)的权限 (RolePermissions) 组成的列表
- * 进行访问控制前 必须 进行 用户认证,案例详见
Module_Auth.Authentications.RBAC_Authentications
=> DEMO- 使用 drf原生authentication ,用法不过多赘述,若不进行用户认证,则 无法 进行 权限自动验证
- 所有 接口 如果需要进行访问控制,都可以 / 应该 进行权限判断 (权限类可使用 新的action装饰器 快速创建,并自动生成对应数据,无需 反复手动创建drf权限类)
- 当接口 手动 / 自动 配置 权限类 后,便会在 Permission表 中 自动生成 对应该接口的 4条 权限数据 (分别为允许 GET/PUT/POST/DELETE 四种方式请求此接口的权限)
- 当用户访问此接口时(已通过drf的 authentication 验证的前提下),若该用户拥有 1.中生成 且与当前 请求方式对应 的权限(UserPermissions表),则可以访问该接口,否则 访问会被拦截
- django 2.2
- MySQL 8
- Django REST framework
- pymsql
- PyJWT
- 在
Module_Key
下创建key.py
文件 内容如下:
""" 修改token盐 数据库链接信息 抽离 settings.secret_key,settings.allowed_hosts """
RBAC_token_salt = ""
project_database = {
"default": {
"ENGINE": "django.db.backends.mysql",
"NAME": "",
"USER": "root",
"PASSWORD": "",
"HOST": "127.0.0.1",
"PORT": "3306"
}
}
project_secret_key = ''
project_allowed_hosts = []
- migrate并运行项目
- permissions表会 自动生成 10条权限数据(除 管理员权限 外,其余9条来自DEMO,详见代码 TODO 注释,可方便定位DEMO位置)
... | name | codeName |
---|---|---|
... | 管理员权限 | AdminPermission |
... | 获取全部用户信息 | GET_UserPermission |
... | 修改全部用户信息 | PUT_UserPermission |
... | 创建全部用户信息 | POST_UserPermission |
... | 删除全部用户信息 | DELETE_UserPermission |
... | 获取特定分组下用户信息 | GET_GroupUserPermission |
... | 修改特定分组下用户信息 | PUT_GroupUserPermission |
... | 创建特定分组下用户信息 | POST_GroupUserPermission |
... | 删除特定分组下用户信息 | DELETE_GroupUserPermission |
... | 获取指定角色用户 | GET_role_user |
- 数据库创建 测试用户 数据
- 为测试用户分配权限 (UserPermissions 表)
- 使用
/v1/RBAC/Login/
接口获取 token 👇
- 请求头中 携带token 即可访问DEMO中提供的路由(需要拥有对应权限,否则会被拦截) 👇
- DEMO提供 3个 案例路由,对应所需权限如下表(具体请查看 👇3. 权限表数据生成 ):
url | name | codeName |
---|---|---|
/v1/RBAC/user/ | 获取全部用户信息 | GET_UserPermission |
修改全部用户信息 | PUT_UserPermission | |
创建全部用户信息 | POST_UserPermission | |
删除全部用户信息 | DELETE_UserPermission | |
/v1/RBAC/user/group_user/ | 获取特定分组下用户信息 | GET_GroupUserPermission |
修改特定分组下用户信息 | PUT_GroupUserPermission | |
创建特定分组下用户信息 | POST_GroupUserPermission | |
删除特定分组下用户信息 | DELETE_GroupUserPermission | |
/v1/RBAC/user/role_user/ | 获取指定角色用户 | GET_role_user |
- 为用户分配某一权限,该用户即可用此权限对应的 请求方式 访问该权限对应的 接口
-
e.g.
角色拥有 <获取全部用户信息GET_UserPermission> 权限时 ,即可对 /v1/RBAC/user/ 接口进行 GET 请求
角色没有 <创建特定分组下用户信息POST_GroupUserPermission> 权限时 ,若对 /v1/RBAC/user/group_user/ 接口进行 POST 请求,则会被拦截
示例1:.../v1/RBAC/user/
示例2:.../v1/RBAC/user/role_user/
示例3:.../v1/RBAC/group_user/role_user/
- 路由对应的 含义 👇:
示例1:域名/版本号/模块名/所有用户/
示例2:域名/版本号/模块名/所有用户/指定为某一种角色的用户/
示例3:域名/版本号/模块名/某一分组的所有用户/指定为某一种角色的用户/
- 路由对应的 数据 👇:
示例1:域名/版本号/模块名/针对某张表的数据/
示例2:域名/版本号/模块名/针对某张表的数据/此表数据中的细分数据/
示例3:域名/版本号/模块名/针对某张表某个顶级分类的数据/此分类数据中的细分数据/
- 路由对应的 生成 👇:
示例1:域名/版本号/模块名/基于ModelViewSet的自动路由/
示例2:域名/版本号/模块名/基于ModelViewSet的自动路由/action生成的自动路由/
示例3:域名/版本号/模块名/基于ModelViewSet的自动路由/action生成的自动路由/
- 路由对应的 权限 👇:
示例1:域名/版本号/模块名/一级权限/
示例2:域名/版本号/模块名/一级权限/二级权限/
示例3:域名/版本号/模块名/一级权限/二级权限/
基础权限类分为两种,均继承自 drf BasePermission类, 仅供 自定义权限类 继承使用
- MainPermission
- 位置:
Module_Custom.Custom_Permission
=> MainPermission
- 位置:
- SecondaryPermission
- 位置:
Module_Custom.Custom_Permission
=> SecondaryPermission
- 位置:
请配置于
Module_Auth.Permissions
中(其中已有DEMORBAC_Permissions.py
)
权限类 必须要写 注释 具体原因请查看 👇3. 权限表数据生成
Module_Auth.Permissions
下所有模块中的类,都会在 Permissions表 中自动生成权限数据
- 具体生成格式请查看 👇3. 权限表数据生成
- 请确保
Module_Auth.Permissions
下所有模块中的类,都继承自基础权限类(MainPermission /SecondaryPermission ),以免产生无效权限表数据
所有继承自 MainPermission类 的权限类皆为 一级权限类
- 创建方法
- 直接继承 MainPermission类 ,如无需重写drf权限类的 has_permission 方法,内部直接 pass 即可
- 必须要写注释 ,注释内容为此权限的 释义 ,用作权限数据生成,具体请查看 👇3. 权限表数据生成
class UserPermission(MainPermission):
"""
全部用户信息
"""
...
- 使用方法
- 一级权限类与drf原有权限类使用方法相同,配置于 permission_classes 之中
- 一级权限往往表示对 整个视图集所有自动路由接口 的权限控制
class Users(ModelViewSet):
# 一级权限认证 ↓
permission_classes = [UserPermission]
authentication_classes = [UserAuthentication]
queryset = User.objects.all()
serializer_class = UserSerializer
所有继承自 SecondaryPermission类 的权限类皆为 二级权限类
- 创建方法
- 直接继承 SecondaryPermission类 ,如无需重写drf权限类的 has_permission 方法,内部直接 pass 即可
- 必须要写 注释 ,注释内容为此权限的 释义 ,用作权限数据生成,具体请查看 👇3. 权限表数据生成
class GroupUserPermission(SecondaryPermission):
"""
特定分组下用户信息
"""
...
- 使用方法
- 普通二级权限类 不能 配置于 permission_classes 之中
- 普通二级权限类需配置于 新action装饰器 的 permission参数 中,具体请查看 👇4. 新action装饰器
- 二级权限往往表示对 视图集中某个action自动路由接口 的权限控制
@action(methods=["get"], detail=False, permission=GroupUserPermission, inherit=False)
def group_user(self, request):
return Response({
"code": 200
})
- 无需手动创建
- 使用方法
- 以 字符串类型 实参传入 新action装饰器 的 permission参数 中即可,此参数作为该权限的 释义
- 新action装饰器 会在内部自动生成一个 通用二级权限类 并进行配置
- 此 通用二级权限类 的类名为 被装饰方法 的方法名
- 以 字符串类型 实参传入 新action装饰器 的 permission参数 中即可,此参数作为该权限的 释义
@action(methods=["get"], detail=False, permission="指定角色用户", inherit=True)
def role_user(self, request):
return Response({
"code": 200
})
👆上述DEMO 会自动生成一个通用二级权限类,其功能 / 自动生成的权限表数据,和下述👇 普通二级权限类 相同
"""伪代码"""
class role_user(SecondaryPermission):
"""
指定角色用户
"""
...
通常情况 下使用 通用二级权限类 即可,更加方便快捷,只需在action装饰器中传入 释义字符串,无需手动创建权限类
- 而以下情况则需要 手动创建 普通二级权限类:
- 需要重写权限类的 has_permission 方法
- 多个二级权限接口 需使用 同一个二级权限 (多个action装饰器 的permission参数传入 同一个 普通二级权限类)
- 👆此类需求较为常见
一 / 二级权限类皆可 单独使用 (只配置一级权限或二级权限)
同时使用时的验证先后顺序请查看 👇4.2 inherit参数
每一个权限类,会自动生成 四条 权限数据,分别对应四种请求方式 如 UserPermission 权限会生成以下四种权限数据(具体请查看 👇3.2 自动生成方式)
- GET: GET_UserPermission
- PUT: PUT_UserPermission
- POST: POST_UserPermission
- DELETE: DELETE_UserPermission
权限表数据拥有两个主要字段:name、codeName
- name: 权限的名称,即 释义
- codeName: 权限的代码,往往由 请求方式_权限类名 组成,供 权限验证 时使用
自动生成来源有两种:
- 手动创建于
Module_Auth.Permissions
中的权限类,包括 一级权限类 和 普通二级权限类- 新action装饰器 中自动生成的通用二级权限类
- 表字段来源如下:
- name =>
f"{请求方式对应的中文}{当前类的注释内容}"
- codeName =>
f"{请求方式}_{当前类名}"
- name =>
class UserPermission(MainPermission):
"""
全部用户信息
"""
...
- 生成的Permissions表数据:
name | codeName |
---|---|
获取全部用户信息 | GET_UserPermission |
修改全部用户信息 | PUT_UserPermission |
创建全部用户信息 | POST_UserPermission |
删除全部用户信息 | DELETE_UserPermission |
- 源码位置
APPS.RBAC.apps
- 表字段来源如下:
- name =>
f"{请求方式对应的中文}{新action装饰器permission参数中的 字符串}"
- codeName =>
f"{请求方式}_{被新action装饰器所装饰方法的 方法名 }"
- name =>
- 生成权限数据数量:
- 和 手动创建权限类 生成数据不同,通用二级权限类 只会生成 新action装饰器中 methods参数 允许请求方式 所对应的 权限数据
@action(methods=["get"], detail=False, permission="指定角色用户", inherit=True)
def role_user(self, request):
return Response({
"code": 200
})
- 生成的Permissions表数据:
name | codeName |
---|---|
获取指定角色用户 | GET_role_user |
- 源码位置
Module_Custom.Custom_Permission.action
内置二级权限认证,新增两个参数:permission 和 inherit
是否需要进行二级权限认证
- 参数类型:二级权限类(class) / 释义 (str)
- 当参数为 二级权限类 时,直接进行权限验证
- 当参数为 释义 时,会先根据释义自动生成通用 二级权限类 ,再进行权限验证
- 参数默认值:None
是否继承一级权限(permission_classes中的权限类)的 认证结果
- 参数类型:bool
- 当参数为 True 时,表示继承 (等同于一二级权 限验证结果 的 或 关系):
- 若 一级权限通过 则 无需进行 二级权限验证
- 若 一级权限未通过 或 没有设置一级权限 时, 再进行 二级权限验证
- 当参数为 Fales 时,表示不继承:
- 无论 一级权限是否通过 ,只以 二级权限验证结果 为准
- 当参数为 True 时,表示继承 (等同于一二级权 限验证结果 的 或 关系):
- 参数默认值:True