Spring Security可以運作在不同的身份認證環境中,當我們推薦使用者使用Spring Security進行身份認證但并不推薦內建到容器管理的身份認證中時,但當你內建到自己的身份認證系統時,它依然是支援的。
1. Spring Security中的身份認證是什麼?
現在讓我們考慮一下每個人都熟悉的标準身份認證場景:
(1)使用者打算使用使用者名和密碼登陸系統
(2)系統驗證使用者名和密碼合法
(3)得到使用者資訊的上下文(角色等資訊)
(4)為使用者建立一個安全上下文
(5)使用者接下來可能執行一些權限通路機制下的受保護的操作,檢查與目前安全上下文有關的必須的權限
上面前三步是身份認證的過程,接下來看看身份認證的詳細過程:
(1)使用者名和密碼獲得之後組合成 UsernamePasswordAuthenticationToken 的執行個體(前文讨論過的Authentication接口的執行個體)
(2)将該令牌傳遞給 AuthenticationManager 執行個體進行驗證
(3)驗證成功後,AuthenticationManager 會傳回填充好的 Authentication 執行個體
(4)通過調用 SecurityContextHolder.getContext().setAuthentication(...) 建立安全上下文的執行個體,傳遞到傳回的身份認證對象上
下面是進行身份認證的代碼片段:
我們寫了一個小程式,要求使用者輸入使用者名和密碼并執行上述序列。我們實作的 AuthenticationManager 會驗證使用者名和密碼是否一緻,它配置設定了一個角色給每個使用者。上面的輸出類似于這樣:
Please enter your username:
favboy
Please enter your password:
favccxx
Authentication failed: Bad Credentials
Successfully authenticated. Security context contains: \
org.springframework.security.authentication.UsernamePasswordAuthenticationToken@441d0230: \
Principal: bob; Password: [PROTECTED]; \
Authenticated: true; Details: null; \
Granted Authorities: ROLE_USER
注意,你通常不需要寫任何代碼。這個過程通常發生在内部,如web身份認證過濾器。上面的代碼僅僅是告訴我們在Spring Security中使用身份認證是如此簡單的事情。當 SecurityContextHolder 包含一個填充的 Authentication 對象時使用者身份就完成了。
2. 直接設定 SecurityContextHolder的内容
實際上,Spring Security并不關心如何将 Authentication對象放到SecurityContextHolder中。唯一的關鍵就是 SecurityContextHolder需要在使用者操作認證的 AbstractSecurityInterceptor 之前已經有了Authentication對象。
對于那些不是Spring Security的系統,你可以自己寫過濾器或MVC控制器與身份認證系統進行內建。比如,你可能使用容器管理的身份認證系統從ThreadLocal或JNDI中得到使用者。也可能你在一個擁有遺留的身份認證系統的公司工作,這是一個企業的“标準”,對此你是無能為力的。在這種情形下,使用Spring Security提供身份認證是非常容易的,你隻需要寫一個過濾器讀取第三方的使用者資訊,然後建構一個Spring Security特定的 Authentication對象,并把它放到AuthenticationContextHolder中即可。在這種情況下,你需要考慮自帶的身份認證的基礎資訊。比如,你需要在響應到用戶端之前,先建立一個HTTP session會話在請求之間緩存上下文。
3 在Web應用中使用身份認證
接下來,我們探究一下Web應用不配置web.xml安全政策的情況下如何使用Spring Security進行身份認證,如何建立使用者身份認證和安全上下文?
下面是web應用身份認證的流程:
(1)通路某應用的首頁,點選某個連結。
(2)發送一個請求到伺服器,伺服器判斷使用者是否正在通路受保護的資源。
(3)由于使用者之前并未進行身份認證,伺服器發送一個響應(該響應可能是HTTP響應代碼,也可能直接跳轉到某web頁面)告訴使用者必須進行身份認證。
(4)身份認證機制決定了浏覽器是跳轉到特定的web頁面讓使用者填寫form表單,或者浏覽器以某種方式(基本的身份認證對話框、cookie或X.509證書)檢索使用者身份。
(5)浏覽器發送響應(包含表單資訊的HTTP POST請求或是包含使用者身份認證詳細資訊的HTTP表頭)回伺服器。
(6)接下來,伺服器會決定之前的憑證是否有效。如果有效的話,會進行下一步。否則的話,浏覽器通常會詢問是否需要重試。
(7)原始的請求會導緻身份認證流程重新進行,重新判斷使用者有足夠的權限通路受保護的資源,如果使用者有權限的話,請求就是成功的。否則的話,會傳回HTTP錯誤碼403,表示使用者沒有權限操作。
Spring Security有具體的類負責上面的步驟,主要的類有 ExceptionTranslationFilter , AuthenticationEntryPoint 和調用AuenticationManager 的“身份認證機制”。
3.1 ExceptionTranslationFilter
顧名思義,ExceptionTranslationFilter是處理Spring Security中異常的過濾器,這些異常都是由提供身份認證服務的 AbstractSecurityInterceptor 抛出。
3.2 AuthenticationEntryPoint
上面步驟3的操作中是 AuenticationEntryPoint 的職責,你能想象每個web應用都有預設的身份認證測試,每個主要的身份認證系統都有 AuthenticationEntryPoint 實作,通常執行步驟3中描述的行動之一。
3.3 身份認證機制
一旦你的浏覽器送出了驗證證書(HTTP表單 POST或 HTTP頭),這需要伺服器上的一些東西儲存這些權限資訊。但是現在進入上面的第6步,在Spring Security中我們有一個特定的名稱,為了手機驗證資訊的操作。從一個使用者代理中(通常是浏覽器),引用它作為一個“驗證機制”。例如基于表單的登陸或BASIC驗證。一旦從使用者代理出收集到驗證細節, Authentication請求對象就會建立,然後送出給AuthenticationManager。
身份認證機制收到填充好的 Authentication 對象之後,它會認為請求合法,把 Authentication放到SecurityContextHolder中,然後重試原始的請求(第7步)。如另一方面, AuthenticationManager拒絕了請求,身份認證機制會讓使用者代理重試(第2步)。
3.4 在請求間儲存 SecurityContext
根據應用類型,需要一個政策在使用者操作之間儲存security上下文。在典型的web應用中,一次使用者登陸日志随後就由它的session id所确定,伺服器為保持session會話會緩存主體資訊。在Spring Security中,在請求間存儲SecurityContext的職責落在了 SecurityContextPersistenceFilter上,預設情況下,SecurityContextPersistenceFilter會在HTTP請求中将上下文存儲在HttpSession屬性上。每次請求的上下文都會存儲在 SecurityContextHolder上,而且,最重要的是,當請求完成時它會清除 SecurityContextHolder。為了安全方面考慮,使用者不應該直接操作 HttpSession,這裡有簡單的方法實作-使用 SecurityContextHolder代替。
很多其他類型的應用(比如一個無狀态的REST Web服務)不會使用HTTP會話,會在每次請求時重新驗證。然而,将 SecurityContextPersistenceFilter包含了請求鍊中仍然是非常重要的,這樣就會確定在每次請求後 SecurityContextHolder會被清空。