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
}