Django基于RBAC的權(quán)限組件

Django基于RBAC的權(quán)限組件


RBAC前奏

  1. RBAC概念
    RBAC(Role-Based Access Control,基于角色的訪問(wèn)控制),就是用戶通過(guò)角色與權(quán)限進(jìn)行關(guān)聯(lián)堵第。簡(jiǎn)單地說(shuō)踏志,一個(gè)用戶擁有若干角色针余,每一個(gè)角色擁有若干權(quán)限。這樣忍级,就構(gòu)造成“用戶-角色-權(quán)限”的授權(quán)模型轴咱。在這種模型中烈涮,用戶與角色之間坚洽,角色與權(quán)限之間讶舰,一般者是多對(duì)多的關(guān)系需了。
  2. 擴(kuò)展
  • 角色是什么援所?可以理解為一定數(shù)量的權(quán)限的集合住拭,權(quán)限的載體滔岳。例如:一個(gè)論壇系統(tǒng)谱煤,“超級(jí)管理員”刘离、“版主”都是角色硫惕。版主可管理版內(nèi)的帖子恼除、可管理版內(nèi)的用戶等豁辉,這些是權(quán)限徽级。要給某個(gè)用戶授予這些權(quán)限聊浅,不需要直接將權(quán)限授予用戶狗超,可將“版主”這個(gè)角色賦予該用戶努咐。
  • 當(dāng)用戶的數(shù)量非常大時(shí)渗稍,要給系統(tǒng)每個(gè)用戶逐一授權(quán)(授角色),是件非常煩瑣的事情灸姊。這時(shí)力惯,就需要給用戶分組父晶,每個(gè)用戶組內(nèi)有多個(gè)用戶甲喝。除了可給用戶授權(quán)外埠胖,還可以給用戶組授權(quán)淳玩。這樣一來(lái)凯肋,用戶擁有的所有權(quán)限侮东,就是用戶個(gè)人擁有的權(quán)限與該用戶所在用戶組擁有的權(quán)限之和悄雅。
  • 在應(yīng)用系統(tǒng)中宽闲,權(quán)限表現(xiàn)成什么握牧?對(duì)功能模塊的操作容诬,對(duì)上傳文件的刪改,菜單的訪問(wèn)沿腰,甚至頁(yè)面上某個(gè)按鈕览徒、某個(gè)圖片的可見(jiàn)性控制,都可屬于權(quán)限的范疇颂龙。有些權(quán)限設(shè)計(jì)习蓬,會(huì)把功能操作作為一類(lèi)纽什,而把文件芦缰、菜單、頁(yè)面元素等作為另一類(lèi)涕俗,這樣構(gòu)成“用戶-角色-權(quán)限-資源”的授權(quán)模型。而在做數(shù)據(jù)表建模時(shí)元镀,可把功能操作和資源統(tǒng)一管理,也就是都直接與權(quán)限表進(jìn)行關(guān)聯(lián),這樣可能更具便捷性和易擴(kuò)展性萝快。
  • 請(qǐng)留意權(quán)限表中有一列“權(quán)限類(lèi)型”,我們根據(jù)它的取值來(lái)區(qū)分是哪一類(lèi)權(quán)限奄容,如“MENU”表示菜單的訪問(wèn)權(quán)限、“OPERATION”表示功能模塊的操作權(quán)限、“FILE”表示文件的修改權(quán)限奕谭、“ELEMENT”表示頁(yè)面元素的可見(jiàn)性控制等官册。
  • 這樣設(shè)計(jì)的好處有二。其一员淫,不需要區(qū)分哪些是權(quán)限操作沃斤,哪些是資源徘公,(實(shí)際上,有時(shí)候也不好區(qū)分等太,如菜單辛燥,把它理解為資源呢還是功能模塊權(quán)限呢徘六?)。其二和屎,方便擴(kuò)展,當(dāng)系統(tǒng)要對(duì)新的東西進(jìn)行權(quán)限控制時(shí),我只需要建立一個(gè)新的關(guān)聯(lián)表“權(quán)限XX關(guān)聯(lián)表”绪氛,并確定這類(lèi)權(quán)限的權(quán)限類(lèi)型字符串。
  • 這里要注意的是,權(quán)限表與權(quán)限菜單關(guān)聯(lián)表宛琅、權(quán)限菜單關(guān)聯(lián)表與菜單表都是一對(duì)一的關(guān)系片效。(文件昙读、頁(yè)面權(quán)限點(diǎn)只嚣、功能操作等同理)蕴掏。也就是每添加一個(gè)菜單,就得同時(shí)往這三個(gè)表中各插入一條記錄徐伐。這樣祸穷,可以不需要權(quán)限菜單關(guān)聯(lián)表需曾,讓權(quán)限表與菜單表直接關(guān)聯(lián)车份,此時(shí)出爹,須在權(quán)限表中新增一列用來(lái)保存菜單的ID梢为,權(quán)限表通過(guò)“權(quán)限類(lèi)型”和這個(gè)ID來(lái)區(qū)分是種類(lèi)型下的哪條記錄。
  • 隨著系統(tǒng)的日益龐大旁理,為了方便管理,可引入角色組對(duì)角色進(jìn)行分類(lèi)管理郁副,跟用戶組不同既荚,角色組不參與授權(quán)吸占。例如:某電網(wǎng)系統(tǒng)的權(quán)限管理模塊中,角色就是掛在區(qū)局下,而區(qū)局在這里可當(dāng)作角色組绪杏,它不參于權(quán)限分配僧著。另外站故,為方便上面各主表自身的管理與查找憋活,可采用樹(shù)型結(jié)構(gòu)辜梳,如菜單樹(shù)析二、功能樹(shù)等,當(dāng)然這些可不需要參于權(quán)限分配。
  1. 基于rbac的實(shí)現(xiàn)
    flask-rbac
    simple-rbac

