基于Django構(gòu)建靈活的權(quán)限系統(tǒng)

注:本文更多的是對(duì)Django的權(quán)限擴(kuò)展以及如何通過(guò)擴(kuò)展解決實(shí)際的業(yè)務(wù)問(wèn)題展開(kāi)討論整葡,并不會(huì)對(duì)Django權(quán)限系統(tǒng)的具體實(shí)現(xiàn)和使用方法進(jìn)行介紹件余,這方面知識(shí)未儲(chǔ)備好的,請(qǐng)先自行腦補(bǔ)。

一啼器、Django的權(quán)限系統(tǒng)

在介紹Django的權(quán)限系統(tǒng)前旬渠,我們先來(lái)認(rèn)識(shí)一下RBAC。RBAC全稱Role-Based Access Control端壳,即基于角色的訪問(wèn)控制告丢。太學(xué)術(shù)的東西就不講了,我們可以從企業(yè)運(yùn)營(yíng)的角度來(lái)理解RBAC损谦,企業(yè)運(yùn)營(yíng)過(guò)程中岖免,需要做很多的事情來(lái)達(dá)成企業(yè)的目標(biāo),但這些事情并非所有人都可以去做的照捡,比如業(yè)務(wù)人員不可能去處理公司的財(cái)務(wù)問(wèn)題颅湘,所以需要給要做的這些事情設(shè)置相應(yīng)的權(quán)限,然后將這些權(quán)限分配給指定的人栗精,但問(wèn)題來(lái)了闯参,如果某個(gè)員工離職了,換了一個(gè)新員工來(lái)頂替他的位置悲立,這時(shí)你需要一一的告訴該新員工他應(yīng)該做什么鹿寨,他不能做什么,當(dāng)工作項(xiàng)非常多的時(shí)候薪夕,這將是非常繁瑣的過(guò)程脚草,且非常容易出錯(cuò),這時(shí)就引入了角色的概念寥殖,角色大家可以理解為企業(yè)管理中的崗位玩讳,通過(guò)將工作權(quán)限分配給崗位,再將員工安排至相應(yīng)的工作崗位嚼贡,那么該員工就擁有了該崗位應(yīng)有的權(quán)限熏纯,當(dāng)人員變動(dòng)時(shí),只需要將新員工安排至該崗位即可粤策,整個(gè)過(guò)程就變得簡(jiǎn)單多了樟澜。這時(shí)你也許會(huì)問(wèn),那崗位的工作內(nèi)容變動(dòng)時(shí)叮盘,你不是一樣需要重新給崗位一一分配權(quán)限嗎秩贰,增加了角色反而增加了系統(tǒng)的復(fù)雜性?話是沒(méi)錯(cuò)柔吼,但在企業(yè)管理中毒费,崗位是相對(duì)穩(wěn)定的,而人員則是易變的愈魏,所以引入角色將極大的提升系統(tǒng)的靈活性和易用性觅玻。關(guān)于RBAC就簡(jiǎn)單介紹到這想际,當(dāng)然RBAC還包含很多的內(nèi)容,希望深入了解的朋友可以Google相關(guān)的信息溪厘。

Django的權(quán)限系統(tǒng)胡本,可以認(rèn)為是輕量級(jí)的RBAC系統(tǒng),其中組可以對(duì)應(yīng)RBAC的角色畸悬,通過(guò)將權(quán)限分配給組侧甫,再將用戶分配到組中,從而實(shí)現(xiàn)了授權(quán)的過(guò)程蹋宦,同時(shí)Django還支持直接對(duì)用戶進(jìn)行授權(quán)披粟,這對(duì)應(yīng)對(duì)一些特殊情況是十分有用的。關(guān)于Django的權(quán)限使用的詳情妆档,可以參考官方文檔僻爽,在此不做詳述。

Django的權(quán)限系統(tǒng)實(shí)現(xiàn)了最基本的權(quán)限需求贾惦,但在實(shí)際的業(yè)務(wù)開(kāi)發(fā)過(guò)程中胸梆,仍然存在許多無(wú)法滿足的需求,好在Django是一套異常強(qiáng)大的系統(tǒng)须板,系統(tǒng)內(nèi)部提供了一套靈活的機(jī)制碰镜,使得開(kāi)發(fā)人員可以方便的對(duì)系統(tǒng)的權(quán)限進(jìn)行擴(kuò)展。本文就我在實(shí)際項(xiàng)目過(guò)程中所遇到的各種權(quán)限方面的問(wèn)題习瑰,看我如何通過(guò)擴(kuò)展Django的權(quán)限系統(tǒng)绪颖,實(shí)現(xiàn)靈活的權(quán)限管理。

