本節(jié)介紹如何將Spring Security?與servlet API集成。?servletapi-xml示例應(yīng)用程序演示了這些方法的用法。
10.3.1?Servlet 2.5+ Integration
HttpServletRequest.getRemoteUser()
HttpServletRequest.getRemoteUser() 將返回SecurityContextHolder.getContext().getAuthentication().getName()的結(jié)果,后者通常是當(dāng)前用戶名。如果要在應(yīng)用程序中顯示當(dāng)前用戶名,這將非常有用。此外,可以使用檢查這是否為空來(lái)指示用戶是否已經(jīng)過(guò)身份驗(yàn)證或是匿名的。知道用戶是否經(jīng)過(guò)身份驗(yàn)證對(duì)于確定是否應(yīng)顯示某些UI元素很有用(即,只有在用戶經(jīng)過(guò)身份驗(yàn)證時(shí)才應(yīng)顯示注銷鏈接)。
HttpServletRequest.getUserPrincipal()
HttpServletRequest.getUserPrincipal()?將返回SecurityContextHolder.getContext().getAuthentication()的結(jié)果。這意味著它是一個(gè)身份驗(yàn)證,在使用用戶名和基于密碼的身份驗(yàn)證時(shí),它通常是UsernamePasswordAuthenticationToken的實(shí)例。如果您需要有關(guān)用戶的其他信息,這將非常有用。例如,您可能創(chuàng)建了一個(gè)自定義的UserDetailsService,該服務(wù)返回一個(gè)包含您的用戶名字和姓氏的自定義用戶詳細(xì)信息。您可以通過(guò)以下方式獲得此信息:
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();
應(yīng)該注意的是,在整個(gè)應(yīng)用程序中執(zhí)行如此多的邏輯通常是不好的做法。相反,應(yīng)該集中它以減少Spring Security?和servlet API之間的任何耦合。
HttpServletRequest.isUserInRole(String)
HttpServletRequest.isUserInRole(String)?將確定SecurityContextHolder.getContext().getAuthentication().getAuthorities() 是否包含傳遞給 isUserInRole(string)的角色的授權(quán)。通常,用戶不應(yīng)將“ROLE_”前綴傳入此方法,因?yàn)樗亲詣?dòng)添加的。例如,如果要確定當(dāng)前用戶是否具有“ROLE_ADMIN”權(quán)限,可以使用以下內(nèi)容:
boolean isAdmin = httpServletRequest.isUserInRole("ADMIN");
這可能有助于確定是否應(yīng)顯示某些UI組件。例如,只有當(dāng)當(dāng)前用戶是管理員時(shí),才能顯示管理員鏈接。
10.3.2?Servlet 3+ Integration
以下部分介紹Spring Security集成的servlet 3方法。
HttpServletRequest.authenticate(HttpServletRequest,HttpServletResponse)
HttpServletRequest.authenticate(HttpServletRequest,HttpServletResponse)?方法可以用來(lái)確保用戶已經(jīng)完成了身份驗(yàn)證。如果它們未經(jīng)過(guò)身份驗(yàn)證,則配置的 AuthenticationEntryPoint?將用于請(qǐng)求用戶進(jìn)行身份驗(yàn)證(即重定向到登錄頁(yè))。
HttpServletRequest.login(String,String)
HttpServletRequest.login(String,String)方法使用當(dāng)前的?AuthenticationManager?對(duì)用戶進(jìn)行身份驗(yàn)證。例如,下面將嘗試使用用戶名“user”和密碼“password”進(jìn)行身份驗(yàn)證:
try{
????httpServletRequest.login("user","password");
}catch(ServletException e) {
????// fail to authenticate
}
如果希望Spring Security?處理失敗的身份驗(yàn)證嘗試,則不必捕獲ServletException。
HttpServletRequest.logout()
HttpServletRequest.logout()?方法可用于注銷當(dāng)前用戶。
通常這意味著?SecurityContextHolder?將被清除,HttpSession?將失效,任何“Remember Me”身份驗(yàn)證將被清除,等等。但是,配置的Logoothandler實(shí)現(xiàn)將根據(jù)您的Spring安全配置而變化。需要注意的是,在調(diào)用HttpServletRequest.logout() 之后,您仍然在改寫(xiě)響應(yīng)。通常會(huì)重定向到歡迎頁(yè)面。
AsyncContext.start(Runnable)
AsynchContext.start(Runnable)?方法,用于確保將憑據(jù)傳播到新線程。使用Spring Security 的并發(fā)支持,Spring Security?將重寫(xiě)AsyncContext.Start(Runnable),以確保在處理Runnable時(shí)使用當(dāng)前的SecurityContext。例如,以下內(nèi)容將輸出當(dāng)前用戶的身份驗(yàn)證:
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);
? ? ? ? }
? ? }
});
Async Servlet Support
如果您使用的是基于Java的配置,那么您已經(jīng)準(zhǔn)備好了。如果您使用的是XML配置,則需要進(jìn)行一些更新。第一步是確保已更新web.xml以至少使用3.0架構(gòu),如下所示:
<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>
接下來(lái),您需要確保為處理異步請(qǐng)求設(shè)置了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>
就是這樣!現(xiàn)在,Spring安全性將確保SecurityContext也在異步請(qǐng)求上傳播。
那么它是如何工作的呢?如果你真的不感興趣,可以跳過(guò)這一部分的其余部分,否則請(qǐng)繼續(xù)閱讀。其中大部分都內(nèi)置于servlet規(guī)范中,但是Spring Security?做了一些調(diào)整,以確保異步請(qǐng)求能夠正常工作。在Spring Security3.2之前,只要提交HttpServletResponse,就會(huì)自動(dòng)保存SecurityContextHolder中的SecurityContext。這可能導(dǎo)致異步環(huán)境中出現(xiàn)問(wèn)題。例如,考慮以下內(nèi)容:
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();
問(wèn)題是這個(gè)線程不知道Spring Security,所以SecurityContext不會(huì)傳播給它。這意味著當(dāng)我們提交HttpServletResponse?時(shí)沒(méi)有SecuriytContext。當(dāng)Spring Security在提交httpServletResponse時(shí)自動(dòng)保存SecurityContext時(shí),它將丟失我們的登錄用戶。
從3.2版開(kāi)始,Spring Security就足夠智能,一旦調(diào)用了HttpServletRequest.StartAsync(),就不會(huì)在提交HttpServletResponse時(shí)自動(dòng)保存SecurityContext。
10.3.3?Servlet 3.1+ Integration
以下部分介紹Spring Security集成的servlet 3.1方法。
HttpServletRequest#changeSessionId()
HttpServletRequest.changeSessionId()是servlet 3.1及更高版本中防止會(huì)話固定攻擊的默認(rèn)方法。