Handbuch zum AuthenticationManagerResolver in Spring Security

1. Einleitung

In diesem Lernprogramm stellen wir AuthenticationManagerResolver vor und zeigen dann, wie es für Basic- und OAuth2-Authentifizierungsabläufe verwendet wird.

2. Was ist der AuthenticationManager ?

Einfach ausgedrückt ist der AuthenticationManager die Hauptstrategie-Schnittstelle für die Authentifizierung.

Wenn der Principal der Eingabeauthentifizierung gültig und überprüft ist, gibt AuthenticationManager # authenticate eine Authentifizierungsinstanz zurück, deren authentifiziertes Flag auf true gesetzt ist . Andernfalls löst der Principal eine AuthenticationException aus , wenn er nicht gültig ist . Im letzten Fall wird null zurückgegeben, wenn keine Entscheidung getroffen werden kann.

ProviderManager ist die Standardimplementierung von AuthenticationManager . Es delegiert den Authentifizierungsprozess an eine Liste von AuthenticationProvider- Instanzen.

Wir können den globalen oder lokalen AuthenticationManager einrichten, wenn wir WebSecurityConfigurerAdapter erweitern . Für einen lokalen AuthenticationManager können wir configure (AuthenticationManagerBuilder) überschreiben .

AuthenticationManagerBuilder ist eine Hilfsklasse , die das Einrichten von UserDetailService , AuthenticationProvider und anderen Abhängigkeiten zum Erstellen eines AuthenticationManager vereinfacht .

Für einen globalen AuthenticationManager sollten wir einen AuthenticationManager als Bean definieren.

3. Warum der AuthenticationManagerResolver ?

Mit AuthenticationManagerResolver kann Spring einen AuthenticationManager pro Kontext auswählen . Es ist eine neue Funktion, die Spring Security in Version 5.2.0 hinzugefügt wurde:

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

AuthenticationManagerResolver # resolve kann eine Instanz von AuthenticationManager basierend auf einem generischen Kontext zurückgeben. Mit anderen Worten, wir können eine Klasse als Kontext festlegen, wenn wir den AuthenticationManager entsprechend auflösen möchten .

Spring Security hat den AuthenticationManagerResolver mit HttpServletRequest und ServerWebExchange als Kontext in den Authentifizierungsablauf integriert .

4. Nutzungsszenario

Lassen Sie uns sehen, wie AuthenticationManagerResolver in der Praxis verwendet wird.

Angenommen, ein System mit zwei Benutzergruppen: Mitarbeiter und Kunden. Diese beiden Gruppen haben eine spezifische Authentifizierungslogik und separate Datenspeicher. Darüber hinaus dürfen Benutzer in einer dieser Gruppen nur ihre zugehörigen URLs aufrufen.

5. Wie funktioniert AuthenticationManagerResolver ?

Wir können AuthenticationManagerResolver überall dort verwenden, wo wir einen AuthenticationManager dynamisch auswählen müssen. In diesem Lernprogramm möchten wir ihn jedoch in integrierten Authentifizierungsabläufen verwenden.

Richten Sie zunächst einen AuthenticationManagerResolver ein und verwenden Sie ihn dann für Basic- und OAuth2-Authentifizierungen.

5.1. Einrichten von AuthenticationManagerResolver

Beginnen wir mit der Erstellung einer Klasse für die Sicherheitskonfiguration. Wir sollten WebSecurityConfigurerAdapter erweitern :

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

Fügen wir dann eine Methode hinzu, die den AuthenticationManager für Kunden zurückgibt :

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

Der AuthenticationManager für Mitarbeiter ist logisch identisch, nur dass wir isCustomer durch isEmployee ersetzen :

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

Fügen Sie abschließend einen AuthenticationManagerResolver hinzu , der gemäß der URL der Anforderung aufgelöst wird:

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

5.2. Für die Standardauthentifizierung

Wir können AuthenticationFilter verwenden , um den AuthenticationManager pro Anforderung dynamisch aufzulösen . AuthenticationFilter wurde in Version 5.2 zu Spring Security hinzugefügt.

Wenn wir es unserer Sicherheitsfilterkette hinzufügen, prüft es zunächst für jede übereinstimmende Anforderung, ob es ein Authentifizierungsobjekt extrahieren kann oder nicht. Wenn ja, fragt es den AuthenticationManagerResolver nach einem geeigneten AuthenticationManager und setzt den Ablauf fort.

Fügen wir zunächst eine Methode in unseren CustomWebSecurityConfigurer ein , um einen AuthenticationFilter zu erstellen :

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

Der Grund für das Festlegen des AuthenticationFilter # successHandler mit einem No-op SuccessHandler besteht darin, das Standardverhalten der Umleitung nach erfolgreicher Authentifizierung zu verhindern.

Anschließend können wir diesen Filter zu unserer Sicherheitsfilterkette hinzufügen, indem wir WebSecurityConfigurerAdapter # configure (HttpSecurity) in unserem CustomWebSecurityConfigurer überschreiben :

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

5.3. Für die OAuth2-Authentifizierung

BearerTokenAuthenticationFilter ist für die OAuth2-Authentifizierung verantwortlich. Die BearerTokenAuthenticationFilter # doFilterInternal- Methode sucht in der Anforderung nach einem BearerTokenAuthenticationToken. Wenn es verfügbar ist, wird der entsprechende AuthenticationManager aufgelöst , um das Token zu authentifizieren.

OAuth2ResourceServerConfigurer wird zum Einrichten von BearerTokenAuthenticationFilter verwendet.

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

In diesem Artikel haben wir AuthenticationManagerResolver für Basic- und OAuth2-Authentifizierungen in einem einfachen Szenario verwendet.

Außerdem haben wir die Verwendung von ReactiveAuthenticationManagerResolver in reaktiven Spring-Webanwendungen für Basic- und OAuth2-Authentifizierungen untersucht.

Wie immer ist der Quellcode über GitHub verfügbar. Unser reaktives Beispiel ist auch auf GitHub verfügbar.