ReactiveSecurityContextHolder.getContext() 为空但@AuthenticationPrincipal 有效
ReactiveSecurityContextHolder.getContext() is empty but @AuthenticationPrincipal works
我一直在 Spring 安全 + Webflux 中使用 ReactiveAuthenticationManager。它被定制为 return UsernamePasswordAuthenticationToken
的一个实例,据我所知,这是我调用 ReactiveSecurityContextHolder.getContext().map(ctx -> ctx.getAuthentication()).block()
时应该收到的内容。据我所知,我无法通过以下两种方式访问身份验证上下文:
SecurityContextHolder.getContext().getAuthentication();
或
ReactiveSecurityContextHolder.getContext().map(ctx -> ctx.getAuthentication()).block()
并且尝试从控制器或组件访问这些解析为 null
。我曾怀疑我是否真的 return 在我的自定义管理器中创建了一个 Authentication
实例,看起来我是:
@Override
public Mono<Authentication> authenticate(final Authentication authentication) {
if (authentication instanceof PreAuthentication) {
return Mono.just(authentication)
.publishOn(Schedulers.parallel())
.switchIfEmpty(Mono.defer(this::throwCredentialError))
.cast(PreAuthentication.class)
.flatMap(this::authenticatePayload)
.publishOn(Schedulers.parallel())
.onErrorResume(e -> throwCredentialError())
.map(userDetails -> new AuthenticationToken(userDetails, userDetails.getAuthorities()));
}
return Mono.empty();
}
其中 PreAuthentication
是 AbstractAuthenticationToken
的实例并且 AuthenticationToken
扩展了 UsernamePasswordAuthenticationToken
有趣的是,尽管 ReactiveSecurityContextHolder.getContext().map(ctx -> ctx.getAuthentication()).block()
在控制器中不起作用,但我可以在控制器中成功地将带有 @AuthenticationPrincipal
注释的身份验证主体作为方法参数注入。
这似乎是一个配置问题,但我不知道是哪里。任何人都知道为什么我不能 return 身份验证?
由于您使用的是 WebFlux,因此您正在使用事件循环处理请求。
您不再使用 thread-per-request 模型,如 Tomcat.
Authentication is stored per context.
使用Tomcat,当请求到达时,Spring将身份验证存储在SecurityContextHolder
中。
SecurityContextHolder
使用 ThreadLocal
变量存储 身份验证 。特定的 authentication 对象对你可见,只有当你试图从设置它的同一线程中的 ThreadLocal
对象中获取它时。
这就是为什么您可以通过静态调用在控制器中获得 authentication 的原因。
ThreadLocal 对象知道要 return 给你什么,因为它知道你的上下文 - 你的线程。
使用 WebFlux,您可以仅使用 1 个线程处理所有请求。
像这样的静态调用将不再 return 预期结果:
SecurityContextHolder.getContext().getAuthentication();
因为没有办法再使用ThreadLocal对象了。
为您获得身份验证的唯一方法是在控制器的方法签名中请求它,或者...
Return a reactive-chain from method, that is making a ReactiveSecurityContextHolder.getContext()
call.
由于您正在 return 反应运算符链,Spring 订阅您的链,以便执行它。
Spring 做的时候,it provides a security context to whole chain。因此,此链中的每个调用 ReactiveSecurityContextHolder.getContext()
都会 return 预期数据。
您可以阅读有关 Mono/Flux 上下文 here 的更多信息,因为它是 Reactor 特定的功能。
我也遇到了类似的问题。所以,我上传了我的 github 用于带有 JWT 令牌示例的 ReactiveSecurityContext。
https://github.com/heesuk-ahn/spring-webflux-jwt-auth-example
希望对你有所帮助
我们需要创建自定义 authentication filter
。
如果在该过滤器中身份验证成功,则它将 principal
信息存储在 ReactiveSecurityHolder
中。我们可以从控制器访问ReactiveSecurityHolder
。
今天通过以下方式解决了此类问题:
Authentication authentication = new UserPasswordAuthenticationToken(userCredentials, null, userCredentials.getAuthorities());
ReactiveSecurityContextHolder.getContext()
.map(context -> context.getAuthentication().getPrincipal())
.cast(UserCredentials.class)
.contextWrite(ReactiveSecurityContextHolder.withAuthentication(authentication))
.subscribe();
UserCredential.class
- 自定义 class,其中包含用户 ID、姓名首字母、登录名和权限。
如果你想访问webfilter中的认证对象,你可以这样做。
@Override
public Mono<Void> filter(ServerWebExchange exchange, @NotNull WebFilterChain chain) {
return exchange.getSession().flatMap((session) -> {
SecurityContext context = session.getAttribute(SPRING_SECURITY_CONTEXT_ATTR_NAME);
logger.debug((context != null)
? LogMessage.format("Found SecurityContext '%s' in WebSession: '%s'", context, session)
: LogMessage.format("No SecurityContext found in WebSession: '%s'", session));
if(context == null){
return filterHelper(exchange, chain);
}else{
// access the principal object here
}
我一直在 Spring 安全 + Webflux 中使用 ReactiveAuthenticationManager。它被定制为 return UsernamePasswordAuthenticationToken
的一个实例,据我所知,这是我调用 ReactiveSecurityContextHolder.getContext().map(ctx -> ctx.getAuthentication()).block()
时应该收到的内容。据我所知,我无法通过以下两种方式访问身份验证上下文:
SecurityContextHolder.getContext().getAuthentication();
或
ReactiveSecurityContextHolder.getContext().map(ctx -> ctx.getAuthentication()).block()
并且尝试从控制器或组件访问这些解析为 null
。我曾怀疑我是否真的 return 在我的自定义管理器中创建了一个 Authentication
实例,看起来我是:
@Override
public Mono<Authentication> authenticate(final Authentication authentication) {
if (authentication instanceof PreAuthentication) {
return Mono.just(authentication)
.publishOn(Schedulers.parallel())
.switchIfEmpty(Mono.defer(this::throwCredentialError))
.cast(PreAuthentication.class)
.flatMap(this::authenticatePayload)
.publishOn(Schedulers.parallel())
.onErrorResume(e -> throwCredentialError())
.map(userDetails -> new AuthenticationToken(userDetails, userDetails.getAuthorities()));
}
return Mono.empty();
}
其中 PreAuthentication
是 AbstractAuthenticationToken
的实例并且 AuthenticationToken
扩展了 UsernamePasswordAuthenticationToken
有趣的是,尽管 ReactiveSecurityContextHolder.getContext().map(ctx -> ctx.getAuthentication()).block()
在控制器中不起作用,但我可以在控制器中成功地将带有 @AuthenticationPrincipal
注释的身份验证主体作为方法参数注入。
这似乎是一个配置问题,但我不知道是哪里。任何人都知道为什么我不能 return 身份验证?
由于您使用的是 WebFlux,因此您正在使用事件循环处理请求。 您不再使用 thread-per-request 模型,如 Tomcat.
Authentication is stored per context.
使用Tomcat,当请求到达时,Spring将身份验证存储在SecurityContextHolder
中。
SecurityContextHolder
使用 ThreadLocal
变量存储 身份验证 。特定的 authentication 对象对你可见,只有当你试图从设置它的同一线程中的 ThreadLocal
对象中获取它时。
这就是为什么您可以通过静态调用在控制器中获得 authentication 的原因。
ThreadLocal 对象知道要 return 给你什么,因为它知道你的上下文 - 你的线程。
使用 WebFlux,您可以仅使用 1 个线程处理所有请求。 像这样的静态调用将不再 return 预期结果:
SecurityContextHolder.getContext().getAuthentication();
因为没有办法再使用ThreadLocal对象了。 为您获得身份验证的唯一方法是在控制器的方法签名中请求它,或者...
Return a reactive-chain from method, that is making a
ReactiveSecurityContextHolder.getContext()
call.
由于您正在 return 反应运算符链,Spring 订阅您的链,以便执行它。
Spring 做的时候,it provides a security context to whole chain。因此,此链中的每个调用 ReactiveSecurityContextHolder.getContext()
都会 return 预期数据。
您可以阅读有关 Mono/Flux 上下文 here 的更多信息,因为它是 Reactor 特定的功能。
我也遇到了类似的问题。所以,我上传了我的 github 用于带有 JWT 令牌示例的 ReactiveSecurityContext。
https://github.com/heesuk-ahn/spring-webflux-jwt-auth-example
希望对你有所帮助
我们需要创建自定义 authentication filter
。
如果在该过滤器中身份验证成功,则它将 principal
信息存储在 ReactiveSecurityHolder
中。我们可以从控制器访问ReactiveSecurityHolder
。
今天通过以下方式解决了此类问题:
Authentication authentication = new UserPasswordAuthenticationToken(userCredentials, null, userCredentials.getAuthorities());
ReactiveSecurityContextHolder.getContext()
.map(context -> context.getAuthentication().getPrincipal())
.cast(UserCredentials.class)
.contextWrite(ReactiveSecurityContextHolder.withAuthentication(authentication))
.subscribe();
UserCredential.class
- 自定义 class,其中包含用户 ID、姓名首字母、登录名和权限。
如果你想访问webfilter中的认证对象,你可以这样做。
@Override
public Mono<Void> filter(ServerWebExchange exchange, @NotNull WebFilterChain chain) {
return exchange.getSession().flatMap((session) -> {
SecurityContext context = session.getAttribute(SPRING_SECURITY_CONTEXT_ATTR_NAME);
logger.debug((context != null)
? LogMessage.format("Found SecurityContext '%s' in WebSession: '%s'", context, session)
: LogMessage.format("No SecurityContext found in WebSession: '%s'", session));
if(context == null){
return filterHelper(exchange, chain);
}else{
// access the principal object here
}