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

認可の移行

次の手順は、認可の実行方法に関する変更に関連しています。

メソッドセキュリティに AuthorizationManager を使用する

メソッドのセキュリティは、AuthorizationManager APISpring AOP の直接使用によって簡素化されました。

これらの変更を行う際に問題が発生した場合、@EnableGlobalMethodSecurity は非推奨ですが、6.0 では削除されないことに注意してください。古いアノテーションを使用することでオプトアウトできます。

グローバルメソッドセキュリティメソッドセキュリティに置き換える

@EnableGlobalMethodSecurity (Javadoc) <global-method-security> は非推奨となり、それぞれ @EnableMethodSecurity (Javadoc) <method-security> が推奨されます。新しいアノテーションと XML 要素は、デフォルトで Spring の事前投稿アノテーションを有効にし、内部で AuthorizationManager を使用します。

これは、次の 2 つのリストが関数に同等であることを意味します。

  • Java

  • Kotlin

  • XML

@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableGlobalMethodSecurity(prePostEnabled = true)
<global-method-security pre-post-enabled="true"/>

および:

  • Java

  • Kotlin

  • XML

@EnableMethodSecurity
@EnableMethodSecurity
<method-security/>

プリポストアノテーションを使用しないアプリケーションの場合は、望ましくない動作がアクティブにならないように、必ずオフにしてください。

例: 次のようなリスト:

  • Java

  • Kotlin

  • XML

@EnableGlobalMethodSecurity(securedEnabled = true)
@EnableGlobalMethodSecurity(securedEnabled = true)
<global-method-security secured-enabled="true"/>

次のように変更する必要があります。

  • Java

  • Kotlin

  • XML

@EnableMethodSecurity(securedEnabled = true, prePostEnabled = false)
@EnableMethodSecurity(securedEnabled = true, prePostEnabled = false)
<method-security secured-enabled="true" pre-post-enabled="false"/>

@EnableTransactionManagement の order 値を変更します

@EnableTransactionManagement と @EnableGlobalMethodSecurity は同じ order 値 Integer.MAX_VALUE を持ちます。これは、Spring AOP Advisor チェーン 内での相互の順序が未定義であることを意味します。

ほとんどのメソッドセキュリティ式は正しく機能するためにオープントランザクションを必要としないため、これは多くの場合問題ありません。ただし、歴史的には、order 値を設定することで、一方が他方よりも先に発生するようにする必要がある場合がありました。

@EnableMethodSecurity は複数のインターセプターを公開するため、order 値を持ちません。実際、すべてのインターセプターを同じアドバイザーチェーン の場所に設定できないため、@EnableTransactionManagement との下位互換性を試みることはできません。

代わりに、@EnableMethodSecurity インターセプターの値はオフセット 0 に基づいています。@PreFilter インターセプターの順序は 100、@PostAuthorize は 200 などです。

更新後に、オープンなトランザクションがないためにメソッドのセキュリティ式が機能していないことが判明した場合は、トランザクションアノテーション定義を次から変更してください。

  • Java

  • Kotlin

  • XML

@EnableTransactionManagement
@EnableTransactionManagement
<tx:annotation-driven ref="txManager"/>

to:

  • Java

  • Kotlin

  • XML

@EnableTransactionManagement(order = 0)
@EnableTransactionManagement(order = 0)
<tx:annotation-driven ref="txManager" order="0"/>

このようにして、トランザクション AOP アドバイスが Spring Security のアドバイスの前に配置され、認可 SpEL 式が評価されるときにトランザクションがオープンされます。

DefaultMethodSecurityExpressionHandler をサブクラス化する代わりにカスタム @Bean を使用する

パフォーマンスの最適化として、Authentication の代わりに Supplier<Authentication> を取る新しいメソッドが MethodSecurityExpressionHandler に導入されました。

これにより、Spring Security は Authentication のルックアップを延期でき、@EnableGlobalMethodSecurity の代わりに @EnableMethodSecurity を使用すると自動的に利用されます。

ただし、コードが DefaultMethodSecurityExpressionHandler を継承し、createSecurityExpressionRoot(Authentication, MethodInvocation) をオーバーライドしてカスタム SecurityExpressionRoot インスタンスを返すとします。これは、@EnableMethodSecurity がセットアップする配置が代わりに createEvaluationContext(Supplier<Authentication>, MethodInvocation) を呼び出すため、機能しなくなります。

幸いなことに、このようなレベルのカスタマイズは不要なことがよくあります。代わりに、必要な認証方法を使用してカスタム Bean を作成できます。

例: @PostAuthorize("hasAuthority('ADMIN')") のカスタム評価が必要だとしましょう。次のようなカスタム @Bean を作成できます。

  • Java

  • Kotlin

class MyAuthorizer {
	boolean isAdmin(MethodSecurityExpressionOperations root) {
		boolean decision = root.hasAuthority("ADMIN");
		// custom work ...
        return decision;
	}
}
class MyAuthorizer {
	fun isAdmin(val root: MethodSecurityExpressionOperations): boolean {
		val decision = root.hasAuthority("ADMIN");
		// custom work ...
        return decision;
	}
}

そして、次のようにアノテーションで参照します。

  • Java

  • Kotlin

@PreAuthorize("@authz.isAdmin(#root)")
@PreAuthorize("@authz.isAdmin(#root)")

DefaultMethodSecurityExpressionHandler をサブクラス化したい

DefaultMethodSecurityExpressionHandler のサブクラス化を継続する必要がある場合でも、そうすることができます。代わりに、次のように createEvaluationContext(Supplier<Authentication>, MethodInvocation) メソッドをオーバーライドします。

  • Java

  • Kotlin

