rbac权限管理

  • A+
所属分类:系统文档

概述

RBAC : 基于角色的权限访问控制(Role-Based Access Control),通过角色绑定权限,然后给用户划分角色。在web应用中,可以将权限理解为url,一个权限对应一个url。
基于角色的访问控制方法(RBAC)的显著的两大特征是:

1.由于角色/权限之间的变化比角色/用户关系之间的变化相对要慢得多,减小了授权管理的复杂性,降低管理开销。

2.灵活地支持企业的安全策略,并对企业的变化有很大的伸缩性。

实现步骤

1 创建项目,包含两个应用app01rbac

2 setting中配置:

INSTALLED_APPS DATABASES STATICFILES_DIRS

3 设计表关系

基于上述分析,在设计表关系时,起码要有5张表:用户,角色,权限,权限组,菜单:

  • 用户可以绑定多个角色,从而实现灵活的权限组合 :用户和角色,多对多关系

  • 每个角色下,绑定多个权限,一个权限也可以属于多个角色:角色和权限,多对多关系

  • 多个权限附属在一个权限组下,一个权限组下可以有多个权限:权限和权限组,多对一关系

  • 一个菜单包含多个权限组:权限组和菜单,多对一关系

  • 一个菜单下可能有多个子菜单,也可能有一个父菜单:菜单和菜单是自引用关系

在rbac的models中定义这几张表:

from django.db import models# Create your models here.class User(models.Model):
    """
    用户表
    """
    username = models.CharField(max_length=32,verbose_name='用户名')
    password = models.CharField(max_length=32,verbose_name='密码')
    roles = models.ManyToManyField('Role',verbose_name='与角色多对多绑定')class Role(models.Model):
    """
    角色表,多对多绑定权限
    """
    name = models.CharField(max_length=32,verbose_name='角色名称')
    Permissions = models.ManyToManyField('Permission',verbose_name='与权限多对多绑定')class Permission(models.Model):
    """
    权限表
    """
    name = models.CharField(max_length=32,verbose_name='权限名称')
    url = models.CharField(max_length=32,verbose_name='对应路径')
    code = models.CharField(max_length=32,verbose_name='别名')
    menu_group = models.ForeignKey(to='Permission',related_name='xxx',null=True,blank=True,default=None,verbose_name='所属菜单组')
    PermissionGroup = models.ForeignKey('PermissionGroup',null=True,verbose_name='所属权限组')class PermissionGroup(models.Model):
    """
    权限分组
    """
    name = models.CharField(max_length=32,verbose_name='权限组名称')
    Menu = models.ForeignKey('Menu',null=True,verbose_name='所属菜单')class Menu(models.Model):
    """
    菜单
    """
    name = models.CharField(max_length=32,verbose_name='菜单名称')

4 录入数据

models.Role.objects.create(name='CEO')
models.Role.objects.create(name='总监')
models.Role.objects.create(name='经理')
models.Role.objects.create(name='业务员')

models.User.objects.create(username='番禺',password=123)
models.User.objects.create(username='鲁宁',password=123)
models.User.objects.create(username='肾松',password=123)
models.User.objects.create(username='文飞',password=123)
models.User.objects.create(username='成栋',password=123)

models.Menu.objects.create(name='菜单一')
models.Menu.objects.create(name='菜单二')

models.PermissionGroup.objects.create(name='用户组',menu_id=1)
models.PermissionGroup.objects.create(name='订单组',menu_id=2)

models.Permission.objects.create(url='/userinfo/',name='用户列表',permissionGroup_id=1,code='list')
models.Permission.objects.create(url='/userinfo/add/',name='添加用户',permissionGroup_id=1,code='add')
models.Permission.objects.create(url='/userinfo/edit/(\d+)/',name='编辑用户',permissionGroup_id=1,code='edit')
models.Permission.objects.create(url='/userinfo/del/(\d+)/',name='删除用户',permissionGroup_id=1,code='del')
models.Permission.objects.create(url='/order/', name='订单列表',permissionGroup_id=2,code='list')
models.Permission.objects.create(url='/order/add/', name='添加订单',permissionGroup_id=2,code='add')
models.Permission.objects.create(url='/order/edit/(\d+)/', name='编辑订单',permissionGroup_id=2,code='edit')
models.Permission.objects.create(url='/order/del/(\d+)/', name='删除订单',permissionGroup_id=2,code='del')