知識(shí)點(diǎn)儲(chǔ)備

  1. Django ORM
    常用的orm方法,all,values,values_list等
    from models import User
  • all
    models.User.objects.all(),返回值為queryset類(lèi)型,形如:[obj,obj,obj]
  • values
    models.User.objects.values('id','username','password'),返回值為queryset類(lèi)型,形如:[{'id':1,'username':'shuke','password':'123456'}]
  • values_list
    models.User.objects.values_list('id','username','password'),返回值為queryset類(lèi)型,形如: [(1,'shuke','123456'),(2,'mary','123456')]
  1. 一對(duì)多及多對(duì)多
  • 一對(duì)多(FK常用操作)
from django.db import models
class A(models.Model):
    name = models.CharField(max_length=32)

class B(models.Model):
    title = models.CharField(max_length=32)
    fk = models.ForeignKey(to="A")
# 跨表操作
a. all()
b_list = models.B.objects.all()
for item in b_list:
    item.id
    item.name
    item.fk_id
    item.fk
    item.fk.name
    item.fk.id
b. values()
b_list = models.B.objects.values('id','name','fk_id','fk__name')
for item in b_list:
    item['id']
    item['name']
    item['fk_id']
    item['fk__name']
c. values_list()
b_list = models.B.objects.values_list('id','name','fk_id','fk__name')
for item in b_list:
    item[0]  # id
    item[1]  # name
    item[2]  # fk_id
    item[3]  # fk__name
d. 查找名稱是"Jack"的用戶所有B表中的數(shù)據(jù)
models.B.objects.filter(fk__name="Jack").all()    
  • 多對(duì)多(ManyToMany)
from django.db import models
class A(models.Model):
    name = models.CharField(max_length=32)

class B(models.Model):
    title = models.CharField(max_length=32)
    m2m = models.ManyToMany(to="A")