@Component
class MyExpressionHandler extends DefaultMethodSecurityExpressionHandler {
    @Override
    public EvaluationContext createEvaluationContext(
            Supplier<Authentication> authentication, MethodInvocation mi) {
		StandardEvaluationContext context = (StandardEvaluationContext) super.createEvaluationContext(authentication, mi);
        MySecurityExpressionRoot root = new MySecurityExpressionRoot(authentication, invocation);
	    root.setPermissionEvaluator(getPermissionEvaluator());
	    root.setTrustResolver(new AuthenticationTrustResolverImpl());
        root.setRoleHierarchy(getRoleHierarchy());
        context.setRootObject(root);
        return context;
    }
}
@Component
class MyExpressionHandler: DefaultMethodSecurityExpressionHandler {
    override fun createEvaluationContext(val authentication: Supplier<Authentication>,
        val mi: MethodInvocation): EvaluationContext {
		val context = super.createEvaluationContext(authentication, mi) as StandardEvaluationContext;
        val root = new MySecurityExpressionRoot(authentication, invocation);
	    root.setPermissionEvaluator(getPermissionEvaluator());
	    root.setTrustResolver(new AuthenticationTrustResolverImpl());
        root.setRoleHierarchy(getRoleHierarchy());
        context.setRootObject(root);
        return context;
    }
}

オプトアウトの手順

これらの変更をオプトアウトする必要がある場合は、@EnableMethodSecurity の代わりに @EnableGlobalMethodSecurity を使用できます。

PermissionEvaluator の代わりに MethodSecurityExpressionHandler を発行する

@EnableMethodSecurity は PermissionEvaluator をピックアップしません。これにより、API をシンプルに保つことができます。

カスタム PermissionEvaluator (Javadoc)  @Bean がある場合は、次のように変更してください。

  • Java

  • Kotlin

@Bean
static PermissionEvaluator permissionEvaluator() {
	// ... your evaluator
}
companion object {
	@Bean
	fun permissionEvaluator(): PermissionEvaluator {
		// ... your evaluator
	}
}

to:

  • Java

  • Kotlin

@Bean
static MethodSecurityExpressionHandler expressionHandler() {
	var expressionHandler = new DefaultMethodSecurityExpressionHandler();
	expressionHandler.setPermissionEvaluator(myPermissionEvaluator);
	return expressionHandler;
}
companion object {
	@Bean
	fun expressionHandler(): MethodSecurityExpressionHandler {
		val expressionHandler = DefaultMethodSecurityExpressionHandler
		expressionHandler.setPermissionEvaluator(myPermissionEvaluator)
		return expressionHandler
	}
}

カスタムメソッドセキュリティ AccessDecisionManager を置き換えます

アプリケーションには、カスタム AccessDecisionManager (Javadoc) または AccessDecisionVoter (Javadoc) 配置がある場合があります。準備戦略は、各取り決めの理由によって異なります。あなたの状況に最適な方法を見つけるために参照してください。

UnanimousBased を使っています

アプリケーションがデフォルトの投票者で UnanimousBased (Javadoc) を使用する場合、@EnableMethodSecurity (Javadoc) では全会一致ベースがデフォルトの動作であるため、おそらく何もする必要はありません。

ただし、デフォルトの認証マネージャーを受け入れることができないことがわかった場合は、AuthorizationManagers.allOf を使用して独自の取り決めを作成できます。

allOf とは異なり、すべての参加者が棄権した場合に認可が与えられることに注意してください。すべてのデリゲートが棄権したときに認可を拒否する必要がある場合は、一連のデリゲート AuthorizationManager を考慮した複合 AuthorizationManager (Javadoc) を実装してください。

それが完了したら、カスタム AuthorizationManager を追加するためのリファレンスマニュアルの詳細に従ってください。

AffirmativeBased を使っています

アプリケーションが AffirmativeBased (Javadoc) を使用している場合、次のように同等の AuthorizationManager (Javadoc) を作成できます。

  • Java

  • Kotlin

AuthorizationManager<MethodInvocation> authorization = AuthorizationManagers.anyOf(
		// ... your list of authorization managers
)
val authorization = AuthorizationManagers.anyOf(
		// ... your list of authorization managers
)

AuthorizationManager を実装したら、リファレンスマニュアルの詳細に従ってカスタム AuthorizationManager を追加してください

ConsensusBased を使っています

フレームワークが提供する ConsensusBased (Javadoc) に相当するものはありません。その場合、デリゲート AuthorizationManager のセットを考慮した複合 AuthorizationManager (Javadoc) を実装してください。

AuthorizationManager を実装したら、リファレンスマニュアルの詳細に従ってカスタム AuthorizationManager を追加してください

カスタム AccessDecisionVoter を使用しています

クラスを変更して AuthorizationManager (Javadoc) を実装するか、アダプターを作成する必要があります。

カスタム投票者が何をしているかを知らなければ、汎用ソリューションを推奨することはできません。ただし、例として、SecurityMetadataSource (Javadoc) AccessDecisionVoter (Javadoc) を @PreAuthorize に適合させると、次のようになります。

  • Java

public final class PreAuthorizeAuthorizationManagerAdapter implements AuthorizationManager<MethodInvocation> {
    private final SecurityMetadataSource metadata;
    private final AccessDecisionVoter voter;

    public PreAuthorizeAuthorizationManagerAdapter(MethodSecurityExpressionHandler expressionHandler) {
        ExpressionBasedAnnotationAttributeFactory attributeFactory =
                new ExpressionBasedAnnotationAttributeFactory(expressionHandler);
        this.metadata = new PrePostAnnotationSecurityMetadataSource(attributeFactory);
        ExpressionBasedPreInvocationAdvice expressionAdvice = new ExpressionBasedPreInvocationAdvice();
        expressionAdvice.setExpressionHandler(expressionHandler);
        this.voter = new PreInvocationAuthorizationAdviceVoter(expressionAdvice);
    }

    public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation invocation) {
        List<ConfigAttribute> attributes = this.metadata.getAttributes(invocation, AopUtils.getTargetClass(invocation.getThis()));
        int decision = this.voter.vote(authentication.get(), invocation, attributes);
        if (decision == ACCESS_GRANTED) {
            return new AuthorizationDecision(true);
        }
        if (decision == ACCESS_DENIED) {
            return new AuthorizationDecision(false);
        }
        return null; // abstain
    }
}

