1. 程式人生 > >基於角色權限管理:rbac設計分析以及具體細節

基於角色權限管理:rbac設計分析以及具體細節

兩個文件 圖片 session creat 記得 ret back 現在 中間

權限管理---設計分析以及具體細節

說起權限我們大家都知道,不一樣的角色會有不一樣的權限。

比如就像學生管理系統一樣,管理員,老師,學生之間的權限都是不一樣的,那麽展示的頁面也是不一樣的。

所以,我們現在來看看具體操作。

目標:生成一個獨立的組件,到哪都能用!(是不是很厲害)

步驟

一、先創建一個項目,建立一個app01和rbac的應用

二、表結構的設計

  1、先看配置文件合適不,給創建的rbac在配置文件裏面設置一下
   找到INSTALLED_APPS=[‘rbac‘]

  技術分享圖片

  2、 表結構設計      

    models中創建類:五個類,七張表
    角色表:


    用戶表:
    權限表:

    權限組表:

    菜單表:

    角色表和權限表是多對多的關系(一個角色可以有多個權限,一個權限可以對應多個角色)
    用戶表和角色表是多對多的關系(一個用戶可以有多個角色,一個角色有多個用戶)

    所以有會多生成兩張關系表

    一個菜單下面有多個組

    一個組下面有多個菜單

    一個菜單下面有多個權限

 技術分享圖片

from django.db import models

