Spring Security可以運行在不同的身份認證環境中,當我們推薦用戶使用Spring Security進行身份認證但並不推薦集成到容器管理的身份認證中時,但當你集成到自己的身份認證系統時,它依然是支持的。
1. Spring Security中的身份認證是什麼?
現在讓我們考慮一下每個人都熟悉的標准身份認證場景:
(1)用戶打算使用用戶名和密碼登陸系統
(2)系統驗證用戶名和密碼合法
(3)得到用戶信息的上下文(角色等信息)
(4)為用戶建立一個安全上下文
(5)用戶接下來可能執行一些權限訪問機制下的受保護的操作,檢查與當前安全上下文有關的必須的權限
上面前三步是身份認證的過程,接下來看看身份認證的詳細過程:
(1)用戶名和密碼獲得之後組合成 UsernamePasswordAuthenticationToken 的實例(前文討論過的Authentication接口的實例)
(2)將該令牌傳遞給 AuthenticationManager 實例進行驗證
(3)驗證成功後,AuthenticationManager 會返回填充好的 Authentication 實例
(4)通過調用 SecurityContextHolder.getContext().setAuthentication(...) 建立安全上下文的實例,傳遞到返回的身份認證對象上
下面是進行身份認證的代碼片段:
import org.springframework.security.authentication.*;
import org.springframework.security.core.*;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
public class AuthenticationExample {
private static AuthenticationManager am = new SampleAuthenticationManager();
public static void main(String[] args) throws Exception {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
while(true) {
System.out.println("Please enter your username:");
String name = in.readLine();
System.out.println("Please enter your password:");
String password = in.readLine();
try {
Authentication request = new UsernamePasswordAuthenticationToken(name, password);
Authentication result = am.authenticate(request);
SecurityContextHolder.getContext().setAuthentication(result);
break;
} catch(AuthenticationException e) {
System.out.println("Authentication failed: " + e.getMessage());
}
}
System.out.println("Successfully authenticated. Security context contains: " +
SecurityContextHolder.getContext().getAuthentication());
}
}
class SampleAuthenticationManager implements AuthenticationManager {
static final List<GrantedAuthority> AUTHORITIES = new ArrayList<GrantedAuthority>();
static {
AUTHORITIES.add(new SimpleGrantedAuthority("ROLE_USER"));
}
public Authentication authenticate(Authentication auth) throws AuthenticationException {
if (auth.getName().equals(auth.getCredentials())) {
return new UsernamePasswordAuthenticationToken(auth.getName(),
auth.getCredentials(), AUTHORITIES);
}
throw new BadCredentialsException("Bad Credentials");
}
}
我們寫了一個小程序,要求用戶輸入用戶名和密碼並執行上述序列。我們實現的 AuthenticationManager 會驗證用戶名和密碼是否一致,它分配了一個角色給每個用戶。上面的輸出類似於這樣:
Please enter your username:
favboy
Please enter your password:
favccxx
Authentication failed: Bad Credentials
Please enter your username:
favboy
Please enter your password:
favboy
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會被清空。
Spring Security 學習之數據庫認證 http://www.linuxidc.com/Linux/2014-02/97407.htm
Spring Security 學習之LDAP認證 http://www.linuxidc.com/Linux/2014-02/97406.htm
Spring Security 學習之OpenID認證 http://www.linuxidc.com/Linux/2014-02/97405.htm
Spring Security 學習之X.509認證 http://www.linuxidc.com/Linux/2014-02/97404.htm
Spring Security 學習之HTTP基本認證和HTTP摘要認證 http://www.linuxidc.com/Linux/2014-02/97403.htm
Spring Security 學習之HTTP表單驗證 http://www.linuxidc.com/Linux/2014-02/97402.htm