models.Role.objects.get(name='CEO').permissions.add(1,2,3,4,5,6,7,8)
models.Role.objects.get(name='总监').permissions.add(1,2,5,6)
models.Role.objects.get(name='经理').permissions.add(1,5)
models.Role.objects.get(name='业务员').permissions.add(5)

models.User.objects.get(username='番禺').roles.add(1)
models.User.objects.get(username='鲁宁').roles.add(2)
models.User.objects.get(username='肾松').roles.add(3,4)
models.User.objects.get(username='文飞').roles.add(4)
models.User.objects.get(username='成栋').roles.add(4)

5 views.py 登录

from rbac.service.init_permission import init_permissiondef login(request):
    if request.method == 'GET':        return render(request,'login.html')    else:
        username = request.POST.get('username')
        password = request.POST.get('password')
        user = models.User.objects.filter(username=username,password=password).first()        if user:
            init_permission(request,user)            return redirect('/index/')        return render(request, 'login.html',{'msg':'用户名或密码错误'})

6 提取用户权限信息,写入session

在rbac应用下新建一个文件夹service,写一个脚本init_permission.py用来执行初始化权限的操作:用户登录后,取出其权限及所属菜单信息,写入session中

from collections import defaultdictfrom django.conf import settingsdef init_permission(request,user):
    #查询登录用户的权限等信息
    current_url = request.path_info
    userdata = user.roles.values('permissions__id'  # 权限ID
                                 , 'permissions__name'  # 权限名称
                                 , 'permissions__code'  # 别名
                                 , 'permissions__url'  # 权限路径
                                 , 'permissions__menu_group_id'  # 组内菜单ID,Null表示是菜单
                                 , 'permissions__permissionGroup_id'  # 权限所属组ID
                                 , 'permissions__permissionGroup__menu_id'# 菜单ID
                                 , 'permissions__permissionGroup__menu__name'  # 菜单名称
                                 ).distinct()    #权限相关
    """
    permission_url_dict数据结构如下
    {
        1: {
            'codes': ['list', 'add', 'edit', 'del'], 
            'urls': ['/userinfo/', '/userinfo/add/', '/userinfo/edit/(\\d+)/', '/userinfo/del/(\\d+)/']
        },
        2: {
            'codes': ['list', 'add', 'edit', 'del'], 
            'urls': ['/order/', '/order/add/', '/order/edit/(\\d+)/', '/order/del/(\\d+)/']
        }
    }
    """
    permission_url_dict = defaultdict(lambda :{'codes':[],'urls':[]})    for item in userdata:
        permission_url_dict[item['permissions__permissionGroup_id']]['codes'].append(item['permissions__code'])
        permission_url_dict[item['permissions__permissionGroup_id']]['urls'].append(item['permissions__url'])

    request.session[settings.PERMISSION_URL_KEY] = permission_url_dict#用户的权限信息保存到session中

    #菜单相关
    """
    permission_menu_list 数据结构如下
    [
        {'id': 1, 'title': '用户列表', 'url': '/userinfo/', 'menu_gp_id': None, 'menu_id': 1, 'menu_title': '菜单一'}, 
        {'id': 2, 'title': '添加用户', 'url': '/userinfo/add/', 'menu_gp_id': 1, 'menu_id': 1, 'menu_title': '菜单一'}, 
        {'id': 3, 'title': '编辑用户', 'url': '/userinfo/edit/(\\d+)/', 'menu_gp_id': 1, 'menu_id': 1, 'menu_title': '菜单一'}, 
        {'id': 4, 'title': '删除用户', 'url': '/userinfo/del/(\\d+)/', 'menu_gp_id': 1, 'menu_id': 1, 'menu_title':'菜单一'}, 
        {'id': 5, 'title': '订单列表', 'url': '/order/', 'menu_gp_id': None, 'menu_id': 2, 'menu_title': '菜单二'}, 
        {'id': 6, 'title': '添加订单', 'url': '/order/add/', 'menu_gp_id': 5, 'menu_id': 2, 'menu_title': '菜单二'},
        {'id': 7, 'title': '编辑订单', 'url': '/order/edit/(\\d+)/', 'menu_gp_id': 5, 'menu_id': 2, 'menu_title': '菜单二'}, 
        {'id': 8, 'title': '删除订单', 'url': '/order/del/(\\d+)/', 'menu_gp_id': 5, 'menu_id': 2, 'menu_title': '菜单二'}
    ]
    """
    #初步处理数据,在自定义标签中生成左侧菜单数据
    permission_menu_list = []    for item in userdata:
        tpl = {            'id': item['permissions__id'],            'title': item['permissions__name'],            'url': item['permissions__url'],            'menu_gp_id': item['permissions__menu_group_id'],            'menu_id': item['permissions__permissionGroup__menu_id'],            'menu_title': item['permissions__permissionGroup__menu__name'],
        }
        permission_menu_list.append(tpl)
    request.session[settings.PERMISSION_MENU_KEY] = permission_menu_list #用户的菜单信息保存到session中