# Create your models here.
class Role(models.Model):
    title = models.CharField(max_length=32,verbose_name="
角色") permissions = models.ManyToManyField(to="Permission",verbose_name="擁有權限的角色",blank=True) #權限和角色是多對多的關系 def __str__(self): return self.title class Meta: verbose_name_plural = "角色表" class Permission(models.Model): title = models.CharField(max_length=32,verbose_name="
權限名") url = models.CharField(max_length=32,verbose_name="帶正則的url") codes = models.CharField(max_length=32,verbose_name="代碼") group = models.ForeignKey(to="Group",verbose_name="所屬組",blank=True) #組和權限是一對多的關系,一個組有多個權限 menu_gp = models.ForeignKey(to=Permission,related_name=aaa,null=True,blank=True,verbose_name="組內菜單") def __str__(self): return self.title class Meta: verbose_name_plural = "權限表" class UserInfo(models.Model): name = models.CharField(max_length=32,verbose_name="姓名") password = models.CharField(max_length=64,verbose_name="密碼") email = models.CharField(max_length=32,verbose_name="郵箱") roles = models.ManyToManyField(to="Role",blank=True) #用戶和角色是多對多的關系 def __str__(self): return self.name class Meta: verbose_name_plural = "用戶表" class Group(models.Model): title = models.CharField(max_length=32,verbose_name="組名稱") menu = models.ForeignKey(to="Menu",verbose_name="組內菜單",blank=True) #一個組下有多個菜單 def __str__(self): return self.title class Meta: verbose_name_plural = "權限組" class Menu(models.Model): caption = models.CharField(max_length=32,verbose_name="菜單") def __str__(self): return self.caption class Meta: verbose_name_plural = "菜單表"

為什麽要多加個code列和權限組表呢?

1、我們一般是先看到的是列表頁面,在這個頁面上是否顯示添加,是否顯示編輯,是否顯示刪除,

都是需要判斷的有無添加權限,有無刪除權限,有無編輯權限,我們可以給每一個url一個代號

dict = {
    1:{                    代號
          /userinfo/            list
       /userinfo/add/       add
       /userinfo/del(\d+)/    del 
       /userinfo/edit(\d+)/    edit
    }
  }

不僅在列表頁面需要知道他有那些權限,在其他頁面也知道他有那些權限
所以上面的方案還是有點不好,那麽我們采取下面的方案。將代號取出來放在一個列表裏面

dict = {
      1:{
              "codes":["list","add","del","edit"]
             urls:[
                "/userinfo/",
                "/userinfo/add"/,
                "/userinfo/del(\d+)/ ",
                "/userinfo/edit(\d+)/ ",
              ]    
        }
      2:{
           "codes":{"list","add","del","edit"}
            urls:[
                 "/order",
                 "/order/add"/,
                  "/order/del(\d+)/ ",
                 "/order/edit(\d+)/ ",
               ]    
       }
} 


把這個字典存到session中
當你訪問頁面的時候我就知道你有什麽權限
一個url對應一個code
多個url對應一個組

註意:
  關聯字段 null = True 數據庫用的時候可以為空
  關聯字段 blank = True admin用的時候可以為空

當出現錯誤

技術分享圖片

解決辦法:

python manage.py migrate --fake 廢棄

三、通過django-admin錄入權限數據

- 先創建一個超級用戶 python3 manage.py createsuperuser
    - 用戶名 root
    - 密碼 root1234
    - 在admin.py 中
        from rbac import models
        admin.site.register(models.Permission)
        admin.site.register(models.Role)
        admin.site.register(models.UserInfo)
      這樣的話上去的是英文的,如果你想讓中文顯示就在類中加一個類
        class Meta:
           verbose_name_plural = "權限表"
      - 當你給關聯字段錄入數據的時候會有錯誤提示,那麽在類中你的那個關聯字段在加一個屬性blank = True 可以為空
      permissions = models.ManyToManyField(to="Permission",verbose_name="具有的所有權限", blank=True)

四、編寫登錄

1.編寫登錄
2.如果用戶驗證成功就設置session
3.先查出當前用戶的所有的權限
4.從這些權限中找到所有的url,把這些url放到session中
這些都是在rbac裏面的操作,如果我們做一些復雜的操作,可能會有好多的代碼
 我們寫rbac的目的是做成一個公共的組件,為了讓別人省事
 我們在創建一個server的文件夾,裏面創建一個init_permission的py文件。

 結構化數據:方便以後做操作。。。

dict = {
      1:{
              "codes":["list","add","del","edit"]
             urls:[
                "/userinfo/",
                "/userinfo/add"/,
                "/userinfo/del(\d+)/ ",
                "/userinfo/edit(\d+)/ ",
              ]    
        }
      2:{
           "codes":{"list","add","del","edit"}
            urls:[
                 "/order",
                 "/order/add"/,
                  "/order/del(\d+)/ ",
                 "/order/edit(\d+)/ ",
               ]    
       }
}

5.拿到用戶請求的url去session裏面做驗證
  獲取當前請求的url
  獲取session中保存當前用戶的權限
  然後開始驗證
  如果匹配成功就有權訪問
  如果匹配不成功就無權訪問
  用re去匹配的時候,re.match(/userinfo/,/userinfo/add) #都能匹配到
  那麽要記得在匹配正則的時候加個起始符和終止符regex = "^{0}$".format(url)
  def login(request):
    .....
    設置session
  def index(request):
    ....
    獲取session
  def userinfo(request):
    獲取session
這樣如果有好多個函數,就的重復好多代碼,我們可以用中間件來處理
中間件和裝飾器的區別:
  中間件用來做批量處理
  如果函數不多的話可以用加裝飾器的方法

五、中間件:獲取session,並且當用戶匹配成功的時候,先把code保存在request中,方便以後判斷

1、要設置表名單

技術分享圖片

2、必須繼承MiddlewareMixin這個類

class 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 response

六、設計權限管理-----問題:在訪問列表頁面時,是否需要判斷有無添加權限、有無刪除權限、有無編輯權限。

views.py

def userinfo(request):
    # 方式一
    # Page_permission = request.permission_code_list
    # 方式二:實例化
    page_permission = BasePagePermission(request.permission_code_list)
    print("page_permission",request.permission_code_list)
    data_list = [
        {"id":1,"name":"xxx1"},
        {"id":2,"name":"xxx2"},
        {"id":3,"name":"xxx3"},
        {"id":4,"name":"xxx4"},
        {"id":5,"name":"xxx5"},
    ]
    return render(request,"userinfo.html",{"data_list":data_list,"page_permission":page_permission})

在模板userinfo.html中:兩種使用方式

方式一:

<table>
    {% if "add" in Page_permission %}
        <a href="#">添加</a>
    {% endif %}
    {% for row in data_list %}
         <tr>
            <td>{{ row.id }}</td>
            <td>{{ row.name }}</td>
             {% if "edit" in Page_permission %}
                <td><a href="#">編輯</a></td>
             {% endif %}
           {% if "del" in Page_permission %}
                 <td>{<a href="#">刪除</a></td>
           {% endif %}
        </tr>
    {% endfor %}
</table>

如果不想每一個都判斷,就用方式二

方式二:

把permission_code_list處理一下
在views中定義一個類
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_del(self):
        if "del" in self.code_list:
            return True
    def has_edit(self):
        if "edit" in self.code_list:
            return True
實例化:page_permission = BasePagePermission(request.permission_code_list)
在模板中
<table>
    {% if page_permission.has_add %}
        <a href="#">添加</a>
    {% endif %}
    {% for row in data_list %}
         <tr>
            <td>{{ row.id }}</td>
            <td>{{ row.name }}</td>
             {% if page_permission.has_edit %}
                <td><a href="#">編輯</a></td>
             {% endif %}
           {% if page_permission.has_del %}
                 <td>{<a href="#">刪除</a></td>
           {% endif %}
        </tr>
    {% endfor %}
</table>

七、設計菜單管理

1、如何生成菜單

2、怎麽讓這些菜單分級顯示並且如果當前訪問的url權限默認展開如果是組內菜單就加粗或者變紅

3、非菜單url,默認選中原菜單。(如果你是點擊用戶列表進來的,那麽你看到頁面了,

  如果你點擊添加的時候,你的那個用戶列看不見了,這就不好了。

所以要設計當你點擊添加按鈕的時候,那個用戶列表被默認選中)

菜單管理
  菜單一
    用戶管理
    權限管理
  菜單二
    訂單管理
    角色管理

分級做了菜單。這些菜單該顯示什麽菜單?是當前用戶登錄之後從數據庫拿到這個用戶擁有的權限,然後把權限搞成菜單

在表裏面設計了一個組內菜單(自關聯 ),當menu_gp_id為NULL就代表可以作為菜單

技術分享圖片

1、在初始化的時候,初始化權限信息,獲取權限信息並放置在session中

menu_list = []
    for item in permission_list:
        tpl = {
            "id":item["permissions__id"],
            "title":item["permissions__title"],
            "url":item["permissions__url"],
            "menu_gp_id":item["permissions__menu_gp_id"],
            "menu_id":item["permissions__group__menu_id"],
            "menu_title":item["permissions__group__menu__caption"]
        }
        menu_list.append(tpl)
    request.session[settings.PERMISSION_MENU_KEY] = menu_list

因為是要在頁面上渲染,一般我們會在視圖函數的render裏面加{"":變量}這樣渲染,
但是還有個更好用的方法:用自定義的標簽

具體操作:
  1、找到app創建一個templatetags的文件夾
  2、然後在裏面隨便創建一個文件
  3、導入form django.template import Library
      register = Library()

      方式一:
        @register.simple_tag
        def menu():
          return "菜單" 這裏返回啥頁面上就顯示啥
        然後在母版裏面導入mnue.html
        {% load rbac %}

      方式二:
        @register.includsion_tag("xxx.html") #這裏存放的是html文件,,,@register.includsion_tag("xxx.html") 自動會讀這個文件並且把返回值拿到在頁面上渲染
        def menu():
          return "菜單" 這裏返回啥頁面上就顯示啥
        “在母版中:{%menu_html request%} request是參數,記得要加上{% load rbac %}

  4、註意:
   如果有兩個文件夾同名,避免發生沖突:就再創建一個文件夾包起來

2、去Session中獲取菜單相關信息,匹配當前URL,生成菜單

先把和菜單相關的所有字段取出來

menu_list = [
    {‘id‘: 1, ‘title‘: ‘用戶列表‘, ‘url‘: ‘/userinfo/‘, ‘menu_gp_id‘: None, ‘menu_id‘: 2, ‘menu_title‘: ‘菜單二‘}, 
    {‘id‘: 2, ‘title‘: ‘添加用戶‘, ‘url‘: ‘/userinfo/add/‘, ‘menu_gp_id‘: 1, ‘menu_id‘: 2, ‘menu_title‘: ‘菜單二‘}, 
    {‘id‘: 3, ‘title‘: ‘刪除用戶‘, ‘url‘: ‘/userinfo/del/(\\d+)/‘, ‘menu_gp_id‘: 1, ‘menu_id‘: 2, ‘menu_title‘: ‘菜單二‘}, 
    {‘id‘: 4, ‘title‘: ‘編輯用戶‘, ‘url‘: ‘/userinfo/edit/(\\d+)/‘, ‘menu_gp_id‘: 1, ‘menu_id‘: 2, ‘menu_title‘: ‘菜單二‘}, 
    {‘id‘: 5, ‘title‘: ‘訂單列表‘,‘url‘: ‘/order/‘, ‘menu_gp_id‘: None, ‘menu_id‘: 1, ‘menu_title‘: ‘菜單一‘}, 
    {‘id‘: 6, ‘title‘: ‘添加訂單‘, ‘url‘: ‘/order/add/‘, ‘menu_gp_id‘: 2, ‘menu_id‘: 1, ‘menu_title‘: ‘菜單一‘}, 
    {‘id‘: 7, ‘title‘: ‘刪除訂單‘, ‘url‘: ‘/order/del/(\\d+)/‘, ‘menu_gp_id‘: 2, ‘menu_id‘: 1, ‘menu_title‘: ‘菜單一‘}, 
    {‘id‘: 8, ‘title‘: ‘編輯訂單‘, ‘url‘: ‘/order/edit/(\\d+)/‘, ‘menu_gp_id‘: 2, ‘menu_id‘: 1, ‘menu_title‘: ‘菜單一‘}
]

然後循環列表找出可以作為菜單的權限

{
    1: {‘id‘: 1, ‘title‘: ‘用戶列表‘, ‘url‘: ‘/userinfo/‘, ‘menu_gp_id‘: None, ‘menu_id‘: 2, ‘menu_title‘: ‘菜單二‘}, 
    5: {‘id‘: 5, ‘title‘: ‘訂單列表‘, ‘url‘: ‘/order/‘, ‘menu_gp_id‘: None, ‘menu_id‘: 1, ‘menu_title‘: ‘菜單一‘}
}

再次循環列表向上邊的字典中添加active

{
    1: {‘id‘: 1, ‘title‘: ‘用戶列表‘, ‘url‘: ‘/userinfo/‘, ‘menu_gp_id‘: None, ‘menu_id‘: 2, ‘menu_title‘: ‘菜單二‘, ‘active‘: True},
    5: {‘id‘: 5, ‘title‘: ‘訂單列表‘, ‘url‘: ‘/order/‘, ‘menu_gp_id‘: None, ‘menu_id‘: 1, ‘menu_title‘: ‘菜單一‘}
}

結構化數據(吧上面得到的數據化成下面這樣格式的,方便以後使用)

{
    1: {
            ‘menu_id‘: 1,
            ‘menu_title‘: ‘菜單一‘,
            ‘active‘: None, 
            ‘children‘: [
                    {‘title‘: ‘訂單列表‘, ‘url‘: ‘/order/‘, ‘active‘: None}
                ]
            }
    2: {
        ‘menu_id‘: 2, 
        ‘menu_title‘: ‘菜單二‘, 
        ‘active‘: True,
        ‘children‘: [
                {‘title‘: ‘用戶列表‘, ‘url‘: ‘/userinfo/‘, ‘active‘: True}
            ]
        },
    
}

基於角色權限管理:rbac設計分析以及具體細節