二甜奄、擴(kuò)展Django的權(quán)限系統(tǒng)

2.1 對(duì)象級(jí)權(quán)限

在開(kāi)發(fā)過(guò)程中柠横,遇到最多的問(wèn)題應(yīng)該就是對(duì)象級(jí)權(quán)限的問(wèn)題了。Django內(nèi)置的權(quán)限授權(quán)是針對(duì)Model進(jìn)行的授權(quán)课兄,每個(gè)Model默認(rèn)都會(huì)生成三個(gè)權(quán)限:add牍氛、change、delete烟阐。如一旦授予某用戶Change權(quán)限搬俊,那么該用戶就擁有了該Model所有對(duì)象的Change權(quán)限,但有些時(shí)候我們希望僅允許用戶對(duì)該Model下的指定對(duì)象進(jìn)行操作蜒茄,比如新聞發(fā)布系統(tǒng)可以允許所有的編輯人員進(jìn)行新聞的發(fā)布唉擂,但僅允許發(fā)布新聞的編輯人員修改自己所發(fā)布的新聞,這時(shí)候Django的權(quán)限系統(tǒng)就無(wú)法滿足我們的需求了檀葛,我們需要引入更細(xì)粒度的對(duì)象級(jí)權(quán)限支持(此處新聞的例子在下文的系統(tǒng)組的實(shí)現(xiàn)一節(jié)中有更好的解決方案)玩祟。

所謂對(duì)象級(jí)權(quán)限,即針對(duì)的Model對(duì)象實(shí)例進(jìn)行授權(quán)操作屿聋,使用我們可以精確的對(duì)單個(gè)對(duì)象進(jìn)行權(quán)限的控制卵凑,以實(shí)現(xiàn)更細(xì)精度的權(quán)限控制庆聘。

雖然Django的權(quán)限系統(tǒng)沒(méi)有直接提供對(duì)象權(quán)限的實(shí)現(xiàn),但確提供了基礎(chǔ)支持(相關(guān)的API都提供有對(duì)象參數(shù)的傳入)勺卢,使得我可以通過(guò)擴(kuò)展認(rèn)證后端從而實(shí)現(xiàn)對(duì)象級(jí)的權(quán)限支持。目前已經(jīng)有非常好的對(duì)象級(jí)權(quán)限的第三方實(shí)現(xiàn)django-guardian象对,在此就不具體探討了黑忱,想深入了解的朋友可以自己查看相關(guān)的文檔。

注:此節(jié)內(nèi)容感覺(jué)有點(diǎn)虛勒魔,沒(méi)有太多實(shí)質(zhì)性的東西甫煞,但很多業(yè)務(wù)上的權(quán)限需求是需要結(jié)合多種權(quán)限機(jī)制實(shí)現(xiàn)的,在此列出是為方便后文的展開(kāi)冠绢,讓各位先有個(gè)概念上的認(rèn)識(shí)抚吠。

2.2 系統(tǒng)組的實(shí)現(xiàn)

什么是系統(tǒng)組?Django默認(rèn)的權(quán)限系統(tǒng)中只有組的概念弟胀,沒(méi)有什么系統(tǒng)組翱Α?其實(shí)系統(tǒng)組只是我們?yōu)榱藚^(qū)別與現(xiàn)有組的一種稱呼而已孵户,你可以給它起一個(gè)更酷的名字萧朝。那么如何理解系統(tǒng)組呢?我們知道Django里的組都需要我們進(jìn)行硬性的綁定才會(huì)生效的夏哭,比如你創(chuàng)建一個(gè)“編輯”的組检柬,那么你需要給編輯這個(gè)組授予相應(yīng)內(nèi)容的編輯權(quán)限,同時(shí)你需要將相應(yīng)的用戶分配至該“編輯”組竖配,那么用戶才會(huì)真正的擁有了編輯應(yīng)有的權(quán)限何址,這在大多數(shù)情況下是沒(méi)有問(wèn)題的,但這種硬性的綁定極大的限制了系統(tǒng)的靈活性进胯,因?yàn)楹芏鄷r(shí)候系統(tǒng)是需要根據(jù)運(yùn)行時(shí)的環(huán)境來(lái)決定用戶應(yīng)該屬于哪些組的用爪。結(jié)合上文對(duì)象級(jí)權(quán)限中新聞發(fā)布的例子,如果使用對(duì)象級(jí)權(quán)限的方式來(lái)實(shí)現(xiàn)的話龄减,我們就必需在用戶發(fā)布新聞的同時(shí)项钮,向?qū)ο蠹?jí)權(quán)限系統(tǒng)中添加一條權(quán)限分配的記錄(對(duì)象級(jí)權(quán)限系統(tǒng)是需要獨(dú)立的數(shù)據(jù)表來(lái)記錄用戶或組與對(duì)象的權(quán)限關(guān)系),這好像也沒(méi)什么問(wèn)題希停,無(wú)非是重寫(xiě)save方法或都添加一條signals就可以應(yīng)對(duì)了烁巫,但如果我們希望用戶的上級(jí)領(lǐng)導(dǎo)也能夠修改該信息呢,你可能會(huì)說(shuō)給上級(jí)領(lǐng)導(dǎo)也添加一條對(duì)象級(jí)權(quán)限啊宠能,但如果該用戶的上級(jí)領(lǐng)導(dǎo)變更了呢亚隙?如果我們又希望用戶同部門(mén)的員工允許修改呢?是不是開(kāi)始變得麻煩了违崇,如果有一種機(jī)制來(lái)處理這種動(dòng)態(tài)關(guān)系阿弃,那事情就變得簡(jiǎn)單多了诊霹,而實(shí)現(xiàn)這一機(jī)制的就是系統(tǒng)組。系統(tǒng)組就是在系統(tǒng)運(yùn)行期渣淳,根據(jù)運(yùn)行環(huán)境來(lái)決定用戶所隸屬的組脾还,從而實(shí)現(xiàn)靈活的授權(quán)機(jī)制。

