最新の安定バージョンについては、Spring Security 6.4.4 を使用してください!

AuthorizationFilter で HttpServletRequests を認証する

このセクションは、サーブレットベースのアプリケーション内で認可がどのように機能するかを深く掘り下げて、サーブレットのアーキテクチャと実装に基づいています。

AuthorizationFilter は FilterSecurityInterceptor に取って代わります。下位互換性を維持するために、FilterSecurityInterceptor はデフォルトのままです。このセクションでは、AuthorizationFilter がどのように機能するか、およびデフォルト構成をオーバーライドする方法について説明します。

AuthorizationFilter (Javadoc) は、HttpServletRequest認可を提供します。セキュリティフィルターの 1 つとして FilterChainProxy に挿入されます。

SecurityFilterChain を宣言するときに、デフォルトをオーバーライドできます。authorizeRequests を使用する代わりに、次のように authorizeHttpRequests を使用します。

authorizeHttpRequests を使用する
@Bean
SecurityFilterChain web(HttpSecurity http) throws AuthenticationException {
    http
        .authorizeHttpRequests((authorize) -> authorize
            .anyRequest().authenticated();
        )
        // ...

    return http.build();
}

これにより、authorizeRequests がいくつかの点で改善されます。

  1. メタデータソース、構成属性、意思決定マネージャー、投票者の代わりに、簡略化された AuthorizationManager API を使用します。これにより、再利用とカスタマイズが簡単になります。

  2. Authentication ルックアップを遅らせます。リクエストごとに認証を検索する必要はなく、認可の決定で認証が必要なリクエストでのみ認証が検索されます。

  3. Bean ベースの構成のサポート。

authorizeRequests の代わりに authorizeHttpRequests が使用される場合、FilterSecurityInterceptor の代わりに AuthorizationFilter (Javadoc) が使用されます。

authorizationfilter
図 1: HttpServletRequest を認証する
  • number 1 まず、AuthorizationFilter は SecurityContextHolder から認証を取得します。ルックアップを遅らせるために、これを Supplier でラップします。

  • number 2 次に、Supplier<Authentication> と HttpServletRequest を AuthorizationManager に渡します。

    • number 3 認可が拒否された場合、AccessDeniedException がスローされます。この場合、ExceptionTranslationFilter は AccessDeniedException を処理します。

    • number 4 アクセスが許可されると、AuthorizationFilter は FilterChain を続行します。これにより、アプリケーションは正常に処理できます。

Spring Security を構成して、優先順位の高いルールを追加することで、異なるルールを設定できます。

リクエストを承認する
@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
	http
		// ...
		.authorizeHttpRequests(authorize -> authorize                                  (1)
			.requestMatchers("/resources/**", "/signup", "/about").permitAll()         (2)
			.requestMatchers("/admin/**").hasRole("ADMIN")                             (3)
			.requestMatchers("/db/**").access(new WebExpressionAuthorizationManager("hasRole('ADMIN') and hasRole('DBA')"))   (4)
			// .requestMatchers("/db/**").access(AuthorizationManagers.allOf(AuthorityAuthorizationManager.hasRole("ADMIN"), AuthorityAuthorizationManager.hasRole("DBA")))   (5)
			.anyRequest().denyAll()                                                (6)
		);

	return http.build();
}
1 複数の認可ルールが指定されています。各ルールは、宣言された順序で考慮されます。
2 すべてのユーザーがアクセスできる複数の URL パターンを指定しました。具体的には、URL が "/resources/" で始まるか、"/signup" に等しいか、"/about" に等しい場合、すべてのユーザーがリクエストにアクセスできます。
3"/admin/" で始まる URL は、"ROLE_ADMIN" のロールを持つユーザーに制限されます。hasRole メソッドを呼び出しているため、"ROLE_" プレフィックスを指定する必要がないことに気付くでしょう。
4"/db/" で始まる URL には、ユーザーが "ROLE_ADMIN" と "ROLE_DBA" の両方を持っている必要があります。hasRole 式を使用しているため、"ROLE_" プレフィックスを指定する必要がないことに気付くでしょう。
54 からの同じルールは、複数の AuthorizationManager を組み合わせて書くことができます。
6 まだ一致していない URL はアクセスを拒否されます。これは、認可規則の更新を誤って忘れたくない場合に適した戦略です。

