1. 程式人生 > >許可權管理-一級選單-二級選單-三級選單-路徑導航和許可權粒度控制到按鈕級別

許可權管理-一級選單-二級選單-三級選單-路徑導航和許可權粒度控制到按鈕級別

許可權管理 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': [