看完上面的解釋入愧,好像有點(diǎn)明白了鄙漏,但具體該怎么實(shí)現(xiàn)呢?

首先棺蛛,大家需要理解的一點(diǎn)是系統(tǒng)組本身并非系統(tǒng)運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建的怔蚌,而是在開(kāi)發(fā)階段根據(jù)業(yè)務(wù)需要?jiǎng)?chuàng)建的,系統(tǒng)組機(jī)制只是在運(yùn)行時(shí)由系統(tǒng)決定用戶與系統(tǒng)組的關(guān)系而已旁赊。因而在業(yè)務(wù)開(kāi)發(fā)階段桦踊,我們就必需確定好業(yè)務(wù)所需的系統(tǒng)組。當(dāng)然我們也可以從所有的業(yè)務(wù)中抽象出一些具備全局通用性的系統(tǒng)組终畅,以下列出我們抽象出來(lái)的具備通用性的系統(tǒng)組供大家參考:

1) Everyone:所有人籍胯,無(wú)論是用戶還是訪客,都屬于Everyone組声离。
  2) Anonymous:匿名用戶芒炼,非認(rèn)證的用戶都屬于Anonymous組。
  3) Users:用戶术徊,所有認(rèn)證的用戶都屬于Users組本刽。
  4) Creator:創(chuàng)建者,針對(duì)具體信息的創(chuàng)建者赠涮,都屬于Creator組子寓。
  5) Owner:所有者,具體信息的擁有者笋除,都屬于Owner組斜友。

看了上邊所列出的系統(tǒng)組,大家是不是感覺(jué)好像又更理解了一些呢垃它,做過(guò)Windows文件授權(quán)的朋友鲜屏,可能還有點(diǎn)似曾相識(shí)的感覺(jué),對(duì)国拇,這和Windows系統(tǒng)里的特殊組是一樣一樣的洛史。除了以上所列出的系統(tǒng)組,我們還可以針對(duì)特定的業(yè)務(wù)創(chuàng)建針對(duì)性的系統(tǒng)組酱吝,如上例中也殖,允許用戶的上級(jí)領(lǐng)導(dǎo)修改該用戶所發(fā)布的信息,那么我們可以創(chuàng)建一個(gè)名為“信息所有者的上級(jí)領(lǐng)導(dǎo)”這樣的系統(tǒng)組务热。

# 看下面代碼忆嗜,我們聲明了我們所需的系統(tǒng)組的常量己儒,聲明為常量是便于代碼的調(diào)用
SYSTEM_GROUP_EVERYONE = "Everyone"      # 所有人
SYSTEM_GROUP_ANONYMOUS = "Anonymous"    # 匿名用戶
SYSTEM_GROUP_USERS = "Users"            # 用戶
SYSTEM_GROUP_STAFFS = "Staffs"          # 職員
SYSTEM_GROUP_CREATOR = "Creator"        # 創(chuàng)建者
SYSTEM_GROUP_OWNER = "Owner"            # 所有者

