HttpServletRequests を認証する

Spring Security を使用すると、リクエストレベルで認可をモデル化できます。例: Spring Security では、/admin のすべてのページには 1 つの権限が必要ですが、他のすべてのページには認証が必要であると言えます。

デフォルトでは、Spring Security はすべてのリクエストが認証されることをリクエストします。ただし、HttpSecurity インスタンスを使用するときは常に、認可ルールを宣言する必要があります。

HttpSecurity インスタンスがある場合は、少なくとも次のことを行う必要があります。

authorizeHttpRequests を使用する
  • Java

  • Kotlin

  • XML

http
    .authorizeHttpRequests((authorize) -> authorize
        .anyRequest().authenticated()
    )
http {
    authorizeHttpRequests {
        authorize(anyRequest, authenticated)
    }
}
<http>
    <intercept-url pattern="/**" access="authenticated"/>
</http>

これにより、アプリケーション内のエンドポイントを許可するには少なくともセキュリティコンテキストが認証される必要があることが Spring Security に伝えられます。

多くの場合、認可ルールはそれよりも複雑になるため、次の使用例を検討してください。

リクエスト認可コンポーネントの仕組みを理解する

このセクションは、サーブレットのアーキテクチャと実装に基づいて、サーブレットベースのアプリケーションのリクエストレベルで認可がどのように機能するかを詳しく掘り下げます。
authorizationfilter
図 1: HttpServletRequest を認証する
  • number 1 まず、AuthorizationFilter は SecurityContextHolder から認証を取得する Supplier を構築します。

  • number 2 次に、Supplier<Authentication> と HttpServletRequest を AuthorizationManager に渡します。AuthorizationManager はリクエストを authorizeHttpRequests のパターンと照合し、対応するルールを実行します。

AuthorizationFilter はデフォルトで最後です

AuthorizationFilter は、デフォルトでは Spring Security フィルターチェーンの最後にあります。これは、Spring Security の認証フィルターエクスプロイト保護、およびその他のフィルター統合には認可が必要ないことを意味します。AuthorizationFilter の前に独自のフィルターを追加する場合も、認可は必要ありません。そうでなければ、彼らはそうするでしょう。

通常、これが重要になるのは、Spring MVC エンドポイントを追加するときです。これらは DispatcherServlet によって実行され、AuthorizationFilter の後に来るため、エンドポイントを認可するには authorizeHttpRequests に含める必要があります。

すべてのディスパッチは認可されています

AuthorizationFilter は、すべてのリクエストだけでなく、すべてのディスパッチでも実行されます。これは、REQUEST ディスパッチには認可が必要ですが、FORWARDERRORINCLUDE にも認可が必要であることを意味します。

例: Spring MVC は、次のように、Thymeleaf テンプレートをレンダリングするビューリゾルバーにリクエストを FORWARD できます。

サンプル転送 Spring MVC コントローラー
  • Java

  • Kotlin

@Controller
public class MyController {
    @GetMapping("/endpoint")
    public String endpoint() {
        return "endpoint";
    }
}
@Controller
class MyController {
    @GetMapping("/endpoint")
    fun endpoint(): String {
        return "endpoint"
    }
}

この場合、認可は 2 回行われます。1 回は /endpoint を認可するため、もう 1 回は Thymeleaf に転送して「エンドポイント」テンプレートをレンダリングするために使用されます。

このため、すべての FORWARD ディスパッチを許可することができます。

この原則のもう 1 つの例は、Spring Boot がエラーを処理する方法です。コンテナーが例外をキャッチした場合は、次のようにします。

Spring MVC コントローラーのエラー例
  • Java

  • Kotlin

@Controller
public class MyController {
    @GetMapping("/endpoint")
    public String endpoint() {
        throw new UnsupportedOperationException("unsupported");
    }
}
@Controller
class MyController {
    @GetMapping("/endpoint")
    fun endpoint(): String {
        throw UnsupportedOperationException("unsupported")
    }
}

その後、Boot はそれを ERROR ディスパッチにディスパッチします。