PS: 自動(dòng)會(huì)生成第3張表
a. 在A和B表中各插入2條數(shù)據(jù)
models.A.objects.create(name="Jack")
models.A.objects.create(name="Mary")

models.A.objects.create(title="IT")
models.A.objects.create(title="CTO")

b. CTO和['Jack','Mary']創(chuàng)建關(guān)系
obj = models.B.objects.get(title="CTO")
obj.m2m.add(1)   # 此處可以寫(xiě)id也可以寫(xiě)關(guān)聯(lián)的A表中的obj
obj.m2m.add(2)

c. 查找CTO的關(guān)聯(lián)的人
obj = models.B.objects.get(title="CTO")
obj.m2m.all()    # 得到一個(gè)QuerySet列表都许,內(nèi)容為A表中的對(duì)象
  1. 中間件
    中間件其實(shí)就是一個(gè)類(lèi)弧烤,包含2個(gè)方法,形如:
class MiddleWare:
    # 所有的resquest請(qǐng)求都需要經(jīng)過(guò)該方法,且該方法返回值為None時(shí)急波,繼續(xù)請(qǐng)求下一個(gè)中間件
    def process_request(self,request):
        pass
    def process_response(self,request,response):
        pass

注: 中間件編寫(xiě)完成后需要在settings文件中進(jìn)行注冊(cè)使用阱扬,注冊(cè)時(shí)注意中間件順序

  1. Session與Cookie的區(qū)別
  • 由于HTTP協(xié)議是無(wú)狀態(tài)的協(xié)議,所以服務(wù)端需要記錄用戶的狀態(tài)時(shí),就需要用某種機(jī)制來(lái)識(shí)具體的用戶,這個(gè)機(jī)制就是Session.典型的場(chǎng)景比如購(gòu)物車(chē),當(dāng)你點(diǎn)擊下單按鈕時(shí),由于HTTP協(xié)議無(wú)狀態(tài),所以并不知道是哪個(gè)用戶操作的埂陆,所以服務(wù)端要為特定的用戶創(chuàng)建了特定的Session鹃栽,用用于標(biāo)識(shí)這個(gè)用戶,并且跟蹤用戶,這樣才知道購(gòu)物車(chē)?yán)锩嬗袔妆緯?shū)。這個(gè)Session是保存在服務(wù)端的,有一個(gè)唯一標(biāo)識(shí)。在服務(wù)端保存Session的方法很多,內(nèi)存、數(shù)據(jù)庫(kù)、文件都有。集群的時(shí)候也要考慮Session的轉(zhuǎn)移,在大型的網(wǎng)站舟肉,一般會(huì)有專門(mén)的Session服務(wù)器集群整慎,用來(lái)保存用戶會(huì)話够吩,這個(gè)時(shí)候 Session 信息都是放在內(nèi)存的湾笛,使用一些緩存服務(wù)比如Memcached之類(lèi)的來(lái)放 Session库倘。
  • 思考一下服務(wù)端如何識(shí)別特定的客戶饱亿?這個(gè)時(shí)候Cookie就登場(chǎng)了。每次HTTP請(qǐng)求的時(shí)候,客戶端都會(huì)發(fā)送相應(yīng)的Cookie信息到服務(wù)端识埋。實(shí)際上大多數(shù)的應(yīng)用都是用 Cookie 來(lái)實(shí)現(xiàn)Session跟蹤的惠豺,第一次創(chuàng)建Session的時(shí)候热监,服務(wù)端會(huì)在HTTP協(xié)議中告訴客戶端,需要在 Cookie 里面記錄一個(gè)Session ID,以后每次請(qǐng)求把這個(gè)會(huì)話ID發(fā)送到服務(wù)器,我就知道你是誰(shuí)了。有人問(wèn),如果客戶端的瀏覽器禁用了 Cookie 怎么辦?一般這種情況下,會(huì)使用一種叫做URL重寫(xiě)的技術(shù)來(lái)進(jìn)行會(huì)話跟蹤,即每次HTTP交互,URL后面都會(huì)被附加上一個(gè)諸如 sid=xxxxx 這樣的參數(shù),服務(wù)端據(jù)此來(lái)識(shí)別用戶。
  • Cookie其實(shí)還可以用在一些方便用戶的場(chǎng)景下,設(shè)想你某次登陸過(guò)一個(gè)網(wǎng)站犹菇,下次登錄的時(shí)候不想再次輸入賬號(hào)了肌毅,怎么辦笨奠?這個(gè)信息可以寫(xiě)到Cookie里面蔚袍,訪問(wèn)網(wǎng)站的時(shí)候宇整,網(wǎng)站頁(yè)面的腳本可以讀取這個(gè)信息盼玄,就自動(dòng)幫你把用戶名給填了童番,能夠方便一下用戶臂容。這也是Cookie名稱的由來(lái)球散,給用戶的一點(diǎn)甜頭嘁灯。
  • 總結(jié):Session是在服務(wù)端保存的一個(gè)數(shù)據(jù)結(jié)構(gòu)秒旋,用來(lái)跟蹤用戶的狀態(tài)细卧,這個(gè)數(shù)據(jù)可以保存在集群止邮、數(shù)據(jù)庫(kù)、文件中并村;Cookie是客戶端保存用戶信息的一種機(jī)制膝昆,用來(lái)記錄用戶的一些信息骄呼,也是實(shí)現(xiàn)Session的一種方式辟犀。
  1. 正則模塊re
    re.match()方法
    決定RE是否在字符串剛開(kāi)始的位置匹配,返回_sre.SRE_Match對(duì)象跃捣,如果不能匹配返回None。
    注:這個(gè)方法并不是完全匹配。當(dāng)pattern結(jié)束時(shí)若string還有剩余字符猴凹,仍然視為成功进倍。想要完全匹配垂蜗,可以在表達(dá)式末尾加上邊界匹配符'$'
