這篇文章是對Spring Security的Authentication模塊進行一個初步的概念了解,知道它是如何進行用戶認證的
考慮一個大家比較熟悉的標准認證過程:
1.用戶使用username和password登錄
2.系統驗證這個password對於該username是正確的
3.假設第二步驗證成功,獲取該用戶的上下文信息(如他的角色列表)
4.圍繞該用戶建立安全上下文(security context)
5.用戶可能繼續進行的一些操作被一個驗證控制機制潛在的管理,這個驗證機制會根據當前用戶的安全上下文來驗證權限。
認證過程就是又前三項構成的。在Spring Security中是這樣處理這三部分的:
1.username和password被獲得后封裝到一個UsernamePasswordAuthenticationToken(Authentication接口的實例)的實例中
2.這個token被傳遞給AuthenticationManager進行驗證
3.成功認證后AuthenticationManager將返回一個得到完整填充的Authentication實例
4.通過調用SecurityContextHolder.getContext().setAuthentication(...),參數傳遞authentication對象,來建立安全上下文(security context)
可以從一個示例代碼中觀察整個過程(完整代碼參考Spring-Security文檔9.3.1節):
1 public class AuthenticationExample { 2 3 private static AuthenticationManager am = new SampleAuthenticationManager(); 4 5 public static void main(String[] args) throws Exception { 6 7 String name = ""; 8 String password = ""; 9 try { 10 // request就是第一步,使用name和password封裝成為的token 11 Authentication request = new UsernamePasswordAuthenticationToken(name, password); 12 // 將token傳遞給Authentication進行驗證 13 Authentication result = am.authenticate(request); 14 SecurityContextHolder.getContext().setAuthentication(result); 15 break; 16 } catch (AuthenticationException e) { 17 System.out.println("認證失敗:" + e.getMessage()); 18 } 19 System.out.println("認證成功,Security context 包含:" + SecurityContextHolder.getContext().getAuthentication()); 20 } 21 } 22 23 // 自定義驗證方法 24 class SimpleAuthenticationManager implements AuthenticationManager { 25 static final List<GrantedAuthority> AUTHORITIES = new ArrayList<GrantedAuthority>(); 26 27 // 構建一個角色列表 28 static { 29 AUTHORITIES.add(new SimpleGrantedAuthority("ROLE_USER")); 30 } 31 32 // 驗證方法 33 public Authentication authenticate(Authentication auth) throws AuthenticationException { 34 // 這里我們自定義了驗證通過條件:username與password相同就可以通過認證 35 if (auth.getName().equals(auth.getCredentials())) { 36 return new UsernamePasswordAuthenticationToken(auth.getName(), auth.getCredentials(), AUTHORITIES); 37 } 38 // 沒有通過認證則拋出密碼錯誤異常 39 throw new BadCredentialsException("Bad Credentials"); 40 } 41 }
通常不需要像上邊這樣寫代碼,這些過程都是內部自動進行的。當SecurityContextHolder包含一個完整填充的Authentication對象,用戶就是驗證通過了。
Web Application
考慮一個典型的Web應用認證過程:
1.訪問首頁,隨便點擊一個鏈接
2.發送一個請求到服務器,服務器判斷你是否在訪問一個收到保護的資源
3.此時你還沒有進行認證,服務器會返回一個響應告訴你必須先通過認證。這個響應可以是一個HTTP響應碼或者是重定向到指定的web頁面
4.根據認證機制,你的瀏覽器可能會重定向到一個登錄頁面,或者通過某種方式恢復你的身份(通過一個基礎的認證對話框,cookie,X.509證明等)
5.瀏覽器回應服務器。這可以是一個HTTP POST請求,包含你所填寫的表單信息,也可以是一個HTTP請求頭,包含你的認證詳情
6.接下來服務器會判定提交的憑證是否通過認證。如果認證通過,那么繼續下一步。如果沒有通過認證,那么重新進行上邊的步驟
7.你在認證之前,原始的請求(即觸發認證的請求)將會重新發起。
Spring Security已經實現了上述的大多數過程。主要有ExceptionTranslationFilter,AuthenticationEntryPoint和一個認證機制,負責調用上面討論過的AuthenticationManager。
ExceptioTranslationFilter
ExceptionTranslationFilter是用來檢測Spring Security拋出的任何異常的過濾器。
AuthenticationProvider和UserDetails
There is often some confusion about UserDetailsService. It is purely a DAO for user data and performs no other function other than to supply that data to other components within the framework. In particular, it does not authenticate the user, which is done by the AuthenticationManager. In many cases it makes more sense to implement AuthenticationProvider directly if you require a custom authentication process.
AuthenticationManager
用來處理一個認證請求。只有一個authentication(Authentication authentication)函數。
嘗試去認證傳入的Authentication對象,如果認證成功,返回一個完整填充的Authentication對象(包括授予的權限)。
一個AuthenticationManager必須處理以下異常:
- DisabledException:當一個賬戶被禁用且AuthenticationManager可以檢測出來這個狀態,要拋出該異常
- LockedException:當一個賬戶被鎖且AuthenticationManager可以檢測這個狀態,要拋出該異常
- BadCredentialsException:當賬戶認證失敗,必須拋出該異常。(一個AuthenticationManager必須檢測這個狀態)
這些異常應該按照順序拋出,(比如如果一個賬戶被鎖定,那么不進行賬戶認證)。
AuthenticationProvider
用來處理一個指定的認證。有一個authenticate(Authentication authentication)函數和一個supports(Class<?> authentication)函數。
其中authenticate函數的用法與AuthenticationManager的authenticate一樣。
supports函數用來指明該Provider是否適用於該類型的認證,如果不合適,則尋找另一個Provider進行驗證處理。
ProviderManager
通過AuthenticationProviders迭代認證請求。
AuthenticationProviders通常按照順序嘗試,知道返回一個不為null的響應。非空響應代表provider可以提供認證並且不會繼續請求下一個provider。如果后邊的provider成功進行了驗證,那么前邊provider拋出的異常將被忽略。
