1. 概述
在这个快速教程中,我们将关注Servlet 3对异步请求的支持,以及Spring MVC和Spring Security如何处理这些请求。
在Web应用中引入异步性的基本动机是为了处理长时间运行的请求。在大多数情况下,我们需要确保Spring Security的主体(principal)能够传播到这些线程中。
当然,Spring Security也与@Async
【注:Spring Security与@Async的集成】在MVC之外的范围以及处理HTTP请求时进行整合。
2. Maven依赖
为了在Spring MVC中使用异步集成,我们需要将以下依赖项添加到我们的pom.xml
:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>6.1.5</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>6.1.5</version>
</dependency>
Spring Security的最新依赖版本可以在这里找到。
3. Spring MVC与@Async
根据官方文档,Spring Security与WebAsyncManager
【注:WebAsyncManager接口】进行了集成。
首先,我们需要确保我们的springSecurityFilterChain
设置为处理异步请求。我们可以在Java配置中通过在Servlet配置类中添加以下行来实现:
dispatcher.setAsyncSupported(true);
或者在XML配置中:
<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>
我们还需要在Servlet配置中启用async-supported
参数:
<servlet>
...
<async-supported>true</async-supported>
...
</servlet>
现在我们可以发送带有传播的SecurityContext
的异步请求了。
Spring Security内部机制会确保当在其他线程中提交响应时,我们的SecurityContext
不会被清除,从而避免用户登出。
4. 使用场景
让我们通过一个简单的示例来演示这一点:
@Override
public Callable<Boolean> checkIfPrincipalPropagated() {
Object before
= SecurityContextHolder.getContext().getAuthentication().getPrincipal();
log.info("Before new thread: " + before);
return new Callable<Boolean>() {
public Boolean call() throws Exception {
Object after
= SecurityContextHolder.getContext().getAuthentication().getPrincipal();
log.info("New thread: " + after);
return before == after;
}
};
}
我们想要检查Spring的SecurityContext
是否已传播到新线程。
上述方法将自动在其Callable
执行时包含SecurityContext
,如日志所示:
web - 2017-01-02 10:42:19,011 [http-nio-8081-exec-3] INFO
o.baeldung.web.service.AsyncService - Before new thread:
org.springframework.security.core.userdetails.User@76507e51:
Username: temporary; Password: [PROTECTED]; Enabled: true;
AccountNonExpired: true; credentialsNonExpired: true;
AccountNonLocked: true; Granted Authorities: ROLE_ADMIN
web - 2017-01-02 10:42:19,020 [MvcAsync1] INFO
o.baeldung.web.service.AsyncService - New thread:
org.springframework.security.core.userdetails.User@76507e51:
Username: temporary; Password: [PROTECTED]; Enabled: true;
AccountNonExpired: true; credentialsNonExpired: true;
AccountNonLocked: true; Granted Authorities: ROLE_ADMIN
如果没有设置SecurityContext
的传播,第二个请求最终将得到null
值。
还有其他重要的使用场景需要使用带有传播SecurityContext
的异步请求:
- 我们想要发起多个外部请求,它们可以并行运行,可能需要很长时间才能执行
- 我们本地有大量处理工作,而外部请求可以与之并行执行
- 其他代表"fire-and-forget"场景,例如发送电子邮件
需要注意的是,如果我们的多个方法调用以前是以同步方式串联在一起的,那么将它们转换为异步方法可能需要同步结果。
5. 总结
在这篇简短的教程中,我们展示了在认证上下文中的异步请求处理Spring支持【注:原文为authenticated context,此处保留原文,但可理解为有身份验证的上下文】。
从编程模型的角度来看,新的功能看似简单,但实际上确实有一些方面需要深入理解。
这个示例也可作为Maven项目在Github上获取【注:链接为https://github.com/eugenp/tutorials/tree/master/spring-security-modules/spring-security-web-rest】。