java ReactiveSecurityContextHolder.getContext()为空,但@AuthenticationPrincipal有效

6mzjoqzu  于 2023-05-21  发布在  Java
关注(0)|答案(5)|浏览(163)

我一直在Spring Security + Webflux中使用ReactiveAuthenticationManager。它被定制为返回一个UsernamePasswordAuthenticationToken的示例,从我可以告诉的是,当我调用ReactiveSecurityContextHolder.getContext().map(ctx -> ctx.getAuthentication()).block()时,我应该接收到的。据我所知,我无法通过以下两种方式访问身份验证上下文:

SecurityContextHolder.getContext().getAuthentication();

ReactiveSecurityContextHolder.getContext().map(ctx -> ctx.getAuthentication()).block()

而试图从控制器或组件访问这些文件则会解析为null。我怀疑我是否真的在我的自定义管理器中返回了一个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的instance,AuthenticationToken扩展UsernamePasswordAuthenticationToken
有趣的是,尽管ReactiveSecurityContextHolder.getContext().map(ctx -> ctx.getAuthentication()).block()在控制器中不起作用,但我可以在控制器中成功地将带有@AuthenticationPrincipal注解的身份验证主体作为方法参数注入。
这似乎是一个配置问题,但我不知道在哪里。有人知道为什么我不能返回验证吗?

wbrvyc0a

wbrvyc0a1#

由于您使用的是WebFlux,因此您使用的是事件循环来处理请求。您不再像使用Tomcat那样使用每请求一个线程的模型。
身份验证按上下文存储。

使用Tomcat,当请求到达时,Spring将authentication存储在SecurityContextHolder中。SecurityContextHolder使用ThreadLocal变量存储身份验证。特定的authentication对象是可见的,只有当你试图从ThreadLocal对象中获取它时,在同一个线程中,它被设置。这就是为什么你可以通过静态调用在控制器中获得身份验证。ThreadLocal对象知道要返回什么给你,因为它知道你的上下文-你的线程。
使用WebFlux,只需一个线程即可处理所有请求。像这样的静态调用将不再返回预期结果:

SecurityContextHolder.getContext().getAuthentication();
因为没有办法再使用ThreadLocal对象了。为您获取身份验证的唯一方法是在控制器的方法签名中请求它,或者...
返回一个reactive-chain from方法,即调用ReactiveSecurityContextHolder.getContext()
因为你返回了一个React操作符链,Spring订阅你的链,以便执行它。当Spring这样做时,它为整个链提供了一个安全上下文。因此,该链中的每个调用ReactiveSecurityContextHolder.getContext()都将返回预期的数据。
你可以在这里阅读更多关于Mono/Flux的内容,因为它是一个特定于Reactor的特性。

iyfjxgzm

iyfjxgzm2#

我也有类似的问题。所以,我上传了我的Github ReactiveSecurityContext与JWT Token示例。
https://github.com/heesuk-ahn/spring-webflux-jwt-auth-example
希望对你有帮助。
我们需要创建一个自定义的authentication filter。如果在该过滤器中验证成功,则它将principal信息存储在ReactiveSecurityHolder中。我们可以从控制器访问ReactiveSecurityHolder

h7appiyu

h7appiyu3#

今天解决这样的问题在下一个方式:

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-自定义类,包含userId、姓名首字母、登录名和权限。

t2a7ltrp

t2a7ltrp4#

除了Alex Derkach所说的,用更简单的话来说:

ReactiveSecurityContextHolder.getContext().map(ctx -> ctx.getAuthentication()).block()

将产生无效,因为你正在破坏React堆链。
您可以做的是直接在RestController中返回Mono对象然后您将看到身份验证上下文确实存在。

ix0qys7i

ix0qys7i5#

如果你想访问一个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 
      }

相关问题