Guida al AuthenticationManagerResolver in Spring Security

1. Introduzione

In questo tutorial, introduciamo AuthenticationManagerResolver e quindi mostriamo come usarlo per i flussi di autenticazione di base e OAuth2.

2. Che cos'è AuthenticationManager ?

In poche parole, AuthenticationManager è l'interfaccia principale della strategia per l'autenticazione.

Se l'entità dell'autenticazione di input è valida e verificata, AuthenticationManager # authenticate restituisce un'istanza di autenticazione con il flag di autenticazione impostato su true . In caso contrario, se l'entità non è valida, genererà un'autenticazioneException . Nell'ultimo caso, restituisce null se non può decidere.

ProviderManager è l'implementazione predefinita di AuthenticationManager . Delega il processo di autenticazione a un elenco di istanze di AuthenticationProvider .

Possiamo impostare AuthenticationManager globale o locale se estendiamo WebSecurityConfigurerAdapter . Per un AuthenticationManager locale , potremmo sovrascrivere configure (AuthenticationManagerBuilder) .

AuthenticationManagerBuilder è una classe helper che semplifica la configurazione di UserDetailService , AuthenticationProvider e altre dipendenze per creare un AuthenticationManager .

Per un AuthenticationManager globale , dovremmo definire un AuthenticationManager come bean.

3. Perché AuthenticationManagerResolver ?

AuthenticationManagerResolver consente a Spring di selezionare un AuthenticationManager per contesto. È una nuova funzionalità aggiunta a Spring Security nella versione 5.2.0:

public interface AuthenticationManagerResolver { AuthenticationManager resolve(C context); }

AuthenticationManagerResolver # resolver può restituire un'istanza di AuthenticationManager basata su un contesto generico. In altre parole, possiamo impostare una classe come contesto se vogliamo risolvere AuthenticationManager in base ad essa.

Spring Security ha integrato AuthenticationManagerResolver nel flusso di autenticazione con HttpServletRequest e ServerWebExchange come contesto.

4. Scenario di utilizzo

Vediamo come utilizzare in pratica AuthenticationManagerResolver .

Ad esempio, supponiamo un sistema che abbia due gruppi di utenti: dipendenti e clienti. Questi due gruppi hanno una logica di autenticazione specifica e hanno datastore separati. Inoltre, gli utenti in uno di questi gruppi possono chiamare solo i propri URL correlati.

5. Come funziona AuthenticationManagerResolver ?

Possiamo usare AuthenticationManagerResolver ovunque sia necessario scegliere un AuthenticationManager in modo dinamico, ma in questo tutorial ci interessa usarlo nei flussi di autenticazione incorporati.

Per prima cosa, configuriamo un AuthenticationManagerResolver , quindi usalo per le autenticazioni di base e OAuth2.

5.1. Configurazione di AuthenticationManagerResolver

Cominciamo creando una classe per la configurazione della sicurezza. Dovremmo estendere WebSecurityConfigurerAdapter :

