#java #spring-boot #security #spring-webflux
#java #spring-boot #Безопасность #spring-webflux
Вопрос:
Я пытаюсь создать простую схему аутентификации с реактивным подходом. Я создал проект с нуля с зависимостями от реактивных компонентов и безопасности. Введен файл конфигурации, в котором я настраиваю диспетчер аутентификации и репозиторий контекста безопасности. Проблема в том, что я замечаю, что Mono, введенный в контроллер, инициирует двойные запросы к конечной точке «входа».
Почему это происходит и как это предотвратить?
Вот код конфигурации:
@Configuration
@EnableWebFluxSecurity
public class WebFluxSecurityConfiguration {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private SecurityContextRepository securityContextRepository;
@Bean
public SecurityWebFilterChain securityWebFilterChain(
ServerHttpSecurity http) {
return http
.csrf().disable()
.cors().disable()
.httpBasic().disable()
.logout().disable()
.formLogin().disable()
.authorizeExchange()
.pathMatchers("/login").permitAll()
.anyExchange().authenticated()
.and()
.authenticationManager(authenticationManager)
.securityContextRepository(securityContextRepository)
.build();
}
}
Вот менеджер аутентификации
@Component
public class AuthenticationManager implements ReactiveAuthenticationManager {
private final WebClient webClient;
public AuthenticationManager(WebClient.Builder webClientBuilder) {
this.webClient = webClientBuilder.baseUrl("http://localhost:8080/login")
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.build();
}
@Override
public Mono<Authentication> authenticate(Authentication authentication) {
return webClient.post()
.header("Authorization","Bearer bla-bla")
.retrieve()
.bodyToMono(String.class)
.map(r->new AuthenticatedUser());
}
}
И вот репозиторий контекста безопасности
@Component
public class SecurityContextRepository implements ServerSecurityContextRepository {
private static final String TOKEN_PREFIX = "Bearer ";
private AuthenticationManager authenticationManager;
List<PathPattern> pathPatternList;
public SecurityContextRepository(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
PathPattern pathPattern1 = new PathPatternParser().parse("/login");
pathPatternList = new ArrayList<>();
pathPatternList.add(pathPattern1);
}
@Override
public Mono load(ServerWebExchange swe) {
ServerHttpRequest request = swe.getRequest();
RequestPath path = request.getPath();
if (pathPatternList.stream().anyMatch(pathPattern -> pathPattern.matches(path.pathWithinApplication()))) {
System.out.println(path.toString() " path excluded");
return Mono.empty();
}
System.out.println("executing logic for " path.toString() " path");
String authHeader = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
String authToken = null;
//test
authHeader = "Bearer bla-bla";
//~test
if (authHeader != null amp;amp; authHeader.startsWith(TOKEN_PREFIX)) {
authToken = authHeader.replace(TOKEN_PREFIX, "");
}else {
System.out.println("couldn't find bearer string, will ignore the header.");
}
if (authToken != null) {
Authentication auth = new UsernamePasswordAuthenticationToken(authToken, authToken);
return this.authenticationManager.authenticate(auth).map((authentication) -> new SecurityContextImpl(authentication));
} else {
return Mono.empty();
}
}
@Override
public Mono<Void> save(ServerWebExchange serverWebExchange, SecurityContext securityContext) {
return null;
}
}
Комментарии:
1. Почему вы устанавливаете токен как имя пользователя, так и пароль? И почему вы не используете встроенный фильтр jwt в spring 5?
2. Это всего лишь синтетическое приложение, в котором я проверяю гипотезу. Цель состоит в том, чтобы реализовать авторизацию в реактивном приложении с помощью стороннего сервера авторизации. Клиент поставляется с некоторым токеном, с помощью которого я могу извлечь профиль пользователя с сервера авторизации, а затем реализовать фильтрацию запроса. Проблема этого приложения заключается в том, что — когда я пытаюсь получить участника в контроллере — webclient отправляет второй запрос на сервер авторизации. И как этого избежать?
3. и затем я снова спрошу, поскольку вы не ответили на мой вопрос, почему вы не используете встроенную функциональность JWTs в spring 5, у нее есть все необходимые возможности настройки?
4. Я думаю, мне нужно провести исследование реактивного стека и предоставить больше встроенной функциональности. Спасибо!
Ответ №1:
Быстрым решением в этом конкретном случае является реализация кэширования результата WebClient. Итак, внутри метода post для сервера авторизации для извлечения профиля пользователя я только что ввел метод cache() .
return webClient.post()
.header("Authorization","Bearer bla-bla")
.retrieve()
.bodyToMono(String.class)
.cache()
.map(r->new AuthenticatedUser());
Это помогло избежать повторных запросов к конечной точке авторизации во время запроса.