認可アーキテクチャ

このセクションでは、認可に適用される Spring Security アーキテクチャーについて説明します。

オーソリティー

Authentication は、すべての Authentication 実装が GrantedAuthority オブジェクトのリストを格納する方法について説明しています。これらは、プリンシパルに付与された権限を表します。GrantedAuthority オブジェクトは、AuthenticationManager によって Authentication オブジェクトに挿入され、後で認可決定を行うときに AccessDecisionManager インスタンスによって読み取られます。

GrantedAuthority インターフェースには 1 つの方法しかありません。

String getAuthority();

このメソッドは、GrantedAuthority の正確な String 表現を取得するために AuthorizationManager インスタンスによって使用されます。表現を String として返すことにより、ほとんどの AuthorizationManager 実装で GrantedAuthority を簡単に「読み取る」ことができます。GrantedAuthority を String として正確に表現できない場合、GrantedAuthority は「複雑」とみなされ、getAuthority() は null を返さなければなりません。

複雑な GrantedAuthority の例としては、さまざまな顧客アカウント番号に適用される操作と権限のしきい値のリストを保存する実装が挙げられます。この複雑な GrantedAuthority を String として表現するのは非常に困難です。その結果、getAuthority() メソッドは null を返す必要があります。これは、AuthorizationManager に対して、その内容を理解するために特定の GrantedAuthority 実装をサポートする必要があることを示します。

Spring Security には、1 つの具体的な GrantedAuthority 実装が含まれています: SimpleGrantedAuthority。この実装により、ユーザー指定の String を GrantedAuthority に変換できます。セキュリティアーキテクチャに含まれるすべての AuthenticationProvider インスタンスは、SimpleGrantedAuthority を使用して Authentication オブジェクトにデータを入力します。

デフォルトでは、ロールベースの認可ルールにはプレフィックスとして ROLE_ が含まれます。これは、セキュリティコンテキストに "USER" のロールを持たせることを要求する認可ルールがある場合、Spring Security はデフォルトで "ROLE_USER" を返す GrantedAuthority#getAuthority を検索することを意味します。

GrantedAuthorityDefaults を使用してこれをカスタマイズできます。GrantedAuthorityDefaults は、ロールベースの認可ルールに使用するプレフィックスをカスタマイズできるようにするために存在します。

次のように、GrantedAuthorityDefaults Bean を公開することにより、異なるプレフィックスを使用するように認可ルールを構成できます。

カスタム MethodSecurityExpressionHandler
  • Java

  • Kotlin

  • XML

@Bean
static GrantedAuthorityDefaults grantedAuthorityDefaults() {
	return new GrantedAuthorityDefaults("MYPREFIX_");
}
companion object {
	@Bean
	fun grantedAuthorityDefaults() : GrantedAuthorityDefaults {
		return GrantedAuthorityDefaults("MYPREFIX_");
	}
}
<bean id="grantedAuthorityDefaults" class="org.springframework.security.config.core.GrantedAuthorityDefaults">
	<constructor-arg value="MYPREFIX_"/>
</bean>

static メソッドを使用して GrantedAuthorityDefaults を公開し、Spring が Spring Security のメソッドセキュリティ @Configuration クラスを初期化する前に GrantedAuthorityDefaults を公開するようにします。

呼び出し処理

Spring Security は、メソッド呼び出しや Web リクエストなどの安全なオブジェクトへのアクセスを制御するインターセプターを提供します。呼び出しの続行が許可されるかどうかに関する呼び出し前の決定は、AuthorizationManager インスタンスによって行われます。また、指定された値を返すかどうかの呼び出し後の決定は、AuthorizationManager インスタンスによって行われます。

AuthorizationManager

AuthorizationManager は両方の AccessDecisionManager および AccessDecisionVoter に取って代わります。

AccessDecisionManager または AccessDecisionVoter をカスタマイズするアプリケーションは、AuthorizationManager使用に変更することをお勧めします。