AuthorizationManager を実装したら、リファレンスマニュアルの詳細に従ってカスタム AuthorizationManager を追加してください

AfterInvocationManager または AfterInvocationProvider を使用しています

AfterInvocationManager (Javadoc) AfterInvocationProvider (Javadoc) は、呼び出しの結果について認可の決定を行います。例: メソッド呼び出しの場合、これらはメソッドの戻り値に関する認可決定を行います。

Spring Security 3.0 では、認可の意思決定が @PostAuthorize および @PostFilter アノテーションに標準化されました。@PostAuthorize は、戻り値全体が返されたかどうかを判断するためのものです。@PostFilter は、返されたコレクション、配列、ストリームから個々のエントリをフィルタリングするためのものです。

AfterInvocationProvider と AfterInvocationManager は現在非推奨であるため、これらの 2 つのアノテーションはほとんどのニーズに対応する必要があり、いずれかまたは両方に移行することをお勧めします。

独自の AfterInvocationManager または AfterInvocationProvider を実装した場合は、まずそれが何をしようとしているのかを自問する必要があります。戻り値の型を承認しようとしている場合は、AuthorizationManager<MethodInvocationResult> の実装と AfterMethodAuthorizationManagerInterceptor の使用を検討してください。または、カスタム Bean を公開し、@PostAuthorize("@myBean.authorize(#root)") を使用します。

フィルタリングしようとしている場合は、カスタム Bean を公開し、@PostFilter("@mybean.authorize(#root)") を使用することを検討してください。または、必要に応じて、例として PostFilterAuthorizationMethodInterceptor および PrePostMethodSecurityConfiguration を見て、独自の MethodInterceptor を実装できます。

RunAsManager を使っています

ただし、必要に応じて RunAsManager を AuthorizationManager API に適合させるのは非常に簡単です。

開始するための疑似コードを次に示します。

  • Java

public final class RunAsAuthorizationManagerAdapter<T> implements AuthorizationManager<T> {
	private final RunAsManager runAs = new RunAsManagerImpl();
	private final SecurityMetadataSource metadata;
    private final AuthorizationManager<T> authorization;

    // ... constructor

    public AuthorizationDecision check(Supplier<Authentication> authentication, T object) {
		Supplier<Authentication> wrapped = (auth) -> {
			List<ConfigAttribute> attributes = this.metadata.getAttributes(object);
			return this.runAs.buildRunAs(auth, object, attributes);
		};
		return this.authorization.check(wrapped, object);
    }
}

AuthorizationManager を実装したら、リファレンスマニュアルの詳細に従ってカスタム AuthorizationManager を追加してください

AnnotationConfigurationException のチェック

@EnableMethodSecurity および <method-security> は、Spring Security の反復不可または互換性のないアノテーションのより厳格な施行を有効にします。いずれかに移動した後、ログに AnnotationConfigurationException が表示される場合は、例外メッセージの指示に従って、アプリケーションのメソッドセキュリティアノテーションの使用をクリーンアップしてください。

メッセージセキュリティに AuthorizationManager を使用する

メッセージセキュリティは、AuthorizationManager API と Spring AOP の直接使用によって改善されました。

これらの変更を行う際に問題が発生した場合は、このセクションの最後にあるオプトアウトの手順に従ってください。

すべてのメッセージに認可規則が定義されていることを確認する

非推奨のメッセージセキュリティサポート (Javadoc) では、デフォルトですべてのメッセージが許可されます。新しいサポートには、すべてのメッセージを拒否する強力なデフォルトがあります。

これに備えて、すべてのリクエストに対して認可ルールが宣言されていることを確認してください。

例: 次のようなアプリケーション構成:

  • Java

  • Kotlin

  • XML

@Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
    messages
        .simpDestMatchers("/user/queue/errors").permitAll()
        .simpDestMatchers("/admin/**").hasRole("ADMIN");
}
override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) {
    messages
        .simpDestMatchers("/user/queue/errors").permitAll()
        .simpDestMatchers("/admin/**").hasRole("ADMIN")
}
<websocket-message-broker>
    <intercept-message pattern="/user/queue/errors" access="permitAll"/>
    <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
</websocket-message-broker>

次のように変更する必要があります。

  • Java

  • Kotlin

  • XML

@Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
    messages
        .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
        .simpDestMatchers("/user/queue/errors").permitAll()
        .simpDestMatchers("/admin/**").hasRole("ADMIN")
        .anyMessage().denyAll();
}
override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) {
    messages
        .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
        .simpDestMatchers("/user/queue/errors").permitAll()
        .simpDestMatchers("/admin/**").hasRole("ADMIN")
        .anyMessage().denyAll()
}
<websocket-message-broker>
    <intercept-message type="CONNECT" access="permitAll"/>
    <intercept-message type="DISCONNECT" access="permitAll"/>
    <intercept-message type="UNSUBSCRIBE" access="permitAll"/>
    <intercept-message pattern="/user/queue/errors" access="permitAll"/>
    <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
    <intercept-message pattern="/**" access="denyAll"/>
</websocket-message-broker>

@EnableWebSocketSecurity を追加

CSRF を無効にする必要があり、Java 構成を使用している場合、移行手順は少し異なります。@EnableWebSocketSecurity を使用する代わりに、WebSocketMessageBrokerConfigurer の適切なメソッドを自分でオーバーライドします。この手順の詳細については、リファレンスマニュアルを参照してください。

Java 構成を使用している場合は、アプリケーションに @EnableWebSocketSecurity (Javadoc) を追加します。

例: 次のように、websocket セキュリティ構成クラスに追加できます。

  • Java

  • Kotlin

@EnableWebSocketSecurity
@Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
	// ...
}
@EnableWebSocketSecurity
@Configuration
class WebSocketSecurityConfig: AbstractSecurityWebSocketMessageBrokerConfigurer() {
	// ...
}

