有一种场景, 要求为用户赋予一个角色, 基于角色(比如后管理员,总编, 编辑), 用户拥有相应的权限(比如管理员拥有所有权限, 总编可以增删改查, 编辑只能增改, 有些页面的按钮也只有某些角色才能查看)

ENV: Python3.6 + django1.11

应用场景

有一种场景, 要求为用户赋予一个角色, 基于角色(比如后管理员,总编, 编辑), 用户拥有相应的权限(比如管理员拥有所有权限, 总编可以增删改查, 编辑只能增改, 有些页面的按钮也只有某些角色才能查看), 角色可以任意添加, 每个角色的权限也可以任意设置

django 的权限系统

django 默认的权限是基于Model的add, change, delete来做权限判断的, 这种设计方式有一个明显的缺陷, 比如怎么控制对Model的某个字段的修改的权限的控制呢

设计权限

大多数的系统, 都会给用户赋予某个角色, 假如能针对用户的角色, 做权限控制,这个权限控制并不局限于对Model的修改, 可以是任意位置的权限控制, 只要有一个权限名, 即可根据用户角色名下是否拥有权限名判断是否拥有权限

User, Role => UserRole => RolePermissions

User 是用户对象

Role: 角色表

UserRole 是用户角色关系对象

RolePermissions 是角色权限关系对象

因此, 需要创建三个ModelUser,RoleUserRoleRolePermission

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 基于角色的权限控制的更多相关文章

  1. webapi框架搭建-安全机制(四)-可配置的基于角色的权限控制

    webapi框架搭建系列博客 在上一篇的webapi框架搭建-安全机制(三)-简单的基于角色的权限控制,某个角色拥有哪些接口的权限是用硬编码的方式写在接口上的,如RBAuthorize(Roles = ...

  2. webapi框架搭建-安全机制(三)-简单的基于角色的权限控制

    webapi框架搭建系列博客 上一篇已经完成了“身份验证”,如果只是想简单的实现基于角色的权限管理,我们基本上不用写代码,微软已经提供了authorize特性,直接用就行. Authorize特性的使 ...

  3. 图文详解基于角色的权限控制模型RBAC

    我们开发一个系统,必然面临权限控制的问题,即不同的用户具有不同的访问.操作.数据权限.形成理论的权限控制模型有:自主访问控制(DAC: Discretionary Access Control).强制 ...

  4. RBAC: K8s基于角色的权限控制

    文章目录 RBAC: K8s基于角色的权限控制 ServiceAccount.Role.RoleBinding Step 1:创建一个ServiceAccount,指定namespace Step 2 ...

  5. thinkphp基于角色的权限控制详解

    一.什么是RBAC 基于角色的访问控制(Role-Based Access Control)作为传统访问控制(自主访问,强制访问)的有前景的代替受到广泛的关注. 在RBAC中,权限与角色相关联,用户通 ...

  6. IdentityServer4实战 - 基于角色的权限控制及Claim详解

    一.前言 大家好,许久没有更新博客了,最近从重庆来到了成都,换了个工作环境,前面都比较忙没有什么时间,这次趁着清明假期有时间,又可以分享一些知识给大家.在QQ群里有许多人都问过IdentityServ ...

  7. [Kubernetes]基于角色的权限控制之RBAC

    Kubernetes中有很多种内置的编排对象,此外还可以自定义API资源类型和控制器的编写方式.那么,我能不能自己写一个编排对象呢?答案是肯定的.而这,也正是Kubernetes项目最具吸引力的地方. ...

  8. 【Kubernetes】基于角色的权限控制:RBAC

    Kubernetes中所有的API对象,都保存在Etcd里,对这些API对象的操作,一定都是通过访问kube-apiserver实现的,原因是需要APIServer来做授权工作. 在Kubernete ...

  9. RBAC - 基于角色的权限控制

    ThinkPHP中关于RBAC使用详解 自己的源码下载:百度网盘,thinkPHP文件夹下,RBAC文件夹. 重要的是,权限信息的写入函数等.在源码中能找到,Modules/Amin/Common/c ...

随机推荐

  1. 超级大佬已提前布局AI域名,人工智能时代真的来临了?

    近日,郭盛华公司巨资收购的ai域名引起了业界深度关注,ai人工智能行业想必大家都熟悉不会陌生,但是ai域名你知道吗?了解域名行业的米友,对于ai域名肯定都熟悉,为什么今天小编要突然提到ai域名?因为a ...

  2. 基于element ui 实现七牛云自定义key上传文件,并监听更新上传进度

    借助上传Upload 上传组件的 http-request 覆盖默认的上传行为,可以自定义上传的实现 <el-upload multiple ref="sliderUpload&quo ...

  3. 判断一个对象是否为空? js

    其实开发过程中常常会遇到判断对象和数组是否为空?下面介绍3种判断对象是否为空 1. 最常见的思路,for...in...遍历属性,为真则为“非空数组”:否则为“空数组” function judgeO ...

  4. thinkphp一对多关系

    兹有用户表user和评论表comment 一对一 public function returnmany() { return $this->hasOne('commnet','uid','use ...

  5. 使用vue开发输入型组件更好的一种解决方式(子组件向父组件传值,基于2.2.0)

    (本人想封装一个带有input输入框的组件) 之前使用vue开发组件的时候,在遇到子组件向父组件传递值时我采用的方法是这样的: 比如子组件是一个输入框,父组件调用时需要获取到子组件输入的值,子组件通过 ...

  6. c++常见函数记录

    1.bitsset 模板,可以操作二进制字符串,转化成数字等 2.swap()交换函数,将a,b的值交换 3.stringstream类用于字符串和其他类型的转换,操作方便 4.stx,tr1. 5. ...

  7. java 实现 图片与byte 数组互相转换

    package webgate; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import ja ...

  8. [人物存档]【AI少女】【捏脸数据】两个人物

    点击下载(城通网盘):8bcd58f40ad162d9c1fd6f641edfa9ec8b13cdf8.png 点击下载(城通网盘):AISChaF_20191110015122738.png

  9. ubuntu日常

    1804 处理 1,修改为固定ip vim /etc/netplan/01-netcfg.yaml network: version: 2 renderer: networkd ethernets: ...

  10. Maven Waring : GroupId is duplicate of parent groupId 和 Version is duplicate of parent version

    问题描述: 新项目在创建的时候,因为用到了分模块的,所以导致子模块的pom文件,报了 如下警告: 解决办法: 直接 Window --> Preferences -->  Maven -- ...