Как установить context в ReactiveSecurityContextHolder Spring Webflux
В своем приложении пытаюсь провести аутентификацию и авторизацию пользователей по JWT токену. Основная задумка следующая:
- Пользователь получает токен в сервисе аутентификации
- Полученный токен на первом шаге вместе с GET запросом отправляется в cookie в сервис для получения данных
- Сервис с данными, перед тем как отдать данные, проверяет данный токен и извлекает из него имя пользователя и роли, создавая UsernamePasswordAuthenticationToken и складывая его в контекст (если с токеном все в порядке)
1й и 2й шаг выполнены, но с 3м проблемы следующие: при запросе данных я получаю 401 ошибку. Токен в порядке, проверку проходит, UsernamePasswordAuthenticationToken создаётся, очевидно проблема в инициализации ReactiveSecurityContextHolder, но что не так делаю не пойму, нужно подсказка.
Пример веб фильтра в котором инициализируется контекст:
@Component("jwtFilter")
public class JwtFilter implements WebFilter {
private final JwtTokenService jwtService;
private final AuthService authService;
@Autowired
public JwtFilter(JwtTokenService jwtService, AuthService authService) {
this.jwtService = jwtService;
this.authService = authService;
}
@Nonnull
@Override
public Mono<Void> filter(@Nonnull ServerWebExchange exchange, @Nonnull WebFilterChain chain) {
extractJwtCookie(exchange.getRequest())
.ifPresent(tokenCookie -> {
String token = tokenCookie.getValue();
jwtService.checkTokenExpired(token);
Authentication authByJwt = authService.createAuthByJwt(token);
ReactiveSecurityContextHolder.withSecurityContext(
Mono.just(new SecurityContextImpl(authByJwt)));
});
return chain.filter(exchange);
}
private Optional<HttpCookie> extractJwtCookie(ServerHttpRequest request) {
return Optional.ofNullable(request.getCookies().get("authToken"))
.filter(httpCookies -> !httpCookies.isEmpty())
.map(httpCookies -> httpCookies.get(0));
}
}
Сервис, который создает UsernamePasswordAuthenticationToken:
@Service("authService")
public class AuthService {
private final JwtTokenService jwtService;
@Autowired
public AuthService(JwtTokenService jwtService) {
this.jwtService = jwtService;
}
private UserDetails createUserDetails(List<String> roles, String username) {
return JwtUserDetails.builder()
.username(username)
.roles(roles)
.build();
}
public Authentication createAuthByJwt(String jwt) {
UserDetails principal = createUserDetails(
jwtService.getRoleListByToken(jwt),
jwtService.getUsernameByToken(jwt));
return new UsernamePasswordAuthenticationToken(
principal,
principal.getPassword(),
principal.getAuthorities());
}
}
Конфигурация безопасности:
@Configuration
public class SecurityConfig {
private final String allowOrigin;
public SecurityConfig(@Value("${allowOrigin}") String allowOrigin) {
this.allowOrigin = allowOrigin;
}
@Bean("webFilterChain")
public SecurityWebFilterChain getMainConfig(ServerHttpSecurity httpSecurity,
WebFilter jwtFilter) {
return httpSecurity
.addFilterAt(jwtFilter, SecurityWebFiltersOrder.HTTP_BASIC)
.authorizeExchange(auth -> auth
.pathMatchers(HttpMethod.GET, "/articles")
.permitAll()
.pathMatchers(HttpMethod.GET, "/img/**")
.permitAll()
.pathMatchers(HttpMethod.GET, "/article")
.authenticated())
.cors(this::configureCors)
.csrf(ServerHttpSecurity.CsrfSpec::disable)
.requestCache(requestCacheSpec -> requestCacheSpec.requestCache(NoOpServerRequestCache.getInstance()))
.securityContextRepository(NoOpServerSecurityContextRepository.getInstance())
.build();
}
public void configureCors(ServerHttpSecurity.CorsSpec corsSpec) {
UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin(allowOrigin);
corsConfiguration.addAllowedHeader("*");
corsConfiguration.setAllowCredentials(true);
corsConfiguration.addAllowedMethod("*");
corsConfiguration.addExposedHeader(HttpHeaders.LOCATION);
corsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
corsSpec.configurationSource(corsConfigurationSource);
}
}
Ответы (1 шт):
В ходи исследования как работают другие фильтры в цепочке было замечено что контекст хранится в reactor.util.context.Context, соответственно тот способ установки SecurityContext'а был неверен. Вот как правильно устанавливать:
@Component("jwtFilter")
public class JwtFilter implements WebFilter {
private final JwtTokenService jwtService;
private final AuthService authService;
@Autowired
public JwtFilter(JwtTokenService jwtService, AuthService authService) {
this.jwtService = jwtService;
this.authService = authService;
}
@Nonnull
@Override
public Mono<Void> filter(@Nonnull ServerWebExchange exchange, @Nonnull WebFilterChain chain) {
return chain.filter(exchange)
.contextWrite(context -> withSecurityContext(context, exchange));
}
private Context withSecurityContext(Context mainContext, ServerWebExchange exchange) {
return mainContext.putAll(
getMonoForContext(exchange)
.as(ReactiveSecurityContextHolder::withSecurityContext)
.readOnly());
}
private Mono<SecurityContextImpl> getMonoForContext(ServerWebExchange exchange) {
return extractJwtCookie(exchange.getRequest())
.map(this::checkFromCookieTokenAndGetAuth)
.map(authentication -> Mono.just(new SecurityContextImpl(authentication)))
.orElse(Mono.empty());
}
private Authentication checkFromCookieTokenAndGetAuth(HttpCookie tokenCookie) {
jwtService.checkTokenExpired(tokenCookie.getValue());
return authService.createAuthByJwt(tokenCookie.getValue());
}
private Optional<HttpCookie> extractJwtCookie(ServerHttpRequest request) {
return Optional.ofNullable(request.getCookies().get("authToken"))
.filter(httpCookies -> !httpCookies.isEmpty())
.map(httpCookies -> httpCookies.get(0));
}
}