格式:
re.match(pattern, string, flags=0)

print(re.match('com','comwww.runcomoob').group())
print(re.match('com','Comwww.runcomoob',re.I).group())
執(zhí)行結(jié)果如下:
com
com

</br>

RBAC實(shí)現(xiàn)

開(kāi)發(fā)RBAC流程

  1. 表結(jié)構(gòu)設(shè)計(jì)
  2. Django Admin錄入數(shù)據(jù)
  3. 用戶登陸
    • 獲取角色
    • 獲取權(quán)限
    • 對(duì)權(quán)限URL進(jìn)行去重
  4. 生成權(quán)限結(jié)構(gòu)信息,寫(xiě)入session中
{
    1: {
        'urls': ['/userinfo/', '/userinfo/add/', '/userinfo/(\\d+)/delete/', '/userinfo/(\\d+)/change/'],
        'codes': ['list', 'add', 'del', 'edit']
    },
    2: {
        'urls': ['/order/', '/order/add/', '/order/(\\d+)/delete/', '/order/(\\d+)/change/'],
        'codes': ['list', 'add', 'del', 'edit']
    }
}
  1. 注冊(cè)中間件
    • 白名單
    • 獲取當(dāng)前訪問(wèn)url: request.path_info
    • session中獲取權(quán)限黍图,進(jìn)行權(quán)限訪問(wèn)驗(yàn)證
  2. 自動(dòng)生成菜單功能
    • 采用自定義tag方式實(shí)現(xiàn)(inclusion_tag)
    • 作為模板使用{% menu_html request %}方式導(dǎo)入html文件中使用
  3. 通過(guò)Django Admin后臺(tái)進(jìn)行管理及維護(hù)工作

Django ORM表結(jié)構(gòu)設(shè)計(jì)

5個(gè)類(lèi)6張表

  1. 菜單表
