Guide du AuthenticationManagerResolver dans Spring Security

1. Introduction

Dans ce didacticiel, nous présentons AuthenticationManagerResolver , puis montrons comment l'utiliser pour les flux d'authentification de base et OAuth2.

2. Qu'est-ce que AuthenticationManager ?

En termes simples, AuthenticationManager est la principale interface de stratégie pour l'authentification.

Si le principal de l'authentification d'entrée est valide et vérifié, AuthenticationManager # authenticate renvoie une instance d' authentification avec l' indicateur authentifié défini sur true . Sinon, si le principal n'est pas valide, il lèvera une AuthenticationException . Pour le dernier cas, il retourne null s'il ne peut pas décider.

ProviderManager est l'implémentation par défaut d' AuthenticationManager . Il délègue le processus d'authentification à une liste d' instances AuthenticationProvider .

Nous pouvons configurer AuthenticationManager global ou local si nous étendons WebSecurityConfigurerAdapter . Pour un AuthenticationManager local , nous pourrions remplacer configure (AuthenticationManagerBuilder) .

AuthenticationManagerBuilder est une classe d'assistance qui facilite la configuration de UserDetailService , AuthenticationProvider et d'autres dépendances pour créer un AuthenticationManager .

Pour un AuthenticationManager global , nous devons définir un AuthenticationManager comme un bean.

3. Pourquoi AuthenticationManagerResolver ?

AuthenticationManagerResolver permet à Spring de sélectionner un AuthenticationManager par contexte. C'est une nouvelle fonctionnalité ajoutée à Spring Security dans la version 5.2.0:

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

AuthenticationManagerResolver # resolver peut renvoyer une instance d' AuthenticationManager basée sur un contexte générique. En d'autres termes, nous pouvons définir une classe comme contexte si nous voulons résoudre le AuthenticationManager en fonction de celui-ci.

Spring Security a intégré AuthenticationManagerResolver dans le flux d'authentification avec HttpServletRequest et ServerWebExchange comme contexte.

4. Scénario d'utilisation

Voyons comment utiliser AuthenticationManagerResolver dans la pratique.

Par exemple, supposons un système comportant deux groupes d'utilisateurs: les employés et les clients. Ces deux groupes ont une logique d'authentification spécifique et des banques de données distinctes. De plus, les utilisateurs de l'un ou l'autre de ces groupes sont uniquement autorisés à appeler leurs URL associées.

5. Comment fonctionne AuthenticationManagerResolver ?

Nous pouvons utiliser AuthenticationManagerResolver partout où nous devons choisir dynamiquement un AuthenticationManager , mais dans ce didacticiel, nous souhaitons l'utiliser dans des flux d'authentification intégrés.

Commençons par configurer un AuthenticationManagerResolver , puis utilisez-le pour les authentifications de base et OAuth2.

5.1. Configuration d' AuthenticationManagerResolver

Commençons par créer une classe pour la configuration de la sécurité. Nous devrions étendre WebSecurityConfigurerAdapter :

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

Ensuite, ajoutons une méthode qui retourne AuthenticationManager pour les clients:

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

Le AuthenticationManager pour les employés est logiquement la même chose, que nous remplaçons isCustomer avec isEmployee :

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

Enfin, ajoutons un AuthenticationManagerResolver qui se résout en fonction de l'URL de la requête:

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

5.2. Pour l'authentification de base

Nous pouvons utiliser AuthenticationFilter pour résoudre dynamiquement le AuthenticationManager par requête. AuthenticationFilter a été ajouté à Spring Security dans la version 5.2.

Si nous l'ajoutons à notre chaîne de filtres de sécurité, alors pour chaque requête correspondante, il vérifie d'abord s'il peut extraire un objet d'authentification ou non. Si oui, il demande à AuthenticationManagerResolver un AuthenticationManager approprié et continue le flux.

Tout d'abord, ajoutons une méthode dans notre CustomWebSecurityConfigurer pour créer un AuthenticationFilter :

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

La raison pour le réglage du AuthenticationFilter # successHandler avec un no-op SuccessHandler est d'empêcher le comportement par défaut de redirection après une authentification réussie.

Ensuite, nous pouvons ajouter ce filtre à notre chaîne de filtres de sécurité en remplaçant WebSecurityConfigurerAdapter # configure (HttpSecurity) dans notre CustomWebSecurityConfigurer :

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

5.3. Pour l'authentification OAuth2

BearerTokenAuthenticationFilter est responsable de l'authentification OAuth2. La méthode BearerTokenAuthenticationFilter # doFilterInternal recherche un BearerTokenAuthenticationToken dans la demande, et s'il est disponible, elle résout le AuthenticationManager approprié pour authentifier le jeton.

OAuth2ResourceServerConfigurer est utilisé pour configurer BearerTokenAuthenticationFilter.

So, we can set up AuthenticationManagerResolver for our resource server in our CustomWebSecurityConfigurer by overriding WebSecurityConfigurerAdapter#configure(HttpSecurity):

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

6. Resolve AuthenticationManager in Reactive Applications

For a reactive web application, we still can benefit from the concept of resolving AuthenticationManager according to the context. But here we have ReactiveAuthenticationManagerResolver instead:

@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

Dans cet article, nous avons utilisé AuthenticationManagerResolver pour les authentifications de base et OAuth2 dans un scénario simple.

Et nous avons également exploré l'utilisation de ReactiveAuthenticationManagerResolver dans les applications Web Spring réactives pour les authentifications de base et OAuth2.

Comme toujours, le code source est disponible à l'adresse over sur GitHub. Notre exemple réactif est également disponible à l'adresse over sur GitHub.