ReactiveSecurityContextHolder.getContext() пуст, но @AuthenticationPrincipal работает

Я использую ReactiveAuthenticationManager в Spring Security + Webflux. Он настроен для возврата экземпляра 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();
}

куда PreAuthentication является примером AbstractAuthenticationToken а также AuthenticationToken продолжается UsernamePasswordAuthenticationToken

Интересно хотя ReactiveSecurityContextHolder.getContext().map(ctx -> ctx.getAuthentication()).block() не работает в контроллере, я могу ввести принцип аутентификации с помощью @AuthenticationPrincipal аннотация в качестве параметра метода успешно в контроллере.

Это похоже на проблему конфигурации, но я не могу сказать, где. У кого-нибудь есть идеи, почему я не могу вернуть аутентификацию?

5 ответов

Решение

Поскольку вы используете WebFlux, вы обрабатываете запросы, используя цикл обработки событий. Вы больше не используете модель потока на запрос, как с Tomcat.

Аутентификация сохраняется для каждого контекста.

С Tomcat, когда приходит запрос, Spring сохраняет аутентификацию в SecurityContextHolder,SecurityContextHolder использования ThreadLocal переменная для хранения аутентификации. Определенный объект аутентификации виден вам, только если вы пытаетесь получить его из ThreadLocal объект, в том же потоке, в котором он был установлен. Вот почему вы можете получить аутентификацию в контроллере через статический вызов. Объект ThreadLocal знает, что вам возвращать, потому что он знает ваш контекст - ваш поток.

С WebFlux вы можете обрабатывать все запросы, используя только 1 поток. Такой статический вызов больше не даст ожидаемых результатов:

SecurityContextHolder.getContext().getAuthentication();

Потому что больше нет возможности использовать объекты ThreadLocal. Единственный способ получить для вас Аутентификацию - это запросить ее в сигнатуре метода контроллера или...

Вернуть реактивную цепочку из метода, который создает ReactiveSecurityContextHolder.getContext() вызов.

Поскольку вы возвращаете цепочку реактивных операторов, Spring делает подписку на вашу цепочку, чтобы выполнить ее. Когда Spring делает это, он обеспечивает контекст безопасности для всей цепочки. Таким образом, каждый звонок ReactiveSecurityContextHolder.getContext() внутри этой цепочки вернет ожидаемые данные.

Вы можете прочитать больше о контексте Mono/Flux здесь, потому что это особенность Reactor.

У меня тоже была похожая проблема. Итак, я загрузил свой github для ReactiveSecurityContext с примером JWT Token.

https://github.com/heesuk-ahn/spring-webflux-jwt-auth-example

Надеюсь, это поможет вам.

Нам нужно создать кастомный authentication filter. Если аутентификация в этом фильтре прошла успешно, он сохраняетprincipal информация в ReactiveSecurityHolder. Мы можем получить доступ кReactiveSecurityHolder от контроллера.

В дополнение к тому, что сказал Алекс Деркач, говоря более простыми словами:

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

даст ноль, потому что вы нарушаете цепочку реакторов.

Что вы можете сделать, так это вернуть объект Mono непосредственно внутри RestController. Тогда вы увидите, что контекст аутентификации действительно присутствует.

Сегодня решил такую ​​проблему следующим образом:

      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, инициалы, логин и полномочия.

Если вы хотите получить доступ к объекту аутентификации внутри веб-фильтра, вы можете сделать это следующим образом.

      @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 
      }

Другие вопросы по тегам