15. Servlet API 集成

本节描述了如何将 Spring Security 与 Servlet API 集成在一起。 servletapi-xml示例应用程序演示了每种方法的用法。

15.1 Servlet 2.5 集成

15.1.1 HttpServletRequest.getRemoteUser()

HttpServletRequest.getRemoteUser()将返回SecurityContextHolder.getContext().getAuthentication().getName()的结果,该结果通常是当前的用户名。如果要在应用程序中显示当前用户名,这将很有用。此外,检查此属性是否为 null 可以用来指示用户是否已通过身份验证或匿名。知道用户是否通过身份验证对于确定是否应显示某些 UI 元素很有用(即,仅在用户通过身份验证时才显示注销链接)。

15.1.2 HttpServletRequest.getUserPrincipal()

HttpServletRequest.getUserPrincipal()将返回SecurityContextHolder.getContext().getAuthentication()的结果。这意味着它是Authentication,当使用基于用户名和密码的身份验证时,通常是UsernamePasswordAuthenticationToken的实例。如果您需要有关用户的其他信息,这将很有用。例如,您可能已经创建了一个自定义UserDetailsService,该自定义UserDetailsService返回包含用户的名字和姓氏的自定义UserDetails。您可以通过以下方式获取此信息:

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();

Note

应当指出,在整个应用程序中执行如此多的逻辑通常是一种不好的做法。相反,应该将其集中化以减少 Spring Security 和 Servlet API 的任何耦合。

15.1.3 HttpServletRequest.isUserInRole(String)

HttpServletRequest.isUserInRole(String)将确定SecurityContextHolder.getContext().getAuthentication().getAuthorities()是否包含GrantedAuthority并将角色传递给isUserInRole(String)。通常,用户不应将“ ROLE_”前缀传递给此方法,因为它是自动添加的。例如,如果要确定当前用户是否具有权限“ ROLE_ADMIN”,则可以使用以下命令:

boolean isAdmin = httpServletRequest.isUserInRole("ADMIN");

这对于确定是否应显示某些 UI 组件可能很有用。例如,仅当当前用户是 Management 员时,才可以显示 Management 员链接。

15.2 Servlet 3 集成

下一节描述了与 Spring Security 集成的 Servlet 3 方法。

15.2.1 HttpServletRequest.authenticate(HttpServletResponse)

HttpServletRequest.authenticate(HttpServletResponse)方法可用于确保对用户进行身份验证。如果未通过身份验证,则将使用配置的 AuthenticationEntryPoint 来请求用户进行身份验证(即重定向到登录页面)。

15.2.2 HttpServletRequest.login(String,String)

HttpServletRequest.login(String,String)方法可用于通过当前AuthenticationManager认证用户。例如,以下尝试使用用户名“ user”和密码“ password”进行身份验证:

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

Note

如果您想让 Spring Security 处理失败的身份验证尝试,则不必捕获 ServletException。

15.2.3 HttpServletRequest.logout()

HttpServletRequest.logout()方法可用于注销当前用户。

通常,这意味着将清除 SecurityContextHolder,使 HttpSession 无效,将清除所有“记住我”身份验证,依此类推。但是,配置的 LogoutHandler 实现取决于您的 Spring Security 配置。重要的是要注意,在调用 HttpServletRequest.logout()之后,您仍然负责写出响应。通常,这将涉及重定向到欢迎页面。

15.2.4 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);
		}
	}
});

15.2.5 异步 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 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 也可以在异步请求中传播。

那么它是怎样工作的?如果您真的不感兴趣,请随时跳过本节的其余部分,否则请 continue 阅读。大部分内容都内置在 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 不知道该线程,因此不会将 SecurityContext 传播给它。这意味着当我们提交 HttpServletResponse 时,没有 SecuriytContext。当 Spring Security 在提交 HttpServletResponse 时自动保存 SecurityContext 时,它将丢失我们的登录用户。

从 3.2 版本开始,Spring Security 足够聪明,不再在调用 HttpServletRequest.startAsync()时自动保存 SecurityContext 来提交 HttpServletResponse。

15.3 Servlet 3.1 集成

下一节描述了与 Spring Security 集成的 Servlet 3.1 方法。

15.3.1 HttpServletRequest#changeSessionId()

HttpServletRequest.changeSessionId()是 Servlet 3.1 及更高版本中防范Session Fixation攻击的默认方法。