1. 序章

このチュートリアルでは、 AuthenticationManagerResolver を紹介し、それを基本認証フローとOAuth2認証フローに使用する方法を示します。

2. AuthenticationManager とは何ですか?

簡単に言えば、AuthenticationManagerは認証の主要な戦略インターフェースです。

入力認証のプリンシパルが有効で検証されている場合、 AuthenticationManager#authenticate は、authenticationフラグがtrueに設定されたAuthenticationインスタンスを返します。 。 それ以外の場合、プリンシパルが有効でない場合は、AuthenticationExceptionがスローされます。 最後のケースでは、決定できない場合はnullを返します。

ProviderManager は、AuthenticationManagerのデフォルトの実装です。 認証プロセスをAuthenticationProviderインスタンスのリストに委任します。

WebSecurityConfigurerAdapter を拡張すると、グローバルまたはローカルのAuthenticationManagerを設定できます。 ローカルのAuthenticationManagerの場合、 configure(AuthenticationManagerBuilder)をオーバーライドできます。

AuthenticationManagerBuilder は、 UserDetailService AuthenticationProvider 、およびAuthenticationManagerを構築するための他の依存関係のセットアップを容易にするヘルパークラスです。

グローバルAuthenticationManagerの場合、AuthenticationManagerをBeanとして定義する必要があります。

3. なぜAuthenticationManagerResolverなのですか?

AuthenticationManagerResolver を使用すると、SpringはコンテキストごとにAuthenticationManagerを選択できます。 これは、バージョン5.2.0のSpringセキュリティに追加された新機能です。

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

AuthenticationManagerResolver#resolve は、汎用コンテキストに基づいてAuthenticationManagerのインスタンスを返すことができます。 つまり AuthenticationManager をそれに応じて解決したい場合は、クラスをコンテキストとして設定できます。

Spring Securityは、HttpServletRequestおよびServerWebExchangeをコンテキストとして、AuthenticationManagerResolverを認証フローに統合しました。

4. 使用シナリオ

AuthenticationManagerResolverを実際に使用する方法を見てみましょう。

たとえば、従業員と顧客の2つのユーザーグループがあるシステムを想定します。 これらの2つのグループには特定の認証ロジックがあり、別々のデータストアがあります。 さらに、これらのグループのいずれかのユーザーは、関連するURLのみを呼び出すことができます。

5. AuthenticationManagerResolver はどのように機能しますか?

AuthenticationManagerResolver は、 AuthenticationManager を動的に選択する必要がある場合はどこでも使用できますが、このチュートリアルでは、組み込みの認証フローで使用することに関心があります。

まず、 AuthenticationManagerResolver を設定してから、基本認証と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は論理的に同じですが、isCustomerisEmployeeに置き換えるだけです。

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

最後に、リクエストのURLに従って解決するAuthenticationManagerResolverを追加しましょう。

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

5.2. 基本認証の場合

AuthenticationFilter を使用して、リクエストごとにAuthenticationManagerを動的に解決できます。 AuthenticationFilterがバージョン5.2のSpringSecurityに追加されました。

これをセキュリティフィルターチェーンに追加すると、一致したリクエストごとに、最初に認証オブジェクトを抽出できるかどうかがチェックされます。 はいの場合、AuthenticationManagerResolverに適切なAuthenticationManagerを要求し、フローを続行します。

まず、 CustomWebSecurityConfigurer にメソッドを追加して、AuthenticationFilterを作成しましょう。

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

AuthenticationFilter#successHandlerをno-op SuccessHandler で設定する理由は、認証が成功した後のリダイレクトのデフォルトの動作を防ぐためです。

次に、 CustomWebSecurityConfigurerWebSecurityConfigurerAdapter#configure(HttpSecurity)をオーバーライドすることで、このフィルターをセキュリティフィルターチェーンに追加できます。

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

5.3. OAuth2認証の場合

BearerTokenAuthenticationFilterはOAuth2認証を担当します。 BearerTokenAuthenticationFilter#doFilterInternal メソッドは、リクエスト内の BearerTokenAuthenticationToken をチェックし、使用可能な場合は、適切なAuthenticationManagerを解決してトークンを認証します。

OAuth2ResourceServerConfigurer は、BearerTokenAuthenticationFilterを設定するために使用されます。

したがって、 WebSecurityConfigurerAdapter#configure(HttpSecurity)をオーバーライドすることで、CustomWebSecurityConfigurerのリソースサーバーにAuthenticationManagerResolverを設定できます。

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

6. リアクティブアプリケーションでAuthenticationManagerを解決します

リアクティブWebアプリケーションの場合でも、コンテキストに応じてAuthenticationManagerを解決するという概念の恩恵を受けることができます。 ただし、代わりにReactionAuthenticationManagerResolverがあります。

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

ReactiveAuthenticationManagerMonoを返します。 ReactionAuthenticationManager は、 AuthenticationManager と同等のリアクティブであるため、authenticateメソッドはMonoを返します。

6.1. ReactionAuthenticationManagerResolverのセットアップ

セキュリティ構成用のクラスを作成することから始めましょう。

@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. 基本認証の場合

リアクティブWebアプリケーションでは、認証にAuthenticationWebFilterを使用できます。 リクエストを認証し、セキュリティコンテキストを満たします。

AuthenticationWebFilter は、最初にリクエストが一致するかどうかをチェックします。 その後、リクエストに認証オブジェクトがある場合、 ReactionAuthenticationManagerResolverからリクエストに適したReactiveAuthenticationManagerを取得し、認証フローを続行します。

したがって、セキュリティ構成でカスタマイズした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認証の場合

ServerHttpSecurity#oauth2ResourceServerを使用してReactiveAuthenticationManagerResolverを構成できます。 ServerHttpSecurity#build のインスタンスを追加します AuthenticationWebFilter セキュリティフィルターのチェーンへのリゾルバーを使用します。

それでは、セキュリティ構成でOAuth2認証フィルターのAuthenticationManagerResolverを設定しましょう。

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

7. 結論

この記事では、単純なシナリオで基本認証とOAuth2認証にAuthenticationManagerResolverを使用しました。

また、w e’ve は、基本認証とOAuth2認証の両方のリアクティブSpringWebアプリケーションでのReactiveAuthenticationManagerResolverの使用法についても調査しました。

いつものように、ソースコードはGitHubから入手できます。 私たちのリアクティブな例は、GitHubでも利用できます。