在我的 Spring Boot 应用程序中,我使用 spring-security 并实现了
WebFilter
以在 Authentication
中设置 ReactiveSecurityContextHolder
。然而,当我尝试检索它时,Authentication
对象变为空。
这是重现我面临的问题的代码示例片段。
Spring-boot应用程序启动类如下:
@EnableWebFlux
@SpringBootApplication
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
@Configuration
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http
.csrf(csrfSpec -> csrfSpec.disable())
.authorizeExchange(auth -> auth.pathMatchers(HttpMethod.GET, "/**")
.authenticated())
.httpBasic(httpBasicSpec -> httpBasicSpec.disable())
.addFilterBefore(customWebFilter(), SecurityWebFiltersOrder.AUTHENTICATION)
.build();
}
public WebFilter customWebFilter() {
return (exchange, chain) -> {
Authentication authenticatedToken =
new PreAuthenticatedAuthenticationToken("User_PreAuthenticated", "");
authenticatedToken.setAuthenticated(true);
return chain.filter(exchange)
.contextWrite(ReactiveSecurityContextHolder.withAuthentication(authenticatedToken));
};
}
}
这是我尝试从
Authentication
检索 ReactiveSecurityContextHolder
的代码,但是身份验证对象将为空。
// This authentication object is coming null.
Authentication authentication = ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.toFuture().getNow(null);
我已经参考了多个 StackOverflow 问题和文档,但是我无法解决这个特定问题。我错过了什么吗?
更新1:
感谢@kerbermeister详细解释了反应链及其工作原理,根据要求,这是关于为什么我需要反应链之外的当前用户身份验证主体的进一步解释。
我正在将 graphql-java 与此 spring-boot 应用程序一起使用,并且在处理任何 gql 查询期间,我需要 graphql 数据获取器内的当前用户详细信息。 当我使用 Spring MVC 时,我能够使用 SecurityContext (ThreadLocal) 来获取 graphql 数据获取器中经过身份验证的主体。但是,我无法使用 Spring Webflux/Reactive 做类似的事情。
这是 Graphql 查询接线和数据获取器的代码片段。
public List<TypeRuntimeWiring.Builder> buildWiring() {
return Arrays.asList(newTypeWiring("Query").dataFetcher("getUserName",
getCurrentUser()));
}
private DataFetcher getCurrentUser() {
return dataFetchingEnvironment -> {
String currentUser = "NOT_AVAILABLE";
Authentication authentication = ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.toFuture().getNow(null);
// Here the Authentication value is coming null
if (authentication != null) {
Object principal = authentication.getPrincipal();
if (principal != null && principal instanceof User) {
currentUser = ((User) principal).getUsername();
}
}
return currentUser;
};
}
如果有帮助,我还可以为此提供演示应用程序的 github 链接。
当你处于反应范式时,你应该始终保持反应。 在反应式应用程序中,
Authentication
存储在订阅者上下文中,而不是存储在ThreadLocal
中,因为它通常在 Spring MVC 中实现(不是反应式应用程序)。
因此,当您尝试以这种方式检索
Authentication
时:
// This authentication object is coming null.
Authentication authentication = ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.toFuture().getNow(null);
您将获得 null,因为
Authentication
仅限于 subscriber 上下文。
您可能会注意到
ReactiveSecurityContextHolder.getContext()
返回 Mono
,因此您应该在反应链中调用它,而不用阻塞调用或其他 subscribe()
方法来破坏它
例如:
@GetMapping
public Mono<ResponseEntity<String>> endpoint() {
return ReactiveSecurityContextHolder.getContext()
.map(securityContext -> securityContext.getAuthentication())
.map(authentication -> authentication.getName())
.map(userName -> ResponseEntity.ok(userName));
}