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();
}

其中 PreAuthenticationAbstractAuthenticationToken 的实例并且 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 
      }