AuthorizationManager は、Spring Security のリクエストベースメソッドベースメッセージベースの認可コンポーネントによって呼び出され、最終的なアクセス制御の決定を行います。AuthorizationManager インターフェースには 2 つのメソッドが含まれています。

AuthorizationDecision check(Supplier<Authentication> authentication, Object secureObject);

default void verify(Supplier<Authentication> authentication, Object secureObject)
        throws AccessDeniedException {
    // ...
}

AuthorizationManager の check メソッドには、認可の決定を行うために必要なすべての関連情報が渡されます。特に、セキュア Object を渡すと、実際のセキュアオブジェクト呼び出しに含まれる引数をインスペクションできます。例: 安全なオブジェクトが MethodInvocation であると仮定しましょう。MethodInvocation に任意の Customer 引数を照会し、AuthorizationManager に何らかのセキュリティロジックを実装して、プリンシパルがその顧客での操作を認可されていることを確認するのは簡単です。実装は、アクセスが認可された場合は正の AuthorizationDecision を返し、アクセスが拒否された場合は負の AuthorizationDecision を返し、決定を控えた場合は null の AuthorizationDecision を返すことが期待されます。

verify は check を呼び出し、その後、負の AuthorizationDecision の場合は AccessDeniedException をスローします。

デリゲートベースの AuthorizationManager 実装

ユーザーは独自の AuthorizationManager を実装して認証のすべての側面を制御できますが、Spring Security には、個々の AuthorizationManager と連携できる委譲 AuthorizationManager が付属しています。

RequestMatcherDelegatingAuthorizationManager は、リクエストを最も適切なデリゲート AuthorizationManager と照合します。メソッドのセキュリティには、AuthorizationManagerBeforeMethodInterceptor および AuthorizationManagerAfterMethodInterceptor を使用できます。

AuthorizationManager の実装は、関連するクラスを示しています。

authorizationhierarchy
図 1: AuthorizationManager の実装

このアプローチを使用すると、認可の決定時に AuthorizationManager 実装の構成をポーリングできます。

AuthorityAuthorizationManager

Spring Security で提供される最も一般的な AuthorizationManager は AuthorityAuthorizationManager です。現在の Authentication を検索するために、特定の権限のセットで構成されます。Authentication に構成済みの権限のいずれかが含まれている場合は、正の AuthorizationDecision が返されます。それ以外の場合は、負の AuthorizationDecision を返します。

AuthenticatedAuthorizationManager

別のマネージャーは AuthenticatedAuthorizationManager です。これを使用して、匿名の完全認証済みユーザーと remember-me 認証済みユーザーを区別できます。多くのサイトでは、remember-me 認証で特定の制限付きアクセスが許可されていますが、フルアクセスを取得するには、ユーザーがログインして ID を確認する必要があります。

AuthorizationManagers

AuthorizationManagers (Javadoc) には、個々の AuthorizationManager をより洗練された式に構成するための便利な静的ファクトリもあります。

カスタム認可マネージャー

もちろん、カスタム AuthorizationManager を実装することもでき、必要なほぼすべてのアクセス制御ロジックをその中に入れることができます。アプリケーションに固有の場合もあれば(ビジネスロジック関連)、セキュリティ管理ロジックを実装する場合もあります。例: Open PolicyAgent または独自の認証データベースを照会できる実装を作成できます。

Spring Web サイトには、従来の AccessDecisionVoter を使用して、アカウントが一時停止されているユーザーのリアルタイムアクセスを拒否する方法を説明したブログ記事 (英語) があります。代わりに AuthorizationManager を実装することで、同じ結果を達成できます。

AccessDecisionManager と AccessDecisionVoters の適応

AuthorizationManager の前に、Spring Security は AccessDecisionManager および AccessDecisionVoter を公開しました。

古いアプリケーションを移行する場合のように、AccessDecisionManager または AccessDecisionVoter を呼び出す AuthorizationManager を導入することが望ましい場合があります。

既存の AccessDecisionManager を呼び出すには、次のようにします。