これにより、MessageMatcherDelegatingAuthorizationManager.Builder のプロトタイプインスタンスが利用可能になり、拡張ではなく構成による構成が促進されます。

AuthorizationManager<Message<?>> インスタンスを使用する

AuthorizationManager の使用を開始するには、XML で use-authorization-manager 属性を設定するか、Java で AuthorizationManager<Message<?>>@Bean を公開します。

例: 次のアプリケーション構成:

  • Java

  • Kotlin

  • XML

@Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
    messages
        .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
        .simpDestMatchers("/user/queue/errors").permitAll()
        .simpDestMatchers("/admin/**").hasRole("ADMIN")
        .anyMessage().denyAll();
}
override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) {
    messages
        .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
        .simpDestMatchers("/user/queue/errors").permitAll()
        .simpDestMatchers("/admin/**").hasRole("ADMIN")
        .anyMessage().denyAll()
}
<websocket-message-broker>
    <intercept-message type="CONNECT" access="permitAll"/>
    <intercept-message type="DISCONNECT" access="permitAll"/>
    <intercept-message type="UNSUBSCRIBE" access="permitAll"/>
    <intercept-message pattern="/user/queue/errors" access="permitAll"/>
    <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
    <intercept-message pattern="/**" access="denyAll"/>
</websocket-message-broker>

変更:

  • Java

  • Kotlin

  • XML

@Bean
AuthorizationManager<Message<?>> messageSecurity(MessageMatcherDelegatingAuthorizationManager.Builder messages) {
    messages
        .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
        .simpDestMatchers("/user/queue/errors").permitAll()
        .simpDestMatchers("/admin/**").hasRole("ADMIN")
        .anyMessage().denyAll();
	return messages.build();
}
@Bean
fun messageSecurity(val messages: MessageMatcherDelegatingAuthorizationManager.Builder): AuthorizationManager<Message<?>> {
    messages
        .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
        .simpDestMatchers("/user/queue/errors").permitAll()
        .simpDestMatchers("/admin/**").hasRole("ADMIN")
        .anyMessage().denyAll()
    return messages.build()
}
<websocket-message-broker use-authorization-manager="true">
    <intercept-message type="CONNECT" access="permitAll"/>
    <intercept-message type="DISCONNECT" access="permitAll"/>
    <intercept-message type="UNSUBSCRIBE" access="permitAll"/>
    <intercept-message pattern="/user/queue/errors" access="permitAll"/>
    <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
    <intercept-message pattern="/**" access="denyAll"/>
</websocket-message-broker>

AbstractSecurityWebSocketMessageBrokerConfigurer の実装をやめる

Java 構成を使用している場合は、単純に WebSocketMessageBrokerConfigurer を継承できるようになりました。

例: AbstractSecurityWebSocketMessageBrokerConfigurer を継承するクラスが WebSocketSecurityConfig と呼ばれる場合:

  • Java

  • Kotlin

@EnableWebSocketSecurity
@Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
	// ...
}
@EnableWebSocketSecurity
@Configuration
class WebSocketSecurityConfig: AbstractSecurityWebSocketMessageBrokerConfigurer() {
	// ...
}

変更:

  • Java

  • Kotlin

@EnableWebSocketSecurity
@Configuration
public class WebSocketSecurityConfig implements WebSocketMessageBrokerConfigurer {
	// ...
}
@EnableWebSocketSecurity
@Configuration
class WebSocketSecurityConfig: WebSocketMessageBrokerConfigurer {
	// ...
}

オプトアウトの手順

問題が発生した場合は、最適なオプトアウト動作について次のシナリオを参照してください。

すべてのリクエストに対して認可ルールを宣言することはできません

denyAll の anyRequest 認可ルールの設定に問題がある場合は、代わりに permitAll (Javadoc) を次のように使用してください。

  • Java

  • Kotlin

  • XML

@Bean
AuthorizationManager<Message<?>> messageSecurity(MessageMatcherDelegatingAuthorizationManager.Builder messages) {
    messages
        .simpDestMatchers("/user/queue/errors").permitAll()
        .simpDestMatchers("/admin/**").hasRole("ADMIN")
        // ...
        .anyMessage().permitAll();
	return messages.build();
}
@Bean
fun messageSecurity(val messages: MessageMatcherDelegatingAuthorizationManager.Builder): AuthorizationManager<Message<?>> {
    messages
        .simpDestMatchers("/user/queue/errors").permitAll()
        .simpDestMatchers("/admin/**").hasRole("ADMIN")
        // ...
        .anyMessage().permitAll();
    return messages.build()
}
<websocket-message-broker use-authorization-manager="true">
    <intercept-message pattern="/user/queue/errors" access="permitAll"/>
    <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
    <!-- ... -->
    <intercept-message pattern="/**" access="permitAll"/>
</websocket-message-broker>

CSRF が機能しない、他の AbstractSecurityWebSocketMessageBrokerConfigurer 機能が必要、または AuthorizationManager に問題がある

Java の場合は、引き続き AbstractMessageSecurityWebSocketMessageBrokerConfigurer を使用できます。非推奨ですが、6.0 では削除されません。

XML の場合、use-authorization-manager="false" を設定することで AuthorizationManager をオプトアウトできます。

Xml
<websocket-message-broker>
    <intercept-message pattern="/user/queue/errors" access="permitAll"/>
    <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
</websocket-message-broker>

to:

Xml
<websocket-message-broker use-authorization-manager="false">
    <intercept-message pattern="/user/queue/errors" access="permitAll"/>
    <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
</websocket-message-broker>

リクエストセキュリティに AuthorizationManager を使用する

これらの変更を行う際に問題が発生した場合は、このセクションの最後にあるオプトアウトの手順に従ってください。

すべてのリクエストに認可ルールが定義されていることを確認する

Spring Security 5.8 以前では、認可ルールのないリクエストはデフォルトで認可されます。デフォルトで拒否することはより強力なセキュリティ上の立場であるため、すべてのエンドポイントに対して認可規則を明確に定義する必要があります。そのため、6.0 では、Spring Security は既定で、認可規則が欠落しているすべてのリクエストを拒否します。

この変更に備える最も簡単な方法は、適切な anyRequest (Javadoc) ルールを最後の認可ルールとして導入することです。denyAll (Javadoc) が暗黙の 6.0 デフォルトであるため、推奨は denyAll (Javadoc) です。

満足できる anyRequest ルールがすでに定義されている場合は、この手順をスキップできます。

最後に denyAll を追加すると、変更のように見えます。

  • Java

  • Kotlin

  • XML

http
    .authorizeRequests((authorize) -> authorize
        .filterSecurityInterceptorOncePerRequest(true)
        .mvcMatchers("/app/**").hasRole("APP")
        // ...
    )
    // ...
http {
    authorizeRequests {
        filterSecurityInterceptorOncePerRequest = true
        authorize("/app/**", hasRole("APP"))
        // ...
    }
}
<http once-per-request="true">
    <intercept-url pattern="/app/*" access="hasRole('APP')"/>
    <!-- ... -->
</http>

to:

  • Java

  • Kotlin

  • XML

http
    .authorizeRequests((authorize) -> authorize
        .filterSecurityInterceptorOncePerRequest(true)
        .mvcMatchers("/app/**").hasRole("APP")
        // ...
        .anyRequest().denyAll()
    )
    // ...
http {
    authorizeRequests {
        filterSecurityInterceptorOncePerRequest = true
        authorize("/app/**", hasRole("APP"))
        // ...
        authorize(anyRequest, denyAll)
    }
}
<http once-per-request="true">
    <intercept-url pattern="/app/*" access="hasRole('APP')"/>
    <!-- ... -->
    <intercept-url pattern="/**" access="denyAll"/>
</http>

すでに authorizeHttpRequests に移行している場合、推奨される変更は同じです。

AuthorizationManager に切り替え

AuthorizationManager の使用を選択するには、Java または XML にそれぞれ authorizeHttpRequests または use-authorization-manager を使用できます。

変更:

  • Java

  • Kotlin

  • XML

http
    .authorizeRequests((authorize) -> authorize
        .filterSecurityInterceptorOncePerRequest(true)
        .mvcMatchers("/app/**").hasRole("APP")
        // ...
        .anyRequest().denyAll()
    )
    // ...
http {
    authorizeRequests {
        filterSecurityInterceptorOncePerRequest = true
        authorize("/app/**", hasRole("APP"))
        // ...
        authorize(anyRequest, denyAll)
    }
}
<http once-per-request="true">
    <intercept-url pattern="/app/*" access="hasRole('APP')"/>
    <!-- ... -->
    <intercept-url pattern="/**" access="denyAll"/>
</http>

to:

  • Java

  • Kotlin

  • XML

http
    .authorizeHttpRequests((authorize) -> authorize
        .shouldFilterAllDispatcherTypes(false)
        .mvcMatchers("/app/**").hasRole("APP")
        // ...
        .anyRequest().denyAll()
    )
    // ...
http {
    authorizeHttpRequests {
        shouldFilterAllDispatcherTypes = false
        authorize("/app/**", hasRole("APP"))
        // ...
        authorize(anyRequest, denyAll)
    }
}
<http filter-all-dispatcher-types="false" use-authorization-manager="true">
    <intercept-url pattern="/app/*" access="hasRole('APP')"/>
    <!-- ... -->
    <intercept-url pattern="/**" access="denyAll"/>
</http>

hasIpAddress から access(AuthorizationManager) への移行

hasIpAddress には、authorizeHttpRequests に相当する DSL がありません。

 hasIpAddress に呼び出されたものを AuthorizationManager を使用するように変更する必要があります。

まず、次のように IpAddressMatcher を構築します。

Java
IpAddressMatcher hasIpAddress = new IpAddressMatcher("127.0.0.1");

そして、これを次のように変更します。

Java
http
    .authorizeRequests((authorize) -> authorize
        .mvcMatchers("/app/**").hasIpAddress("127.0.0.1")
        // ...
        .anyRequest().denyAll()
    )
    // ...

これに:

Java
http
    .authorizeHttpRequests((authorize) -> authorize
        .requestMatchers("/app/**").access((authentication, context) ->
            new AuthorizationDecision(hasIpAddress.matches(context.getRequest()))
        // ...
        .anyRequest().denyAll()
    )
    // ...
IP アドレスによるセキュリティ保護は、そもそも非常に脆弱です。そのため、このサポートを authorizeHttpRequests に移植する予定はありません。

SpEL 式を AuthorizationManager に移行する

認可ルールについては、SpEL よりも Java の方がテストと保守が容易になる傾向があります。そのため、authorizeHttpRequests には String SpEL を宣言するメソッドがありません。

代わりに、独自の AuthorizationManager 実装を実装するか、WebExpressionAuthorizationManager を使用できます。

完全を期すために、両方のオプションが示されます。

まず、次の SpEL がある場合:

  • Java

  • Kotlin

http
    .authorizeRequests((authorize) -> authorize
        .filterSecurityInterceptorOncePerRequest(true)
        .mvcMatchers("/complicated/**").access("hasRole('ADMIN') || hasAuthority('SCOPE_read')")
        // ...
        .anyRequest().denyAll()
    )
    // ...
http {
    authorizeRequests {
        filterSecurityInterceptorOncePerRequest = true
        authorize("/complicated/**", access("hasRole('ADMIN') || hasAuthority('SCOPE_read')"))
        // ...
        authorize(anyRequest, denyAll)
    }
}

次に、次のように、Spring Security 認可プリミティブを使用して独自の AuthorizationManager を構成できます。

  • Java

  • Kotlin

http
    .authorizeHttpRequests((authorize) -> authorize
        .shouldFilterAllDispatcherTypes(false)
        .mvcMatchers("/complicated/**").access(anyOf(hasRole("ADMIN"), hasAuthority("SCOPE_read"))
        // ...
        .anyRequest().denyAll()
    )
    // ...
http {
    authorizeHttpRequests {
        shouldFilterAllDispatcherTypes = false
        authorize("/complicated/**", access(anyOf(hasRole("ADMIN"), hasAuthority("SCOPE_read"))
        // ...
        authorize(anyRequest, denyAll)
    }
}

または、次の方法で WebExpressionAuthorizationManager を使用できます。

  • Java

  • Kotlin

http
    .authorizeRequests((authorize) -> authorize
        .filterSecurityInterceptorOncePerRequest(true)
        .mvcMatchers("/complicated/**").access(
			new WebExpressionAuthorizationManager("hasRole('ADMIN') || hasAuthority('SCOPE_read')")
        )
        // ...
        .anyRequest().denyAll()
    )
    // ...
http {
    authorizeRequests {
        filterSecurityInterceptorOncePerRequest = true
        authorize("/complicated/**", access(
            WebExpressionAuthorizationManager("hasRole('ADMIN') || hasAuthority('SCOPE_read')"))
        )
        // ...
        authorize(anyRequest, denyAll)
    }
}

すべてのディスパッチャー型をフィルタリングするように切り替えます

Spring Security 5.8 以前は、リクエストごとに 1 回だけ認可を実行します。つまり、REQUEST の後に実行される FORWARD や INCLUDE などのディスパッチャー型は、デフォルトでは保護されていません。

Spring Security ですべてのディスパッチ型を保護することをお勧めします。そのため、6.0 では、Spring Security がこのデフォルトを変更します。

最後に、認可ルールを変更して、すべてのディスパッチャー型をフィルタリングします。

これを行うには、次のように変更する必要があります。

  • Java

  • Kotlin

  • XML

http
    .authorizeHttpRequests((authorize) -> authorize
        .shouldFilterAllDispatcherTypes(false)
        .mvcMatchers("/app/**").hasRole("APP")
        // ...
        .anyRequest().denyAll()
    )
    // ...
http {
    authorizeHttpRequests {
        shouldFilterAllDispatcherTypes = false
        authorize("/app/**", hasRole("APP"))
        // ...
        authorize(anyRequest, denyAll)
    }
}
<http filter-all-dispatcher-types="false" use-authorization-manager="true">
    <intercept-url pattern="/app/*" access="hasRole('APP')"/>
    <!-- ... -->
    <intercept-url pattern="/**" access="denyAll"/>
</http>

to:

  • Java

  • Kotlin

  • XML

http
    .authorizeHttpRequests((authorize) -> authorize
        .shouldFilterAllDispatcherTypes(true)
        .mvcMatchers("/app/**").hasRole("APP")
        // ...
        .anyRequest().denyAll()
    )
    // ...
http {
    authorizeHttpRequests {
        shouldFilterAllDispatcherTypes = true
        authorize("/app/**", hasRole("APP"))
        // ...
        authorize(anyRequest, denyAll)
    }
}
<http filter-all-dispatcher-types="true" use-authorization-manager="true">
    <intercept-url pattern="/app/*" access="hasRole('APP')"/>
    <!-- ... -->
    <intercept-url pattern="/**" access="denyAll"/>
</http>

また、FilterChainProxy は、すべてのディスパッチャー型に対しても登録する必要があります。Spring Boot を使用している場合は、spring.security.filter.dispatcher-types プロパティを変更して、すべてのディスパッチャー型を含める必要があります。

spring.security.filter.dispatcher-types=request,async,error,forward,include

AbstractSecurityWebApplicationInitializer使用している場合は、getSecurityDispatcherTypes メソッドをオーバーライドして、すべてのディスパッチャー型を返す必要があります。

  • Java

import org.springframework.security.web.context.*;

public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {

    @Override
    protected EnumSet<DispatcherType> getSecurityDispatcherTypes() {
        return EnumSet.of(DispatcherType.REQUEST, DispatcherType.ERROR, DispatcherType.ASYNC,
                DispatcherType.FORWARD, DispatcherType.INCLUDE);
    }

}

Spring MVC 使用時に FORWARD を許可する

ビュー名を解決するための Spring MVC を使用している場合は、FORWARD リクエストを認可する必要があります。これは、Spring MVC がビュー名と実際のビュー間のマッピングを検出すると、ビューへの転送を実行するためです。前のセクションで説明したように、Spring Security 6.0 はデフォルトで FORWARD リクエストに認可を適用します。

次の一般的な構成を検討してください。

  • Java

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests((authorize) -> authorize
            .shouldFilterAllDispatcherTypes(true)
            .requestMatchers("/").authenticated()
            .anyRequest().denyAll()
        )
        .formLogin((form) -> form
            .loginPage("/login")
            .permitAll()
        ));
    return http.build();
}

および次の同等の MVC ビューマッピング構成のいずれか:

  • Java

@Controller
public class MyController {

    @GetMapping("/login")
    public String login() {
        return "login";
    }

}
  • Java

@Configuration
public class MyWebMvcConfigurer implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/login").setViewName("login");
    }

}

どちらの構成でも、/login へのリクエストがある場合、Spring MVC はビュー login への転送を実行します。これは、デフォルト構成では src/main/resources/templates/login.html パスにあります。セキュリティ構成は /login へのリクエストを許可しますが、/templates/login.html のビューへの FORWARD リクエストを含め、他のすべてのリクエストは拒否されます。

これを修正するには、FORWARD リクエストを許可するように Spring Security を構成する必要があります。

  • Java

  • Kotlin

  • XML

http
    .authorizeHttpRequests((authorize) -> authorize
        .shouldFilterAllDispatcherTypes(true)
        .dispatcherTypeMatchers(DispatcherType.FORWARD).permitAll()
        .anyRequest().denyAll()
    )
    // ...