その場合、認可も 2 回行われます。/endpoint を認可するために 1 回、エラーをディスパッチするために 1 回です。

このため、すべての ERROR ディスパッチを許可することができます。

Authentication ルックアップは延期されます

これは、リクエストが常に認可されるか、常に拒否される場合の authorizeHttpRequests で重要になります。このような場合、Authentication はクエリされないため、リクエストが高速になります。

エンドポイントの認可

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

USER 権限を持つエンドユーザーのみが /endpoint にアクセスできるようにする場合は、次のようにします。

エンドポイントを承認する
  • Java

  • Kotlin

  • XML

@Bean
public SecurityFilterChain web(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests((authorize) -> authorize
	    .requestMatchers("/endpoint").hasAuthority("USER")
            .anyRequest().authenticated()
        )
        // ...

    return http.build();
}
@Bean
fun web(http: HttpSecurity): SecurityFilterChain {
    http {
        authorizeHttpRequests {
            authorize("/endpoint", hasAuthority("USER"))
            authorize(anyRequest, authenticated)
        }
    }

    return http.build()
}
<http>
    <intercept-url pattern="/endpoint" access="hasAuthority('USER')"/>
    <intercept-url pattern="/**" access="authenticated"/>
</http>

ご覧のとおり、宣言はパターンとルールのペアに分割できます。

