• RBAC权限介绍 制作一个简单的rbac组件


    RBAC是什么?

    RBAC  是基于角色的访问控制(Role-Based Access Control )在 RBAC  中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。这样管理都是层级相互依赖的,权限赋予给角色,而把角色又赋予用户,这样的权限设计很清楚,管理起来很方便。

    RBAC介绍。

    RBAC  认为授权实际上是Who 、What 、How 三元组之间的关系,也就是Who 对What 进行How 的操作,也就是“主体”对“客体”的操作。

    Who:是权限的拥有者或主体(如:User,Role)。

    What:是操作或对象(operation,object)。

    How:具体的权限(Privilege,正向授权与负向授权)。

    然后 RBAC  又分为RBAC0、RBAC1、RBAC2、RBAC3 ,如果你不知道他们有什么区别,你可以百度百科:百度百科-RBAC 估计你看不懂。还是看看我的简单介绍。

    我这里结合我的见解,简单的描述下(去掉那么多的废话)。

    RBAC0、RBAC1、RBAC2、RBAC3简单介绍。

    • RBAC0:是RBAC的核心思想。
    • RBAC1:是把RBAC的角色分层模型。
    • RBAC2:增加了RBAC的约束模型。
    • RBAC3:其实是RBAC2 + RBAC1。

    RBAC0,RBAC的核心。

    RBAC1,基于角色的分层模型

    RBAC2、是RBAC的约束模型。

    RBAC3、就是RBAC1+RBAC2

    估计看完图后,应该稍微清楚一点。

    下面来看个Demo。员工权限设计的模型图,以及对应关系。

    关系图,以及实体设计。

    表设计

    在我们平常的权限系统中,想完全遵循  RBAC  模型是很难的,因为难免系统业务上有一些差异化的业务考量,所以在设计之初,不要太理想,太追求严格的  RBAC  模型设计,因为这样会使得你的系统处处鸡肋,无法拓展。

    所以在这里要说明一下,  RBAC  是一种模型,是一种思想,是一种核心思想,但是就思想而言,不是要你完全参照,而是你在这个基础之上,融入你自己的思想,赋予你的业务之上,达到适用你的业务。所以要批评一下那些说:“RBAC模型是垃圾,按照它思路去执行,结果无法拓展。”之类话语的人。那是你自己不会变通。

    言归正传。

    背景需求:

    需要在“权限”=>“角色”=>“用户”之间,在赋予一个特殊的角色“客服”,这个需求比较常见,我一个用户想把我的权限分配到“客服”角色上,然后由几个“客服”去操作对应的业务流程。比如我们的天猫,淘宝商家后天就是如此,当店铺开到一定的规模,那么就会有分工。

    A客服:负责打单填写发货单。

    B~E客服:负责每天对我们说“亲,您好。祝亲生活愉快!”,也就是和我们沟通交流的客服。

    F~H:负责售后。

    ... ...

    那么这些客服也是归属到这个商家下面去。而且每个商家可能都有类似的客服,分工完全靠商家自己去分配管理。

    这样的系统,融合我们的权限控制,关键要看“客服”用户的添加是在哪添加,如果是由客服直接添加,不走我们的统一注册流程,那建议不要融合到上面这一套 权限、角色、用户之间去,而是给用户再多一个绑定,把多个用户绑定到客服下,并且给客服赋予对应的权限。

    1、权限赋予:

    权限赋予是把当前用户的权限拉出来,然后分配的客服可以小于等于当前用户的权限。

    2、权限加载:

    正常的加载权限,当用户登录后,并且第一次使用权限判断的时候,  Shiro  会去加载权限。

    3、权限判断:

    走正常用户权限判断,但是数据操作需要判断是不是当前归属的用户的数据,其实这个是属于业务层,就算你不是客服,也是需要判断。

    4、禁用|启用:

    禁用启用,也是正常的用户流程,添加到禁用列表里,如果被禁用,就无法操作任何内容。

    总之:不要让框框架架来限制你的业务,也不要让你的业务局限于框框架架。但是也不推荐你去改动框框架架,而是基于框框架架做业务封装。

    下面发布一篇基于RBAC3 的设计模型,设计的 Shiro  SpringMVC  Mybatis   Demo  

    Demo链接:http://www.sojson.com/shiro

    以上摘自:niuli

    在Django项目中创建一个简单的RBAC权限项目:

      一、根据需求创建不同数量app并在settings中设置,一个用于制作RBAC组件,一个用于在视图当中实现增删改查

      二、设计表结构

    from django.db import models
    
    # Create your models here.
    
    
    class User(models.Model):
        nid = models.AutoField(primary_key=True)
        username = models.CharField(max_length=32, )
        password = models.CharField(max_length=32,)
        roles = models.ManyToManyField(to='Roles')
    
        def __str__(self):
            return self.username
    
        class Meta:
            verbose_name = '用户表'
    
    
    class Roles(models.Model):
        nid = models.AutoField(primary_key=True)
        title = models.CharField(max_length=12, verbose_name='角色名')
        permission = models.ManyToManyField(to='Permission', verbose_name='权限')
    
        def __str__(self):
            return self.title
    
        class Meta:
            verbose_name = '角色表'
    
    
    class Permission(models.Model):
        nid = models.AutoField(primary_key=True)
        title = models.CharField(max_length=12)
        urls = models.CharField(max_length=24)
        
        # action和group用于第二种实现权限校验方式需要添加的字段,不添加这些也能实现
        # 主要作用是在HTML页面当中用模板语言判断是否有权限时,url地址不用写很死
        action = models.CharField(max_length=32, null=True)  # 功能 增删改查
        group = models.ForeignKey(to='PermissionGroup', on_delete=models.CASCADE, null=True)  # 权限分组
    
        def __str__(self):
            return self.title
    
        class Meta:
            verbose_name = '权限表'
    
    
    # 权限分组, 用户分组、角色分组等。用于第二种权限校验方式
    class PermissionGroup(models.Model):
        nid = models.AutoField(primary_key=True)
        title = models.CharField(max_length=32)
    
        def __str__(self):
            return self.title
    models.py

      三、登录实现

        1、登录成功后session中需要存放哪些数据结构:

            用户主键ID:用于增删改查操作,判断是否登录等。

            用户名:可以在HTML页面通过模板语言{{request.session.username}}方式获取,用于导航条显示用户名,点击跳转用户界面

            用户权限url:通过列表形式存放

            …………

    def reg_session(request, user):
        """
        登录后将权限和用户id存入session,存入后才能进行权限校验
        :param request:
        :param user: 登录对象
        :return: 没有返回值
        """
        request.session['user_id'] = user.pk
        request.session['username'] = user.username
        # 将用户的所有权限url以列表形式添加到session
        permission_urls_obj_list = user.roles.values('permission__urls').distinct()
        permission_urls_list = [permission_urls['permission__urls'] for permission_urls in permission_urls_obj_list]
        request.session['permission_urls_list'] = permission_urls_list
    
        # 将用户权限分组nid为键(1为用户管理,2为角色管理),action和url为值,以字典形式存入到session,
        #    {1: {
        #         'urls': ['/users/', '/users/add/', '/users/delete/(\d+)', 'users/edit/(\d+)'],
        #         'actions': ['list', 'add', 'delete', 'edit']},
    
        # 第二种校验方式,可以在HTML页面不写死url判断是否有删改的操作,即该不该显示删除、修改或者增加的按钮
        permission_dict = {}
        urls_action_nid = user.roles.values('permission__urls', 'permission__action', 'permission__group__nid').distinct()
    
        for i in urls_action_nid:
            nid = i['permission__group__nid']
            if nid not in permission_dict:
                permission_dict[nid] = {
                        'urls': [i['permission__urls']],
                        'actions': [i['permission__action']]
                        }
            else:
                permission_dict[nid]['urls'].append(i['permission__urls'])
                permission_dict[nid]['actions'].append(i['permission__action'])
    
        request.session['permission_dict'] = permission_dict
    登录成功后调用该方法,将数据结构存入session,用于中间件获取后进行校验

      四、增删改查实现

        1、先校验权限:用中间件实现,属于组件部分,任何项目拿来可以直接用:

            登录页面、admin页面白名单处理(正则校验url)

            通过session取得用户ID判断是否登录

            最后进行权限校验(即校验这个url地址是否在权限当中,由于删除和修改权限需要获取id,url不能写死,用到正则,所以需要正则校验)     

    from django.utils.deprecation import MiddlewareMixin
    import re
    from django.shortcuts import render, redirect, HttpResponse
    
    def re_checkout(permission_urls_list, path):
        flag = False
        for permission_urls in permission_urls_list:
            permission_urls = '^%s$' % permission_urls
            ret = re.search(permission_urls, path)
            if ret:
                flag = True
                break
        return flag
    
    
    class PermissionVerify(MiddlewareMixin):
    
        def process_request(self, request):
    
            # 校验白名单
            path = request.path
            filter_urls = ['/login/', '/reg/', '/admin/.*']
    
            for urls in filter_urls:
                ret = re.search(urls, path)
                if ret:
                    return None
    
            # 校验是否登录
            user_id = request.session.get('user_id', None)
            if not user_id:
                return redirect('/login/')
    
            # 网址存在再进行权限校验
            path_list = ['/user/edit/(d+)/', '/login/', '/user/', '/user/add/', '/roles/']
            flag = re_checkout(path_list, path)
            if not flag:
                return HttpResponse('404')
    
            # 校验权限
            # permission_urls_list = request.session['permission_urls_list']
            #
            # flag = re_checkout(permission_urls_list, path)
            # if not flag:
            #     return HttpResponse('没有权限')
            #
            # return None
    
            # 第二种校验方式,可以在HTML页面不写死url判断是否有删改的操作,即该不该显示删除、修改或者增加的按钮
            permission_dict = request.session.get('permission_dict')
            for urls_actions in permission_dict.values():
                # permission_urls_list = urls_actions['urls']
                # flag = re_checkout(permission_urls_list, path)
                # if not flag:
                #     return HttpResponse('没有权限')
                # # 当前登录对象有那些操作权限
                # request.permission_actions_list = urls_actions['actions']
                # print(request.permission_actions_list)
                permission_urls_list = urls_actions['urls']
    
                for permission_urls in permission_urls_list:
                    new_urls = '^{}$'.format(permission_urls)
                    ret = re.match(new_urls, path)
                    # 将用户权限分组nid为键(1为用户管理,2为角色管理),action和url为值,以字典形式存入到session,
                    #    permission_urls_list={
                    #               1: {
                    #                   'urls': ['/users/', '/users/add/', '/users/delete/(\d+)', 'users/edit/(\d+)'],
                    #                   'actions': ['list', 'add', 'delete', 'edit']},
                    #               2: {
                    #                      'urls': ['/roles/'],
                    #                      'actions': ['list']}
                    #                  }
    
                    # 可以实现访问哪个权限分组(用户管理,角色管理),就能得到这个分组所有的权限url和对action,
                    # 当用户访问的是用户管理时,对应键值为1,那么通过遍历,就能取到这个分组拥有的权限
                    if ret:
                        request.permission_actions_list = urls_actions['actions']
                        print(request.permission_actions_list)
                        return None
            return HttpResponse('没有权限')
    中间件实现

      注意问题:

        1、哪些属于组件部分:登录成功后session中存放数据结构,中间件实现权限校验

        2、数据库(用户表,角色表,权限表,权限分组表)最好在组件包models内生成,项目需要使用组件时,项目用户表一对一组件用户表即可。

    class UserInfo(models.Model):
        """
        员工表
        """
    
        name = models.CharField(verbose_name='员工姓名', max_length=16)
        # username = models.CharField(verbose_name='用户名', max_length=32)
        # password = models.CharField(verbose_name='密码', max_length=64)
        email = models.EmailField(verbose_name='邮箱', max_length=64)
        depart = models.ForeignKey(verbose_name='部门', to="Department", to_field="code", on_delete=models.CASCADE)
        user = models.ForeignKey(to=User, null=True, on_delete=models.CASCADE)
    
        def __str__(self):
            return self.name
    项目user表
    class User(models.Model):
        nid = models.AutoField(primary_key=True)
        username = models.CharField(max_length=32, )
        password = models.CharField(max_length=32,)
        roles = models.ManyToManyField(to='Roles')
    
        def __str__(self):
            return self.username
    
        class Meta:
            verbose_name = '用户表'
    权限组件user表

        3、母版的使用方式

        4、session中的值,在模板语言中可以用request调用

        5、关于表结构中权限表Permission中的字段action和group的作用:

            action字段表示权限(一条url代表一条权限)实现什么功能,用于在HTML页面中通过模板语言判断该用户是否有使用该功能的权限,实现这个功能的标签是否在前端显示(用户有该权限则显示,没有则不显示)

            group字段外键group表,group表用于对权限进行分组,例如用户管理权限,对应用户表的增删改查,角色管理权限同理。可以实现访问什么权限时,就能够获取对该表所拥有的所有权限。

            action和group用于第二种实现权限校验方式需要添加的字段,不添加这些字段也能实现。主要作用是在HTML页面当中用模板语言判断是否有权限时,url地址不用写很死,因为group字段可以获取对该表所拥有的权限    

     # 第二种校验方式,可以在HTML页面不写死url判断是否有删改的操作,即该不该显示删除、修改或者增加的按钮
            permission_dict = request.session.get('permission_dict')
            for urls_actions in permission_dict.values():
                permission_urls_list = urls_actions['urls']
                for permission_urls in permission_urls_list:
                    new_urls = '^{}$'.format(permission_urls)
                    ret = re.match(new_urls, path)
                    # 将用户权限分组:nid为键(1为用户管理,2为角色管理),action和url为值,以字典形式存入到session,
                    #    permission_urls_list={
                    #               1: {
                    #                   'urls': ['/users/', '/users/add/', '/users/delete/(\d+)', 'users/edit/(\d+)'],
                    #                   'actions': ['show', 'add', 'delete', 'edit']},
                    #               2: {
                    #                      'urls': ['/roles/'],
                    #                      'actions': ['show']}
                    #                  }
    
                    # 可以实现访问哪个权限分组(用户管理,角色管理),就能得到这个分组所有的权限url和对action,
                    # 当用户访问的是用户管理时,对应键值为1,那么通过遍历,就能取到这个分组拥有的权限
                    if ret:
                        request.permission_actions_list = urls_actions['actions']
                        print(request.permission_actions_list)
                        return None
            return HttpResponse('没有权限')
    第二种校验方式
       {#    校验一#}
        {#    {% if '/user/add/' in permission_urls_list %}#}
        {#    <a class="btn btn-info pull-right"href="">增加用户</a>#}
        {#    {% endif %}#}
        {# 校验二 #}
        {% if 'add' in permission_action %}
            <a class="btn btn-info pull-right" href="">增加用户</a>
        {% endif %}
    
    
    
    <td>
                        {# 校验一#}
                        {#                {% if '/user/edit/(d+)/' in permission_urls_list %}#}
                        {#                 <a class="btn btn-warning" href="">修改</a>#}
                        {#                {% endif %}#}
                        {#               {% if '/user/delete/(d+)/' in permission_urls_list %}#}
                        {#                 <a class="btn btn-danger" href="">删除</a>#}
                        {#                {% endif %}#}
                        {#校验二#}
                        {% if 'edit' in permission_action %}
                            <a class="btn btn-warning" href="">修改</a>
                        {% endif %}
                        {% if 'delete' in permission_action %}
                            <a class="btn btn-danger" href="">删除</a>
                        {% endif %}
                    </td>
    第二种校验方式实现的地方

        6、菜单栏权限同理,只需要在中间件判断后将需要展示的url添加到列表,传给session后,视图获取,再用模板语言渲染即可。

        menu_permission = user.roles.filter(permission__action='show').values('permission__urls', 'permission__group__title')
        menu_permission_list = []
        for item in menu_permission:
            menu_permission_list.append(item)
    
        request.session['menu_permission_list'] = menu_permission_list
    菜单栏权限中间件

        7、菜单栏属于固定栏,在每个数据展示页面都会显示,所有不管在哪个权限视图中都要获取session中存放url列表的值,此时可以用templatetags方法一次性获取,从而提高代码质量:使用方法

        7、中间件校验权限需要考虑的地方:页面白名单,判断是否登录等

        8、ORM操作时,由于角色与权限属于多对多关系,可能会取到相同的值,distinct()方法可以去重负。更多方法:点击

  • 相关阅读:
    损失函数
    DPM 目标检测1
    编程题
    枚举型和元类
    python 多继承
    网络基础Cisco路由交换一
    网络基础tcp/ip协议五
    网络基础tcp/ip协议四
    网络基础tcp/ip协议三
    网络基础tcp/ip协议二
  • 原文地址:https://www.cnblogs.com/aizhinong/p/12387631.html
Copyright © 2020-2023  润新知