Servlet API integration

Servlet 2.5+ Integration

此部分描述了 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+ 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 进行认证的示例:

  • 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 处理失败的身份验证尝试,则不必捕获 ServletException

HttpServletRequest.logout()

您可以使用 HttpServletRequest.logout() 方法注销当前用户。

通常,这意味着 SecurityContextHolder 被清除,HttpSession 失效,任何 “Remember Me” 认证都被清除,等等。但是,根据您的 Spring Security 配置,配置的 LogoutHandler 实现会有所不同。请注意,在调用 HttpServletRequest.logout() 之后,您仍负责编写响应。通常,这将涉及重定向到欢迎页面。

AsyncContext.start(Runnable)

AsyncContext.start(Runnable) 方法确保您的证书传播到新的 Thread 。通过使用 Spring 安全的并发支持,Spring 安全覆盖 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 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 Security 会确保您的 SecurityContext 也在异步请求中传播。

如何运作?如果您不太关注,请跳过本节的剩余部分。Servlet 规范中内置了大部分内容,但 Spring Security 有一些调整以确保异步请求能够正常运行。在 Spring Security 3.2 之前,SecurityContextHolderSecurityContext 一旦 HttpServletResponse 提交,便会自动保存。这可能会在异步环境中引发问题。考虑以下示例:

  • 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 足够智能,不再在调用 HttpServletRequest.startAsync() 后立即在提交 HttpServletResponse 时自动保存 SecurityContext

Servlet 3.1+ Integration

以下部分介绍 Spring Security 集成的 Servlet 3.1 方法。

HttpServletRequest#changeSessionId()

“@11” 是在 Servlet 3.1 及更高版本中防止 “@12” 攻击的默认方法。