Django 基于角色的权限控制

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单字段的修改权限也就不在话下了

猜你喜欢

转载自www.cnblogs.com/floodwater/p/9987308.html