В настоящее время я использую довольно простой подход для ограничения определенного suburl (все в /api/rest) и всех его подпутей через WebFluxSecurity. Некоторые пути (все прямо под корнем НЕ в /api/rest) исключены, чтобы к ним можно было получить доступ без авторизации. Однако иногда запрашивающая сторона может отправить пустой заголовок авторизации, что приводит к тому, что незащищенные конечные точки возвращают ошибку 401.
См. соответствующий код здесь:
@Configuration
@EnableWebFluxSecurity
public class SecurityConfiguration {
@Value(value = "${...}")
private String user;
@Value(value = "${...}")
private String pw;
@Bean
public MapReactiveUserDetailsService userDetailsService() {
PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
UserDetails user = User
.withUsername(user)
.password(encoder.encode(pw))
.roles("USER")
.build();
return new MapReactiveUserDetailsService(user);
}
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange(exchanges -> exchanges
.pathMatchers("/api/rest/**")
.authenticated()
.anyExchange()
.permitAll()
)
.httpBasic(withDefaults());
return http.build();
}
}
В stackoverflow я нашел только несколько предложений, как справиться с этим с помощью WebSecurity. Однако для меня это невозможно, так как я использую безопасность webflux.
См., например.
Может быть, проще воспроизвести: это не просто пустой заголовок аутентификации, который создает 401. Даже вызов конечных точек, которые должны совпадать с .anyExchange().permitAll()
(то есть незащищенные конечные точки) с неправильной аутентификацией (например, неверный пароль), приводит к 401. Насколько я понимаю это происходит из-за .authorizeExchange
, который выполняет сопоставление с ReactiveUserDetails.
TL;DR
Если вы передадите неверные учетные данные любой конечной точке с включенным
httpBasic()
, она вернет ответ 401.
Здесь важно различать аутентификацию и авторизацию. Метод httpBasic()
DSL добавляет AuthenticationWebFilter
, настроенный для HTTP Basic. Метод authorizeExchange(...)
DSL определяет правила авторизации, такие как authenticated()
и permitAll()
.
Фильтр аутентификации появляется в цепочке фильтров Spring Security раньше, чем фильтр авторизации, поэтому аутентификация происходит первой, как мы и ожидали. Судя по вашим комментариям, вы ожидаете, что аутентификация не произойдет, если вы пометите конечную точку как permitAll()
, но это не так.
Будет ли на самом деле предприниматься попытка проверки подлинности для определенного запроса, зависит от того, как фильтр проверки подлинности соответствует запросу. В случае AuthenticationWebFilter
ServerWebExchangeMatcher
(requiresAuthenticationMatcher
) определяет, требуется ли аутентификация. Для httpBasic()
каждый запрос требует аутентификации. Если вы передадите неверные учетные данные любой конечной точке с включенным httpBasic()
, она вернет ответ 401.
Кроме того, ServerAuthenticationConverter
(authenticationConverter
) используется для чтения заголовка Authorization
и анализа учетных данных. Это то, что потерпит неудачу, если будет указан недопустимый токен (или заголовок Authorization
). ServerHttpBasicAuthenticationConverter
используется для httpBasic()
и довольно прощает недопустимые значения заголовка. Я не нахожу никаких сценариев, которые терпят неудачу и дают ответ 401, за исключением недействительных учетных данных.
Вы упоминаете передачу пустого заголовка
Authorization
. Я не могу воспроизвести поведение, которое вы описываете, и просмотрев код дляServerHttpBasicAuthenticationConverter
, который читает заголовок, он не выглядит так, как будто пустое значение заголовка вызовет ошибку 401. Можете ли вы предоставить пример запроса, который воспроизводит эту проблему?