根據(jù)上文所述,系統(tǒng)組是在開(kāi)發(fā)階段就應(yīng)該確定捆毫,那么確定后的系統(tǒng)組應(yīng)該如何在系統(tǒng)中體現(xiàn)呢闪湾?其實(shí)系統(tǒng)組只是我們定義的一個(gè)概念,為了保證Django系統(tǒng)權(quán)限調(diào)用接口的一至性冻璃,它仍然是一個(gè)組對(duì)象响谓,只是是一個(gè)我們賦予了特殊意義的組,我們?nèi)匀恍枰贒jango的“組”這個(gè)Model中創(chuàng)建相應(yīng)的系統(tǒng)組對(duì)象(當(dāng)然如果考慮到系統(tǒng)組的特殊性省艳,可以通過(guò)一些機(jī)制來(lái)限制對(duì)系統(tǒng)組的修改和刪除操作,以保證系統(tǒng)組始終可用嫁审,如何限制不是權(quán)限系統(tǒng)的核心跋炕,在此不做詳述)。你可以通過(guò)聲明一個(gè)系統(tǒng)組初始化的方法律适,并在適當(dāng)?shù)牡胤秸{(diào)用他辐烂,當(dāng)然你也可以使用Django提供的初始數(shù)據(jù)方法來(lái)初始系統(tǒng)組∥婊撸看代碼:

from django.contrib.auth.models import Group

def init_systemgroups():
    """
    初始化系統(tǒng)組纠修。
    :return:
    """
    Group.objects.get_or_create(name=SYSTEM_GROUP_EVERYONE)
    Group.objects.get_or_create(name=SYSTEM_GROUP_ANONYMOUS)
    Group.objects.get_or_create(name=SYSTEM_GROUP_USERS)
    Group.objects.get_or_create(name=SYSTEM_GROUP_STAFFS)
    Group.objects.get_or_create(name=SYSTEM_GROUP_CREATOR)
    Group.objects.get_or_create(name=SYSTEM_GROUP_OWNER)

好了,現(xiàn)在我們已經(jīng)有了系統(tǒng)組數(shù)據(jù)了厂僧,接下來(lái)我們就需要實(shí)現(xiàn)系統(tǒng)組的核心邏輯了扣草,從Django所提供的權(quán)限驗(yàn)證接口User.has_perm(perm, obj=None),我們可以看出存在兩種情況:一種是不提供obj參數(shù)的情況颜屠,我們可以語(yǔ)義化理解為“用戶(User)是否擁有perm權(quán)限”辰妙;一種是提供了obj參數(shù)的情況,我們可以語(yǔ)義化理解為“用戶(User)是否擁有指定對(duì)象(obj)的perm權(quán)限”甫窟。同樣的密浑,我們系統(tǒng)組也可以分為兩種情況:一種是與obj參數(shù)無(wú)關(guān)的,我們可以語(yǔ)義化理解為“用戶(User)是否為系統(tǒng)組(system group)的成員”粗井,這包括上面所列的Everyone尔破、Anonymous、Users浇衬、Staffs等懒构;一種是與obj參數(shù)有關(guān)的,我們可以語(yǔ)義化理解為“用戶(User)是否為對(duì)象(obj)的系統(tǒng)組(system group)的成員”径玖,包括上面所列的Creator痴脾、Owner等。依賴于相同的參數(shù)梳星,使得我們可以保持與Django一至的驗(yàn)證接口赞赖,我們現(xiàn)在要做的是根據(jù)這些參數(shù)滚朵,給系統(tǒng)返回恰當(dāng)?shù)南到y(tǒng)組,先來(lái)看看與obj參數(shù)無(wú)關(guān)的情況的代碼:

def get_user_systemgroups(user):
    """
    獲取指定用戶所屬的系統(tǒng)組集合前域。
    :param user: 指定的用戶辕近。
    :return: set 表示的用戶所屬的系統(tǒng)組名稱集合。
    """
    groups = set()
    groups.add(SYSTEM_GROUP_EVERYONE)
    if user.is_anonymous():
        groups.add(SYSTEM_GROUP_ANONYMOUS)
    else:
        groups.add(SYSTEM_GROUP_USERS)
        if user.is_staff:
            groups.add(SYSTEM_GROUP_STAFFS)

    return groups

OK匿垄,是不是很簡(jiǎn)單移宅,我們只是對(duì)User進(jìn)行簡(jiǎn)單的驗(yàn)證,就可以獲得User有關(guān)的系統(tǒng)組了椿疗,而第二種與obj參數(shù)有關(guān)的情況就比較復(fù)雜了漏峰,比如Creator組,我們必需要獲取對(duì)象的創(chuàng)建者届榄,我們才能與User進(jìn)行比較浅乔,從而驗(yàn)證User是否為obj的創(chuàng)建者,然而Creator組是我們抽象出來(lái)的全局通用的組铝条,意味著我們的Model需要提供一至的方法來(lái)獲取對(duì)象的創(chuàng)建者靖苇,這時(shí)我們需要定義一個(gè)接口(Python沒(méi)有提供接口的概念,我們只是通過(guò)抽象的方法來(lái)模擬)來(lái)實(shí)現(xiàn):

