本节介绍如何将Spring Security 与servlet API集成。 servletapi-xml示例应用程序演示了这些方法的用法。
10.3.1 Servlet 2.5+ Integration
HttpServletRequest.getRemoteUser()
HttpServletRequest.getRemoteUser() 将返回SecurityContextHolder.getContext().getAuthentication().getName()的结果,后者通常是当前用户名。如果要在应用程序中显示当前用户名,这将非常有用。此外,可以使用检查这是否为空来指示用户是否已经过身份验证或是匿名的。知道用户是否经过身份验证对于确定是否应显示某些UI元素很有用(即,只有在用户经过身份验证时才应显示注销链接)。
HttpServletRequest.getUserPrincipal()
HttpServletRequest.getUserPrincipal() 将返回SecurityContextHolder.getContext().getAuthentication()的结果。这意味着它是一个身份验证,在使用用户名和基于密码的身份验证时,它通常是UsernamePasswordAuthenticationToken的实例。如果您需要有关用户的其他信息,这将非常有用。例如,您可能创建了一个自定义的UserDetailsService,该服务返回一个包含您的用户名字和姓氏的自定义用户详细信息。您可以通过以下方式获得此信息:
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();
应该注意的是,在整个应用程序中执行如此多的逻辑通常是不好的做法。相反,应该集中它以减少Spring Security 和servlet API之间的任何耦合。
HttpServletRequest.isUserInRole(String)
HttpServletRequest.isUserInRole(String) 将确定SecurityContextHolder.getContext().getAuthentication().getAuthorities() 是否包含传递给 isUserInRole(string)的角色的授权。通常,用户不应将“ROLE_”前缀传入此方法,因为它是自动添加的。例如,如果要确定当前用户是否具有“ROLE_ADMIN”权限,可以使用以下内容:
boolean isAdmin = httpServletRequest.isUserInRole("ADMIN");
这可能有助于确定是否应显示某些UI组件。例如,只有当当前用户是管理员时,才能显示管理员链接。
10.3.2 Servlet 3+ Integration
以下部分介绍Spring Security集成的servlet 3方法。
HttpServletRequest.authenticate(HttpServletRequest,HttpServletResponse)
HttpServletRequest.authenticate(HttpServletRequest,HttpServletResponse) 方法可以用来确保用户已经完成了身份验证。如果它们未经过身份验证,则配置的 AuthenticationEntryPoint 将用于请求用户进行身份验证(即重定向到登录页)。
HttpServletRequest.login(String,String)
HttpServletRequest.login(String,String)方法使用当前的 AuthenticationManager 对用户进行身份验证。例如,下面将尝试使用用户名“user”和密码“password”进行身份验证:
try{
httpServletRequest.login("user","password");
}catch(ServletException e) {
// fail to authenticate
}
如果希望Spring Security 处理失败的身份验证尝试,则不必捕获ServletException。
HttpServletRequest.logout()
HttpServletRequest.logout() 方法可用于注销当前用户。
通常这意味着 SecurityContextHolder 将被清除,HttpSession 将失效,任何“Remember Me”身份验证将被清除,等等。但是,配置的Logoothandler实现将根据您的Spring安全配置而变化。需要注意的是,在调用HttpServletRequest.logout() 之后,您仍然在改写响应。通常会重定向到欢迎页面。
AsyncContext.start(Runnable)
AsynchContext.start(Runnable) 方法,用于确保将凭据传播到新线程。使用Spring Security 的并发支持,Spring Security 将重写AsyncContext.Start(Runnable),以确保在处理Runnable时使用当前的SecurityContext。例如,以下内容将输出当前用户的身份验证:
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的配置,那么您已经准备好了。如果您使用的是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安全性将确保SecurityContext也在异步请求上传播。
那么它是如何工作的呢?如果你真的不感兴趣,可以跳过这一部分的其余部分,否则请继续阅读。其中大部分都内置于servlet规范中,但是Spring Security 做了一些调整,以确保异步请求能够正常工作。在Spring Security3.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,所以SecurityContext不会传播给它。这意味着当我们提交HttpServletResponse 时没有SecuriytContext。当Spring Security在提交httpServletResponse时自动保存SecurityContext时,它将丢失我们的登录用户。
从3.2版开始,Spring Security就足够智能,一旦调用了HttpServletRequest.StartAsync(),就不会在提交HttpServletResponse时自动保存SecurityContext。
10.3.3 Servlet 3.1+ Integration
以下部分介绍Spring Security集成的servlet 3.1方法。
HttpServletRequest#changeSessionId()
HttpServletRequest.changeSessionId()是servlet 3.1及更高版本中防止会话固定攻击的默认方法。