class Menu(models.Models):
    """
    菜單表
    """
    title = models.CharField(max_length=32,verbose_name='菜單標(biāo)題')
    
     # django admin后臺(tái)顯示用
    class Meta:
        verbose_name_plural = "菜單表"
    # 重寫(xiě)__str__方法幅虑,實(shí)例化后的對(duì)象將以字符串的形式展示郁妈,但實(shí)際是一個(gè)obj,所以拘央,請(qǐng)不要相信你的眼睛,必要時(shí)使用type(arg)進(jìn)行驗(yàn)證
    def __str__(self):
        return self.title
  1. 權(quán)限組表
class Group(models.Model):
   """
   權(quán)限組
   """
   caption = models.CharField(max_length=32, verbose_name="組名稱")
   menu = models.ForeignKey(to="Menu", default=1, blank=True, verbose_name="關(guān)聯(lián)的菜單")

   class Meta:
       verbose_name_plural = "權(quán)限組"

   def __str__(self):
       return self.caption
  1. 權(quán)限表
class Permission(models.Model):
    """
    權(quán)限表
    """
    title = models.CharField(max_length=32, verbose_name="標(biāo)題")
    url = models.CharField(max_length=128, verbose_name="含正則的URL")
    # menu_gp為null說(shuō)明是title為菜單項(xiàng)
    menu_gp = models.ForeignKey(to="Permission", null=True, blank=True, verbose_name="默認(rèn)選中的組內(nèi)權(quán)限ID", related_name="pm")
    code = models.CharField(max_length=16, verbose_name="權(quán)限碼")
    group = models.ForeignKey(to="Group", blank=True, verbose_name="所屬組")

    class Meta:
        verbose_name_plural = "權(quán)限表"

    def __str__(self):
        return self.title
  1. 用戶表
class User(models.Model):
    """
    用戶表
    """
    username = 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, verbose_name="用戶關(guān)聯(lián)的角色")

    class Meta:
        verbose_name_plural = "用戶表"

    def __str__(self):
        return self.username
  1. 角色表
class Role(models.Model):
    """
    角色表
    """
    title = models.CharField(max_length=32, verbose_name="角色名稱")
    permissions = models.ManyToManyField(to="Permission", blank=True, verbose_name="角色關(guān)聯(lián)的權(quán)限")

    class Meta:
        verbose_name_plural = "角色表"

    def __str__(self):
        return self.title
  1. 附加
  • 創(chuàng)建數(shù)據(jù)庫(kù)表結(jié)構(gòu)信息
  • 創(chuàng)建超級(jí)用戶
  • 在admin.py中注冊(cè)models類(lèi)
  • 登陸admin管理后臺(tái)添加數(shù)據(jù),進(jìn)行管理

</br>

settings中添加配置項(xiàng)

在文件末尾添加配置信息

vim projectname/settings.py
# ########################### 權(quán)限管理相關(guān) ###########################3
PERMISSION_MENU_KEY = "asdkjalsdf9uajsdf"
PERMISSION_URL_DICT_KEY = "iujmsufnsdflsdkf"

VALID_URL= [
    '^/login/',
    "^/admin*"
]

初始化權(quán)限信息

#cat rbac/service/init_permission.py

from django.conf import settings