AuthorizationFilter はこれらのペアをリストされた順序で処理し、最初に一致したもののみをリクエストに適用します。これは、/** が /endpoint にも一致するとしても、上記のルールは問題ないことを意味します。上記のルールの読み方は、「リクエストが /endpoint の場合は USER 権限が必要、それ以外の場合は認証のみが必要」です。

Spring Security は、いくつかのパターンといくつかのルールをサポートしています。それぞれをプログラムで独自に作成することもできます。

承認されたら、次の方法でセキュリティのテストサポートを使用してテストできます。

エンドポイント認可のテスト
  • Java

@WithMockUser(authorities="USER")
@Test
void endpointWhenUserAuthorityThenAuthorized() {
    this.mvc.perform(get("/endpoint"))
        .andExpect(status().isOk());
}

@WithMockUser
@Test
void endpointWhenNotUserAuthorityThenForbidden() {
    this.mvc.perform(get("/endpoint"))
        .andExpect(status().isForbidden());
}

@Test
void anyWhenUnauthenticatedThenUnauthorized() {
    this.mvc.perform(get("/any"))
        .andExpect(status().isUnauthorized());
}

マッチングリクエスト

上記では、リクエストを照合する 2 つの方法をすでに見てきました。

最初に見たものは最もシンプルで、あらゆるリクエストに適合します。

2 つ目は、URI パターンによる照合です。Spring Security は、URI パターンマッチング用に Ant (上記のとおり) と正規表現の 2 つの言語をサポートします。

Ant を使用したマッチング

Ant は、Spring Security がリクエストの照合に使用するデフォルトの言語です。

これを使用して単一のエンドポイントまたはディレクトリを照合したり、後で使用するためにプレースホルダーをキャプチャーしたりすることもできます。特定の HTTP メソッドのセットに一致するように調整することもできます。

/endpoint エンドポイントを照合するのではなく、/resource ディレクトリ内のすべてのエンドポイントを照合したいとします。その場合は、次のようなことができます。

Ant と合わせる
  • Java

  • Kotlin

  • XML

http
    .authorizeHttpRequests((authorize) -> authorize
        .requestMatchers("/resource/**").hasAuthority("USER")
        .anyRequest().authenticated()
    )
http {
    authorizeHttpRequests {
        authorize("/resource/**", hasAuthority("USER"))
        authorize(anyRequest, authenticated)
    }
}
<http>
    <intercept-url pattern="/resource/**" access="hasAuthority('USER')"/>
    <intercept-url pattern="/**" access="authenticated"/>
</http>

これを読み取る方法は、「リクエストが /resource またはサブディレクトリの場合は、USER 権限が必要です。それ以外の場合は、認証のみが必要です」です。

以下に示すように、リクエストからパス値を抽出することもできます。

承認と抽出
  • Java

  • Kotlin

  • XML

http
    .authorizeHttpRequests((authorize) -> authorize
        .requestMatchers("/resource/{name}").access(new WebExpressionAuthorizationManager("#name == authentication.name"))
        .anyRequest().authenticated()
    )
http {
    authorizeHttpRequests {
        authorize("/resource/{name}", WebExpressionAuthorizationManager("#name == authentication.name"))
        authorize(anyRequest, authenticated)
    }
}
<http>
    <intercept-url pattern="/resource/{name}" access="#name == authentication.name"/>
    <intercept-url pattern="/**" access="authenticated"/>
</http>

承認されたら、次の方法でセキュリティのテストサポートを使用してテストできます。

ディレクトリ認証のテスト
  • Java

@WithMockUser(authorities="USER")
@Test
void endpointWhenUserAuthorityThenAuthorized() {
    this.mvc.perform(get("/endpoint/jon"))
        .andExpect(status().isOk());
}

@WithMockUser
@Test
void endpointWhenNotUserAuthorityThenForbidden() {
    this.mvc.perform(get("/endpoint/jon"))
        .andExpect(status().isForbidden());
}

@Test
void anyWhenUnauthenticatedThenUnauthorized() {
    this.mvc.perform(get("/any"))
        .andExpect(status().isUnauthorized());
}
Spring Security はパスのみに一致します。クエリパラメーターを照合したい場合は、カスタムリクエストマッチャーが必要になります。

正規表現を使用したマッチング

Spring Security は、正規表現に対するリクエストの照合をサポートします。これは、サブディレクトリに対して ** よりも厳密な一致条件を適用する場合に便利です。

例: ユーザー名を含むパスと、すべてのユーザー名が英数字でなければならないというルールを考慮します。次のように RegexRequestMatcher (Javadoc) を使用して、このルールを考慮できます。

正規表現と一致する
  • Java

  • Kotlin

  • XML

http
    .authorizeHttpRequests((authorize) -> authorize
        .requestMatchers(RegexRequestMatcher.regexMatcher("/resource/[A-Za-z0-9]+")).hasAuthority("USER")
        .anyRequest().denyAll()
    )
http {
    authorizeHttpRequests {
        authorize(RegexRequestMatcher.regexMatcher("/resource/[A-Za-z0-9]+"), hasAuthority("USER"))
        authorize(anyRequest, denyAll)
    }
}
<http>
    <intercept-url request-matcher="regex" pattern="/resource/[A-Za-z0-9]+" access="hasAuthority('USER')"/>
    <intercept-url pattern="/**" access="denyAll"/>
</http>

Http メソッドによるマッチング

HTTP メソッドでルールを照合することもできます。これが便利な場所の 1 つは、read または write 特権の付与など、付与されたアクセス認可によって認証する場合です。

すべての GET に read 権限を持たせ、すべての POST に write 権限を持たせるには、次のようにします。

HTTP メソッドによる照合
  • Java

  • Kotlin

  • XML

http
    .authorizeHttpRequests((authorize) -> authorize
        .requestMatchers(HttpMethod.GET).hasAuthority("read")
        .requestMatchers(HttpMethod.POST).hasAuthority("write")
        .anyRequest().denyAll()
    )
http {
    authorizeHttpRequests {
        authorize(HttpMethod.GET, hasAuthority("read"))
        authorize(HttpMethod.POST, hasAuthority("write"))
        authorize(anyRequest, denyAll)
    }
}
<http>
    <intercept-url http-method="GET" pattern="/**" access="hasAuthority('read')"/>
    <intercept-url http-method="POST" pattern="/**" access="hasAuthority('write')"/>
    <intercept-url pattern="/**" access="denyAll"/>
</http>

これらの認可ルールは、「リクエストが GET の場合は read 権限が必要です。それ以外の場合、リクエストが POST の場合は write 権限が必要です。それ以外の場合はリクエストを拒否します。」となります。

デフォルトでリクエストを拒否することは、一連のルールを許可リストに変えるため、健全なセキュリティ慣行です。

承認されたら、次の方法でセキュリティのテストサポートを使用してテストできます。

HTTP メソッドの認可をテストする
  • Java

@WithMockUser(authorities="read")
@Test
void getWhenReadAuthorityThenAuthorized() {
    this.mvc.perform(get("/any"))
        .andExpect(status().isOk());
}

@WithMockUser
@Test
void getWhenNoReadAuthorityThenForbidden() {
    this.mvc.perform(get("/any"))
        .andExpect(status().isForbidden());
}

@WithMockUser(authorities="write")
@Test
void postWhenWriteAuthorityThenAuthorized() {
    this.mvc.perform(post("/any").with(csrf()))
        .andExpect(status().isOk());
}

@WithMockUser(authorities="read")
@Test
void postWhenNoWriteAuthorityThenForbidden() {
    this.mvc.perform(get("/any").with(csrf()))
        .andExpect(status().isForbidden());
}

ディスパッチャー型によるマッチング

この機能は現在 XML ではサポートされていません

前述したように、Spring Security はデフォルトですべてのディスパッチャー型を承認します。また、REQUEST ディスパッチで確立されたセキュリティコンテキストは後続のディスパッチに引き継がれますが、微妙な不一致により予期しない AccessDeniedException が発生することがあります。

これに対処するには、次のように Spring Security Java 構成を構成して、FORWARD や ERROR などのディスパッチャー型を許可します。

ディスパッチャー型による一致
  • Java

  • Kotlin

http
    .authorizeHttpRequests((authorize) -> authorize
        .dispatcherTypeMatchers(DispatcherType.FORWARD, DispatcherType.ERROR).permitAll()
        .requestMatchers("/endpoint").permitAll()
        .anyRequest().denyAll()
    )
http {
    authorizeHttpRequests {
        authorize(DispatcherTypeRequestMatcher(DispatcherType.FORWARD), permitAll)
        authorize(DispatcherTypeRequestMatcher(DispatcherType.ERROR), permitAll)
        authorize("/endpoint", permitAll)
        authorize(anyRequest, denyAll)
    }
}

MvcRequestMatcher を使用する

一般に、上で示したように requestMatchers(String) を使用できます。

ただし、Spring MVC を別のサーブレットパスにマップする場合は、セキュリティ構成でそれを考慮する必要があります。

例: Spring MVC が / (デフォルト) ではなく /spring-mvc にマップされている場合、/spring-mvc/my/controller のようなエンドポイントを承認する必要がある可能性があります。

次のように、MvcRequestMatcher を使用して、構成内のサーブレットパスとコントローラーパスを分割する必要があります。

MvcRequestMatcher による試合
  • Java

  • Kotlin

  • XML

@Bean
MvcRequestMatcher.Builder mvc(HandlerMappingIntrospector introspector) {
	return new MvcRequestMatcher.Builder(introspector).servletPath("/spring-mvc");
}

@Bean
SecurityFilterChain appEndpoints(HttpSecurity http, MvcRequestMatcher.Builder mvc) {
	http
        .authorizeHttpRequests((authorize) -> authorize
            .requestMatchers(mvc.pattern("/my/controller/**")).hasAuthority("controller")
            .anyRequest().authenticated()
        );

	return http.build();
}
@Bean
fun mvc(introspector: HandlerMappingIntrospector): MvcRequestMatcher.Builder =
    MvcRequestMatcher.Builder(introspector).servletPath("/spring-mvc");

@Bean
fun appEndpoints(http: HttpSecurity, mvc: MvcRequestMatcher.Builder): SecurityFilterChain =
    http {
        authorizeHttpRequests {
            authorize(mvc.pattern("/my/controller/**"), hasAuthority("controller"))
            authorize(anyRequest, authenticated)
        }
    }
<http>
    <intercept-url servlet-path="/spring-mvc" pattern="/my/controller/**" access="hasAuthority('controller')"/>
    <intercept-url pattern="/**" access="authenticated"/>
</http>

このニーズは、少なくとも 2 つの異なる方法で発生する可能性があります。

  • spring.mvc.servlet.path Boot プロパティを使用してデフォルトのパス (/) を別のパスに変更する場合

  • Spring MVC DispatcherServlet を複数登録する場合 (そのうちの 1 つがデフォルトのパスではないことが必要です)

カスタムマッチャーの使用

この機能は現在 XML ではサポートされていません

Java 構成では、次のように独自の RequestMatcher (Javadoc) を作成し、それを DSL に提供できます。

ディスパッチャー型による承認
  • Java

  • Kotlin

RequestMatcher printview = (request) -> request.getParameter("print") != null;
http
    .authorizeHttpRequests((authorize) -> authorize
        .requestMatchers(printview).hasAuthority("print")
        .anyRequest().authenticated()
    )
val printview: RequestMatcher = { (request) -> request.getParameter("print") != null }
http {
    authorizeHttpRequests {
        authorize(printview, hasAuthority("print"))
        authorize(anyRequest, authenticated)
    }
}
RequestMatcher (Javadoc) は関数インターフェースであるため、DSL でラムダとして指定できます。ただし、リクエストから値を抽出したい場合は、default メソッドをオーバーライドする必要があるため、具象クラスが必要になります。

承認されたら、次の方法でセキュリティのテストサポートを使用してテストできます。

カスタム認証のテスト
  • Java

@WithMockUser(authorities="print")
@Test
void printWhenPrintAuthorityThenAuthorized() {
    this.mvc.perform(get("/any?print"))
        .andExpect(status().isOk());
}

@WithMockUser
@Test
void printWhenNoPrintAuthorityThenForbidden() {
    this.mvc.perform(get("/any?print"))
        .andExpect(status().isForbidden());
}

リクエストの承認

リクエストが一致すると、permitAlldenyAllhasAuthority などのすでに見られたいくつかの方法でリクエストを承認できます。

簡単にまとめると、DSL に組み込まれている認可ルールは次のとおりです。

  • permitAll - リクエストには認可は必要なく、パブリックエンドポイントです。この場合、Authentication はセッションから取得されないことに注意してください。

  • denyAll - このリクエストはいかなる状況でも許可されません。この場合、Authentication はセッションから取得されないことに注意してください。

  • hasAuthority - このリクエストでは、Authentication が指定された値と一致する GrantedAuthority を持っている必要があります。

  • hasRole - ROLE_ またはデフォルトのプレフィックスとして設定されているものをプレフィックスとする hasAuthority のショートカット

  • hasAnyAuthority - このリクエストでは、指定された値のいずれかに一致する GrantedAuthority が Authentication にあることが必要です。

  • hasAnyRole - ROLE_ またはデフォルトのプレフィックスとして設定されているものをプレフィックスとする hasAnyAuthority のショートカット

  • access - リクエストはこのカスタム AuthorizationManager を使用してアクセスを決定します

パターン、ルール、組み合わせる方法を学習したため、このより複雑な例で何が起こっているかを理解できるはずです。

リクエストを承認する
  • Java

import static jakarta.servlet.DispatcherType.*;

import static org.springframework.security.authorization.AuthorizationManagers.allOf;
import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasAuthority;
import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasRole;

@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
	http
		// ...
		.authorizeHttpRequests(authorize -> authorize                                  (1)
            .dispatcherTypeMatchers(FORWARD, ERROR).permitAll() (2)
			.requestMatchers("/static/**", "/signup", "/about").permitAll()         (3)
			.requestMatchers("/admin/**").hasRole("ADMIN")                             (4)
			.requestMatchers("/db/**").access(allOf(hasAuthority("db"), hasRole("ADMIN")))   (5)
			.anyRequest().denyAll()                                                (6)
		);

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

SpEL で認可を表現する

具体的な AuthorizationManager の使用が推奨されますが、<intercept-url> や JSP Taglib など、式が必要な場合もあります。そのため、このセクションでは、これらのドメインの例に焦点を当てます。

それを踏まえて、Spring Security の Web Security Authorization SpEL API についてもう少し詳しく説明しましょう。

Spring Security は、すべての認証フィールドとメソッドをルートオブジェクトのセットにカプセル化します。最も一般的なルートオブジェクトは SecurityExpressionRoot と呼ばれ、WebSecurityExpressionRoot の基礎となります。Spring Security は、認可式の評価を準備するときに、このルートオブジェクトを StandardEvaluationContext に提供します。

認可式のフィールドとメソッドの使用

これにより最初に提供されるのは、SpEL 式に対する認可フィールドとメソッドの強化されたセットです。以下に、最も一般的な方法の概要を示します。

  • permitAll - このリクエストを呼び出すために認可は必要ありません。この場合、Authentication はセッションから取得されないことに注意してください。

  • denyAll - このリクエストはいかなる状況でも許可されません。この場合、Authentication はセッションから取得されないことに注意してください。

  • hasAuthority - このリクエストでは、Authentication が指定された値と一致する GrantedAuthority を持っている必要があります。

  • hasRole - ROLE_ またはデフォルトのプレフィックスとして設定されているものをプレフィックスとする hasAuthority のショートカット

  • hasAnyAuthority - このリクエストでは、指定された値のいずれかに一致する GrantedAuthority が Authentication にあることが必要です。

  • hasAnyRole - ROLE_ またはデフォルトのプレフィックスとして設定されているものをプレフィックスとする hasAnyAuthority のショートカット

  • hasPermission - オブジェクトレベルの認証を行うための PermissionEvaluator インスタンスへのフック

最も一般的なフィールドを簡単に説明します。

  • authentication - このメソッド呼び出しに関連付けられた Authentication インスタンス

  • principal - このメソッド呼び出しに関連付けられた Authentication#getPrincipal 

パターン、ルール、組み合わせる方法を学習したため、このより複雑な例で何が起こっているかを理解できるはずです。

SpEL を使用してリクエストを承認する
  • XML

<http>
    <intercept-url pattern="/static/**" access="permitAll"/> (1)
    <intercept-url pattern="/admin/**" access="hasRole('ADMIN')"/> (2)
    <intercept-url pattern="/db/**" access="hasAuthority('db') and hasRole('ADMIN')"/> (3)
    <intercept-url pattern="/**" access="denyAll"/> (4)
</http>
1 すべてのユーザーがアクセスできる URL パターンを指定しました。具体的には、URL が "/static/" で始まる場合、どのユーザーもリクエストにアクセスできます。
2"/admin/" で始まる URL は、"ROLE_ADMIN" のロールを持つユーザーに制限されます。hasRole メソッドを呼び出しているため、"ROLE_" プレフィックスを指定する必要がないことに気付くでしょう。
3"/db/" で始まる URL では、ユーザーに "db" 権限と "ROLE_ADMIN" の両方が付与されている必要があります。hasRole 式を使用しているため、"ROLE_" プレフィックスを指定する必要がないことがわかります。
4 まだ一致していない URL はアクセスを拒否されます。これは、認可規則の更新を誤って忘れたくない場合に適した戦略です。

パスパラメーターの使用

さらに、Spring Security はパスパラメーターを検出するメカニズムを提供するため、SpEL 式でもパスパラメーターにアクセスできます。

例: 次の方法で SpEL 式のパスパラメーターにアクセスできます。

SpEL パス変数を使用してリクエストを承認する
  • XML

<http>
    <intercept-url pattern="/resource/{name}" access="#name == authentication.name"/>
    <intercept-url pattern="/**" access="authenticated"/>
</http>

この式は /resource/ の後のパス変数を参照し、それが Authentication#getName と等しいことを必要とします。

認可データベース、ポリシーエージェント、その他のサービスを使用する

認証に別のサービスを使用するように Spring Security を構成する場合は、独自の AuthorizationManager を作成し、それを anyRequest と照合できます。

まず、AuthorizationManager は次のようになります。

オープンポリシーエージェント認証マネージャー
  • Java

@Component
public final class OpenPolicyAgentAuthorizationManager implements AuthorizationManager<RequestAuthorizationContext> {
    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext context) {
        // make request to Open Policy Agent
    }
}

次に、次の方法でそれを Spring Security に接続できます。

リクエストはすべて リモートサービスに送信されます
  • Java

@Bean
SecurityFilterChain web(HttpSecurity http, AuthorizationManager<RequestAuthorizationContext> authz) throws Exception {
	http
		// ...
		.authorizeHttpRequests((authorize) -> authorize
            .anyRequest().access(authz)
		);

	return http.build();
}

ignoring よりも permitAll を優先します

静的リソースがある場合、これらの値を無視するようにフィルターチェーンを構成したくなる場合があります。より安全なアプローチは、次のように permitAll を使用して許可することです。

静的リソースを許可する
  • Java

  • Kotlin

http
    .authorizeHttpRequests((authorize) -> authorize
        .requestMatchers("/css/**").permitAll()
        .anyRequest().authenticated()
    )
http {
    authorizeHttpRequests {
        authorize("/css/**", permitAll)
        authorize(anyRequest, authenticated)
    }
}

静的リソースであっても安全なヘッダーを記述することが重要であるため、より安全です。リクエストが無視された場合、Spring Security はこれを行うことができません。

これまでは、リクエストごとにセッションが Spring Security によって参照されるため、これにはパフォーマンスのトレードオフが伴いました。ただし、Spring Security 6 以降、認可ルールでリクエストされない限り、セッションは ping されなくなりました。パフォーマンスへの影響が解決されたため、Spring Security ではすべてのリクエストに対して少なくとも permitAll を使用することをお勧めします。

authorizeRequests からの移行

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

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

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

authorizeHttpRequests を使用する
  • Java

@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 (Javadoc) の代わりに AuthorizationFilter (Javadoc) が使用されます。

式の移行

可能な場合は、SpEL の代わりに型安全な認可マネージャーを使用することをお勧めします。Java 構成の場合、レガシー SpEL の移行に役立つ WebExpressionAuthorizationManager (Javadoc) を利用できます。

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

  • Java

  • Kotlin

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

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

  • Java

  • Kotlin

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

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

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

セキュリティマッチャー

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

  • Java

  • Kotlin

@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();
	}
}
@Configuration
@EnableWebSecurity
open class SecurityConfig {

    @Bean
    open fun web(http: HttpSecurity): SecurityFilterChain {
        http {
            securityMatcher("/api/**")                                           (1)
            authorizeHttpRequests {
                authorize("/user/**", hasRole("USER"))                           (2)
                authorize("/admin/**", hasRole("ADMIN"))                         (3)
                authorize(anyRequest, authenticated)                             (4)
            }
        }
        return http.build()
    }

}
1/api/ で始まる URL にのみ適用されるように HttpSecurity を構成する
2USER ロールを持つユーザーに /user/ で始まる URL へのアクセスを許可する
3ADMIN ロールを持つユーザーに /admin/ で始まる URL へのアクセスを許可する
4 上記のルールに一致しないその他のリクエストには、認証が必要です

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

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

  • Java

  • Kotlin

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) {
        // ...
    }
}
import org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher (1)
import org.springframework.security.web.util.matcher.RegexRequestMatcher.regexMatcher

@Configuration
@EnableWebSecurity
open class SecurityConfig {

    @Bean
    open fun web(http: HttpSecurity): SecurityFilterChain {
        http {
            securityMatcher(antMatcher("/api/**"))                               (2)
            authorizeHttpRequests {
                authorize(antMatcher("/user/**"), hasRole("USER"))               (3)
                authorize(regexMatcher("/admin/**"), hasRole("ADMIN"))           (4)
                authorize(MyCustomRequestMatcher(), hasRole("SUPERVISOR"))       (5)
                authorize(anyRequest, authenticated)
            }
        }
        return http.build()
    }

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

参考文献

アプリケーションのリクエストを保護したため、そのメソッドの保護を検討してください。アプリケーションのテストや、Spring Security とデータレイヤートレース、メトリクスなどのアプリケーションの他の側面との統合についてさらに詳しく読むこともできます。