AccessDecisionManager の適応
  • Java

@Component
public class AccessDecisionManagerAuthorizationManagerAdapter implements AuthorizationManager {
    private final AccessDecisionManager accessDecisionManager;
    private final SecurityMetadataSource securityMetadataSource;

    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, Object object) {
        try {
            Collection<ConfigAttribute> attributes = this.securityMetadataSource.getAttributes(object);
            this.accessDecisionManager.decide(authentication.get(), object, attributes);
            return new AuthorizationDecision(true);
        } catch (AccessDeniedException ex) {
            return new AuthorizationDecision(false);
        }
    }

    @Override
    public void verify(Supplier<Authentication> authentication, Object object) {
        Collection<ConfigAttribute> attributes = this.securityMetadataSource.getAttributes(object);
        this.accessDecisionManager.decide(authentication.get(), object, attributes);
    }
}

そしてそれをあなたの SecurityFilterChain に接続します。

または、AccessDecisionVoter のみを呼び出すには、次のようにします。

AccessDecisionVoter の適応
  • Java

@Component
public class AccessDecisionVoterAuthorizationManagerAdapter implements AuthorizationManager {
    private final AccessDecisionVoter accessDecisionVoter;
    private final SecurityMetadataSource securityMetadataSource;

    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, Object object) {
        Collection<ConfigAttribute> attributes = this.securityMetadataSource.getAttributes(object);
        int decision = this.accessDecisionVoter.vote(authentication.get(), object, attributes);
        switch (decision) {
        case ACCESS_GRANTED:
            return new AuthorizationDecision(true);
        case ACCESS_DENIED:
            return new AuthorizationDecision(false);
        }
        return null;
    }
}

そしてそれをあなたの SecurityFilterChain に接続します。

階層的なロール

アプリケーションの特定のロールが他のロールを自動的に「含める」ことが一般的な要件です。例: 「管理者」と「ユーザー」のロールの概念を持つアプリケーションでは、管理者が通常のユーザーができることをすべて行えるようにしたい場合があります。これを実現するには、すべての管理ユーザーにも「ユーザー」ロールが割り当てられていることを確認します。または、"user" ロールに "admin" ロールも含める必要があるすべてのアクセス制約を変更できます。アプリケーションにさまざまなロールがある場合、これは非常に複雑になる可能性があります。

ロール階層を使用すると、どのロール (または権限) に他のロールを含めるかを設定できます。これは、HttpSecurity#authorizeHttpRequests のフィルターベースの認可と、事前リアクティブアノテーションの DefaultMethodSecurityExpressionHandler@Secured の SecuredAuthorizationManager、および JSR-250 アノテーションの Jsr250AuthorizationManager によるメソッドベースの認可でサポートされています。次の方法で、これらすべての動作を一度に設定できます。

階層的なロールの構成
  • Java

  • XML

@Bean
static RoleHierarchy roleHierarchy() {
    return RoleHierarchyImpl.withDefaultRolePrefix()
        .role("ADMIN").implies("STAFF")
        .role("STAFF").implies("USER")
        .role("USER").implies("GUEST")
        .build();
}

// and, if using pre-post method security also add
@Bean
static MethodSecurityExpressionHandler methodSecurityExpressionHandler(RoleHierarchy roleHierarchy) {
	DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
	expressionHandler.setRoleHierarchy(roleHierarchy);
	return expressionHandler;
}
<bean id="roleHierarchy"
		class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl" factory-method="fromHierarchy">
	<constructor-arg>
		<value>
			ROLE_ADMIN > ROLE_STAFF
			ROLE_STAFF > ROLE_USER
			ROLE_USER > ROLE_GUEST
		</value>
	</constructor-arg>
</bean>

<!-- and, if using method security also add -->
<bean id="methodSecurityExpressionHandler"
        class="org.springframework.security.access.expression.method.MethodSecurityExpressionHandler">
    <property ref="roleHierarchy"/>
</bean>

ここでは、階層 ROLE_ADMIN ⇒ ROLE_STAFF ⇒ ROLE_USER ⇒ ROLE_GUEST に 4 つのロールがあります。ROLE_ADMIN で認証されたユーザーは、フィルターまたはメソッドベースのルールに対してセキュリティ制約が評価されるときに、4 つのロールすべてを持っているかのように動作します。

> シンボルは「含む」という意味であると考えられます。

ロール階層は、アプリケーションのアクセス制御構成データを簡素化したり、ユーザーに割り当てる必要のある権限の数を減らしたりする便利な手段を提供します。より複雑な要件については、アプリケーションに必要な特定のアクセス権とユーザーに割り当てられているロール間の論理マッピングを定義し、ユーザー情報をロードするときに 2 つを変換することができます。

レガシー認証コンポーネント

Spring Security には、いくつかのレガシーコンポーネントが含まれています。それらはまだ削除されていないため、ドキュメントは歴史的な目的で含まれています。推奨される代替は上記のとおりです。

AccessDecisionManager

AccessDecisionManager は AbstractSecurityInterceptor によって呼び出され、最終的なアクセス制御の決定を行います。AccessDecisionManager インターフェースには 3 つのメソッドが含まれています。

void decide(Authentication authentication, Object secureObject,
	Collection<ConfigAttribute> attrs) throws AccessDeniedException;

boolean supports(ConfigAttribute attribute);

boolean supports(Class clazz);

AccessDecisionManager の decide メソッドには、権限決定を行うために必要なすべての関連情報が渡されます。特に、セキュア Object を渡すと、実際のセキュアオブジェクト呼び出しに含まれる引数をインスペクションできます。例: セキュアオブジェクトが MethodInvocation であると想定します。MethodInvocation に任意の Customer 引数を照会してから、AccessDecisionManager に何らかのセキュリティロジックを実装して、プリンシパルがその顧客での操作を認可されていることを確認できます。アクセスが拒否された場合、実装は AccessDeniedException をスローすることが期待されます。

supports(ConfigAttribute) メソッドは、起動時に AbstractSecurityInterceptor によって呼び出され、AccessDecisionManager が渡された ConfigAttribute を処理できるかどうかを判別します。supports(Class) メソッドは、セキュリティインターセプターの実装によって呼び出され、構成された AccessDecisionManager がセキュリティインターセプターが提示する型のセキュアオブジェクトをサポートするようにします。

投票ベースの AccessDecisionManager 実装

ユーザーは独自の AccessDecisionManager を実装して認可のすべての側面を制御できますが、Spring Security には投票に基づくいくつかの AccessDecisionManager 実装が含まれています。投票決定マネージャーは、関連するクラスについて説明しています。

次のイメージは、AccessDecisionManager インターフェースを示しています。

access decision voting
図 2: 投票決定マネージャー

このアプローチを使用することにより、一連の AccessDecisionVoter 実装が認可決定でポーリングされます。次に、AccessDecisionManager は、投票の評価に基づいて AccessDeniedException をスローするかどうかを決定します。

AccessDecisionVoter インターフェースには 3 つのメソッドがあります。

int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attrs);

boolean supports(ConfigAttribute attribute);

boolean supports(Class clazz);

具体的な実装は int を返し、可能な値は ACCESS_ABSTAINACCESS_DENIEDACCESS_GRANTED という名前の AccessDecisionVoter 静的フィールドに反映されます。投票の実装は、認可の決定について意見がない場合、ACCESS_ABSTAIN を返します。意見がある場合は、ACCESS_DENIED または ACCESS_GRANTED のいずれかを返す必要があります。

投票を集計するために Spring Security で提供される 3 つの具体的な AccessDecisionManager 実装があります。ConsensusBased 実装は、非棄権投票のコンセンサスに基づいてアクセスを許可または拒否します。プロパティは、投票が等しい場合、またはすべての投票が棄権された場合の動作を制御するために提供されています。AffirmativeBased 実装は、1 つ以上の ACCESS_GRANTED 投票が受信された場合にアクセスを許可します(つまり、少なくとも 1 つの許可投票があった場合、拒否投票は無視されます)。ConsensusBased の実装と同様に、すべての投票者が棄権した場合の動作を制御するパラメーターがあります。UnanimousBased プロバイダーは、棄権を無視して、アクセスを許可するために全会一致の ACCESS_GRANTED 投票を期待しています。ACCESS_DENIED 投票がある場合は、アクセスを拒否します。他の実装と同様に、すべての投票者が棄権した場合の動作を制御するパラメーターがあります。

投票を異なる方法で集計するカスタム AccessDecisionManager を実装できます。例: 特定の AccessDecisionVoter からの投票は追加の重み付けを受け取る可能性がありますが、特定の投票者からの拒否投票は拒否権の影響を与える可能性があります。

RoleVoter

Spring Security で提供される最も一般的に使用される AccessDecisionVoter は RoleVoter です。これは、構成属性をロール名として扱い、ユーザーにそのロールが割り当てられている場合にアクセスを許可するために投票します。

ConfigAttribute が ROLE_ プレフィックスで始まるかどうかを投票します。ROLE_ プレフィックスで始まる 1 つ以上の ConfigAttributes と正確に等しい String 表現(getAuthority() メソッドから)を返す GrantedAuthority がある場合、アクセスを許可することに投票します。ROLE_ で始まる ConfigAttribute の完全一致がない場合、RoleVoter はアクセスを拒否するために投票します。ConfigAttribute が ROLE_ で始まらない場合、投票者は棄権します。

AuthenticatedVoter

暗黙のうちに見たもう 1 つの投票者は、AuthenticatedVoter です。これを使用して、匿名ユーザー、完全認証ユーザー、覚えているユーザーを区別できます。多くのサイトでは、remember-me 認証で特定の制限付きアクセスが許可されていますが、ユーザーはフルアクセスのためにログインして ID を確認する必要があります。

IS_AUTHENTICATED_ANONYMOUSLY 属性を使用して匿名アクセスを許可した場合、この属性は AuthenticatedVoter によって処理されていました。詳細については、AuthenticatedVoter (Javadoc) を参照してください。

カスタム投票者

カスタム AccessDecisionVoter を実装して、必要なアクセス制御ロジックをほぼすべて配置することもできます。アプリケーションに固有の場合もあれば(ビジネスロジック関連)、セキュリティ管理ロジックを実装する場合もあります。例: Spring Web サイトで、アカウントが停止されているユーザーへのリアルタイムのアクセスを拒否するために投票者を使用する方法を説明するブログ記事を見 (英語) つけることができます。

after invocation
図 3: 呼び出し実装後

Spring Security の他の多くの部分と同様に、AfterInvocationManager には、AfterInvocationProvider のリストをポーリングする単一の具象実装 AfterInvocationProviderManager があります。各 AfterInvocationProvider は、戻りオブジェクトを変更するか、AccessDeniedException をスローできます。実際、前のプロバイダーの結果がリスト内の次のプロバイダーに渡されるため、複数のプロバイダーがオブジェクトを変更できます。

AfterInvocationManager を使用している場合は、MethodSecurityInterceptor の AccessDecisionManager が操作を許可できるようにする構成属性が引き続き必要です。一般的な Spring Security に含まれる AccessDecisionManager 実装を使用している場合、特定のセキュアなメソッド呼び出しに定義された構成属性がないと、各 AccessDecisionVoter は投票を控えます。次に、AccessDecisionManager プロパティ "allowIfAllAbstainDecisions" が false の場合、AccessDeniedException がスローされます。この潜在的な課題を回避するには、(i) "allowIfAllAbstainDecisions" を true に設定するか(これは一般的に推奨されません)、または(ii) AccessDecisionVoter がアクセスを許可するために投票する構成属性が少なくとも 1 つあることを確認します。この後者の(推奨)アプローチは、通常 ROLE_USER または ROLE_AUTHENTICATED 構成属性によって実現されます。