7 登陆成功后跳转到index页面,运用模板继承

8 定义中间件,处理所有请求。

在rbac应用下新建一个目录middleware,用来存放自定义中间件,新建rbac.py,在其中实现检查用户权限,控制访问:

import refrom django.conf import settingsfrom django.shortcuts import HttpResponse,render,redirectclass MiddlewareMixin(object):
    def __init__(self, get_response=None):
        self.get_response = get_response
        super(MiddlewareMixin, self).__init__()    def __call__(self, request):
        response = None
        if hasattr(self, 'process_request'):
            response = self.process_request(request)        if not response:
            response = self.get_response(request)        if hasattr(self, 'process_response'):
            response = self.process_response(request, response)        return responseclass RbacMiddleware(MiddlewareMixin):

    def process_request(self,request):
        current_url = request.path_info        # 白名单处理
        for url in settings.VALID_URLS:            if re.match(url, current_url):                return None

        # 当前用户的权限列表
        permission_dict = request.session.get(settings.PERMISSION_URL_KEY)        if not permission_dict:            return redirect('/login/')

        flag = False
        for group_id,codes_urls in permission_dict.items():            for permission_url in codes_urls['urls']:
                regex = "^{0}$".format(permission_url)                if re.match(regex,current_url):
                    request.permission_code_list = codes_urls['codes']
                    flag = True
                    break
            if flag:                break
        
        if not flag:            return HttpResponse('无权访问')

MIDDLEWARE = ['rbac.middlewares.rbac.RbacMiddleware',]

9 自定义标签 - 左侧菜单
显示菜单要处理三个问题:

  • 第一,只显示用户权限对应的菜单,因此不同用户看到的菜单可能是不一样的

  • 第二,对用户当前访问的菜单下的url作展开显示,其余菜单折叠;

  • 第三,菜单的层级是不确定的(而且,后面要实现权限的后台管理,允许管理员添加菜单和权限);
    在rabc应用的目录下新建templatetags目录,写一个脚本my_tags.py,写一个函数menu_html,并加上自定义标签的装饰器:

import refrom django.template import Libraryfrom django.conf import settings