class CreatorMixin(object):
    """
    實(shí)現(xiàn)創(chuàng)建者的 Model 基類班缰。
    """
    def get_creator(self):
        """
        獲取對(duì)象的創(chuàng)建者贤壁,子類重寫(xiě)該方法實(shí)現(xiàn)創(chuàng)建者對(duì)象的獲取。
        :return: 當(dāng)前對(duì)象的創(chuàng)建者埠忘。
        """
        return None

    def set_creator(self, user):
        """
        設(shè)置對(duì)象的創(chuàng)建者脾拆,子類重寫(xiě)該方法實(shí)現(xiàn)創(chuàng)建者對(duì)象的設(shè)置。
        :param creator: 要設(shè)置為創(chuàng)建者的User對(duì)象给梅。
        :return:
        """
        pass
class OwnerMixin(object):
    """
    實(shí)現(xiàn)所有者的 Model 基類假丧。
    """
    def get_owner(self):
        """
        獲取對(duì)象的所有者,子類重寫(xiě)該方法實(shí)現(xiàn)所有者對(duì)象的獲取动羽。
        :return: 當(dāng)前對(duì)象的所有者包帚。
        """
        return None

    def set_owner(self, user):
        """
        設(shè)置對(duì)象的所有者,子類重寫(xiě)該方法實(shí)現(xiàn)所有者對(duì)象的設(shè)置运吓。
        :param owner: 要設(shè)置為所有者的User對(duì)象渴邦。
        :return:
        """
        pass

現(xiàn)在再來(lái)看看第二種情況的實(shí)現(xiàn):

def get_user_systemgroups_for_obj(user, obj):
    """
    獲取指定用戶相對(duì)于指定的對(duì)象所屬的系統(tǒng)組集合。
    :param user: 指定的用戶拘哨。
    :param obj: 相對(duì)于指定的對(duì)象谋梭。
    :return: set 表示的用戶所屬的系統(tǒng)組名稱集合。
    """
    groups = set()
    if isinstance(obj, CreatorMixin) and obj.get_creator() == user:
        groups.add(SYSTEM_GROUP_CREATOR)
    if isinstance(obj, OwnerMixin) and obj.get_owner() == user:
        groups.add(SYSTEM_GROUP_OWNER)
    return groups

現(xiàn)在倦青,我們?yōu)榱吮WC系統(tǒng)組的擴(kuò)展性瓮床,我們需要定義一套規(guī)則,使得你可以在你自己的應(yīng)用中,擴(kuò)展實(shí)現(xiàn)自己業(yè)務(wù)所需要的系統(tǒng)組隘庄,我們約定在你的應(yīng)用中應(yīng)該存在一個(gè)模塊踢步,模塊中應(yīng)該包含有以上聲明的get_user_systemgroups(user)和get_user_systemgroups_for_obj(user, obj)兩個(gè)方法,同時(shí)你需要在項(xiàng)目的settings.py文件中丑掺,告訴系統(tǒng)你的系統(tǒng)組實(shí)現(xiàn)的模塊路徑获印,類似如下:

# 自定義系統(tǒng)組實(shí)現(xiàn)
SYSTEM_GROUP_IMPLEMENTERS = ['systemgroups.systemgroups', '你自己實(shí)現(xiàn)的系統(tǒng)組的路徑'…]

同時(shí),我們需要提供一個(gè)方法來(lái)根據(jù)上面規(guī)則街州,依次獲取所有應(yīng)用中的用戶所屬的系統(tǒng)組集合兼丰,代碼如下:

def get_user_systemgroups(user):
    """
    從所有應(yīng)用中獲取指定用戶所屬的系統(tǒng)組集合。
    :param user: 指定的用戶唆缴。
    :return: set 表示的用戶所屬的系統(tǒng)組名稱集合鳍征。
    """
    imps = SYSTEM_GROUP_IMPLEMENTERS
    groups = set()
    if not imps:
        return groups
    for imp in imps:
        imp = importlib.import_module(imp)
        if hasattr(imp, "get_user_systemgroups"):
            groups.update(imp.get_user_systemgroups(user))
    return groups