def init_permission(request,user):
    """
    用戶權(quán)限信息初始化侄非,獲取當(dāng)前用戶所有權(quán)限信息,并保存到Session中
    此處的request以及user參數(shù)均為對(duì)象业岁,user為登陸成功時(shí)在數(shù)據(jù)庫(kù)中查詢到的user對(duì)象
    :param request:
    :param user:
    :return:
    """
    # 去空去重
    permission_list = user.roles.filter(permissions__id__isnull=False).values(
        'permissions__id',
        'permissions__title',                # 用戶列表
        'permissions__url',
        'permissions__code',
        'permissions__menu_gp_id',           # 組內(nèi)菜單ID,Null表示是菜單
        'permissions__group_id',             # 權(quán)限的組ID
        'permissions__group__menu_id',       # 當(dāng)前權(quán)限所在組的菜單ID
        'permissions__group__menu__title',   # 當(dāng)前權(quán)限所在組的菜單名稱
    ).distinct()
    
    # 菜單相關(guān)配置低散,在inclusion_tag中使用
    menu_permission_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__title'] 
        }
        menu_permission_list.append(tpl)
        request.session[settings.PERMISSION_MENU_KEY] = menu_permission_list
        # 形如
        """
        {"url": "/host/","menu_title": "主機(jī)管理","title": "主機(jī)列表","id": 1,"menu_gp_id": null,"menu_id": 1},
        {"url": "/host/add/","menu_title": "主機(jī)管理","title": "添加主機(jī)","id": 2,"menu_gp_id": 1,"menu_id": 1},
        {"url": "/host/(\\d+)/delete/","menu_title": "主機(jī)管理","title": "刪除主機(jī)","id": 3,"menu_gp_id": 1,"menu_id": 1},
        {"url": "/host/(\\d+)/change/","menu_title": "主機(jī)管理","title": "修改主機(jī)","id": 4,"menu_gp_id": 1,"menu_id": 1}
        {"url": "/userinfo/","menu_title": "用戶管理","title": "用戶列表","id": 5,"menu_gp_id": null,"menu_id": 2},
        {"url": "/userinfo/add/","menu_title": "用戶管理","title": "添加用戶","id": 6,"menu_gp_id": 5,"menu_id": 2},
        ......
        """

    # 權(quán)限相關(guān)梦重,中間件使用
    permission_dict = {}
    for item in permission_list:
        group_id = item['permissions__group_id']
        code = item['permissions__code']
        url = item['permissions__url']
        if group_id in permission_dict:
            permission_dict[group_id]['codes'].append(code)
            permission_dict[group_id]['urls'].append(url)
        else:
            permission_dict[group_id] = {"codes": [code, ], "urls": [url, ]}
    request.session[settings.PERMISSION_URL_DICT_KEY] = permission_dict
    # 形如
    """
    {
        "1": {
            "codes": ["list","add","delete","edit"],
            "urls": ["/host/","/host/add/","/host/(\\d+)/delete/","/host/(\\d+)/change/"]
         },
        "2": {
            "codes": ["list","add","delete","change"],
            "urls": ["/userinfo/","/userinfo/add/","/userinfo/(\\d+)/delete/","/userinfo/(\\d+)/change/"]
         }
    }
    """

注: 用戶登陸成功后進(jìn)行初始化權(quán)限信息,在處理用戶權(quán)限時(shí)需要進(jìn)行數(shù)據(jù)去重

菜單List及權(quán)限D(zhuǎn)ict格式如下所示:

# 菜單List request.session[settings.PERMISSION_MENU_KEY]
[
    {
        "url": "/host/",
        "menu_title": "主機(jī)管理",
        "title": "主機(jī)列表",
        "id": 1,
        "menu_gp_id": null,
        "menu_id": 1
    },
    {
        "url": "/host/add/",
        "menu_title": "主機(jī)管理",
        "title": "添加主機(jī)",
        "id": 2,
        "menu_gp_id": 1,
        "menu_id": 1
    },
    {
        "url": "/host/(\\d+)/delete/",
        "menu_title": "主機(jī)管理",
        "title": "刪除主機(jī)",
        "id": 3,
        "menu_gp_id": 1,
        "menu_id": 1
    },
    {
        "url": "/host/(\\d+)/change/",
        "menu_title": "主機(jī)管理",
        "title": "修改主機(jī)",
        "id": 4,
        "menu_gp_id": 1,
        "menu_id": 1
    },
    {
        "url": "/userinfo/",
        "menu_title": "用戶管理",
        "title": "用戶列表",
        "id": 5,
        "menu_gp_id": null,
        "menu_id": 2
    },
    {
        "url": "/userinfo/add/",
        "menu_title": "用戶管理",
        "title": "添加用戶",
        "id": 6,
        "menu_gp_id": 5,
        "menu_id": 2
    },
    {
        "url": "/userinfo/(\\d+)/delete/",
        "menu_title": "用戶管理",
        "title": "刪除用戶",
        "id": 7,
        "menu_gp_id": 5,
        "menu_id": 2
    },
    {
        "url": "/userinfo/(\\d+)/change/",
        "menu_title": "用戶管理",
        "title": "修改用戶",
        "id": 8,
        "menu_gp_id": 5,
        "menu_id": 2
    }
]

# 權(quán)限D(zhuǎn)ict request.session[settings.PERMISSION_URL_DICT_KEY]
{
    "1": {
        "codes": [
            "list",
            "add",
            "delete",
            "edit"
        ],
        "urls": [
            "/host/",
            "/host/add/",
            "/host/(\\d+)/delete/",
            "/host/(\\d+)/change/"
        ]
    },
    "2": {
        "codes": [
            "list",
            "add",
            "delete",
            "change"
        ],
        "urls": [
            "/userinfo/",
            "/userinfo/add/",
            "/userinfo/(\\d+)/delete/",
            "/userinfo/(\\d+)/change/"
        ]
    }
} 

</br>

中間件

cat rbac/middleware/rbac.py

from django.shortcuts import redirect,HttpResponse
from django.conf import settings

# 在后續(xù)版本中可能會(huì)被廢棄叹哭,故在此直接引入
#from django.utils.deprecation import 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

class RbacMiddleware(MiddlewareMixin):
    def process_request(self,request):
        # 1. 當(dāng)前請(qǐng)求URL
        current_request_url = request.path_info

        # 2. 處理白名單,如login及admin頁(yè)面需開(kāi)放訪問(wèn)權(quán)限室琢,根據(jù)實(shí)際情況而定
        for url in settings.VALID_URL_LIST:
            if re.match(url,current_request_url):
                return None

        # 3. 獲取session中保存的權(quán)限信息
        permission_dict = request.session.get(settings.PERMISSION_MENU_LIST)
        if not permission_dict:
            # 登陸頁(yè)面
            return redirect(settings.RBAC_LOGIN_URL)

        flag = False
        for group_id, values in permission_dict.items():
            for url in values['urls']:
                regex = settings.URL_FORMAT.format(url)
                if re.match(regex, current_request_url):
                    flag = True
                    break
            if flag:
                break
        if not flag:
            # 無(wú)權(quán)訪問(wèn)頁(yè)面,可以直接redirect
            return HttpResponse('無(wú)權(quán)訪問(wèn)')

</br>

自動(dòng)生成菜單template tags

  1. template tags部分
#! /usr/bin/env python
# -*- coding: utf-8 -*-
# __author__ = "shuke"
# Date: 2017/11/20

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

register = Library()


@register.inclusion_tag('menu.html')
def menu_html(request):
    """
    獲取session中的菜單信息芽世,匹配當(dāng)前URL,生成菜單
    :param request: 請(qǐng)求的requst對(duì)象
    :return:
    """
    menu_list = request.session.get(settings.PERMISSION_MENU_KEY)
    # 當(dāng)前請(qǐng)求URL
    current_url = request.path_info

    menu_dict = {}
    # menu_gp_id為空則是菜單
    for item in menu_list:
        if not item['menu_gp_id']:
            menu_dict[item['id']] = item

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

    result = {}
    for item in menu_dict.values():
        active = item.get('active')
        menu_id = item['menu_id']
        if menu_id in result:
            result[menu_id]['children'].append({'title': item['title'], 'url': item['url'], 'active': active})
            if active:
                result[menu_id]['active'] = True
        else:
            result[menu_id] = {
                'menu_id': item['menu_id'],
                'menu_title': item['menu_title'],
                'active': active,
                'children': [
                    {'title': item['title'], 'url': item['url'], 'active': active}
                ]
            }
    print(json.dumps(result, indent=4, ensure_ascii=False))
    return {'menu_dict': result}
  1. 生成的菜單樹(shù)格式如下
{
    "1": {
        "children": [
            {
                "url": "/host/",
                "active": null,
                "title": "主機(jī)列表"
            }
        ],
        "menu_id": 1,
        "menu_title": "主機(jī)管理",
        "active": null
    },
    "2": {
        "children": [
            {
                "url": "/userinfo/",
                "active": null,
                "title": "用戶列表"
            }
        ],
        "menu_id": 2,
        "menu_title": "用戶管理",
        "active": null
    }
}
  1. menu_tpl.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 %}
  1. HTML部分使用tags
# 上文中的menu_html函數(shù)依賴request參數(shù)盼铁,此處需要傳入
{% load rbac %}
{% menu_html request %}

注: 自定義tags只支持傳入1個(gè)參數(shù)
</br>

注冊(cè)中間件使用

project/settings.py

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'rbac.middleware.rbac.RbacMiddleware',

]

維護(hù)

在Django Admin中維護(hù)rbac的權(quán)限系統(tǒng)并使用

總結(jié)

至此,基于role實(shí)現(xiàn)的rbac組件基本開(kāi)發(fā)完成找筝,在Django中作為app引入在settings文件中注冊(cè)后就可以生效使用了,engoy it!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子凌那,更是在濱河造成了極大的恐慌佃乘,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,110評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異来涨,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)泳秀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)乒融,“玉大人,你說(shuō)我怎么就攤上這事》啡疲” “怎么了勃救?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,474評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)荆永。 經(jīng)常有香客問(wèn)我,道長(zhǎng)眷射,這世上最難降的妖魔是什么撕彤? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,881評(píng)論 1 295
  • 正文 為了忘掉前任专肪,我火速辦了婚禮次洼,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘隶糕。我一直安慰自己,他們只是感情好绵估,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著诵原,像睡著了一般纯出。 火紅的嫁衣襯著肌膚如雪暂筝。 梳的紋絲不亂的頭發(fā)上墨状,一...
    開(kāi)封第一講書(shū)人閱讀 51,698評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音斋扰,去河邊找鬼屎鳍。 笑死宏娄,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的逮壁。 我是一名探鬼主播孵坚,決...
    沈念sama閱讀 40,418評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼貌踏!你這毒婦竟也來(lái)了十饥?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,332評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤祖乳,失蹤者是張志新(化名)和其女友劉穎逗堵,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體眷昆,經(jīng)...
    沈念sama閱讀 45,796評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蜒秤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了亚斋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片作媚。...
    茶點(diǎn)故事閱讀 40,110評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖帅刊,靈堂內(nèi)的尸體忽然破棺而出纸泡,到底是詐尸還是另有隱情,我是刑警寧澤赖瞒,帶...
    沈念sama閱讀 35,792評(píng)論 5 346
  • 正文 年R本政府宣布女揭,位于F島的核電站,受9級(jí)特大地震影響栏饮,放射性物質(zhì)發(fā)生泄漏吧兔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評(píng)論 3 331
  • 文/蒙蒙 一袍嬉、第九天 我趴在偏房一處隱蔽的房頂上張望境蔼。 院中可真熱鬧,春花似錦伺通、人聲如沸箍土。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,003評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)涮帘。三九已至,卻和暖如春笑诅,著一層夾襖步出監(jiān)牢的瞬間调缨,已是汗流浹背疮鲫。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,130評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留弦叶,地道東北人俊犯。 一個(gè)月前我還...
    沈念sama閱讀 48,348評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像伤哺,于是被迫代替她去往敵國(guó)和親燕侠。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容