Servlet API 整合
Servlet 2.5+ 整合
本節說明 Spring Security 如何與 Servlet 2.5 規範整合。
HttpServletRequest.getRemoteUser()
HttpServletRequest.getRemoteUser()
傳回 SecurityContextHolder.getContext().getAuthentication().getName()
的結果,這通常是目前的使用者名稱。如果您想在應用程式中顯示目前的使用者名稱,這會很有用。此外,您可以檢查此值是否為 null,以判斷使用者是否已通過身份驗證或為匿名使用者。了解使用者是否已通過身份驗證對於判斷是否應顯示某些 UI 元素(例如,僅在使用者通過身份驗證時才應顯示的登出連結)可能很有用。
HttpServletRequest.getUserPrincipal()
HttpServletRequest.getUserPrincipal()
傳回 SecurityContextHolder.getContext().getAuthentication()
的結果。這表示它是一個 Authentication
,在使用基於使用者名稱和密碼的身份驗證時,通常是 UsernamePasswordAuthenticationToken
的實例。如果您需要有關使用者的其他資訊,這會很有用。例如,您可能已建立自訂 UserDetailsService
,其傳回包含使用者名字和姓氏的自訂 UserDetails
。您可以使用以下方式取得此資訊
-
Java
-
Kotlin
Authentication auth = httpServletRequest.getUserPrincipal();
// assume integrated custom UserDetails called MyCustomUserDetails
// by default, typically instance of UserDetails
MyCustomUserDetails userDetails = (MyCustomUserDetails) auth.getPrincipal();
String firstName = userDetails.getFirstName();
String lastName = userDetails.getLastName();
val auth: Authentication = httpServletRequest.getUserPrincipal()
// assume integrated custom UserDetails called MyCustomUserDetails
// by default, typically instance of UserDetails
val userDetails: MyCustomUserDetails = auth.principal as MyCustomUserDetails
val firstName: String = userDetails.firstName
val lastName: String = userDetails.lastName
應注意,在整個應用程式中執行如此多的邏輯通常是不好的做法。相反地,應該將其集中化,以減少 Spring Security 和 Servlet API 的任何耦合。 |
HttpServletRequest.isUserInRole(String)
HttpServletRequest.isUserInRole(String)
判斷 SecurityContextHolder.getContext().getAuthentication().getAuthorities()
是否包含具有傳遞至 isUserInRole(String)
角色的 GrantedAuthority
。通常,使用者不應將 ROLE_
字首傳遞給此方法,因為它是自動新增的。例如,如果您想判斷目前的使用者是否具有 "ROLE_ADMIN" 權限,您可以使用以下方式
-
Java
-
Kotlin
boolean isAdmin = httpServletRequest.isUserInRole("ADMIN");
val isAdmin: Boolean = httpServletRequest.isUserInRole("ADMIN")
這可能對於判斷是否應顯示某些 UI 元件很有用。例如,您可能只在目前使用者是管理員時才顯示管理員連結。
Servlet 3+ 整合
以下章節說明 Spring Security 整合的 Servlet 3 方法。
HttpServletRequest.authenticate(HttpServletRequest,HttpServletResponse)
您可以使用 HttpServletRequest.authenticate(HttpServletRequest,HttpServletResponse)
方法來確保使用者已通過身份驗證。如果他們未通過身份驗證,則會使用已組態的 AuthenticationEntryPoint
來請求使用者進行身份驗證(重新導向至登入頁面)。
HttpServletRequest.login(String,String)
您可以使用 HttpServletRequest.login(String,String)
方法,使用目前的 AuthenticationManager
驗證使用者身份。例如,以下程式碼將嘗試使用使用者名稱 user
和密碼 password
進行身份驗證
-
Java
-
Kotlin
try {
httpServletRequest.login("user","password");
} catch(ServletException ex) {
// fail to authenticate
}
try {
httpServletRequest.login("user", "password")
} catch (ex: ServletException) {
// fail to authenticate
}
如果您希望 Spring Security 處理失敗的身份驗證嘗試,則無需捕捉 |
HttpServletRequest.logout()
您可以使用 HttpServletRequest.logout()
方法登出目前使用者。
通常,這表示 SecurityContextHolder
已清除、HttpSession
已失效、任何「記住我」身份驗證都已清除等等。但是,已組態的 LogoutHandler
實作會因您的 Spring Security 組態而異。請注意,在叫用 HttpServletRequest.logout()
之後,您仍然負責寫出回應。通常,這會涉及重新導向至歡迎頁面。
AsyncContext.start(Runnable)
AsyncContext.start(Runnable)
方法可確保您的憑證傳播到新的 Thread
。透過使用 Spring Security 的並行支援,Spring Security 會覆寫 AsyncContext.start(Runnable)
,以確保在處理 Runnable 時使用目前的 SecurityContext
。以下範例輸出目前使用者的身份驗證
-
Java
-
Kotlin
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 ex) {
throw new RuntimeException(ex);
}
}
});
val async: AsyncContext = httpServletRequest.startAsync()
async.start {
val authentication: Authentication = SecurityContextHolder.getContext().authentication
try {
val asyncResponse = async.response as HttpServletResponse
asyncResponse.status = HttpServletResponse.SC_OK
asyncResponse.writer.write(String.valueOf(authentication))
async.complete()
} catch (ex: Exception) {
throw RuntimeException(ex)
}
}
Async Servlet 支援
如果您使用基於 Java 的組態,則可以開始使用。如果您使用 XML 組態,則需要進行一些更新。第一步是確保您已更新 web.xml
檔案以至少使用 3.0 結構描述
<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 https://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
就會自動儲存。這可能會在非同步環境中造成問題。請考慮以下範例
-
Java
-
Kotlin
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 ex) {
ex.printStackTrace();
}
}
}.start();
httpServletRequest.startAsync()
object : Thread("AsyncThread") {
override fun run() {
try {
// Do work
TimeUnit.SECONDS.sleep(1)
// Write to and commit the httpServletResponse
httpServletResponse.outputStream.flush()
} catch (ex: java.lang.Exception) {
ex.printStackTrace()
}
}
}.start()
問題在於 Spring Security 不知道這個 Thread
,因此 SecurityContext
不會傳播到它。這表示當我們提交 HttpServletResponse
時,沒有 SecurityContext
。當 Spring Security 在提交 HttpServletResponse
時自動儲存 SecurityContext
時,它會遺失已登入的使用者。
自 3.2 版起,Spring Security 已足夠聰明,不再在提交 HttpServletResponse
時自動儲存 SecurityContext
,只要叫用 HttpServletRequest.startAsync()
即可。
Servlet 3.1+ 整合
以下章節說明 Spring Security 整合的 Servlet 3.1 方法。
HttpServletRequest#changeSessionId()
HttpServletRequest.changeSessionId() 是 Servlet 3.1 及更高版本中用於防範 Session Fixation 攻擊的預設方法。