register = Library()@register.inclusion_tag('siderbar_menu.html')def menu_html(request):
    menu_list = request.session.get(settings.PERMISSION_MENU_KEY)
    current_url = request.path_info    """
    menu_list 数据结构
    [
        {'id': 1, 'title': '用户列表', 'url': '/userinfo/', 'menu_gp_id': None, 'menu_id': 1, 'menu_title': '菜单一', 'active': True}, 
        {'id': 2, 'title': '添加用户', 'url': '/userinfo/add/', 'menu_gp_id': 1, 'menu_id': 1, 'menu_title': '菜单一'}, 
        {'id': 3, 'title': '编辑用户', 'url': '/userinfo/edit/(\\d+)/', 'menu_gp_id': 1, 'menu_id': 1, 'menu_title': '菜单一'}, 
        {'id': 4, 'title': '删除用户', 'url': '/userinfo/del/(\\d+)/', 'menu_gp_id': 1, 'menu_id': 1, 'menu_title': '菜单一'}, 
        {'id': 5, 'title': '订单列表', 'url': '/order/', 'menu_gp_id': None, 'menu_id': 2, 'menu_title': '菜单二'}, 
        {'id': 6, 'title': '添加订单', 'url': '/order/add/', 'menu_gp_id': 5, 'menu_id': 2, 'menu_title': '菜单二'}, 
        {'id': 7, 'title': '编辑订单', 'url': '/order/edit/(\\d+)/', 'menu_gp_id': 5, 'menu_id': 2, 'menu_title': '菜单二'}, 
        {'id': 8, 'title': '删除订单', 'url': '/order/del/(\\d+)/', 'menu_gp_id': 5, 'menu_id': 2,'menu_title': '菜单二'}
    ]
    
    """

    """
    menu_dict 数据结构
    {
        1: {'id': 1, 'title': '用户列表', 'url': '/userinfo/', 'menu_gp_id': None, 'menu_id': 1, 'menu_title': '菜单一', 'active': True}, 
        5: {'id': 5, 'title': '订单列表', 'url': '/order/', 'menu_gp_id': None, 'menu_id': 2, 'menu_title': '菜单二'}
    }
    """

    menu_dict = {}    for item in menu_list:        if not item['menu_gp_id']:
            menu_dict[item['id']] = item    for item in menu_list:
        regex = "^{0}$".format(item['url'])        if re.match(regex,current_url):            if not item['menu_gp_id']:
                menu_dict[item['id']]['active'] = True
            else:
                menu_dict[item['menu_gp_id']]['active'] = True

    """
    result数据结构:
    {
        1: {
            'menu_id': 1, 
            'menu_title': '菜单一', 
            'active': True, 
            'children': [
                {'title': '用户列表', 'url': '/userinfo/', 'active': True}
            ]
        },
         2: {
            'menu_id': 2, 
            'menu_title': '菜单二', 
            'active': None, 
            'children': [
                {'title': '订单列表','url': '/order/', 'active': None}
            ]
        }
    }

    """

    result = {}    for item in menu_dict.values():
        active = item.get('active')
        menu_id = item['menu_id']        if item['menu_id'] not in result:
            result[menu_id] = {                'menu_id': menu_id,                'menu_title': item['menu_title'],                'active': active,                'children': [
                    {'title': item['title'], 'url': item['url'], 'active': active},
                ]
            }        else:
            result[menu_id]['children'].append({'title': item['title'], 'url': item['url'], 'active': active})            if active:
                result[menu_id]['active'] = True

    return {'menu_dict': result}

需要渲染的标签页面:siderbar_menu.html

{% for k,item in menu_dict.items %}    <div class="item">
        <div class="item-title">{{ item.menu_title }}</div>
        {% if item.active %}            <div class="item-permission">
        {% else %}            <div class="item-permission hide">
        {% endif %}
            {% for v in item.children %}

                {% if v.active %}                    <a href="{{ v.url }}" class="active">{{ v.title }}</a>
                {% else %}                    <a href="{{ v.url }}">{{ v.title }}</a>
                {% endif %}

            {% endfor %}        </div>
    </div>{% endfor %}

10 页面的按钮按权限显示 -- 面向对象

class BasePagePermission(object):
    def __init__(self,code_list):        self.code_list = code_list    def has_add(self):        if 'add' in self.code_list:
            return True    def has_edit(self):        if 'edit' in self.code_list:
            return True    def has_del(self):        if 'del' in self.code_list:
            return True
def userinfo(request):
    print(request.permission_code_list) #['list', 'add', 'edit', 'del']
    pagePermission = BasePagePermission(request.permission_code_list)
    user_list = models.User.objects.all()    return render(request,'userinfo.html',{'user_list':user_list,'pagePermission':pagePermission})
{% extends 'base.html' %}

{% block content %}
    {% if pagePermission.has_add %}        <a href="/userinfo/add">添加用户</a>
    {% endif %}    <table>
        {% for user in user_list %}            <tr>
                <td>{{ user.id }}</td>
                <td>{{ user.username }}</td>
                {% if pagePermission.has_edit %}                <td><a href="">编辑</a></td>
                {% endif %}
                {% if pagePermission.has_del %}                <td><a href="">编辑</a></td>
                {% endif %}            </tr>
        {% endfor %}    </table>{% endblock %}

总结

RBAC中的代码
- models.py
- admin.py # 录入数据
- service.init_permission.py
- middlewares.rbac.py
- templatetags.my_tags.py
- static
配置文件

# 权限相关PERMISSION_URL_KEY = 'aaaa'PERMISSION_MENU_KEY = 'bbbbb'VALID_URLS = [    '/login/',    '/logout/',    '/index/',    '/test/',    'admin.*',    'rbac.*',
]

FROM:https://www.jianshu.com/p/f45b54768aa9

  • 我的微信
  • 这是我的微信扫一扫
  • weinxin
  • 我的微信公众号
  • 我的微信公众号扫一扫
  • weinxin

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: