当从 Spring Boot 3.1.2 Web MVC 控制器返回反应类型(例如
Mono<T>
)时,安全过滤器链将被调用两次,一次在调用 API 方法之前,然后再次调用。
当返回类型为非响应式时,不会发生这种情况。
为了重现,这里有一个最小的可重现示例:
使用 Spring Initialzr 创建 Spring Boot 3.1.2 项目,包括 web、webflux 和安全依赖项。
添加仅记录的自定义过滤器:
public class CustomFilter extends GenericFilterBean {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
logger.info("Custom filter called");
chain.doFilter(request, response);
}
}
添加安全过滤器链bean来添加过滤器:
@Configuration
public class CustomWebSecurityConfigurerAdapter {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.addFilterAfter(new CustomFilter(), BasicAuthenticationFilter.class);
return http.build();
}
}
添加 REST API 进行测试:
@RestController
public class RestApi {
@GetMapping("/hello-reactive")
Mono<String> helloReactive() {
return Mono.just("hello world");
}
@GetMapping("/hello")
String hello() {
return "hello world";
}
}
现在我们可以展示问题了:
首先是非反应式方法:
$ curl localhost:8080/hello
hello world
记录:
2023-08-11T10:30:02.232+01:00 INFO 24000 --- [nio-8080-exec-3] com.example.demo.CustomFilter : Custom filter called
现在是反应式方法:
$ curl localhost:8080/hello-reactive
hello world
记录:
2023-08-11T10:32:12.776+01:00 INFO 24000 --- [nio-8080-exec-6] com.example.demo.CustomFilter : Custom filter called
2023-08-11T10:32:12.779+01:00 INFO 24000 --- [nio-8080-exec-6] com.example.demo.CustomFilter : Custom filter called
为了不让上述安全自定义过滤器应用两次,您可以简单地允许 SecurityFilterChain 中的所有异步调度程序。通过此设置,只有请求调度程序才会应用自定义过滤器。
....
http
.authorizeHttpRequests(authorizeHttpRequestsCustomizer -> {
authorizeHttpRequestsCustomizer
.dispatcherTypeMatchers(DispatcherType.ASYNC).permitAll()
...
});
以下 github 问题可能值得阅读以了解其他替代方案:https://github.com/spring-projects/spring-security/issues/11962