16.2. Servlet 3+ 整合

16.2. Servlet 3+ 整合

這個段落會說明 Spring Security 與 Servlet 3 方法的整合。

16.2.1. HttpServletRequest.authenticate(HttpServletRequest,HttpServletResponse)

HttpServletRequest.authenticate(HttpServletRequest,HttpServletResponse) 方法可以用來確保使用者經過驗證。

如果還沒驗證,則設定好的 AuthenticationEntryPoint 會被觸發來要求使用者作驗證,例如轉導到登入頁面。

16.2.2. HttpServletRequest.login(String,String)

HttpServletRequest.login(String,String) 方法可以被用來使用當前的 AuthenticationManager 來驗證使用者。

例如以下範例會試圖以使用者名稱 user 及密碼 password 來作驗證:

try {
    httpServletRequest.login("user","password");
} catch (ServletException e) {
    // fail to authenticate
}

如果你希望 Spring Security 來處理失敗的驗證,則不需要 catch ServletException

16.2.3. HttpServletRequest.logout()

HttpServletRequest.logout() 方法可以用來登出目前的使用者。

通常這代表 SecurityContextHolder 會被清空,HttpSession 會被廢止,任何「記得我」認證都會被清除等等。但是設定好的 LogoutHandler 實作也可以能因為你的 Spring Security 設定而有所不同。所以在 HttpServletRequest.logout() 被觸發後,你還能控制回應寫出是非常重要的,例如通常會轉導到歡迎頁面。

16.2.4. AsyncContext.start(Runnable)

AsyncContext.start(Runnable) 方法是用來確保你的憑證會被傳遞到新的 Thread

使用 Spring Security 對併發的支援,Spring Security 會覆蓋 AsyncContext.start(Runnable) 方法來確保處理 Runnable 時使用的是當前的 SecurityContext

例如以下範例會回應目前使用者的 Authentication

final AsyncContext async = httpServletRequest.startAsync();
async.start(new Runnable() {
	public void run() {
		Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
		try {
			final HttpServletResponse asyncResponse = (HttpServletResponse) async.getResponse();
			asyncResponse.setStatus(HttpServletResponse.SC_OK);
			asyncResponse.getWriter().write(String.valueOf(authentication));
			async.complete();
		} catch(Exception e) {
			throw new RuntimeException(e);
		}
	}
});

16.2.5. 非同步 Servlet 支援

如果你是使用 Java 設定,你可以直接使用非同步 Servlet 支援。但如果你是使用 XML,那還有一些必要的設定要做。

第一步是確保你將你的 web.xml 更新到最起碼 3.0 的 schema,如以下:

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">

</web-app>

然後要確保你的 springSecurityFilterChain 被設定為處理非同步請求。

<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <async-supported>true</async-supported>
</filter>
<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>ASYNC</dispatcher>
</filter-mapping>

就這樣!現在 Spring Security 會確保你的 SecurityContext 也會傳遞到非同步的請求中。

那它是怎麼進行的?如果你沒有特別想知道,你完全可以跳過這段落剩下的部分。

這功能大部分都是建立在 Servlet 規格上,但 Spring Security 有做了一些些調整來確保適當的處理非同步請求。

在 Spring Security 3.2 以前,當 HttpServletResponse 被提交時,SecurityContextHolder 內的 SecurityContext 會立刻被自動保存。這在非同步的環境下會造成一些問題。

例如,考慮以下狀況:

httpServletRequest.startAsync();
new Thread("AsyncThread") {
	@Override
	public void run() {
		try {
			// Do work
			TimeUnit.SECONDS.sleep(1);

			// Write to and commit the httpServletResponse
			httpServletResponse.getOutputStream().flush();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}.start();

這個狀況的問題是 Spring Security 並不知道這個 Thread,所以 SecurityContext 並不會傳遞給它,也就是說當我們提交 HttpServletResponse 的時候,SecurityContext 並不存在。所以當提交 HttpServletResponse 並且要自動保存 SecurityContext 的時候,就會失去已經登入的使用者。

從 3.2 版開始,當提交 HttpServletResponse 並觸發 HttpServletRequest.startAsync() 時,Spring Security 就不再會自動保存 SecurityContext