http {
    authorizeHttpRequests {
        shouldFilterAllDispatcherTypes = true
        authorize(DispatcherTypeRequestMatcher(DispatcherType.FORWARD), permitAll)
        authorize(anyRequest, denyAll)
    }
}
<http filter-all-dispatcher-types="true" use-authorization-manager="true">
    <intercept-url request-matcher-ref="forwardRequestMatcher" access="permitAll()" />
    <!-- ... -->
    <intercept-url pattern="/**" access="denyAll"/>
</http>

<bean name="forwardRequestMatcher" class="org.springframework.security.web.util.matcher.DispatcherTypeRequestMatcher">
    <constructor-arg value="FORWARD"/>
</bean>

カスタムフィルターセキュリティ AccessDecisionManager を置き換えます

アプリケーションには、カスタム AccessDecisionManager (Javadoc) または AccessDecisionVoter (Javadoc) 配置がある場合があります。準備戦略は、各取り決めの理由によって異なります。あなたの状況に最適な方法を見つけるために参照してください。

UnanimousBased を使っています

アプリケーションで UnanimousBased (Javadoc) を使用する場合、最初に AccessDecisionVoter を適応または置換してから、次のように AuthorizationManager を構築する必要があります。

  • Java

  • Kotlin

  • XML

@Bean
AuthorizationManager<RequestAuthorizationContext> requestAuthorization() {
    PolicyAuthorizationManager policy = ...;
    LocalAuthorizationManager local = ...;
    return AuthorizationManagers.allOf(policy, local);
}
@Bean
fun requestAuthorization(): AuthorizationManager<RequestAuthorizationContext> {
    val policy: PolicyAuthorizationManager = ...
    val local: LocalAuthorizationManager = ...
    return AuthorizationManagers.allOf(policy, local)
}
<bean id="requestAuthorization" class="org.springframework.security.authorization.AuthorizationManagers"
        factory-method="allOf">
    <constructor-arg>
        <util:list>
            <bean class="my.PolicyAuthorizationManager"/>
            <bean class="my.LocalAuthorizationManager"/>
        </util:list>
    </constructor-arg>
</bean>

次に、次のように DSL に接続します。

  • Java

  • Kotlin

  • XML

http
    .authorizeHttpRequests((authorize) -> authorize.anyRequest().access(requestAuthorization))
    // ...
http {
    authorizeHttpRequests {
        authorize(anyRequest, requestAuthorization)
    }
    // ...
}
<http authorization-manager-ref="requestAuthorization"/>

authorizeHttpRequests は、カスタム AuthorizationManager を任意の URL パターンに適用できるように設計されています。詳細については、リファレンスを参照してください。

AffirmativeBased を使っています

アプリケーションが AffirmativeBased (Javadoc) を使用している場合、次のように同等の AuthorizationManager (Javadoc) を作成できます。

  • Java

  • Kotlin

  • XML

@Bean
AuthorizationManager<RequestAuthorizationContext> requestAuthorization() {
    PolicyAuthorizationManager policy = ...;
    LocalAuthorizationManager local = ...;
    return AuthorizationManagers.anyOf(policy, local);
}
@Bean
fun requestAuthorization(): AuthorizationManager<RequestAuthorizationContext> {
    val policy: PolicyAuthorizationManager = ...
    val local: LocalAuthorizationManager = ...
    return AuthorizationManagers.anyOf(policy, local)
}
<bean id="requestAuthorization" class="org.springframework.security.authorization.AuthorizationManagers"
        factory-method="anyOf">
    <constructor-arg>
        <util:list>
            <bean class="my.PolicyAuthorizationManager"/>
            <bean class="my.LocalAuthorizationManager"/>
        </util:list>
    </constructor-arg>
</bean>

次に、次のように DSL に接続します。

  • Java

  • Kotlin

  • XML

http
    .authorizeHttpRequests((authorize) -> authorize.anyRequest().access(requestAuthorization))
    // ...
http {
    authorizeHttpRequests {
        authorize(anyRequest, requestAuthorization)
    }
    // ...
}
<http authorization-manager-ref="requestAuthorization"/>

authorizeHttpRequests は、カスタム AuthorizationManager を任意の URL パターンに適用できるように設計されています。詳細については、リファレンスを参照してください。

ConsensusBased を使っています

フレームワークが提供する ConsensusBased (Javadoc) に相当するものはありません。その場合、デリゲート AuthorizationManager のセットを考慮した複合 AuthorizationManager (Javadoc) を実装してください。

AuthorizationManager を実装したら、リファレンスマニュアルの詳細に従ってカスタム AuthorizationManager を追加してください

カスタム AccessDecisionVoter を使用しています

クラスを変更して AuthorizationManager (Javadoc) を実装するか、アダプターを作成する必要があります。

カスタム投票者が何をしているかを知らなければ、汎用ソリューションを推奨することはできません。ただし、例として、SecurityMetadataSource (Javadoc) AccessDecisionVoter (Javadoc) を anyRequest().authenticated() に適合させると、次のようになります。

  • Java

public final class AnyRequestAuthenticatedAuthorizationManagerAdapter implements AuthorizationManager<RequestAuthorizationContext> {
    private final SecurityMetadataSource metadata;
    private final AccessDecisionVoter voter;

    public PreAuthorizeAuthorizationManagerAdapter(SecurityExpressionHandler expressionHandler) {
        Map<RequestMatcher, List<ConfigAttribute>> requestMap = Collections.singletonMap(
                AnyRequestMatcher.INSTANCE, Collections.singletonList(new SecurityConfig("authenticated")));
        this.metadata = new DefaultFilterInvocationSecurityMetadataSource(requestMap);
        WebExpressionVoter voter = new WebExpressionVoter();
        voter.setExpressionHandler(expressionHandler);
        this.voter = voter;
    }

