- 简单示意流程图
- RBAC分析:
- 基于角色的权限管理;
- 权限等于用户可以访问的URL;
- 通过限制URL来限制权限;
- RBAC表结构组成:
from django.db import models class Menu(models.Model): """ 菜单表: """ title = models.CharField(verbose_name='菜单名称', max_length=32, db_index=True) # 创建索引 icon = models.CharField(verbose_name='图标', max_length=32) def __str__(self): return self.title class Meta: # db_table = 'menu' verbose_name = '菜单' verbose_name_plural = '菜单' class Jurisdiction(models.Model): """ 权限表 """ url = models.CharField(max_length=32) title = models.CharField(verbose_name='权限名称', max_length=32) name = models.CharField(verbose_name='反向解析别名', max_length=32, unique=True) menu = models.ForeignKey(to='Menu', null=True, blank=True) def __str__(self): return self.title class Meta: verbose_name = '权限' verbose_name_plural = '权限' class Role(models.Model): """ 角色表 """ name = models.CharField(verbose_name='角色名称', max_length=32) permissions = models.ManyToManyField(to='Jurisdiction') def __str__(self): return self.name class Meta: verbose_name = '角色' verbose_name_plural = '角色' class User(models.Model): """用户表""" name = models.CharField(verbose_name='用户名称', max_length=32) password = models.CharField(verbose_name='密码', max_length=64) roles = models.ManyToManyField(to="Role") def __str__(self): return self.name class Meta: verbose_name = '用户' verbose_name_plural = '用户'
- 菜单表;(用于生成二级菜单)
- 字段:
- id:
- title:
- icon:
- 用户表;
- 字段:
- id:
- name:
- pwd:
- 角色表;
- 字段:
- id:
- name:
- 权限表;
- 字段:
- id:
- url:
- name:
- menu:
- 用户与角色关联表;
- 字段:
- id
- uid
- rid
- 角色与权限关联表;
- 字段:
- id
- rid
- pid
- 请求访问流程:
- 中间件:
- 详情代码:
import re from django.shortcuts import redirect, HttpResponse from django.utils.deprecation import MiddlewareMixin from django.conf import settings class RbacMiddleware(MiddlewareMixin): """ 权限校验的中间件 """ def process_request(self, request): """ 请求校验 :param request: :return: """ # 1. 处理白名单 for ele in settings.VALID_LIST: if re.match(ele, request.path_info): return None # 通过白名单,无需再做权限校验 # 2. 权限校验;去session中获取权限然后对用户请求的url 一一进行匹配。 permission_dict = request.session.get(settings.RBAC_PERMISSION_SESSION_KEY) if not permission_dict: return redirect('/login/') flag = False for name, info in permission_dict.items(): reg = "^%s$" % info['url'] if re.match(reg, request.path_info): flag = True break if not flag: return HttpResponse('无权访问')
- 进行白名单设置,若访问的是白名单中的URL不做任何限制,例如访问:login/ admin/.* 等等
- 非白名单中的URL访问的验证;
- 获取用户浏览器中的session信息,若不存在,则让用户进行登录;
- login视图:
- 登录验证:
- 代码详情:
from django.shortcuts import render, redirect from rbac import models from crm.utils.md5 import gen_md5 from rbac.service.permission import init_permission def login(request): """ 用户登录 :param request: :return: """ if request.method == "GET": return render(request, 'login.html') user = request.POST.get('user') pwd = request.POST.get('pwd') pwd_md5 = gen_md5(pwd) # 根据用户名和密码去数据库校验,是否用户合法。 user_object = models.User.objects.filter(name=user, password=pwd_md5).first() if not user_object: return render(request, 'login.html', {'msg': '用户名或密码错误'}) # 用户登录成功,获取用户权限信息并放入到session中。 # 初始化session数据 init_permission(user_object, request) return redirect('/user/')
- 获取form表单信息与数据库进行比较,验证不通过,则返回错误信息;
- 验证通过,则进行数据初始化,并通过session写入用户浏览器中;
- 返回重定向;
- 数据初始化:
- 根据用户姓名从数据库获取数据,组成相对应的数据结构,写入session;
- 详情代码:
from django.conf import settings def init_permission(user, request): """ 权限初始化 :param user: 用户对象 :param request: 请求相关信息:request.session :return: 无 """ # 1. 获取权限信息 # user.roles.all() permission_queryset = user.roles.filter(permissions__id__isnull=False).values('permissions__id', 'permissions__title', 'permissions__url', 'permissions__name', 'permissions__menu_id', 'permissions__menu__title', 'permissions__menu__icon').distinct() # 2. 将权限和菜单信息放入到session中。设计:权限和菜单的数据结构。 """ 权限结构 = { 'user':{'url':'/user/'}, 'user_add':{'url':'/user/add/'}, ... } 菜单结构 = { 菜单id:{ title:'xxx', icon:'xx', children:[ {'title':'xxx','name':'xxx','url':'xxx'} # 能做菜单的权限 ] } } 生成的两种数据结构: - permission_dict: 权限认证的字典 - menu_dict:生成二级菜单以及权限控制到按钮级别的字典 """ permission_dict = {} menu_dict = {} for row in permission_queryset: permission_dict[row["permissions__name"]] = {"url": row["permissions__url"]} if not row.get("permissions__menu_id"): continue if not menu_dict.get(row["permissions__menu_id"]): menu_dict[row["permissions__menu_id"]] = { "title": row["permissions__menu__title"], "icon": row["permissions__menu__icon"], "children": [] } menu_dict[row["permissions__menu_id"]]["children"].append( { "title": row["permissions__title"], "name": row["permissions__name"], "url": row["permissions__url"], } ) # 3. 写入session request.session[settings.RBAC_PERMISSION_SESSION_KEY] = permission_dict request.session[settings.RBAC_MENU_SESSION_KEY] = menu_dict
- 难点:
- 表关联关系;
- 第一版: - 根据用户所拥有的权限多少进行限制: - 需要将用户和权限进行关联: - 用户表 - 权限表 - 假若用户很多,以及权限很多; - 生成的关联表中的数据十分多,不利于查询; - 第二版: - 在第一版的基础上为用户创建角色: - 让用户表与角色进行关联; - 角色表与权限表进行关联; - 通过对不同的角色赋予不同的权限,在通过用户的角色来限制用户的权限; - 用户与角色需要多对多的关系,生成第三张关联表; - 介与用户或许会有许多不同的角色身份 - 每个角色都会有许多用户的存在 - 生成第三张 用户与角色关联表; - 同时 权限与角色也是需要多对多的关系: - 每个权限都有可能会有不同的角色都使用 - 每个角色都有会有不同的多个权限使用; - 生成第四张 角色与权限关联表 - 补充:菜单表: - 为了在生成二级菜单时,辅助生成的一张表; - 详情可见下面的菜单结构的分析
- 数据结构搭建;
- 两种数据结构:
权限结构 = { 'user':{'url':'/user/'}, 'user_add':{'url':'/user/add/'}, ... } 菜单结构 = { 菜单id:{ title:'xxx', icon:'xx', children:[ {'title':'xxx','name':'xxx','url':'xxx'} # 能做菜单的权限 ] } }
- 权限结构:
- 在权限结构中,用url的别名作为key,url的路由信息作为value,组成字典;
- 在中间件中,读取用户携带的该权限字典中所有的values,然后循环判断即可;
- 若想省事,可以直接组成列表;
- 菜单结构:
- 在所有的权限中,可以做菜单的权限不多;例如:添加,删除,更改,便不能作为菜单显示,只能作为按钮显示;
- 需要在数据库中将可以做菜单,和不可以做菜单的表做出区别;
- 在这里采取的做法是另外创建一张菜单表,和权限表关联,如果某条权限可以作为菜单,则将该信息和菜单表进行关联,若不可,则为None;
- 在显示菜单的时候,根据该字段是否有值来进行判断,以便于生成菜单结构字典;
- 若该菜单id的值存在,且在菜单结构中买有该菜单id的key则新建一个字典,该字典中所对应的 children为空列表。
- 往所有菜单id相对应的值中的字典中的children对应的列表里添加所有的信息字典;
- 没有菜单id的权限则continue掉;
- 扩展点:
- 权限控制到按钮级别;
- 母板:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="x-ua-compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Title</title> </head> <body> <div style=" 20%;float: left"> {% load rbac %} {% menu request %} </div> <div style=" 80%;float: left"> {% block content %} {% endblock %} </div> </body> </html>
- filter函数;
from django.template import Library from django.conf import settings register = Library() @register.filter def has_permission(name, request): permission_dict = request.session[settings.RBAC_PERMISSION_SESSION_KEY] if name in permission_dict: return True
- inclusion_tag函数;
# rbac.py from django.template import Library from django.conf import settings register = Library() @register.inclusion_tag('menu.html') def menu(request): menu_dict = request.session[settings.RBAC_MENU_SESSION_KEY] return {'menu_dict': menu_dict} ########################### # menu.html <ul> {% for menu in menu_dict.values %} <li>{{ menu.title }}</li> <ul> {% for child in menu.children %} <li><a href="{{ child.url }}">{{ child.title }}</a></li> {% endfor %} </ul> {% endfor %} </ul>
- 继承母板以及控制到按钮级别权限的简单示例:
{% extends 'layout.html' %} {% load rbac %} {% block content %} <h1>右侧内容</h1> {% if 'user_add'|has_permission:request %} <a href="">添加</a> {% endif %} <table> <tbody> {% for row in data_list %} <tr> <td>{{ row.name }}</td> <td>{{ row.age }}</td> <td> {% if 'user_edit'|has_permission:request %} <a href="">编辑</a> {% endif %} {% if 'user_del'|has_permission:request %} <a href="">删除</a> {% endif %} </td> </tr> {% endfor %} </tbody> </table> {% endblock %}
- js控制菜单hide
- 分页组件;
-