zoukankan      html  css  js  c++  java
  • 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单字段的修改权限也就不在话下了

  • 相关阅读:
    今天做了个小项目
    了解类与对象
    装机时键盘选择失误?教你修改kali Linux键盘到美式。
    一些模塊的用法
    你也是全员模块?
    金额保留2位小数 xx.00
    maven项目统一管理版本
    启动项目报错——找不到或无法加载主类 io.vertx.core.Launcher
    以jar包方式启动
    支付业务接口功能(二)
  • 原文地址:https://www.cnblogs.com/floodwater/p/9987308.html
Copyright © 2011-2022 走看看