1. Введение
В этом руководстве мы познакомим вас с AuthenticationManagerResolver
, а затем покажем, как использовать его для потоков аутентификации Basic и OAuth2.
2. Что такое AuthenticationManager
?
Проще говоря, AuthenticationManager
— это основной интерфейс стратегии аутентификации.
Если принципал входной аутентификации действителен и проверен, AuthenticationManager#authenticate
возвращает экземпляр Authentication с установленным в
true
флагом аутентификации
. В противном случае, если принципал недействителен, будет выдано исключение AuthenticationException
. В последнем случае он возвращает null
, если не может принять решение.
``
ProviderManager
— это реализация AuthenticationManager
по умолчанию . Он делегирует процесс аутентификации списку экземпляров AuthenticationProvider .
Мы можем настроить глобальный или локальный AuthenticationManager
, если расширим WebSecurityConfigurerAdapter
. Для локального AuthenticationManager
мы могли бы переопределить configure(AuthenticationManagerBuilder)
.
AuthenticationManagerBuilder
— это вспомогательный класс, упрощающий настройку UserDetailService
, AuthenticationProvider
и других зависимостей для создания AuthenticationManager
.
Для глобального AuthenticationManager
мы должны определить AuthenticationManager
как bean-компонент.
3. Почему AuthenticationManagerResolver
?
AuthenticationManagerResolver
позволяет Spring выбирать AuthenticationManager
для каждого контекста. Это новая функция , добавленная в Spring Security в версии 5.2.0:
public interface AuthenticationManagerResolver<C> {
AuthenticationManager resolve(C context);
}
AuthenticationManagerResolver#resolve
может возвращать экземпляр AuthenticationManager
на основе общего контекста. Другими словами , мы можем установить класс в качестве контекста, если мы хотим разрешить AuthenticationManager
в соответствии с ним.
Spring Security интегрировала AuthenticationManagerResolver
в поток аутентификации с HttpServletRequest
и ServerWebExchange
в качестве контекста.
4. Сценарий использования
Давайте посмотрим, как использовать AuthenticationManagerResolver
на практике.
Например, предположим, что в системе есть две группы пользователей: сотрудники и клиенты. Эти две группы имеют определенную логику проверки подлинности и отдельные хранилища данных. Более того, пользователям любой из этих групп разрешено вызывать только соответствующие URL-адреса.
5. Как работает AuthenticationManagerResolver
?
Мы можем использовать AuthenticationManagerResolver
везде, где нам нужно динамически выбирать AuthenticationManager
, но в этом руководстве мы заинтересованы в использовании его во встроенных потоках проверки подлинности.
Сначала настроим AuthenticationManagerResolver
, а затем используем его для аутентификации Basic и OAuth2.
5.1. Настройка AuthenticationManagerResolver
Начнем с создания класса для настройки безопасности. Мы должны расширить WebSecurityConfigurerAdapter
:
@Configuration
public class CustomWebSecurityConfigurer extends WebSecurityConfigurerAdapter {
// ...
}
Затем добавим метод, который возвращает AuthenticationManager
для клиентов:
AuthenticationManager customersAuthenticationManager() {
return authentication -> {
if (isCustomer(authentication)) {
return new UsernamePasswordAuthenticationToken(/*credentials*/);
}
throw new UsernameNotFoundException(/*principal name*/);
};
}
AuthenticationManager для сотрудников логически такой же, только мы заменяем
isCustomer
на isEmployee
: ``
public AuthenticationManager employeesAuthenticationManager() {
return authentication -> {
if (isEmployee(authentication)) {
return new UsernamePasswordAuthenticationToken(/*credentials*/);
}
throw new UsernameNotFoundException(/*principal name*/);
};
}
Наконец, давайте добавим AuthenticationManagerResolver
, который разрешается в соответствии с URL-адресом запроса:
AuthenticationManagerResolver<HttpServletRequest> resolver() {
return request -> {
if (request.getPathInfo().startsWith("/employee")) {
return employeesAuthenticationManager();
}
return customersAuthenticationManager();
};
}
5.2. Для базовой аутентификации
Мы можем использовать AuthenticationFilter
для динамического разрешения AuthenticationManager
для каждого запроса. AuthenticationFilter
был добавлен в Spring Security в версии 5.2.
Если мы добавим его в нашу цепочку фильтров безопасности, то для каждого сопоставленного запроса он сначала проверит, может ли он извлечь какой-либо объект аутентификации или нет. Если да, то он запрашивает у AuthenticationManagerResolver
подходящий AuthenticationManager
и продолжает поток.
Во-первых, давайте добавим в наш CustomWebSecurityConfigurer метод
для создания AuthenticationFilter
:
private AuthenticationFilter authenticationFilter() {
AuthenticationFilter filter = new AuthenticationFilter(
resolver(), authenticationConverter());
filter.setSuccessHandler((request, response, auth) -> {});
return filter;
}
Причина установки AuthenticationFilter#successHandler
с неактивным SuccessHandler
заключается в том, чтобы предотвратить поведение перенаправления по умолчанию после успешной аутентификации.
Затем мы можем добавить этот фильтр в нашу цепочку фильтров безопасности, переопределив WebSecurityConfigurerAdapter#configure(HttpSecurity)
в нашем CustomWebSecurityConfigurer
:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(
authenticationFilter(),
BasicAuthenticationFilter.class);
}
5.3. Для аутентификации OAuth2
BearerTokenAuthenticationFilter
отвечает за аутентификацию OAuth2. Метод BearerTokenAuthenticationFilter#doFilterInternal
проверяет BearerTokenAuthenticationToken
в запросе и, если он доступен, разрешает соответствующий AuthenticationManager
для аутентификации токена.
OAuth2ResourceServerConfigurer
используется для настройки BearerTokenAuthenticationFilter.
Итак, мы можем настроить AuthenticationManagerResolver
для нашего сервера ресурсов в нашем CustomWebSecurityConfigurer
, переопределив WebSecurityConfigurerAdapter#configure(HttpSecurity)
:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.oauth2ResourceServer()
.authenticationManagerResolver(resolver());
}
6. Разрешить AuthenticationManager
в реактивных приложениях
Для реактивного веб-приложения мы все еще можем извлечь выгоду из концепции разрешения AuthenticationManager
в соответствии с контекстом. Но здесь вместо этого у нас есть ReactiveAuthenticationManagerResolver
:
@FunctionalInterface
public interface ReactiveAuthenticationManagerResolver<C> {
Mono<ReactiveAuthenticationManager> resolve(C context);
}
Он возвращает Mono
ReactiveAuthenticationManager
. ReactiveAuthenticationManager
является реактивным эквивалентом AuthenticationManager
, поэтому его метод аутентификации
возвращает Mono
.
6.1. Настройка ReactiveAuthenticationManagerResolver
Начнем с создания класса для настройки безопасности:
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class CustomWebSecurityConfig {
// ...
}
Далее давайте определим ReactiveAuthenticationManager
для клиентов в этом классе:
ReactiveAuthenticationManager customersAuthenticationManager() {
return authentication -> customer(authentication)
.switchIfEmpty(Mono.error(new UsernameNotFoundException(/*principal name*/)))
.map(b -> new UsernamePasswordAuthenticationToken(/*credentials*/));
}
И после этого определим ReactiveAuthenticationManager
для сотрудников:
public ReactiveAuthenticationManager employeesAuthenticationManager() {
return authentication -> employee(authentication)
.switchIfEmpty(Mono.error(new UsernameNotFoundException(/*principal name*/)))
.map(b -> new UsernamePasswordAuthenticationToken(/*credentials*/));
}
Наконец, мы настроили ReactiveAuthenticationManagerResolver
на основе нашего сценария:
ReactiveAuthenticationManagerResolver<ServerWebExchange> resolver() {
return exchange -> {
if (match(exchange.getRequest(), "/employee")) {
return Mono.just(employeesAuthenticationManager());
}
return Mono.just(customersAuthenticationManager());
};
}
6.2. Для базовой аутентификации
В реактивном веб-приложении мы можем использовать AuthenticationWebFilter
для аутентификации. Он аутентифицирует запрос и заполняет контекст безопасности.
AuthenticationWebFilter
сначала проверяет соответствие запроса. После этого, если в запросе есть объект аутентификации, он получает подходящий ReactiveAuthenticationManager
для запроса от ReactiveAuthenticationManagerResolver
и продолжает процесс аутентификации.
Следовательно, мы можем настроить наш собственный AuthenticationWebFilter
в нашей конфигурации безопасности:
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http
.authorizeExchange()
.pathMatchers("/**")
.authenticated()
.and()
.httpBasic()
.disable()
.addFilterAfter(
new AuthenticationWebFilter(resolver()),
SecurityWebFiltersOrder.REACTOR_CONTEXT
)
.build();
}
Сначала мы отключаем ServerHttpSecurity#httpBasic
, чтобы предотвратить обычный поток аутентификации, а затем вручную заменяем его на AuthenticationWebFilter
, передавая наш пользовательский преобразователь.
6.3. Для аутентификации OAuth2
Мы можем настроить ReactiveAuthenticationManagerResolver
с помощью ServerHttpSecurity#oauth2ResourceServer
. ServerHttpSecurity#build
добавляет экземпляр AuthenticationWebFilter
с нашим преобразователем в цепочку фильтров безопасности.
Итак, давайте установим наш AuthenticationManagerResolver
для фильтра аутентификации OAuth2 в нашей конфигурации безопасности:
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http
// ...
.and()
.oauth2ResourceServer()
.authenticationManagerResolver(resolver())
.and()
// ...;
}
7. Заключение
В этой статье мы использовали AuthenticationManagerResolver
для базовой аутентификации и аутентификации OAuth2 в простом сценарии.
Мы также изучили использование ReactiveAuthenticationManagerResolver
в реактивных веб-приложениях Spring для аутентификации Basic и OAuth2 .
Как всегда, исходный код доступен на GitHub . Наш реактивный пример также доступен на GitHub .