次のように独自の RequestMatcherDelegatingAuthorizationManager を構築することにより、Bean ベースのアプローチをとることができます。

RequestMatcherDelegatingAuthorizationManager の設定
@Bean
SecurityFilterChain web(HttpSecurity http, AuthorizationManager<RequestAuthorizationContext> access)
        throws AuthenticationException {
    http
        .authorizeHttpRequests((authorize) -> authorize
            .anyRequest().access(access)
        )
        // ...

    return http.build();
}

@Bean
AuthorizationManager<RequestAuthorizationContext> requestMatcherAuthorizationManager(HandlerMappingIntrospector introspector) {
    MvcRequestMatcher.Builder mvcMatcherBuilder = new MvcRequestMatcher.Builder(introspector);
    RequestMatcher permitAll =
            new AndRequestMatcher(
                    mvcMatcherBuilder.pattern("/resources/**"),
                    mvcMatcherBuilder.pattern("/signup"),
                    mvcMatcherBuilder.pattern("/about"));
    RequestMatcher admin = mvcMatcherBuilder.pattern("/admin/**");
    RequestMatcher db = mvcMatcherBuilder.pattern("/db/**");
    RequestMatcher any = AnyRequestMatcher.INSTANCE;
    AuthorizationManager<HttpServletRequest> manager = RequestMatcherDelegatingAuthorizationManager.builder()
            .add(permitAll, (context) -> new AuthorizationDecision(true))
            .add(admin, AuthorityAuthorizationManager.hasRole("ADMIN"))
            .add(db, AuthorityAuthorizationManager.hasRole("DBA"))
            .add(any, new AuthenticatedAuthorizationManager())
            .build();
    return (context) -> manager.check(context.getRequest());
}

リクエストマッチャーに対して独自のカスタム認証マネージャーを接続することもできます。

カスタム認可マネージャーを my/authorized/endpoint にマッピングする例を次に示します。

カスタム認証マネージャー
@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests((authorize) -> authorize
            .requestMatchers("/my/authorized/endpoint").access(new CustomAuthorizationManager());
        )
        // ...

    return http.build();
}

または、以下に示すように、すべてのリクエストに提供できます。

すべてのリクエストに対するカスタム認証マネージャー
@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests((authorize) -> authorize
            .anyRequest().access(new CustomAuthorizationManager());
        )
        // ...

    return http.build();
}

デフォルトでは、AuthorizationFilter は DispatcherType.ERROR および DispatcherType.ASYNC には適用されません。shouldFilterAllDispatcherTypes 方式を使用して、すべてのディスパッチャー型に認可ルールを適用するように Spring Security を設定できます。

shouldFilterAllDispatcherTypes を true に設定する
@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests((authorize) -> authorize
            .shouldFilterAllDispatcherTypes(true)
            .anyRequest.authenticated()
        )
        // ...

    return http.build();
}

これで、すべてのディスパッチャー型に適用される認可ルールにより、それらの認可をより詳細に制御できるようになりました。例: shouldFilterAllDispatcherTypes を true に構成したいが、ディスパッチャー型 ASYNC または FORWARD のリクエストには認可を適用したくない場合があります。

ASYNC および FORWARD ディスパッチャー型を許可する
@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests((authorize) -> authorize
            .shouldFilterAllDispatcherTypes(true)
            .dispatcherTypeMatchers(DispatcherType.ASYNC, DispatcherType.FORWARD).permitAll()
            .anyRequest().authenticated()
        )
        // ...

    return http.build();
}

ディスパッチャー型に特定のロールを要求するようにカスタマイズすることもできます。

ディスパッチャー型のエラーに管理者が必要
@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests((authorize) -> authorize
            .shouldFilterAllDispatcherTypes(true)
            .dispatcherTypeMatchers(DispatcherType.ERROR).hasRole("ADMIN")
            .anyRequest().authenticated()
        )
        // ...

    return http.build();
}

リクエストマッチャー

