目標
在原公司有專門的登入驗證和許可權管理服務,換公司後在最近項目中需要使用Spring Security自主實現分布式系統的使用者驗證授權及許可權驗證功能,因此花了兩天時間研究並實現了該方案:
功能點細分:
1. 基於REST請求的登入
2. 使用者名稱密碼驗證及驗證成功後給使用者授權
3. http請求的許可權配置和驗證
4. 方法層級的許可權配置和驗證
5. 分布式環境中使用者權限共用 分析及實現 一、基於REST請求的登入 1.1 分析
Spring Security預設是通過表單提交使用者登入請求,可通過修改配置實現自訂登入請求 1.2 實現
在spring-security.xml中配置如下為自訂的登入入口,並在entry-point-ref中配置登入url(只需關注紅框標註部分)
註:中別的配置將在下文中分別介紹 二、使用者名稱密碼驗證及驗證成功後給使用者授權 2.1 分析
Spring Security的底層是通過一系列Filter管理的,當我們使用NameSpace時,Spring Security會自動建立FilterChain及預設包含的Filter。其中幾個關鍵Filter的位置和作用如下,完整資訊可參考(http://wiki.jikexueyuan.com/project/spring-security/filter.html)
| 過濾器 |
位置 |
作用 |
| SecurityContextPersistenceFilter |
SECURITY_CONTEXT_FILTER |
進行 request 時在 SecurityContextHolder 中建立一個 SecurityContext |
| UsernamePasswordAuthenticationFilter |
FORM_LOGIN_FILTER |
認證及授權處理, 為SecurityContextHolder添加一個有效 Authentication |
| FilterSecurityInterceptor |
SECURITY_CONTEXT_FILTER |
保護 Http 資源,判斷使用者是否有許可權訪問相應的資源 |
由此可見該功能點是通過UsernamePasswordAuthenticationFilter實現的。UsernamePasswordAuthenticationFilter會調用AuthenticationManager處理認證請求的介面。AuthenticationManager又會進一步把該請求委託給AuthenticationProvider處理。AuthenticationProvider會根據請求的使用者名稱調用UserDetailsService擷取使用者詳情UserDetails。Spring Security有內建UserDetailsService實現,如: CachingUserDetailsService從緩衝中載入使用者詳情 JdbcDaoImpl從資料庫中載入使用者詳情 InMemoryUserDetailsManager從記憶體中載入使用者詳情
這些均不能滿足當前系統的需求,我們需要調用系統中使用者服務的介面來載入使用者詳情,所以這裡需要定義自己的UserDetailsService實現。 2.2 實現
(1) 自訂UserInfoService實現UserDetailsService介面,重寫其loadUserByUsername()方法實現從使用者服務的介面來載入使用者詳情
(2) 在spring-security.xml中配置UsernamePasswordAuthenticationFilter執行個體authenticationFilter,並為其逐級注入自訂的UserDetailsService執行個體userInfoService。
(3) 用authenticationFilter替換FilterChain中預設的認證過濾器UsernamePasswordAuthenticationFilter
之前介紹過FilterChain中的預設的Filter都有自己的位置,UsernamePasswordAuthenticationFilter 對應的位置為FORM_LOGIN_FILTER。Spring Security支援通過 position、before 或者 after 指定自訂 Filter放置的位置。紅框中語句的含義就是將authenticationFilter放置到FORM_LOGIN_FILTER位置,也就實現了替換預設UsernamePasswordAuthenticationFilter的功能
(4) 整個使用者認證及授權的流程如下:
註:紅框部分“分布式環境中使用者權限共用”功能的部分實現,後面介紹 三、HTTP請求的許可權配置和驗證 3.1 分析
HTTP請求的許可權配置就是自訂url和要求的權限的對應關係。在設定檔中實現
HTTP請求的許可權驗證就是檢查使用者登入授權階段擷取到的使用者權限是否可以訪問該url。Spring Security中FilterSecurityInterceptor通過調用AccessDecisonManager的相應介面完成許可權認證,其中AccessDecisonManager或配置一個或多個AccessDecisionVoter的vote()方法來進行投票(成功返回1,失敗返回-1),並根據每個AccessDecisionVoter的投票結果及一定的策略決定該使用者是否有許可權訪問該url。Spring Security預設使用的AccessDecisonManager實作類別AffirmativeBased的策略是只要有一個AccessDecisionVoter投了反對票(即vote方法返回-1),則拒絕訪問url。Spring Security中也有多個AccessDecisionVoter實作類別,這裡介紹兩個常用的AccessDecisionVoter: WebExpressionVoter 基於運算式匹配的投票方法,即只要使用者權限中包含url所需的許可權即附議 RoleVoter 類似於WebExpressionVoter但是需要配置許可權首碼,預設首碼為ROLE_ 3.2 實現
(1) 在spring-security.xml中配置url及其許可權的對應關係
紅框語句的含義就是為能匹配“/dicts**”運算式的url請求配置ROLE_DICT許可權
(2) 許可權檢查的過濾器FilterSecurityInterceptor及其配置均使用Spring Security預設配置。
(3) 一次非登入的Http請求的完整流程如下:
註:紅框部分“分布式環境中使用者權限共用”功能的部分實現,後面介紹 四、方法層級的許可權配置和驗證 4.1 分析
可基於註解實現,參考http://blog.csdn.net/w605283073/article/details/51327182 4.2 實現
(1) 在spring-security.xml中開啟@PreAuthorize的註解支援
(2) 為需要許可權驗證的方法添加@PreAuthorize註解 五、分布式環境中使用者權限共用 5.1 分析
當前分布式系統已經實現了基於Redis的分布式Session,也即同一次訪問過程中經過不同主機上不同服務之間是共用Session的。因此當我們需要實現使用者權限共用時,只需要在登入驗證階段擷取到的使用者詳情資訊(包含使用者權限)寫入到Session中即可。通過上一節的“使用者認證及授權的流程”中可知使用者完成驗證後的使用者詳情資訊會封裝為Authentication執行個體並寫入SecurityContext中,因此我們只需將SecurityContext寫入共用Session即可。在需要使用使用者權限的地方(如訪問特定許可權的url或方法的驗證階段)只需要取出Session中的SecurityContext 5.2 實現
(1) 自訂AuthenticationSuccessHandler實作類別SessionAuthenticationSuccessHandler,重寫onAuthenticationSuccess()方法,將SecurityContext儲存到session中
(2) 在spring-security.xml中為登入驗證授權過濾器authenticationFilter配置授權成功後的處理Handler為SessionAuthenticationSuccessHandler
(3) 自訂過濾器ValidationFilter,在doFilter()方法中實現取出session中的SecurityContext儲存到SecurityContextHolder中
(4) 在spring-security.xml為預設FilterChian添加ValidationFilter,位於FilterSecurityInterceptor (位置為FILTER_SECURITY_INTERCEPTOR)前面,這樣就可以實現在許可權驗證之前先經過ValidationFilter將session中的SecurityContext儲存到SecurityContextHolder中
此處使用了before關鍵字定義了validationFilter的插入位置