def get_user_systemgroups_for_obj(user, obj):
    """
    從所有應(yīng)用中獲取指定用戶相對(duì)于指定的對(duì)象所屬的系統(tǒng)組集合。
    :param user: 指定的用戶面徽。
    :param obj: 相對(duì)于指定的對(duì)象蟆技。
    :return: set 表示的用戶所屬的系統(tǒng)組名稱集合。
    """
    imps = SYSTEM_GROUP_IMPLEMENTERS
    groups = set()
    if not imps:
        return groups
    for imp in imps:
        imp = importlib.import_module(imp)
        if hasattr(imp, "get_user_systemgroups_for_obj"):
            groups.update(imp.get_user_systemgroups_for_obj(user, obj))
    return groups

最后斗忌,我們來(lái)實(shí)現(xiàn)我們的認(rèn)證后端:

def get_group_permissions(name):
    """
    獲取指定名稱的組所擁有的權(quán)限集合。
    :param name: 組的名稱旺聚。
    :return: 權(quán)限集合织阳。
    """
    perms = Permission.objects.filter(group__name = name)
    perms = perms.values_list('content_type__app_label', 'codename').order_by()
    return set(["%s.%s" % (ct, name) for ct, name in perms])


def get_groups_permissions(names):
    """
    獲取指定名稱的組所擁有的權(quán)限集合。
    :param names: 組的名稱集合砰粹。
    :return: 權(quán)限集合唧躲。
    """
    perms = set()
    for name in names:
        perms.update(get_group_permissions(name))
    return perms


class SystemGroupBackend(object):
    def authenticate(self, username=None, password=None, **kwargs):
        return None

    def has_perm(self, user_obj, perm, obj=None):
        return perm in self.get_all_permissions(user_obj, obj)

    def get_all_permissions(self, user_obj, obj=None):
        perms = self.get_group_permissions(user_obj, obj)
        return perms

    def get_group_permissions(self, user_obj, obj=None):
        result_perms = set()

        groups = get_user_systemgroups(user_obj)
        perms = get_groups_permissions(groups)
        result_perms.update(perms)

        if obj is None:
            return result_perms

        groups = get_user_systemgroups_for_obj(user_obj, obj)
        perms = get_groups_permissions(groups)
        result_perms.update(perms)

        return result_perms

至此,我們完整的實(shí)現(xiàn)了系統(tǒng)組的機(jī)制碱璃,以上代碼因篇幅原因刪減了部分與主邏輯關(guān)系不大的代碼弄痹,如權(quán)限的緩存等,以便于閱讀和理解嵌器,完整的代碼請(qǐng)參考我的GitHub項(xiàng)目:https://github.com/Kidwind/django-systemgroups肛真,同時(shí)因項(xiàng)目時(shí)間較短,仍然存在很多不足和問(wèn)題爽航,也歡迎大家指正蚓让。

2.3 權(quán)限映射

權(quán)限映射又是什么呢?要回答這個(gè)問(wèn)題讥珍,我們還是以上文中的新聞發(fā)布的例子來(lái)展開(kāi)历极,我們的新聞應(yīng)該是根據(jù)性質(zhì)進(jìn)行分類的,比如實(shí)事新聞衷佃、財(cái)經(jīng)新聞趟卸、體育新聞等等,這時(shí)我們就形成了新聞?lì)悇e(InfoCategory)和新聞(Info)這兩個(gè)一對(duì)多關(guān)系的Model,隨著工作的細(xì)分锄列,我們需要將不同的分類授權(quán)不同的部門(mén)來(lái)進(jìn)行管理图云,這時(shí)你想到的是什么?對(duì)右蕊,就是上文中所提到的對(duì)象級(jí)權(quán)限琼稻,我們給新聞?lì)悇e(InfoCategory)創(chuàng)建一個(gè)用于控制新聞?lì)悇e下的新聞的修改權(quán)限,名為change_info_by_category饶囚,通過(guò)針對(duì)新聞?lì)悇e(InfoCategory)進(jìn)行對(duì)象級(jí)的change_info_by_category授權(quán)帕翻,如果此時(shí)我們要進(jìn)行某篇新聞的修改權(quán)限驗(yàn)證,我們需要對(duì)新聞所在欄目進(jìn)行change_info_by_category的權(quán)限驗(yàn)證萝风,像這樣user.has_perm(‘a(chǎn)pp_label. change_info_by_category’, obj=info.category)嘀掸,有什么問(wèn)題嗎?似乎也沒(méi)什么問(wèn)題规惰,但我們細(xì)細(xì)分析一下睬塌,我們?cè)緦?duì)新聞(Info)進(jìn)行修改的權(quán)限驗(yàn)證方法user.has_perm(‘a(chǎn)pp_label.change_info’, obj=info),需要人為的轉(zhuǎn)換為上面的權(quán)限驗(yàn)證歇万,相應(yīng)的Django提供的Admin我們需要重寫(xiě)相應(yīng)的方法來(lái)修改權(quán)限驗(yàn)證的邏輯揩晴,如果新聞(Info)本身還提供對(duì)象級(jí)的權(quán)限檢測(cè),我們的邏輯就需要改為要對(duì)兩個(gè)方法都進(jìn)行驗(yàn)證贪磺,還有更多復(fù)雜的情況硫兰,情況一變,我們就需要重寫(xiě)我們的權(quán)限驗(yàn)證邏輯嗎寒锚,很麻煩不是嗎劫映,如果有一種方法能夠?qū)崿F(xiàn)上述權(quán)限驗(yàn)證的自動(dòng)轉(zhuǎn)換,能夠保證我們的權(quán)限調(diào)用方法不變刹前,那事情就簡(jiǎn)單多了泳赋,而這一方法,就是我們所說(shuō)的權(quán)限映射喇喉。簡(jiǎn)而言之祖今,權(quán)限映射就是將用戶對(duì)當(dāng)前對(duì)象所執(zhí)行的權(quán)限驗(yàn)證轉(zhuǎn)換為用戶對(duì)另一個(gè)對(duì)象的另一個(gè)權(quán)限進(jìn)行驗(yàn)證的過(guò)程≡桑看下圖:

權(quán)限映射示意圖.png

好了衅鹿,分析到這里,相信大家對(duì)權(quán)限映射的作用有了大概的認(rèn)識(shí)过咬,下面我們來(lái)對(duì)代碼實(shí)現(xiàn)做簡(jiǎn)單的分析和了解大渤。同系統(tǒng)組一樣,我們的Model需要提供一至的方法來(lái)根據(jù)當(dāng)前的權(quán)限驗(yàn)證參數(shù)掸绞,獲取映射后的權(quán)限驗(yàn)證參數(shù)泵三,我們定義接口如下:

class PermMappableMixin(object):
    """
    實(shí)現(xiàn)權(quán)限映射的 Model 基類耕捞。
    """
    @classmethod
    def mapping_permission(cls, perm, obj=None):
        """
        根據(jù)當(dāng)前的權(quán)限驗(yàn)證參數(shù),獲取映射后的權(quán)限驗(yàn)證參數(shù)烫幕。(此類方法僅為標(biāo)記方法俺抽,子類應(yīng)實(shí)現(xiàn)相應(yīng)的方法)
        :param perm: 當(dāng)前檢測(cè)的權(quán)限。
        :param obj: 當(dāng)前進(jìn)行檢測(cè)權(quán)限的對(duì)象较曼。
        :return: 返回值包含兩個(gè)參數(shù):第一個(gè)參數(shù)為映射后的權(quán)限磷斧;第二個(gè)參數(shù)為對(duì)應(yīng)映射后的對(duì)象,其應(yīng)為映射后權(quán)限所對(duì)應(yīng)的 Model 的實(shí)例捷犹。
        """
        return None, None

接下來(lái)弛饭,我們只需要實(shí)現(xiàn)我們的認(rèn)證后端就可以了:

from django.contrib.contenttypes.models import ContentType

class PermMappingBackend(object):
    def authenticate(self, username=None, password=None, **kwargs):
        return None

    def has_perm(self, user_obj, perm, obj=None):
        app_label, codename = perm.split('.')
        content_types = ContentType.objects.filter(
            app_label = app_label,
            permission__codename = codename)  # 根據(jù)權(quán)限獲取其對(duì)應(yīng)的ContentType實(shí)例。
        for content_type in content_types:
            model_class = content_type.model_class()    # 根據(jù) ContentType 實(shí)例獲取對(duì)應(yīng)的 Model 類
            if issubclass(model_class, PermMappableMixin):
                mapped_perm, mapped_obj = model_class.mapping_permission(perm, obj = obj)
                if mapped_perm and user_obj.has_perm(mapped_perm, obj=mapped_obj):
                    return True
        return False

是不是很簡(jiǎn)單萍歉,接下來(lái)看看我們新聞例子的代碼:

from django.utils.translation import ugettext as _
from django.db import models

class InfoCategory(models.Model):
    name = models.CharField(max_length=128, verbose_name=_('分類名稱'))

    class Meta:
        permissions = (
            ("add_info_by_category", _("允許添加分類信息")),
            ("change_info_by_category", _("允許修改分類信息")),
            ("delete_info_by_category", _("允許刪除分類信息")),
        )


