权限管理 RBAC

 

  1. 权限管理

1. 为什么要有权限?

 

2. 开发一套权限的组件。为什么要开发组件?

 

3. 权限是什么?

web 开发中 URL 约等于 权限

 

4. 表结构的设计

 

权限表

ID URL

1 /customer/list/

2 /customer/add/

 

 

用户表

ID name pwd

1 ward 123

 

 

用户和权限的关系表(多对多)

ID user_id permission_id

1 1 1

1 1 2

 

5. 写代码

1. 查询出用户的权限写入session

2. 读取权限信息,判断是否有权限

 

最初版的权限管理梳理流程

表结构

from django.db import models


class Permission(models.Model):
   """
  权限表
  """
   title = models.CharField(max_length=32, verbose_name=\'标题\')
   url = models.CharField(max_length=32, verbose_name=\'权限\')
   
   class Meta:
       verbose_name_plural = \'权限表\'
       verbose_name = \'权限表\'
   
   def __str__(self):
       return self.title


class Role(models.Model):
   name = models.CharField(max_length=32, verbose_name=\'角色名称\')
   permissions = models.ManyToManyField(to=\'Permission\', verbose_name=\'角色所拥有的权限\', blank=True)
   
   def __str__(self):
       return self.name


class User(models.Model):
   """
  用户表
  """
   name = models.CharField(max_length=32, verbose_name=\'用户名\')
   password = models.CharField(max_length=32, verbose_name=\'密码\')
   roles = models.ManyToManyField(to=\'Role\', verbose_name=\'用户所拥有的角色\', blank=True)
   
   def __str__(self):
       return self.name
  • settings文件配置

    • #  ###### 权限相关的配置 ######
      PERMISSION_SESSION_KEY = \'permissions\'
      WHITE_URL_LIST = [
         r\'^/login/$\',
         r\'^/logout/$\',
         r\'^/reg/$\',
         r\'^/admin/.*\',
      ]
  • 其实权限就是用户能够访问那些url,不能访问那些url,我们所做的就是将每个不同身份的人

    分配不同的url

  • 在最初用户登录的时候就查询出用户的权限。并将此次权限存入到session中

    • 为什么要存入session中啊,为了不重复读取数据库,存到session中

      我们可以配置session然后将session存到缓存中(非关系型数据库中)

      这样读取的速度回很快

  • 登录成功后如何查看当前用户的权限并将其写入到session中

    • from django.shortcuts import render, HttpResponse, redirect, reverse
      from rbac import models
      from django.conf import settings

      ...

      user = models.User.objects.filter(name=username, password=pwd).first()
      # 登录成功
             # 将权限信息写入到session
             
             # 1. 查当前登录用户拥有的权限
             permission_list = user.roles.filter(permissions__url__isnull=False).values_list(
                                                                                        \'permissions__url\').distinct()
             # for i in permission_list:
             #     print(i)
             
             # 2. 将权限信息写入到session # 这里的键值我们做了全局配置
             request.session[settings.PERMISSION_SESSION_KEY] = list(permission_list)
             # 得到的permission_list是一个QuerySet的元组对象,因为session的存储是有数据类型限制所以转换为列表(列表中套元组)
  • 然后,该用户能够访问那些,不能访问那些,这时,我们可以将这个逻辑写在中间件这里

    • from django.utils.deprecation import MiddlewareMixin
      from django.conf import settings
      from django.shortcuts import HttpResponse
      import re


      class PermissionMiddleware(MiddlewareMixin):
         # 每一个请求来,都会走这个钩子函数
         def process_request(self, request):
             # 对权限进行校验
             # 1. 当前访问的URL
             current_url = request.path_info

             # 白名单的判断我们这里将白名单设置在了settings中,往settings中加就ok
             for i in settings.WHITE_URL_LIST:
                 if re.match(i, current_url):
                     return

             # 2. 获取当前用户的所有权限信息
             permission_list = request.session.get(settings.PERMISSION_SESSION_KEY)
             # 3. 权限的校验
             print(current_url)  # Django的session做了转换将元组转换成为一个列表
             for item in permission_list:
                 url = item[0]
                 if re.match("^{}$".format(url), current_url):
                     return
             else:
                 return HttpResponse(\'没有权限\')

升级版

动态生成一级菜单

 

表结构的设计

from django.db import models


class Permission(models.Model):
   """
  权限表
  """
   title = models.CharField(max_length=32, verbose_name=\'标题\')
   url = models.CharField(max_length=32, verbose_name=\'权限\')
# 用来判断哪些url是菜单,哪些不是菜单
   is_menu = models.BooleanField(default=False, verbose_name=\'是否是菜单\')
   # 记录该菜单对应的图标信息(这里是属性样式类)
   icon = models.CharField(max_length=32, verbose_name=\'图标\', null=True, blank=True)

   class Meta:
       verbose_name_plural = \'权限表\'
       verbose_name = \'权限表\'
   
   def __str__(self):
       return self.title


class Role(models.Model):
   name = models.CharField(max_length=32, verbose_name=\'角色名称\')
   permissions = models.ManyToManyField(to=\'Permission\', verbose_name=\'角色所拥有的权限\', blank=True)
   
   def __str__(self):
       return self.name


class User(models.Model):
   """
  用户表
  """
   name = models.CharField(max_length=32, verbose_name=\'用户名\')
   password = models.CharField(max_length=32, verbose_name=\'密码\')
   roles = models.ManyToManyField(to=\'Role\', verbose_name=\'用户所拥有的角色\', blank=True)
   
   def __str__(self):
       return self.name

 

注册层成功之后:

user = models.User.objects.filter(name=username, password=pwd).first()
# 将权限信息写入到session中
init_permission(request, user)
def init_permission(request, user):
   # 1. 查当前登录用户拥有的权限
   permission_query = user.roles.filter(permissions__url__isnull=False).values(
       \'permissions__url\',
       \'permissions__is_menu\',
       \'permissions__icon\',
       \'permissions__title\'
  ).distinct()
   print(\'permission_query\', permission_query)
   # 存放权限信息
   permission_list = []
   # 存放菜单信息
   menu_list = []
   for item in permission_query:
       permission_list.append({\'url\': item[\'permissions__url\']})
       if item.get(\'permissions__is_menu\'):  # 如若菜单这个字段为True
           # 将这个菜单的信息先存入一个字典,然后存入session
           menu_list.append({
               \'url\': item[\'permissions__url\'],  # 权限信息
               \'icon\': item[\'permissions__icon\'],  # 图标(Bootstrap的类样式)
               \'title\': item[\'permissions__title\'],  # 标题
          })

   # 2. 将权限信息写入到session
   request.session[settings.PERMISSION_SESSION_KEY] = permission_list
   # 将菜单的信息写入到session中
   request.session[settings.MENU_SESSION_KEY] = menu_list

母版中的菜单(一级菜单)

在母版中合适的位置导入这个include_tag

{% load rbac %}
{% menu request %}

在templatetags下的rbac.py文件中写(自定义过滤器)

import re
from django import template
from django.conf import settings

register = template.Library()


@register.inclusion_tag(\'rbac/menu.html\')
def menu(request):
   menu_list = request.session.get(settings.MENU_SESSION_KEY)
   for item in menu_list:
       url = item.get(\'url\')
       if re.match(\'^{}$\'.format(url), request.path_info):
           item[\'class\'] = \'active\'
   return {"menu_list": menu_list}

在templates下的rbac文件夹下创建enum.html

<div class="static-menu">

  {% for item in menu_list %}
       <a href="{{ item.url }}" class="{{ item.class }}">
           <span class="icon-wrap"><i class="fa {{ item.icon }}"></i></span>{{ item.title }}</a>
  {% endfor %}

</div>
<--这个代码的样式可以放到该app文件夹下的static下的css中建立一个menu.css-->

因为将数据存入了session中,所以我们可以通过request.session.来获取数据

.left-menu .menu-body .static-menu {

}

.left-menu .menu-body .static-menu .icon-wrap {
   width: 20px;
   display: inline-block;
   text-align: center;
}

.left-menu .menu-body .static-menu a {
   text-decoration: none;
   padding: 8px 15px;
   border-bottom: 1px solid #ccc;
   color: #333;
   display: block;
   background: #efefef;
   background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #efefef), color-stop(1, #fafafa));
   background: -ms-linear-gradient(bottom, #efefef, #fafafa);
   background: -moz-linear-gradient(center bottom, #efefef 0%, #fafafa 100%);
   background: -o-linear-gradient(bottom, #efefef, #fafafa);
   filter: progid:dximagetransform.microsoft.gradient(startColorStr=\'#e3e3e3\', EndColorStr=\'#ffffff\');
   -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr=\'#fafafa\',EndColorStr=\'#efefef\')";
   box-shadow: inset 0px 1px 1px white;
}

.left-menu .menu-body .static-menu a:hover {
   color: #2F72AB;
   border-left: 2px solid #2F72AB;
}

.left-menu .menu-body .static-menu a.active {
   color: #2F72AB;
   border-left: 2px solid #2F72AB;
}

settings的配置

#  ###### 权限相关的配置 ######
PERMISSION_SESSION_KEY = \'permissions\'
MENU_SESSION_KEY = \'menus\'
WHITE_URL_LIST = [
   r\'^/login/$\',
   r\'^/logout/$\',
   r\'^/reg/$\',
   r\'^/admin/.*\',
]

 

中间件的配置

在middlewares目录(中间件目录中)创建rbac.py文件

from django.utils.deprecation import MiddlewareMixin
from django.conf import settings
from django.shortcuts import HttpResponse
import re


class PermissionMiddleware(MiddlewareMixin):
   def process_request(self, request):
       # 对权限进行校验
       # 1. 当前访问的URL
       current_url = request.path_info

       # 白名单的判断(settings中配置好了)
       for i in settings.WHITE_URL_LIST:
           if re.match(i, current_url):
               return

       # 2. 获取当前用户的所有权限信息
       permission_list = request.session.get(settings.PERMISSION_SESSION_KEY)
       
       # 3. 权限的校验
       for item in permission_list:
           url = item[\'url\']
           if re.match("^{}$".format(url), current_url):
               return
       else:
           return HttpResponse(\'没有权限\')

应用rbac组件

 

1、拷贝rbac组件到新的项目中并注册APP

2、配置权限的相关信息

#  ###### 权限相关的配置 ######
PERMISSION_SESSION_KEY = \'permissions\'
MENU_SESSION_KEY = \'menus\'
WHITE_URL_LIST = [
   r\'^/login/$\',
   r\'^/logout/$\',
   r\'^/reg/$\',
   r\'^/admin/.*\',
]

3、创建跟权限相关的表

  • 执行命令

    • python3 manage.py makemigrations

    • python3 manage.py migrate

4、录入权限信息

  • 创建超级用户

  • 录入所有权限信息

  • 创建角色 给角色分权限

  • 创建用户 给用户分角色

5、在登录成功之后 写入权限和菜单的信息到session中

6、配置上中间件,进行权限的校验

7、使用动态菜单

<!-导入静态文件-->
<link rel="stylesheet" href="{% static \'css/menu.css\' %}">

使用inclusion_tag
<div class="left-menu">
   <div class="menu-body">
      {% load rbac %}
      {% menu request %}
   </div>
</div>

 

母版中的菜单(动态生成二级菜单)

 

信息管理

客户列表

财务管理

缴费列表

 

User name pwd

Role name permissions(FK) 2user

Permission title(二) url menu(FK) 2role

Menu title(一)

Models.py

from django.db import models


class Menu(models.Model):
   """
  一级菜单
  """
   title = models.CharField(max_length=32, unique=True)  # 一级菜单的名字
   icon = models.CharField(max_length=32, verbose_name=\'图标\', null=True, blank=True)

   class Meta:
       verbose_name_plural = \'菜单表\'
       verbose_name = \'菜单表\'

   def __str__(self):
       return self.title


class Permission(models.Model):
   """
  权限表
  有关联Menu的二级菜单
  没有关联Menu的不是二级菜单,是不可以做菜单的权限
  """
   title = models.CharField(max_length=32, verbose_name=\'标题\')
   url = models.CharField(max_length=32, verbose_name=\'权限\')
   menu = models.ForeignKey(\'Menu\', null=True, blank=True)

   class Meta:
       verbose_name_plural = \'权限表\'
       verbose_name = \'权限表\'

   def __str__(self):
       return self.title


class Role(models.Model):
   name = models.CharField(max_length=32, verbose_name=\'角色名称\')
   permissions = models.ManyToManyField(to=\'Permission\', verbose_name=\'角色所拥有的权限\', blank=True)

   def __str__(self):
       return self.name


class User(models.Model):
   """
  用户表
  """
   name = models.CharField(max_length=32, verbose_name=\'用户名\')
   password = models.CharField(max_length=32, verbose_name=\'密码\')
   roles = models.ManyToManyField(to=\'Role\', verbose_name=\'用户所拥有的角色\', blank=True)

   def __str__(self):
       return self.name

登录

 

from django.shortcuts import render, HttpResponse, redirect, reverse
from rbac import models
from django.conf import settings
import copy
from rbac.server.init_permission import init_permission


def login(request):
   if request.method == \'POST\':
       username = request.POST.get(\'username\')
       pwd = request.POST.get(\'pwd\')

       user = models.User.objects.filter(name=username, password=pwd).first()

       if not user:
           err_msg = \'用户名或密码错误\'
           return render(request, \'login.html\', {\'err_msg\': err_msg})
       # 登录成功
       # 将权限信息写入到session
       init_permission(request, user)
       return redirect(reverse(\'customer\'))
   return render(request, \'login.html\')
def init_permission(request, user):
   # 1. 查当前登录用户拥有的权限
   permission_query = user.roles.filter(permissions__url__isnull=False).values(
       \'permissions__url\',
       \'permissions__title\',
       \'permissions__menu_id\',
       \'permissions__menu__title\',
       \'permissions__menu__icon\',
  ).distinct()
   print(permission_query)
   # 存放权限信息
   permission_list = []
   # 存放菜单信息
   menu_dict = {}
   for item in permission_query:
       permission_list.append({\'url\': item[\'permissions__url\']})
       menu_id = item.get(\'permissions__menu_id\')
       if not menu_id:
           continue
       if menu_id not in menu_dict:
           menu_dict[menu_id] = {
               \'title\': item[\'permissions__menu__title\'],
               \'icon\': item[\'permissions__menu__icon\'],
               \'children\': [
                  {
                       \'title\': item[\'permissions__title\'],
                       \'url\': item[\'permissions__url\']}
              ]
          }
       else:
           menu_dict[menu_id][\'children\'].append(
              {\'title\': item[\'permissions__title\'], \'url\': item[\'permissions__url\']})

   # 2. 将权限信息写入到session
   request.session[settings.PERMISSION_SESSION_KEY] = permission_list
   # 将菜单的信息写入到session中
   request.session[settings.MENU_SESSION_KEY] = menu_dict

将拿到的数据存入session

写在一个自定义inclusion_tag

母版

{% load rbac %}
{% menu request %}

rbac.py

import re
from django import template
from django.conf import settings

register = template.Library()


@register.inclusion_tag(\'rbac/menu.html\')
def menu(request):
  menu_list = request.session.get(settings.MENU_SESSION_KEY)
  return {"menu_list": menu_list}

menu.html

<div class="multi-menu">
  {% for item in menu_list.values %}
       <div class="item">
           <div class="title"><i class="fa {{ item.icon }}"></i> {{ item.title }}</div>
           <div class="body hide">
              {% for child in item.children %}
                   <a href="{{ child.url }}">{{ child.title }}</a>
              {% endfor %}
           </div>
       </div>
  {% endfor %}
</div>

menu.css0

.static-menu .icon-wrap {
   width: 20px;
   display: inline-block;
   text-align: center;
}

.static-menu a {
   text-decoration: none;
   padding: 8px 15px;
   border-bottom: 1px solid #ccc;
   color: #333;
   display: block;
   background: #efefef;
   background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #efefef), color-stop(1, #fafafa));
   background: -ms-linear-gradient(bottom, #efefef, #fafafa);
   background: -moz-linear-gradient(center bottom, #efefef 0%, #fafafa 100%);
   background: -o-linear-gradient(bottom, #efefef, #fafafa);
   filter: progid:dximagetransform.microsoft.gradient(startColorStr=\'#e3e3e3\', EndColorStr=\'#ffffff\');
   -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr=\'#fafafa\',EndColorStr=\'#efefef\')";
   box-shadow: inset 0px 1px 1px white;
}

.static-menu a:hover {
   color: #2F72AB;
   border-left: 2px solid #2F72AB;
}

.static-menu a.active {
   color: #2F72AB;
   border-left: 2px solid #2F72AB;
}

.multi-menu .item {
   background-color: white;
}

.multi-menu .item > .title {
   padding: 10px 5px;
   border-bottom: 1px solid #dddddd;
   cursor: pointer;
   color: #333;
   display: block;
   background: #efefef;
   background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #efefef), color-stop(1, #fafafa));
   background: -ms-linear-gradient(bottom, #efefef, #fafafa);
   background: -o-linear-gradient(bottom, #efefef, #fafafa);
   filter: progid:dximagetransform.microsoft.gradient(startColorStr=\'#e3e3e3\', EndColorStr=\'#ffffff\');
   -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr=\'#fafafa\',EndColorStr=\'#efefef\')";
   box-shadow: inset 0 1px 1px white;
}

.multi-menu .item > .body {
   border-bottom: 1px solid #dddddd;
}

.multi-menu .item > .body a {
   display: block;
   padding: 5px 20px;
   text-decoration:
版权声明:本文为xiao-xue-di原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/xiao-xue-di/p/9885998.html