@Configuration public class CustomWebSecurityConfigurer extends WebSecurityConfigurerAdapter { // ... }

Quindi, aggiungiamo un metodo che restituisca AuthenticationManager per i clienti:

AuthenticationManager customersAuthenticationManager() { return authentication -> { if (isCustomer(authentication)) { return new UsernamePasswordAuthenticationToken(/*credentials*/); } throw new UsernameNotFoundException(/*principal name*/); }; }

Il AuthenticationManager per i dipendenti è logicamente lo stesso, solo che sostituiamo isCustomer con isEmployee :

public AuthenticationManager employeesAuthenticationManager() { return authentication -> { if (isEmployee(authentication)) { return new UsernamePasswordAuthenticationToken(/*credentials*/); } throw new UsernameNotFoundException(/*principal name*/); }; }

Infine, aggiungiamo un AuthenticationManagerResolver che si risolve in base all'URL della richiesta:

AuthenticationManagerResolver resolver() { return request -> { if (request.getPathInfo().startsWith("/employee")) { return employeesAuthenticationManager(); } return customersAuthenticationManager(); }; }

5.2. Per l'autenticazione di base

Possiamo usare AuthenticationFilter per risolvere dinamicamente AuthenticationManager per richiesta. AuthenticationFilter è stato aggiunto a Spring Security nella versione 5.2.

Se lo aggiungiamo alla nostra catena di filtri di sicurezza, per ogni richiesta abbinata, controlla prima se può estrarre o meno qualsiasi oggetto di autenticazione. In caso affermativo, chiede a AuthenticationManagerResolver un AuthenticationManager adatto e continua il flusso.

Innanzitutto, aggiungiamo un metodo nel nostro CustomWebSecurityConfigurer per creare un AuthenticationFilter :

private AuthenticationFilter authenticationFilter() { AuthenticationFilter filter = new AuthenticationFilter( resolver(), authenticationConverter()); filter.setSuccessHandler((request, response, auth) -> {}); return filter; }

Il motivo per impostare AuthenticationFilter # successHandler con un SuccessHandler non operativo è impedire il comportamento predefinito del reindirizzamento dopo l'autenticazione riuscita.

Quindi, possiamo aggiungere questo filtro alla nostra catena di filtri di sicurezza sovrascrivendo WebSecurityConfigurerAdapter # configure (HttpSecurity) nel nostro CustomWebSecurityConfigurer :

@Override protected void configure(HttpSecurity http) throws Exception { http.addFilterBefore( authenticationFilter(), BasicAuthenticationFilter.class); }

5.3. Per l'autenticazione OAuth2

BearerTokenAuthenticationFilter è responsabile dell'autenticazione OAuth2. Il metodo BearerTokenAuthenticationFilter # doFilterInternal verifica la presenza di BearerTokenAuthenticationToken nella richiesta e, se è disponibile, risolve il AuthenticationManager appropriato per autenticare il token.

OAuth2ResourceServerConfigurer viene utilizzato per impostare BearerTokenAuthenticationFilter.

Quindi, possiamo impostare AuthenticationManagerResolver per il nostro server di risorse nel nostro CustomWebSecurityConfigurer sovrascrivendo WebSecurityConfigurerAdapter # configure (HttpSecurity) :

@Override protected void configure(HttpSecurity http) throws Exception { http .oauth2ResourceServer() .authenticationManagerResolver(resolver()); }

6. Risolvere AuthenticationManager in applicazioni reattive

Per un'applicazione web reattiva, possiamo ancora trarre vantaggio dal concetto di risoluzione di AuthenticationManager in base al contesto. Ma qui abbiamo invece ReactiveAuthenticationManagerResolver :

@FunctionalInterface public interface ReactiveAuthenticationManagerResolver { Mono resolve(C context); }

It returns a Mono of ReactiveAuthenticationManager. ReactiveAuthenticationManager is the reactive equivalent to AuthenticationManager, hence its authenticate method returns Mono.

6.1. Setting Up ReactiveAuthenticationManagerResolver

Let's start by creating a class for security configuration:

@EnableWebFluxSecurity @EnableReactiveMethodSecurity public class CustomWebSecurityConfig { // ... }

Next, let's define ReactiveAuthenticationManager for customers in this class:

ReactiveAuthenticationManager customersAuthenticationManager() { return authentication -> customer(authentication) .switchIfEmpty(Mono.error(new UsernameNotFoundException(/*principal name*/))) .map(b -> new UsernamePasswordAuthenticationToken(/*credentials*/)); } 

And after that, we'll define ReactiveAuthenticationManager for employees:

public ReactiveAuthenticationManager employeesAuthenticationManager() { return authentication -> employee(authentication) .switchIfEmpty(Mono.error(new UsernameNotFoundException(/*principal name*/))) .map(b -> new UsernamePasswordAuthenticationToken(/*credentials*/)); }

Lastly, we set up a ReactiveAuthenticationManagerResolver based on our scenario:

ReactiveAuthenticationManagerResolver resolver() { return exchange -> { if (match(exchange.getRequest(), "/employee")) { return Mono.just(employeesAuthenticationManager()); } return Mono.just(customersAuthenticationManager()); }; }

6.2. For Basic Authentication

In a reactive web application, we can use AuthenticationWebFilter for authentication. It authenticates the request and fills the security context.

AuthenticationWebFilter first checks if the request matches. After that, if there's an authentication object in the request, it gets the suitable ReactiveAuthenticationManager for the request from ReactiveAuthenticationManagerResolver and continues the authentication flow.

Hence, we can set up our customized AuthenticationWebFilter in our security configuration:

@Bean public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { return http .authorizeExchange() .pathMatchers("/**") .authenticated() .and() .httpBasic() .disable() .addFilterAfter( new AuthenticationWebFilter(resolver()), SecurityWebFiltersOrder.REACTOR_CONTEXT ) .build(); }

First, we disable ServerHttpSecurity#httpBasic to prevent the normal authentication flow, then manually replace it with an AuthenticationWebFilter, passing in our custom resolver.

6.3. For OAuth2 Authentication

We can configure the ReactiveAuthenticationManagerResolver with ServerHttpSecurity#oauth2ResourceServer. ServerHttpSecurity#build adds an instance of AuthenticationWebFilter with our resolver to the chain of security filters.

So, let's set our AuthenticationManagerResolver for OAuth2 authentication filter in our security configuration:

@Bean public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { return http // ... .and() .oauth2ResourceServer() .authenticationManagerResolver(resolver()) .and() // ...; }

7. Conclusion

In questo articolo, abbiamo utilizzato AuthenticationManagerResolver per le autenticazioni di base e OAuth2 all'interno di uno scenario semplice.

Inoltre, abbiamo anche esplorato l'utilizzo di ReactiveAuthenticationManagerResolver nelle applicazioni Web Spring reattive per le autenticazioni di base e OAuth2.

Come sempre, il codice sorgente è disponibile su GitHub. Il nostro esempio reattivo è disponibile anche su GitHub.