class Info(PermMappableMixin, models.Model):
    category = models.ForeignKey(InfoCategory, verbose_name=_("所屬分類"))
    title = models.CharField(max_length=256, verbose_name=_('標(biāo)題'))

    @classmethod
    def mapping_permission(cls, perm, obj=None):
        mapped_perm = None
        mapped_obj = None

        if perm == "permmapping.add_info":
            mapped_perm = "permmapping.add_info_by_category"
        elif perm == "permmapping.change_info":
            mapped_perm = "permmapping.change_info_by_category"
        elif perm == "permmapping.delete_info":
            mapped_perm = "permmapping.delete_info_by_category"

        if isinstance(obj, cls):
            mapped_obj = obj.category

        return mapped_perm, mapped_obj

三侣颂、寫(xiě)在最后

Django系統(tǒng)提供了一套靈活的認(rèn)證系統(tǒng),使得我們可以通過(guò)擴(kuò)展其實(shí)現(xiàn)靈活的權(quán)限控制策略枪孩,本文結(jié)合我在項(xiàng)目過(guò)程中的實(shí)踐經(jīng)驗(yàn)憔晒,為大家展示了通過(guò)對(duì)象級(jí)權(quán)限、系統(tǒng)組的實(shí)現(xiàn)蔑舞、權(quán)限映射來(lái)解決項(xiàng)目中所遇到的幾種權(quán)限問(wèn)題拒担,同時(shí)實(shí)踐中也可以通過(guò)組合這幾種權(quán)限機(jī)制,實(shí)現(xiàn)更多更為復(fù)雜的權(quán)限策略攻询。其它類的開(kāi)發(fā)語(yǔ)言澎蛛,也可以借鑒Django及上文所提到的幾種權(quán)限系統(tǒng)的擴(kuò)展的思路,實(shí)現(xiàn)各自平臺(tái)的權(quán)限系統(tǒng)蜕窿。

好了,先到這里了呆馁,如果大家在實(shí)踐中有什么問(wèn)題桐经,可以給我留言,Bye~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末浙滤,一起剝皮案震驚了整個(gè)濱河市阴挣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌纺腊,老刑警劉巖畔咧,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異揖膜,居然都是意外死亡誓沸,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)壹粟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)拜隧,“玉大人,你說(shuō)我怎么就攤上這事『樘恚” “怎么了垦页?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)干奢。 經(jīng)常有香客問(wèn)我痊焊,道長(zhǎng),這世上最難降的妖魔是什么忿峻? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任薄啥,我火速辦了婚禮,結(jié)果婚禮上炭菌,老公的妹妹穿的比我還像新娘罪佳。我一直安慰自己,他們只是感情好赘艳,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著克握,像睡著了一般菩暗。 火紅的嫁衣襯著肌膚如雪停团。 梳的紋絲不亂的頭發(fā)上朗若,一...
    開(kāi)封第一講書(shū)人閱讀 51,125評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音容达,去河邊找鬼埠偿。 笑死纱昧,一個(gè)胖子當(dāng)著我的面吹牛瘪撇,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播履恩,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼锰茉,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了切心?” 一聲冷哼從身側(cè)響起洞辣,我...
    開(kāi)封第一講書(shū)人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤咐刨,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后扬霜,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體定鸟,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年著瓶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了联予。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡材原,死狀恐怖沸久,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情余蟹,我是刑警寧澤卷胯,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站威酒,受9級(jí)特大地震影響窑睁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜葵孤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一担钮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧尤仍,春花似錦箫津、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至赡模,卻和暖如春暖眼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背纺裁。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留司澎,地道東北人欺缘。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像挤安,于是被迫代替她去往敵國(guó)和親谚殊。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

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

  • 經(jīng)過(guò)對(duì)django的初步學(xué)習(xí)蛤铜,我們已經(jīng)對(duì)后臺(tái)的基本流程以及django的運(yùn)作有了一定的了解嫩絮,但是這還不足夠丛肢,dja...
    coder_ben閱讀 3,829評(píng)論 8 34
  • 本文涉及的技術(shù),已應(yīng)用于我基于django 1.8+ 開(kāi)發(fā)的博客系統(tǒng)——MayBlog剿干,歡迎交流蜂怎。 1. Djan...
    Gevin閱讀 38,474評(píng)論 7 104
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,071評(píng)論 25 707
  • 1.iOS7之后 要想改變navigationbar的顏色 可以這樣子改 self.navigationContr...
    寒橋閱讀 20,909評(píng)論 10 33
  • 今天我被拒稿了。這么說(shuō)好像我沒(méi)有被拒過(guò)一樣因?yàn)橐淮蔚木芨寰褪懿涣酥枚N沂堑谝淮伪痪芨懿剑圆艜?huì)失落。更因?yàn)槲沂堑谝淮瓮?..
    壇_閱讀 799評(píng)論 43 17