RequestMatcher インターフェースは、リクエストが特定のルールに一致するかどうかを判断するために使用されます。securityMatchers を使用して、特定の HttpSecurity を特定のリクエストに適用する必要があるかどうかを判断します。同様に、requestMatchers を使用して、特定のリクエストに適用する必要がある認可規則を決定できます。次の例を参照してください。

@Configuration
@EnableWebSecurity
public class SecurityConfig {

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		http
			.securityMatcher("/api/**")                            (1)
			.authorizeHttpRequests(authorize -> authorize
				.requestMatchers("/user/**").hasRole("USER")       (2)
				.requestMatchers("/admin/**").hasRole("ADMIN")     (3)
				.anyRequest().authenticated()                      (4)
			)
			.formLogin(withDefaults());
		return http.build();
	}
}
1/api/ で始まる URL にのみ適用されるように HttpSecurity を構成する
2USER ロールを持つユーザーに /user/ で始まる URL へのアクセスを許可する
3ADMIN ロールを持つユーザーに /admin/ で始まる URL へのアクセスを許可する
4 上記のルールに一致しないその他のリクエストには、認証が必要です

securityMatcher(s) および requestMatcher(s) メソッドは、アプリケーションに最適な RequestMatcher 実装を決定します。Spring MVC がクラスパスにある場合は MvcRequestMatcher が使用され、それ以外の場合は AntPathRequestMatcher が使用されます。Spring MVC 統合の詳細については、こちらを参照してください。

特定の RequestMatcher を使用したい場合は、実装を securityMatcher および / または requestMatcher メソッドに渡すだけです:

import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher; (1)
import static org.springframework.security.web.util.matcher.RegexRequestMatcher.regexMatcher;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		http
			.securityMatcher(antMatcher("/api/**"))                              (2)
			.authorizeHttpRequests(authorize -> authorize
				.requestMatchers(antMatcher("/user/**")).hasRole("USER")         (3)
				.requestMatchers(regexMatcher("/admin/.*")).hasRole("ADMIN")     (4)
				.requestMatchers(new MyCustomRequestMatcher()).hasRole("SUPERVISOR")     (5)
				.anyRequest().authenticated()
			)
			.formLogin(withDefaults());
		return http.build();
	}
}

public class MyCustomRequestMatcher implements RequestMatcher {

    @Override
    public boolean matches(HttpServletRequest request) {
        // ...
    }
}
1AntPathRequestMatcher および RegexRequestMatcher から静的ファクトリメソッドをインポートして、RequestMatcher インスタンスを作成します。
2AntPathRequestMatcher を使用して、/api/ で始まる URL にのみ適用されるように HttpSecurity を構成します。
3AntPathRequestMatcher を使用して、USER ロールを持つユーザーに /user/ で始まる URL へのアクセスを許可する
4RegexRequestMatcher を使用して、ADMIN ロールを持つユーザーに /admin/ で始まる URL へのアクセスを許可する
5 カスタム RequestMatcher を使用して、SUPERVISOR ロールを持つユーザーに MyCustomRequestMatcher に一致する URL へのアクセスを許可します

SpEL の代わりに型 セーフな認可マネージャーを使用することをお勧めします。ただし、レガシー SpEL の移行に役立つ WebExpressionAuthorizationManager を利用できます。

WebExpressionAuthorizationManager を使用するには、次のように、移行しようとしている式を使用して作成できます。

.requestMatchers("/test/**").access(new WebExpressionAuthorizationManager("hasRole('ADMIN') && hasRole('USER')"))

@webSecurity.check(authentication, request) のように式で Bean を参照している場合は、代わりに Bean を直接呼び出すことをお勧めします。これは次のようになります。

.requestMatchers("/test/**").access((authentication, context) ->
    new AuthorizationDecision(webSecurity.check(authentication.get(), context.getRequest())))

Bean 参照やその他の式を含む複雑な命令については、変更して AuthorizationManager を実装し、.access(AuthorizationManager) を呼び出して参照することをお勧めします。

それができない場合は、Bean リゾルバーを使用して DefaultHttpSecurityExpressionHandler を構成し、それを WebExpressionAuthorizationManager#setExpressionhandler に提供できます。