Перейти к основному содержимому

Руководство по AuthenticationManagerResolver в Spring Security

· 6 мин. чтения

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 .