SpringSecurity實現登入認證及許可權驗證
目標
在原公司有專門的登入驗證和許可權管理服務,換公司後在最近專案中需要使用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 分析
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的插入位置