Uniauth是一個基于CAS和Spring Security開源產(chǎn)品而開發(fā)的權(quán)限控制框架,它的目標(biāo)是服務(wù)于點融網(wǎng)內(nèi)部的各個子系統(tǒng)涨岁,只要集成了該框架拐袜,就能以很小的代價實現(xiàn)認(rèn)證和授權(quán)功能。而權(quán)限設(shè)計是開發(fā)系統(tǒng)中不可規(guī)避的一個環(huán)節(jié)梢薪。
為什么要做權(quán)限控制框架
每個公司內(nèi)部都有大量的子系統(tǒng)蹬铺,點融網(wǎng)也不例外。
這些子系統(tǒng)有為普通員工服務(wù)的秉撇,比如各種OA系統(tǒng)甜攀;有為專門業(yè)務(wù)操作人員服務(wù)的,比如電銷系統(tǒng)琐馆,庫存管理系統(tǒng)规阀;也有為技術(shù)人員服務(wù)的,比如我們常見的git啡捶,confluence姥敛,jira等系統(tǒng)。
很多時候瞎暑,這些子系統(tǒng)都有各自獨立的賬戶體系和權(quán)限控制方式彤敛。當(dāng)有新員工入職時,需要在各個必須的子系統(tǒng)中為其開設(shè)帳號并受以權(quán)限了赌,浪費了大量的人力物力墨榄。而且,各個子系統(tǒng)權(quán)限控制的模式參差不齊勿她,權(quán)限控制的數(shù)據(jù)模型也不盡相同袄秩,很多時候都留有安全隱患。比如,使用Fiddler等工具攔截http報文后之剧,修改一些參數(shù)便可以訪問到本不該被訪問到的隱私內(nèi)容郭卫。
另外,作為使用這些子系統(tǒng)的員工也比較痛苦背稼,因為他們有可能在這些子系統(tǒng)中設(shè)置不同的密碼贰军,密碼記憶混亂甚至忘記密碼也常有發(fā)生。
如果這些子系統(tǒng)可以集成一套成熟的權(quán)限控制框架蟹肘,使用唯一的一套賬戶體系词疼,能夠完成統(tǒng)一登錄和登出(Single?Sign?On/Off),甚至各個子系統(tǒng)的管理員可以自行分配屬于自己系統(tǒng)的組帘腹、角色贰盗、權(quán)限等信息,將會是一件大快人心的事阳欲!
綜上所述舵盈,您可能認(rèn)為我們確實應(yīng)該設(shè)計一套權(quán)限控制的框架來幫我們來做這件事。在設(shè)計這套框架之前胸完,讓我們來看看需要做權(quán)限控制的一些常見業(yè)務(wù)場景书释。
需要權(quán)限控制的常見業(yè)務(wù)場景
頁面級別(視圖層)
控制菜單展現(xiàn),按鈕顯示赊窥,數(shù)據(jù)渲染
方法訪問控制
方法和類,事前和事后狸页,aop切點語法支持
數(shù)據(jù)過濾
同樣一份數(shù)據(jù)锨能,對不同的人而言看到的數(shù)量或?qū)傩圆煌?/p>
請求的URL的攔截
只有屬于某些/個角色的登錄用戶才可以訪問某些模式的URL
某些方法調(diào)用需要將入?yún)⒑彤?dāng)前用戶相關(guān)聯(lián)的情況
比如對于密碼修改方法,登錄人員的只能修改自己賬戶的密碼
更細(xì)粒度的控制
使用ACL對域?qū)ο笾贫ǜ?xì)粒度的權(quán)限訪問策略
其他更多種的訪問控制策略的引入
比如基于IP(支持CIDR表示)芍耘,基于訪問時間段址遇,remember?me功能等等
可以看到,做到盡善盡美的權(quán)限控制十分不易斋竞,如果自己去實現(xiàn)所有這些業(yè)務(wù)場景的控制將花費大量力氣倔约,而且也不一定能做好。
那究竟該如何這個設(shè)計權(quán)限控制框架坝初,或者說它的設(shè)計原則又是什么呢浸剩?
Uniauth的設(shè)計原則
我們認(rèn)為,Uniauth的設(shè)計原則應(yīng)該體現(xiàn)在以下方面:
兼容目前子系統(tǒng)的權(quán)限控制模型
子系統(tǒng)的權(quán)限模型可以適配轉(zhuǎn)換進(jìn)入新系統(tǒng)的權(quán)限模型鳄袍。
具有廣泛的第三方鑒權(quán)系統(tǒng)或協(xié)議的可接納性
需要廣泛的鑒權(quán)系統(tǒng)/協(xié)議的集成支持绢要,包括但不限于:sso,oauth拗小,ntlm/kerberos重罪,openid,ldap/ms?active?directory,saml...因為說不清未來要集成什么東西剿配。但有需要時可以通過配置搅幅,或以較小的代價接入。
具有良好擴(kuò)展性
擴(kuò)展性表現(xiàn)在認(rèn)證和授權(quán)的各個階段和環(huán)節(jié)呼胚,每個環(huán)節(jié)都有默認(rèn)實現(xiàn)茄唐,但可以提供途徑根據(jù)需要進(jìn)行覆寫和干預(yù)。這通常發(fā)生在子系統(tǒng)認(rèn)為框架提供的某個環(huán)節(jié)不爽的場景砸讳,比如我覺得公共登錄頁很丑琢融,我要定制自己的登錄頁。
方便的組簿寂,角色漾抬,人員權(quán)限分配和控制
當(dāng)新加入業(yè)務(wù)操作人員時,管理人員只需要簡短操作就可以方便的進(jìn)行賬戶權(quán)限分配常遂,控制纳令,管理,權(quán)限開關(guān)設(shè)置等克胳,
代碼侵入性
權(quán)限控制對系統(tǒng)業(yè)務(wù)級代碼無侵入性平绩,或有較少侵入性。
使用成熟解決方案漠另,不重復(fù)造輪子
使用業(yè)界久經(jīng)考驗的成熟開源方案捏雌,少些代碼,遇到問題通過社區(qū)很快得到解決笆搓。
基于以上設(shè)計原則性湿,我們選擇了CAS?+?Spring?Security的開源組合來作為我們Uniauth框架開發(fā)的基礎(chǔ)。
Uniauth簡介
Uniauth是一個基于CAS和Spring?Security開源產(chǎn)品而開發(fā)的權(quán)限控制框架满败,其中統(tǒng)一認(rèn)證功能由CAS提供肤频,當(dāng)訪問多個集成了Uniauth框架的業(yè)務(wù)子系統(tǒng)時,只要用戶登錄一次算墨,便可以訪問所有其他業(yè)務(wù)子系統(tǒng)(SSO功能)宵荒;授權(quán)功能由Spring?Security提供,所有Spring?Security的功能都可以使用净嘀。
同時我們對Spring?Security的某些常用關(guān)鍵功能進(jìn)行了封裝和再增強报咳,以便使您更專注于權(quán)限控制的業(yè)務(wù)實現(xiàn)(比如通過注解,表達(dá)式或JSP?Security?Tag)而無需了解Spring?Security的底層配置和機(jī)制面粮。所以少孝,Uniauth框架基于Spring?Security,而又不僅僅是一個Spring?Security熬苍。
事實上稍走,我們提供的是一個定制版的Spring?Security框架袁翁,更好用,更強大婿脸,這主要體現(xiàn)在以下幾點:
1粱胜、無縫集成CAS實現(xiàn)SSO
在集成Uniauth框架后,用戶訪問本業(yè)務(wù)系統(tǒng)狐树,一旦檢測到用戶未登陸焙压,會被自動引導(dǎo)到CAS統(tǒng)一登錄頁,完成登錄后再跳轉(zhuǎn)到業(yè)務(wù)系統(tǒng)抑钟,業(yè)務(wù)系統(tǒng)可以自動獲取該用戶的基本屬性涯曲,如用戶名,域信息在塔,角色信息等(還可對屬性進(jìn)行擴(kuò)展幻件,見下文)。同時蛔溃,無需再登陸即可訪問其他集成的業(yè)務(wù)子系統(tǒng)绰沥。
2、對XML和數(shù)據(jù)庫兩方定義的URL攔截數(shù)據(jù)進(jìn)行合并
在普通情況下使用Spring?Security贺待,對URL攔截的定義位于其配置文件的標(biāo)簽下的中徽曲,如:
。
如果進(jìn)行定制的話麸塞,常規(guī)做法是實現(xiàn)FilterInvocationSecurityMetadataSource接口秃臣,用于加載預(yù)定義在數(shù)據(jù)庫中的intercept-url定義,以便用于運行時權(quán)限判斷哪工。
但上面兩種情況通常是互斥的甜刻,這是因為配置文件中的定義由Spring?Security框架在啟動時加載,并且注入到一個FilterSecurityInterceptor實例中正勒;來自數(shù)據(jù)庫定義的MetadataSource會被注入到另外的一個自定義FilterSecurityInterceptor實例中。在運行時權(quán)限判斷中傻铣,兩個FilterSecurityInterceptor無論誰先執(zhí)行誰后執(zhí)行章贞,都會在基于Role的判斷中影響或覆蓋另外一個。
在Uniauth中會自動對這兩邊定義的intercept-url進(jìn)行合并非洲,并注入到同一個FilterSecurityInterceptor實例中鸭限,所有intercept-url定義都會起作用。
3两踏、在URL定義中尋找最優(yōu)匹配請求路徑的條目
在權(quán)限控制的開始階段败京,我們通常會做出粗糙控制,比如規(guī)定只有登陸用戶才能訪問本業(yè)務(wù)系統(tǒng):
隨著業(yè)務(wù)的不斷精細(xì)化梦染,我們可能會定義各種不同匹配模式(基于ant或正則)赡麦,如只有ROLE_ADMIN角色才能訪問/admin開始的url:
在普通的Spring?Security使用中朴皆,這兩個定義的先后順序很重要,因為它會自上而下進(jìn)行掃描泛粹,一旦發(fā)現(xiàn)有一條路徑匹配就會選用遂铡。
這樣就會產(chǎn)生問題,比如一個普通用戶(ROLE_USER)晶姊,訪問了/admin/update/info這個url扒接,因為第一條定義表明只要是認(rèn)證過的用戶就可以訪問所有形式的url,這樣就造成了普通用戶也可以訪問管理員才可以訪問的頁面们衙。
在Uniauth中不會產(chǎn)生這個問題钾怔,Uniauth會把來自配置文件或數(shù)據(jù)庫中的所有匹配請求路徑的url定義聚集起來,然后從中選擇一個最匹配(最長匹配)的進(jìn)行選用蒙挑,無論url定義的順序是什么宗侦。
對應(yīng)于上面的例子,/admin/update/info這個請求路徑對于兩個url定義都匹配脆荷,但是顯然更匹配第二個凝垛,所以會選用第二個定義。但第二個要求是ROLE_ADMIN角色才能訪問蜓谋,用戶本身是ROLE_USER梦皮,所以會拒絕他的請求。
我們建議對所有需要保護(hù)的資源url進(jìn)行定義切分桃焕,因為不被保護(hù)的url資源屬于公用資源剑肯,誰都可以訪問。
4观堂、添加hasPermission表達(dá)式支持
我們在Uniauth中添加了對hasPermission表達(dá)式的支持让网,hasPermission表達(dá)式是最精細(xì)的港令,最徹底的權(quán)限控制方式骏掀,用于判斷當(dāng)前登陸用戶對于某個業(yè)務(wù)對象是否有某種訪問權(quán)限沪哺。
在Uniauth中隐轩,您只要實現(xiàn)UniauthPermissionEvaluator接口炭臭,或者從UniauthPermissionEvaluatorImpl擴(kuò)展奶镶,根據(jù)自己的需要覆寫兩個hasPermission方法或其中一個即可羹奉,hasPermission表達(dá)式可以內(nèi)嵌在@PreAuthorize注解中鲫尊,或者也可以使用在JSP安全標(biāo)簽中笔横。
5竞滓、對用戶登錄session進(jìn)行并發(fā)控制
為了安全考慮,我們配置了并發(fā)Session訪問控制吹缔,即Concurrent?Session?Control商佑。當(dāng)同一個賬戶在不同的Session會話中訪問同一個業(yè)務(wù)系統(tǒng)時,第二個Session會將第一個Session會話踢出厢塘,如果用戶在第一個Session會話中繼續(xù)活動茶没,會被提示“對不起肌幽,您的會話超時,或者您的賬號在另外一個窗口中已登錄礁叔,導(dǎo)致本次會話結(jié)束牍颈,如有需要,請重新登錄琅关!”煮岁,引導(dǎo)用戶登陸或徹底退出登錄系統(tǒng)。
6涣易、對UserDetails對象隨需進(jìn)行擴(kuò)展
UserDetails在Spring?Security中是一個很重要的對象画机,它代表了通過認(rèn)證后的用戶實體,即principal對象新症。
我們通常使用principal對象在@PreAuthorize中結(jié)合SpEL(Spring?Expression?Language)進(jìn)行基本的權(quán)限控制步氏,看看當(dāng)前用戶實體是否有權(quán)限進(jìn)行某個方法的調(diào)用,這是權(quán)限控制中很重要的一個環(huán)節(jié)徒爹。比如對如下方法控制:
@PreAuthorize("hasRole('ROLE_SUPER_ADMIN')?and?principal.permMap['DOMAIN']?!=?null?and?principal.permMap['DOMAIN'].contains('techops')")
public?Response?resetPassword(@RequestBody?UserParam?userParam)?{?...?}
從UserDetails中可以獲取到跟當(dāng)前登錄用戶相關(guān)聯(lián)的屬性荚醒,比如用戶名,密碼隆嗅,賬戶是否被鎖定界阁,密碼是否過期,用戶被授予的角色列表等基本信息胖喳。
在Uniauth框架中對UserDetails進(jìn)行了基本擴(kuò)充泡躯,除包含上述用戶信息外,我們還添加了當(dāng)前用戶登錄的域信息丽焊,用戶在該域上的權(quán)限(privilege)信息等较剃。
另外,每個子系統(tǒng)可能有不同業(yè)務(wù)需要技健,我們添加了UserInfoCallBack回調(diào)接口写穴,只要業(yè)務(wù)系統(tǒng)實現(xiàn)了它,就能在UserDetails中添加自己需要的擴(kuò)展用戶屬性雌贱,比如該用戶所有的組列表信息确垫,或者跟自己業(yè)務(wù)系統(tǒng)相關(guān)的其他用戶屬性信息等。
在大部分情況下帽芽,添加擴(kuò)展屬性的目的是用于上面介紹的@PreAuthorize表達(dá)式判斷,或者用于自身業(yè)務(wù)操作的其他目的翔冀。
Uniauth框架和子系統(tǒng)的集成框架圖
Uniauth系統(tǒng)已經(jīng)上線导街,歡迎有集成需求的系統(tǒng)跟我們聯(lián)系,我們將竭誠提供集成支撐服務(wù)纤子。
本文作者:許增偉(David Xu)搬瑰,現(xiàn)任點融網(wǎng)成都團(tuán)隊架構(gòu)組開發(fā)工程師款票,主要開發(fā)與業(yè)務(wù)無關(guān)的橫切系統(tǒng)和組件,如權(quán)限控制系統(tǒng)泽论,消息組件艾少,日志組件和一些公共管理工具等等。