Django 基于角色的权限控制
ENV: Python3.6 + django1.11
应用场景
有一种场景, 要求为用户赋予一个角色, 基于角色(比如后管理员,总编, 编辑), 用户拥有相应的权限(比如管理员拥有所有权限, 总编可以增删改查, 编辑只能增改, 有些页面的按钮也只有某些角色才能查看), 角色可以任意添加, 每个角色的权限也可以任意设置
django 的权限系统
django 默认的权限是基于Model
的add, change, delete来做权限判断的, 这种设计方式有一个明显的缺陷, 比如怎么控制对Model
的某个字段的修改的权限的控制呢
设计权限
大多数的系统, 都会给用户赋予某个角色, 假如能针对用户的角色, 做权限控制,这个权限控制并不局限于对Model
的修改, 可以是任意位置的权限控制, 只要有一个权限名, 即可根据用户角色名下是否拥有权限名判断是否拥有权限
User, Role => UserRole => RolePermissions
User 是用户对象
Role: 角色表
UserRole 是用户角色关系对象
RolePermissions 是角色权限关系对象
因此, 需要创建三个Model
: User
,Role
, UserRole
, RolePermission
User
可以使用django 默认的User
对象
其他Model 如下
class Role(models.Model):
"""角色表"""
# e.g add_user
role_code = models.CharField('role code', max_length=64, unique=True, help_text = '用户角色标识')
# e.g 新增用户
role_name = models.CharField('role name', max_length=64, help_text = '用户角色名') class UserRole(models.Model):
"""用户角色关系表"""
user_id = models.IntegerField('user id', blank=False, help_text='用户id', unique=True)
role_codes = models.CharField('role codes', blank=True, default=None, max_length=256, help_text='用户的角色codes') class RolePermission(models.Model):
"""角色权限关系表"""
role_code = models.CharField('role code', max_length=64, blank=False, help_text = '用户角色标识')
pm_code = models.CharField('permission code', blank=False, max_length=64, help_text='权限code') class Meta:
unique_together = ('role_code', 'pms_code')
其中 Role
和 RolePermission
用于管理角色和对应的权限的关系
UserRole
用于管理用户和角色的映射关系
权限管理
用户角色拥有哪些权限是在代码里定义好的, 比如:
PMS_MAP = (
('PM_ADD_USER', '新增用户'),
('PM_SET_MAIL', '编辑邮箱'),
...
)
PM_ADD_USER
是权限code码, 新增用户
是权限名, 在这里, 权限名由我们定义, 后面在需要使用的地方做has_perm(<pm_coede>)
判断时, 用的就是这是这个code
角色管理
在定义好权限后, 我们就可以做角色管理了,
在这里, 我们可以创建任意的角色, 为其分配任意的权限, 当然, 最好创建有意义的角色
角色表单定义(forms.py
)
role_regex_validator = RegexValidator(r"[a-zA-Z0-9]", "角色标记只能包含字母,数字, 下划线")
class RoleForm(forms.Form):
role_row_code = forms.IntegerField(required=False, widget=forms.HiddenInput())
role_code = forms.CharField(label='角色标记', min_length=3, max_length=64, validators=[role_regex_validator])
role_name = forms.CharField(label='角色名', min_length=3, max_length=64)
OPTIONS = PMS_MAP
pms = forms.MultipleChoiceField(label='权限列表', widget=forms.SelectMultiple(choices=OPTIONS)
角色编辑views.py
def role_edit(request):
"""角色编辑"""
if request.method == 'POST':
role_row_id = request.POST.get('role_row_id', 0)
role_code = request.POST.get('role_code', '')
role_name = request.POST.get('role_name', '')
pms = request.POST.getlist('pms', []) # 表单校验
role_form = RoleForm({
'role_row_id': role_row_id,
'role_code': role_code,
'role_name': role_name,
'pms': pms
})
# 表单校验
if not role_form.is_valid():
return render(request, 'role_form.html', {'form': role_form) role_row_id = role_form.cleaned_data.get('role_row_id', None)
if role_row_id:
# 角色更新
return update_role(request, role_form, role_row_id=role_row_id, role_code=role_code,
role_name=role_name, pms=pms)
else:
# 角色创建
return add_role(request, role_form, role_code, role_name, pms=pms) else:
# 角色编辑页面
role_row_id = request.GET.get('id')
try:
role_item = Role.objects.get(pk=role_row_id)
except Role.DoesNotExist as e:
role_item = None
if role_item:
# 编辑已有角色表单
# 获取角色权限列表
role_pms_rows = RolePermission.objects.filter(role_code=role_item.role_code)
pms_codes = [role_pms_row.pms_code for role_pms_row in role_pms_rows] role_form = RoleForm({
'role_row_id': role_row_id,
'role_code': role_item.role_code,
'role_name': role_item.role_name,
'pms': pms_codes
})
else:
# 新增角色表单
role_form = RoleForm() return render(request, 'role_form.html', {'form': role_form}) def add_role(request, role_form, role_code, role_name, pms=()):
"""新增角色"""
try:
with transaction.atomic():
role_item = Role.objects.create(role_code=role_code, role_name=role_name)
for pm_code in pms:
RolePermission.objects.update_or_create(role_code=role_code, pms_code=pm_code)
return redirect('{}?id={}'.format(reverse('user_role_edit'), role_item.pk))
except IntegrityError as e:
# 创建出错
role_form.add_error('role_code', '角色已经存在: {}'.format(role_code))
return render(request, 'role_form.html', {'form': role_form}) def update_role(request, role_form, role_row_id, role_code, role_name, pms=()):
"""更新角色""" try:
with transaction.atomic():
role_item = Role.objects.get(pk=role_row_id)
# 校验合法性
if not role_item:
raise Http404('非法的role记录id')
role_item.role_name = role_name
role_item.save() # 删除原角色权限设置
RolePermission.objects.filter(role_code=role_code).delete() for pm_code in pms:
RolePermission.objects.update_or_create(role_code=role_code, pms_code=pm_code)
return redirect('{}?id={}'.format(reverse('user_role_edit'), role_row_id))
except IntegrityError as e:
# 更新出错
role_form.add_error('role_name', '更新角色名出错:{}'.format(role_name))
return render(request, 'role_form.html', {'form': role_form})
表单部分html
<form class='form-horizontal' action='/user/role/edit' method='POST'>
<p>用户角色编辑</p>
{{form.non_field_errors}}
{% csrf_token %}
{% for hidden_field in form.hidden_fields %}
{{ hidden_field }}
{% endfor %} {% for field in form.visible_fields %}
<div class='form-group'>
<label class='col-lg-2 control-label'>{{field.label}}</label>
{% if field.errors %}
<div class='col-lg-3 has-error'>
{{field}}
{% for error in field.errors %}
<p><span class='help-block m-b-none'>{{error}}</span><p>
{% endfor %}
</div>
{% else %}
<div class='col-lg-3'>
{{field}}
</div>
{% endif%}
</div>
{% endfor %}
<div class="form-group">
<div class="col-lg-offset-2 col-lg-10">
<button class="btn btn-sm btn-white" type="submit">Save</button>
</div>
</div>
</form>
至此用户角色编辑就完成了
还有一部分是用户角色分配
说白了就是编辑用户时, 给用户选择一个角色, 将用户id和角色code 通过UserRole
存到数据库中, 这一部分请各位自己实现吧 :)
权限判断
如果我们有了一个用户, 并赋予了一个角色, 应该怎么判断其是否有某个权限呢
在django的认证体系配置里, 有一项配置是AUTHENTICATION_BACKENDS
, 比如, 我们希望对接sso 单点登录, 就可以在这里添加配置
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'cas.backends.CASBackend', # 单点登录实现
)
这个配置项下每个字符串都对应一个类, 每个类继承并了一组接口实现中的全部或其中一部分
这组接口定义了用户认证和授权的相关内容, 如下
authenticate
get_user_permissions
get_group_permissions
has_perm
...
其中 has_perm 就是直接判断是否拥有某个权限的接口
在 AUTHENTICATION_BACKENDS
中, 只要有一个类的 has_perm 判定用户拥有某个权限即可认为用户拥有该权限
因此, 我们实现自己的has_perm
实现
class PermBackend(ModelBackend):
def has_perm(self, user_obj, pms_code, obj):
if not user_obj.is_active:
return False
# 超级管理员拥有所有权限
if user_obj.is_superuser:
return True try:
user_roles_record = UserRole.objects.get(user_id=user_obj.pk)
except UserRole.DoesNotExist as e:
return False # 获取用户的角色(暂时是单个角色)
user_roles = user_roles_record.role_codes.split(',')
# 角色对应的权限集合
role_pms_rows = RolePermission.objects.filter(role_code__in=user_roles) pms_codes = [role_pms_row.pms_code for role_pms_row in role_pms_rows] # pms_code 是否在用户的权限code集合中
return pms_code in pms_codes
当然, 别忘了把PermBackend
放到 AUTHENTICATION_BACKENDS
最后
现在, 我们在views funcion的实现前添加 @permission_required(<pms_code>)
装饰器就能根据当前用户的角色, 判定是否拥有某个<pms_code>
权限啦
无论是任何地方, 只要定义唯一的pms_code
, 赋给角色, 并将角色分配给某个用户, 就可以实现粒度很深的权限控制, 在本文开始的地方的所说的对某个Model
单字段的修改权限也就不在话下了
Django 基于角色的权限控制的更多相关文章
- webapi框架搭建-安全机制(四)-可配置的基于角色的权限控制
webapi框架搭建系列博客 在上一篇的webapi框架搭建-安全机制(三)-简单的基于角色的权限控制,某个角色拥有哪些接口的权限是用硬编码的方式写在接口上的,如RBAuthorize(Roles = ...
- webapi框架搭建-安全机制(三)-简单的基于角色的权限控制
webapi框架搭建系列博客 上一篇已经完成了“身份验证”,如果只是想简单的实现基于角色的权限管理,我们基本上不用写代码,微软已经提供了authorize特性,直接用就行. Authorize特性的使 ...
- 图文详解基于角色的权限控制模型RBAC
我们开发一个系统,必然面临权限控制的问题,即不同的用户具有不同的访问.操作.数据权限.形成理论的权限控制模型有:自主访问控制(DAC: Discretionary Access Control).强制 ...
- RBAC: K8s基于角色的权限控制
文章目录 RBAC: K8s基于角色的权限控制 ServiceAccount.Role.RoleBinding Step 1:创建一个ServiceAccount,指定namespace Step 2 ...
- thinkphp基于角色的权限控制详解
一.什么是RBAC 基于角色的访问控制(Role-Based Access Control)作为传统访问控制(自主访问,强制访问)的有前景的代替受到广泛的关注. 在RBAC中,权限与角色相关联,用户通 ...
- IdentityServer4实战 - 基于角色的权限控制及Claim详解
一.前言 大家好,许久没有更新博客了,最近从重庆来到了成都,换了个工作环境,前面都比较忙没有什么时间,这次趁着清明假期有时间,又可以分享一些知识给大家.在QQ群里有许多人都问过IdentityServ ...
- [Kubernetes]基于角色的权限控制之RBAC
Kubernetes中有很多种内置的编排对象,此外还可以自定义API资源类型和控制器的编写方式.那么,我能不能自己写一个编排对象呢?答案是肯定的.而这,也正是Kubernetes项目最具吸引力的地方. ...
- 【Kubernetes】基于角色的权限控制:RBAC
Kubernetes中所有的API对象,都保存在Etcd里,对这些API对象的操作,一定都是通过访问kube-apiserver实现的,原因是需要APIServer来做授权工作. 在Kubernete ...
- RBAC - 基于角色的权限控制
ThinkPHP中关于RBAC使用详解 自己的源码下载:百度网盘,thinkPHP文件夹下,RBAC文件夹. 重要的是,权限信息的写入函数等.在源码中能找到,Modules/Amin/Common/c ...
随机推荐
- final修饰符—不可变
final 修饰符 修饰类 不可以有子类 修饰变量 变量一旦获得初始值就不可改变,不能被重新赋值 成员变量:初始值必须有程序员显式设置,系统不会对其隐式初始化 类变量:静态初始化块 | 声明该类变量时 ...
- Tomcat的3种部署方式
tomcat部署的三种方式及优缺点对比 部署方法一:将编译成功的web项目直接放到webapps目录中(注意是编译成功的web项目,也可以是war包): 部署方式二:修改tomcat安装目录下的 ...
- @Autowired与@Resource的区别(转载)
原文地址:http://bhdweb.iteye.com/blog/1663907 1.@Autowired与@Resource都可以用来装配bean. 都可以写在字段上,或写在setter方法上. ...
- "东坡肉”
“东坡肉” 苏东坡成名后,求师的人蜂涌而至,有学书法的,有学文章的.也有这么一个人,人问他学什么?他说:我学的是“东坡肉”呢! 苏东坡会不会煮猪肉?从他咏黄州猪肉“慢著火.少著水.火候足时它自美”的诗 ...
- Hadoop中配置环境后重启失效解决方法
Ubuntu下设置环境变量有三种方法,一种用于当前终端,一种用于当前用户,一种用于所有用户: 一:用于当前终端: 在当前终端中输入:export PATH=$PATH:<路径> 不过上面的 ...
- chrome模拟慢速3G网络
谷歌调试控制台中network中可以设置,add为自定义
- 好好理解一下python的函数和python的缩进
缩进相当于其他语言的括号,括号中的语句才是一起执行的 这一个函数的功能应该是计算平均分 所以d这个dict应该是作为参数传进来的,而不是写到函数内部 正确的写法 d = { 'Adam': 95, ' ...
- C#任务调度——LimitedConcurrencyLevelTaskScheduler
这是参考大佬分享的代码写的有问题请提出指正,谢谢. using Serilog; using System; using System.Collections.Generic; using Syste ...
- AQS源码分析笔记
经过昨晚的培训.对AQS源码的理解有所加强,现在写个小笔记记录一下 同样,还是先写个测试代码,debug走一遍流程, 然后再总结一番即可. 测试代码 import java.util.concurre ...
- UML——概述
1. 静态视图(类图) 静态视图不描述与时间相关的系统行为,这种行为在其他视图中描述,因此称之为静态试图. 静态视图用类图来实现,正因为它以类图为中心,因此也称之为类图. ...