    public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext context) {
        List<ConfigAttribute> attributes = this.metadata.getAttributes(context);
        int decision = this.voter.vote(authentication.get(), invocation, attributes);
        if (decision == ACCESS_GRANTED) {
            return new AuthorizationDecision(true);
        }
        if (decision == ACCESS_DENIED) {
            return new AuthorizationDecision(false);
        }
        return null; // abstain
    }
}

AuthorizationManager を実装したら、リファレンスマニュアルの詳細に従ってカスタム AuthorizationManager を追加してください

GrantedAuthorityDefaults を使用する場合は、hasRole を hasAuthority に置き換えます。

現在、authorizeHttpRequests 内の hasRole メソッドは、authorizeRequests のような GrantedAuthorityDefaults Bean をサポートしていません。GrantedAuthorityDefaults を使用してロールのプレフィックスを変更する場合は、hasRole の代わりに hasAuthority を使用する必要があります。

例: 以下から変更する必要があります:

カスタムロールプレフィックスを含む authorizeRequests
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .authorizeRequests((authorize) -> authorize
            .anyRequest().hasRole("ADMIN")
        );
    return http.build();
}

@Bean
public GrantedAuthorityDefaults grantedAuthorityDefaults() {
    return new GrantedAuthorityDefaults("MYPREFIX_");
}

to:

hasAuthority とカスタムロールプレフィックスを使用した authorizeHttpRequests
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests((authorize) -> authorize
            .anyRequest().hasAuthority("MYPREFIX_ADMIN")
        );
    return http.build();
}

これは将来的にサポートされる予定です。詳細については、gh-13227 [GitHub] (英語) を参照してください。

オプトアウトの手順

問題が発生した場合は、最適なオプトアウト動作について次のシナリオを参照してください。

すべてのディスパッチャー型を保護することはできません

すべてのディスパッチャー型を保護できない場合は、最初に次のように、認可を必要としないディスパッチャー型を宣言してみてください。

  • Java

  • Kotlin

  • XML

http
    .authorizeHttpRequests((authorize) -> authorize
        .shouldFilterAllDispatcherTypes(true)
        .dispatcherTypeMatchers(FORWARD, INCLUDE).permitAll()
        .mvcMatchers("/app/**").hasRole("APP")
        // ...
        .anyRequest().denyAll()
    )
    // ...
http {
    authorizeHttpRequests {
        shouldFilterAllDispatcherTypes = true
        authorize(DispatcherTypeRequestMatcher(FORWARD, INCLUDE), permitAll)
        authorize("/app/**", hasRole("APP"))
        // ...
        authorize(anyRequest, denyAll)
    }
}
<http filter-all-dispatcher-types="true" use-authorization-manager="true">
    <intercept-url request-matcher-ref="dispatchers"/>
    <intercept-url pattern="/app/*" access="hasRole('APP')"/>
    <!-- ... -->
    <intercept-url pattern="/**" access="denyAll"/>
</http>

<bean id="dispatchers" class="org.springframework.security.web.util.matcher.DispatcherTypeRequestMatcher">
    <constructor-arg>
        <util:list value-type="javax.servlet.DispatcherType">
            <value>FORWARD</value>
            <value>INCLUDE</value>
        </util:list>
    </constructor-arg>
</bean>

または、それが機能しない場合は、filter-all-dispatcher-types および filterAllDispatcherTypes を false に設定することで、明示的に動作をオプトアウトできます。

  • Java

  • Kotlin

  • XML

http
    .authorizeHttpRequests((authorize) -> authorize
        .filterAllDispatcherTypes(false)
        .mvcMatchers("/app/**").hasRole("APP")
        // ...
    )
    // ...
http {
    authorizeHttpRequests {
        filterAllDispatcherTypes = false
        authorize("/messages/**", hasRole("APP"))
        // ...
    }
}
<http filter-all-dispatcher-types="false" use-authorization-manager="true">
    <intercept-url pattern="/app/*" access="hasRole('APP')"/>
    <!-- ... -->
</http>

または、まだ authorizeRequests または use-authorization-manager="false" を使用している場合は、oncePerRequest を true に設定します。

  • Java

  • Kotlin

  • XML

http
    .authorizeRequests((authorize) -> authorize
        .filterSecurityInterceptorOncePerRequest(true)
        .mvcMatchers("/app/**").hasRole("APP")
        // ...
    )
    // ...
http {
    authorizeRequests {
        filterSecurityInterceptorOncePerRequest = true
        authorize("/messages/**", hasRole("APP"))
        // ...
    }
}
<http once-per-request="true" use-authorization-manager="false">
    <intercept-url pattern="/app/*" access="hasRole('APP')"/>
    <!-- ... -->
</http>

すべてのリクエストに対して認可ルールを宣言することはできません

denyAll の anyRequest 認可ルールの設定に問題がある場合は、代わりに permitAll (Javadoc) を次のように使用してください。

  • Java

  • Kotlin

  • XML

http
    .authorizeHttpReqeusts((authorize) -> authorize
        .mvcMatchers("/app/*").hasRole("APP")
        // ...
        .anyRequest().permitAll()
    )
http {
    authorizeHttpRequests {
        authorize("/app*", hasRole("APP"))
        // ...
        authorize(anyRequest, permitAll)
    }
}
<http>
    <intercept-url pattern="/app/*" access="hasRole('APP')"/>
    <!-- ... -->
    <intercept-url pattern="/**" access="permitAll"/>
</http>

SpEL または AccessDecisionManager を移行できません

SpEL や AccessDecisionManager に問題がある場合、または <http> または authorizeRequests で使用し続ける必要がある他の機能がある場合は、次のことを試してください。

まず、まだ authorizeRequests が必要な場合は、引き続き使用してください。非推奨ですが、6.0 では削除されていません。

次に、カスタム access-decision-manager-ref がまだ必要な場合、または AuthorizationManager をオプトアウトするその他の理由がある場合は、次のようにします。

Xml
<http use-authorization-manager="false">
    <intercept-url pattern="/app/*" access="hasRole('APP')"/>
    <!-- ... -->
</http>