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

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. hdu 6501 transaction transaction transaction 最长路/树形DP/网络流

    最长路: 设置一个虚拟起点和虚拟终点,每个点与起点间一条负边,值为这个点书的价值的相反数(代表买书花钱),每个点与终点连一条正边,值为这个点的书的价格(代表卖书赚钱). 然后按照图中给的边建无向边,权 ...

  2. 单调队列优化DP || [NOI2005]瑰丽华尔兹 || BZOJ 1499 || Luogu P2254

    题外话:题目极好,做题体验极差 题面:[NOI2005]瑰丽华尔兹 题解: F[t][i][j]表示第t时刻钢琴位于(i,j)时的最大路程F[t][i][j]=max(F[t-1][i][j],F[t ...

  3. No application found. Either work inside a view function or push an application context.

    flask报了这个错,字面意思是说没有应用上下文,字面给的解决意见是要么放置在一个视图内,要么提供一个应用(flask)上下文. 查看文档发现文档给了个解决方案: 一个是通过app.app_conte ...

  4. Java JDK下载方法

    https://jingyan.baidu.com/album/574c5219fb033c2c8d9dc194.html?picindex=5  也可以参考这个 ‘’‘’ 大家下载的时候一定要按照步 ...

  5. 通过继承Thread类实现多线程

    (1)继承Thread类(2)重写run(方法(3)通过start0方法启动线程 一定的缺点: Java中的类是单继承的,一旦继承了Thread类,就不允许再去继承其它的类 线程和主方法之间的执行顺序 ...

  6. pyqt5-表格TableWidGet

    from PyQt5.QtWidgets import QApplication,QTableWidget,QWidget,QHeaderView,QPushButton,QTableWidgetIt ...

  7. NOIP2015普及组总结

    NOIP2015普及组总结 这次考试总体感觉不错,不过觉得时间有点紧,在最后30分钟才打完. 第一题(金币coin):大大的W!爆搜O(N),一分钟打完: 第二题(扫雷游戏mine):同上: 第三题( ...

  8. mybatis——mybatis打印sql 接口工作原理

    https://blog.csdn.net/Lxinccode/article/details/79218566 接口工作原理: Dao接口即Mapper接口.接口的全限名,就是映射文件中的names ...

  9. 【leetcode】Find Largest Value in Each Tree Row

    You need to find the largest value in each row of a binary tree. Example: Input: 1 / \ 3 2 / \ \ 5 3 ...

  10. vsftp配置文件

    直接使用,本地用户可以使用账号密码登录 # Example config file /etc/vsftpd/vsftpd.conf # # The default compiled in settin ...