開源權限控制框架Uniauth簡介

導語

Uniauth是一個基於CAS和Spring
Security開源產品而開發的權限控制框架,它的目標是服務於點融網內部的各個子系統,只要集成了該框架,就能以很小的代價實現認證和授權功能。而權限設計是開發系統中不可規避的一個環節。
代碼開源:https://github.com/dianrong/UniAuth

爲什麼做權限控制框架

每個公司內部都有大量的子系統,點融網也不例外。
這些子系統有爲普通員工服務的,比如各種OA系統;有爲專門業務操作人員服務的,比如電銷系統,庫存管理系統;也有爲技術人員服務的,比如我們常見的git,confluence,jira等系統。
這裏寫圖片描述

很多時候,這些子系統都有各自獨立的賬戶體系和權限控制方式。當有新員工入職時,需要在各個必須的子系統中爲其開設帳號並受以權限,浪費了大量的人力物力。而且,各個子系統權限控制的模式參差不齊,權限控制的數據模型也不盡相同,很多時候都留有安全隱患。比如,使用Fiddler等工具攔截http報文後,修改一些參數便可以訪問到本不該被訪問到的隱私內容。

另外,作爲使用這些子系統的員工也比較痛苦,因爲他們有可能在這些子系統中設置不同的密碼,密碼記憶混亂甚至忘記密碼也常有發生。

如果這些子系統可以集成一套成熟的權限控制框架,使用唯一的一套賬戶體系,能夠完成統一登錄和登出(Single Sign On/Off),甚至各個子系統的管理員可以自行分配屬於自己系統的組、角色、權限等信息,將會是一件大快人心的事!

綜上所述,您可能認爲我們確實應該設計一套權限控制的框架來幫我們來做這件事。在設計這套框架之前,讓我們來看看需要做權限控制的一些常見業務場景。

需要權限控制的常見業務場景

  • 頁面級別(視圖層)
    控制菜單展現,按鈕顯示,數據渲染

  • 方法訪問控制
    方法和類,事前和事後,aop切點語法支持

  • 數據過濾

    同樣一份數據,對不同的人而言看到的數量或屬性不同

  • 請求的URL的攔截

    只有屬於某些/個角色的登錄用戶纔可以訪問某些模式的URL

  • 某些方法調用需要將入參和當前用戶相關聯的情況

    比如對於密碼修改方法,登錄人員的只能修改自己賬戶的密碼

  • 更細粒度的控制

    使用ACL對域對象制定更細粒度的權限訪問策略

  • 其他更多種的訪問控制策略的引入

    比如基於IP(支持CIDR表示),基於訪問時間段,remember me功能等等

可以看到,做到盡善盡美的權限控制十分不易,如果自己去實現所有這些業務場景的控制將花費大量力氣,而且也不一定能做好。

那究竟該如何這個設計權限控制框架,或者說它的設計原則又是什麼呢?

Uniauth的設計原則

我們認爲,Uniauth的設計原則應該體現在以下方面:

  • 兼容目前子系統的權限控制模型

    子系統的權限模型可以適配轉換進入新系統的權限模型。

  • 具有廣泛的第三方鑑權系統或協議的可接納性

    需要廣泛的鑑權系統/協議的集成支持,包括但不限於:sso,oauth,ntlm/kerberos,openid,ldap/ms active directory,saml…因爲說不清未來要集成什麼東西。但有需要時可以通過配置,或以較小的代價接入。

  • 具有良好擴展性

    擴展性表現在認證和授權的各個階段和環節,每個環節都有默認實現,但可以提供途徑根據需要進行覆寫和干預。這通常發生在子系統認爲框架提供的某個環節不爽的場景,比如我覺得公共登錄頁很醜,我要定製自己的登錄頁。

  • 方便的組,角色,人員權限分配和控制

    當新加入業務操作人員時,管理人員只需要簡短操作就可以方便的進行賬戶權限分配,控制,管理,權限開關設置等,

  • 代碼侵入性

    權限控制對系統業務級代碼無侵入性,或有較少侵入性。

  • 使用成熟解決方案,不重複造輪子

    使用業界久經考驗的成熟開源方案,少些代碼,遇到問題通過社區很快得到解決。

基於以上設計原則,我們選擇了CAS + Spring Security的開源組合來作爲我們Uniauth框架開發的基礎。

Uniauth簡介

Uniauth是一個基於CAS和Spring Security開源產品而開發的權限控制框架,其中統一認證功能由CAS提供,當訪問多個集成了Uniauth框架的業務子系統時,只要用戶登錄一次,便可以訪問所有其他業務子系統(SSO功能);授權功能由Spring Security提供,所有Spring Security的功能都可以使用。

同時我們對Spring Security的某些常用關鍵功能進行了封裝和再增強,以便使您更專注於權限控制的業務實現(比如通過註解,表達式或JSP Security Tag)而無需瞭解Spring Security的底層配置和機制。所以,Uniauth框架基於Spring Security,而又不僅僅是一個Spring Security。

事實上,我們提供的是一個定製版的Spring Security框架,更好用,更強大,這主要體現在以下幾點:

  1. 無縫集成CAS實現SSO

    在集成Uniauth框架後,用戶訪問本業務系統,一旦檢測到用戶未登陸,會被自動引導到CAS統一登錄頁,完成登錄後再跳轉到業務系統,業務系統可以自動獲取該用戶的基本屬性,如用戶名,域信息,角色信息等(還可對屬性進行擴展,見下文)。同時,無需再登陸即可訪問其他集成的業務子系統。

  2. 對XML和數據庫兩方定義的URL攔截數據進行合併
    在普通情況下使用Spring Security,對URL攔截的定義位於其配置文件的標籤下的中,如:

<sec:intercept-url pattern="/admin/**" access="hasAnyRole('ROLE_ADMIN', 'ROLE_SUPER_ADMIN')"/>

如果進行定製的話,常規做法是實現FilterInvocationSecurityMetadataSource接口,用於加載預定義在數據庫中的intercept-url定義,以便用於運行時權限判斷。

但上面兩種情況通常是互斥的,這是因爲配置文件中的定義由Spring Security框架在啓動時加載,並且注入到一個FilterSecurityInterceptor實例中;來自數據庫定義的MetadataSource會被注入到另外的一個自定義FilterSecurityInterceptor實例中。在運行時權限判斷中,兩個FilterSecurityInterceptor無論誰先執行誰後執行,都會在基於Role的判斷中影響或覆蓋另外一個。

在Uniauth中會自動對這兩邊定義的intercept-url進行合併,並注入到同一個FilterSecurityInterceptor實例中,所有intercept-url定義都會起作用。

  1. 在URL定義中尋找最優匹配請求路徑的條目
    在權限控制的開始階段,我們通常會做出粗糙控制,比如規定只有登陸用戶才能訪問本業務系統:
<sec:intercept-url pattern="/**" access="isAuthenticated()"/>

隨着業務的不斷精細化,我們可能會定義各種不同匹配模式(基於ant或正則),如只有ROLE_ADMIN角色才能訪問/admin開始的url:

<sec:intercept-url pattern="/admin/**" access="hasRole('ROLE_ADMIN')"/>

在普通的Spring Security使用中,這兩個定義的先後順序很重要,因爲它會自上而下進行掃描,一旦發現有一條路徑匹配就會選用。

這樣就會產生問題,比如一個普通用戶(ROLE_USER),訪問了/admin/update/info這個url,因爲第一條定義表明只要是認證過的用戶就可以訪問所有形式的url,這樣就造成了普通用戶也可以訪問管理員纔可以訪問的頁面。

在Uniauth中不會產生這個問題,Uniauth會把來自配置文件或數據庫中的所有匹配請求路徑的url定義聚集起來,然後從中選擇一個最匹配(最長匹配)的進行選用,無論url定義的順序是什麼。

對應於上面的例子,/admin/update/info這個請求路徑對於兩個url定義都匹配,但是顯然更匹配第二個,所以會選用第二個定義。但第二個要求是ROLE_ADMIN角色才能訪問,用戶本身是ROLE_USER,所以會拒絕他的請求。

我們建議對所有需要保護的資源url進行定義切分,因爲不被保護的url資源屬於公用資源,誰都可以訪問。

  1. 添加hasPermission表達式支持
    我們在Uniauth中添加了對hasPermission表達式的支持,hasPermission表達式是最精細的,最徹底的權限控制方式,用於判斷當前登陸用戶對於某個業務對象是否有某種訪問權限。

在Uniauth中,您只要實現UniauthPermissionEvaluator接口,或者從UniauthPermissionEvaluatorImpl擴展,根據自己的需要覆寫兩個hasPermission方法或其中一個即可,hasPermission表達式可以內嵌在@PreAuthorize註解中,或者也可以使用在JSP安全標籤中。

  1. 對用戶登錄session進行併發控制
    爲了安全考慮,我們配置了併發Session訪問控制,即Concurrent Session Control。當同一個賬戶在不同的Session會話中訪問同一個業務系統時,第二個Session會將第一個Session會話踢出,如果用戶在第一個Session會話中繼續活動,會被提示“對不起,您的會話超時,或者您的賬號在另外一個窗口中已登錄,導致本次會話結束,如有需要,請重新登錄!”,引導用戶登陸或徹底退出登錄系統。

  2. 對UserDetails對象隨需進行擴展
    UserDetails在Spring Security中是一個很重要的對象,它代表了通過認證後的用戶實體,即principal對象。

我們通常使用principal對象在@PreAuthorize中結合SpEL(Spring Expression Language)進行基本的權限控制,看看當前用戶實體是否有權限進行某個方法的調用,這是權限控制中很重要的一個環節。比如對如下方法控制:

@PreAuthorize("hasRole('ROLE_SUPER_ADMIN') and principal.permMap['DOMAIN'] != null and principal.permMap['DOMAIN'].contains('techops')")

public Response<?> resetPassword(@RequestBody UserParam userParam) { ... }

從UserDetails中可以獲取到跟當前登錄用戶相關聯的屬性,比如用戶名,密碼,賬戶是否被鎖定,密碼是否過期,用戶被授予的角色列表等基本信息。

在Uniauth框架中對UserDetails進行了基本擴充,除包含上述用戶信息外,我們還添加了當前用戶登錄的域信息,用戶在該域上的權限(privilege)信息等。

另外,每個子系統可能有不同業務需要,我們添加了UserInfoCallBack回調接口,只要業務系統實現了它,就能在UserDetails中添加自己需要的擴展用戶屬性,比如該用戶所有的組列表信息,或者跟自己業務系統相關的其他用戶屬性信息等。

在大部分情況下,添加擴展屬性的目的是用於上面介紹的@PreAuthorize表達式判斷,或者用於自身業務操作的其他目的。

Uniauth框架和子系統的集成框架圖

這裏寫圖片描述

代碼開源:https://github.com/dianrong/UniAuth
原文:點融黑幫 - 點融網開源權限控制框架Uniauth簡介

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章