11. 認可

Spring Security の高度な認証機能は、その人気の理由の 1 つです。Spring Security が提供するメカニズムやプロバイダーを使用するか、あるいはコンテナーや他の Spring 以外のセキュリティ認証機関と統合するかに関係なく、アプリケーション内で認可サービスを一貫した簡単な方法で使用することができます。

このパートでは、パート I で紹介されたさまざまな  AbstractSecurityInterceptor の実装について説明します。次に、ドメインアクセス制御リストを使用して認可を微調整する方法について説明します。

11.1 認可アーキテクチャ

11.1.1 権限

技術概要で見たように、すべての  Authentication 実装は  GrantedAuthority オブジェクトのリストを保存します。これらは、プリンシパルに付与された権限を表します。 GrantedAuthority オブジェクトは  AuthenticationManager によって  Authentication オブジェクトに挿入され、あとで  AccessDecisionManager によって認可の決定を行うときに読み取られます。

GrantedAuthority は、メソッドが 1 つだけのインターフェースです。

String getAuthority();

この方法により、 AccessDecisionManager は  GrantedAuthority の正確な  String 表現を取得できます。表現を  String として返すことにより、ほとんどの  AccessDecisionManager が  GrantedAuthority を簡単に「読み取る」ことができます。 GrantedAuthority を  String として正確に表すことができない場合、 GrantedAuthority は「複雑」と見なされ、 getAuthority() は  null を返す必要があります。

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

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

11.1.2 呼び出し前の処理

技術概要の章でも見たように、Spring Security はメソッド呼び出しや Web リクエストなどの安全なオブジェクトへのアクセスを制御するインターセプターを提供します。呼び出しの続行を許可するかどうかの呼び出し前の決定は、 AccessDecisionManager によって行われます。

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 であると仮定しましょう。 Customer 引数について  MethodInvocation を照会し、 AccessDecisionManager に何らかのセキュリティロジックを実装して、プリンシパルがその顧客での操作を認可されていることを確認するのは簡単です。アクセスが拒否された場合、実装は  AccessDeniedException をスローすることが期待されています。

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

投票ベースの AccessDecisionManager 実装

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

図 11.1: 投票決定マネージャー

access decision voting

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

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

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

boolean supports(ConfigAttribute attribute);

boolean supports(Class clazz);

具体的な実装は  int を返し、可能な値は  AccessDecisionVoter 静的フィールド  ACCESS_ABSTAIN ACCESS_DENIED ACCESS_GRANTED に反映されます。認可の決定について意見がない場合、投票の実装は  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 と正確に等しい( getAuthority() メソッドを介して)  String 表現を返す  GrantedAuthority がある場合、アクセスを許可するために投票します。 ROLE_ で始まる  ConfigAttribute の正確な一致がない場合、 RoleVoter はアクセスを拒否するために投票します。 ConfigAttribute が  ROLE_ で始まっていない場合、投票者は棄権します。

AuthenticatedVoter

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

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

カスタム投票者

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

11.1.3 呼び出し処理後

AccessDecisionManager は、セキュアオブジェクトの呼び出しを続行する前に  AbstractSecurityInterceptor によって呼び出されますが、一部のアプリケーションでは、セキュアオブジェクトの呼び出しによって実際に返されるオブジェクトを変更する方法が必要です。これを実現するために独自の AOP 懸念事項を簡単に実装できますが、Spring Security は ACL 機能と統合するいくつかの具体的な実装を持つ便利なフックを提供します。

図 11.2: “ 呼び出し実装後 ” は、Spring Security の  AfterInvocationManager とその具体的な実装を示しています。

図 11.2: 呼び出し実装後

after invocation

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 構成属性によって実現されます。

11.1.4 階層的なロール

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

ロール階層を使用すると、他のロール(または権限)を含めるロールを構成できます。Spring Security の RoleVoter の拡張バージョンである  RoleHierarchyVoter は  RoleHierarchy で構成され、そこからユーザーが割り当てられているすべての「到達可能な権限」を取得します。典型的な構成は次のようになります。

<bean id="roleVoter" class="org.springframework.security.access.vote.RoleHierarchyVoter">
    <constructor-arg ref="roleHierarchy" />
</bean>
<bean id="roleHierarchy"
        class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl">
    <property name="hierarchy">
        <value>
            ROLE_ADMIN > ROLE_STAFF
            ROLE_STAFF > ROLE_USER
            ROLE_USER > ROLE_GUEST
        </value>
    </property>
</bean>

ここでは、階層  ROLE_ADMIN ⇒ ROLE_STAFF ⇒ ROLE_USER ⇒ ROLE_GUEST に 4 つのロールがあります。 ROLE_ADMIN で認証されたユーザーは、上記の  RoleHierarchyVoter で構成された  AccessDecisionManager に対してセキュリティ制約が評価された場合、4 つのロールすべてを持っているかのように動作します。 > シンボルは、「含む」という意味と考えることができます。

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

11.2 セキュアなオブジェクトの実装

11.2.1 AOP Alliance(MethodInvocation)セキュリティインターセプター

Spring Security 2.0 以前は、 MethodInvocation を固定するには、非常に多くのボイラープレート構成が必要でした。メソッドセキュリティの推奨されるアプローチは、名前空間の構成を使用 すること です。このようにして、メソッドセキュリティインフラストラクチャ Bean が自動的に設定されるため、実装クラスについて知る必要はありません。ここで関係するクラスの概要を簡単に説明します。

メソッドのセキュリティは、 MethodInvocation を保護する  MethodSecurityInterceptor を使用して実施されます。構成アプローチによっては、インターセプターは単一の Bean に固有であるか、複数の Bean 間で共有される場合があります。インターセプターは、 MethodSecurityMetadataSource インスタンスを使用して、特定のメソッド呼び出しに適用される構成属性を取得します。 MapBasedMethodSecurityMetadataSource は、メソッド名(ワイルドカードを使用可能)をキーとする構成属性を格納するために使用され、 <intercept-methods> または  <protect-point> 要素を使用してアプリケーションコンテキストで属性が定義されるときに内部的に使用されます。他の実装を使用して、アノテーションベースの構成を処理します。

明示的な MethodSecurityInterceptor 設定

もちろん、Spring AOP のプロキシメカニズムの 1 つで使用するために、アプリケーションコンテキストで  MethodSecurityIterceptor を直接構成できます。

<bean id="bankManagerSecurity" class=
    "org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="afterInvocationManager" ref="afterInvocationManager"/>
<property name="securityMetadataSource">
    <sec:method-security-metadata-source>
    <sec:protect method="com.mycompany.BankManager.delete*" access="ROLE_SUPERVISOR"/>
    <sec:protect method="com.mycompany.BankManager.getBalance" access="ROLE_TELLER,ROLE_SUPERVISOR"/>
    </sec:method-security-metadata-source>
</property>
</bean>

11.2.2 AspectJ(JoinPoint)セキュリティインターセプター

AspectJ セキュリティインターセプターは、前のセクションで説明した AOP Alliance セキュリティインターセプターと非常によく似ています。実際、このセクションの違いについてのみ説明します。

AspectJ インターセプターの名前は  AspectJSecurityInterceptor です。Spring アプリケーションコンテキストに依存してプロキシ経由でセキュリティインターセプターを織り込む AOP Alliance セキュリティインターセプターとは異なり、 AspectJSecurityInterceptor は AspectJ コンパイラー経由で織り込まれています。同じアプリケーションで両方の型のセキュリティインターセプターを使用することは珍しくありません。 AspectJSecurityInterceptor はドメインオブジェクトインスタンスのセキュリティに使用され、AOP Alliance  MethodSecurityInterceptor はサービスレイヤーのセキュリティに使用されます。

まず、 AspectJSecurityInterceptor が Spring アプリケーションコンテキストでどのように構成されているかを考えてみましょう。

<bean id="bankManagerSecurity" class=
    "org.springframework.security.access.intercept.aspectj.AspectJMethodSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="afterInvocationManager" ref="afterInvocationManager"/>
<property name="securityMetadataSource">
    <sec:method-security-metadata-source>
    <sec:protect method="com.mycompany.BankManager.delete*" access="ROLE_SUPERVISOR"/>
    <sec:protect method="com.mycompany.BankManager.getBalance" access="ROLE_TELLER,ROLE_SUPERVISOR"/>
    </sec:method-security-metadata-source>
</property>
</bean>

ご覧のとおり、クラス名を除き、 AspectJSecurityInterceptor は AOP Alliance のセキュリティインターセプターとまったく同じです。実際、 SecurityMetadataSource は AOP ライブラリ固有のクラスではなく  java.lang.reflect.Method で動作するため、2 つのインターセプターは同じ  securityMetadataSource を共有できます。もちろん、アクセスの決定は、関連する AOP ライブラリ固有の呼び出し(つまり  MethodInvocation または  JoinPoint)にアクセスできるため、アクセスの決定(メソッドの引数など)を行う際に追加の条件の範囲を考慮することができます。

次に、AspectJ  aspect を定義する必要があります。例:

package org.springframework.security.samples.aspectj;

import org.springframework.security.access.intercept.aspectj.AspectJSecurityInterceptor;
import org.springframework.security.access.intercept.aspectj.AspectJCallback;
import org.springframework.beans.factory.InitializingBean;

public aspect DomainObjectInstanceSecurityAspect implements InitializingBean {

    private AspectJSecurityInterceptor securityInterceptor;

    pointcut domainObjectInstanceExecution(): target(PersistableEntity)
        && execution(public * *(..)) && !within(DomainObjectInstanceSecurityAspect);

    Object around(): domainObjectInstanceExecution() {
        if (this.securityInterceptor == null) {
            return proceed();
        }

        AspectJCallback callback = new AspectJCallback() {
            public Object proceedWithObject() {
                return proceed();
            }
        };

        return this.securityInterceptor.invoke(thisJoinPoint, callback);
    }

    public AspectJSecurityInterceptor getSecurityInterceptor() {
        return securityInterceptor;
    }

    public void setSecurityInterceptor(AspectJSecurityInterceptor securityInterceptor) {
        this.securityInterceptor = securityInterceptor;
    }

    public void afterPropertiesSet() throws Exception {
        if (this.securityInterceptor == null)
            throw new IllegalArgumentException("securityInterceptor required");
        }
    }
}

上記の例では、セキュリティインターセプターは  PersistableEntity のすべてのインスタンスに適用されますが、これは示されていない抽象クラスです(他のクラスまたは  pointcut 式を使用できます)。 proceed(); ステートメントは  around() 本体内でのみ特別な意味を持つため、好奇心の強い人には  AspectJCallback が必要です。 AspectJSecurityInterceptor は、ターゲットオブジェクトを継続させたいときに、この匿名  AspectJCallback クラスを呼び出します。

アスペクトをロードして  AspectJSecurityInterceptor に接続するように Spring を構成する必要があります。これを実現する Bean 宣言を以下に示します。

<bean id="domainObjectInstanceSecurityAspect"
    class="security.samples.aspectj.DomainObjectInstanceSecurityAspect"
    factory-method="aspectOf">
<property name="securityInterceptor" ref="bankManagerSecurity"/>
</bean>

以上です! これで、適切と思われる手段( new Person(); など)を使用して、アプリケーション内のどこからでも Bean を作成でき、セキュリティインターセプターが適用されます。

11.3 式ベースのアクセス制御

Spring Security 3.0 は、以前に見られた構成属性およびアクセス決定投票者の単純な使用に加えて、Spring EL 式を認可メカニズムとして使用する機能を導入しました。式ベースのアクセス制御は同じアーキテクチャに基づいて構築されていますが、複雑なブールロジックを単一の式にカプセル化できます。

11.3.1 概要

Spring Security は、式のサポートに Spring EL を使用します。トピックをより深く理解することに興味がある場合は、それがどのように機能するかを確認してください。式は、評価コンテキストの一部として「ルートオブジェクト」で評価されます。Spring Security は、組み込み式と現在のプリンシパルなどの値へのアクセスを提供するために、Web およびメソッドセキュリティの特定のクラスをルートオブジェクトとして使用します。

一般的な組み込み式

式ルートオブジェクトの基本クラスは  SecurityExpressionRoot です。これにより、Web セキュリティとメソッドセキュリティの両方で使用できる一般的な式が提供されます。

テーブル 11.1: 一般的な組み込み式

説明

hasRole(String role)

現在のプリンシパルに指定されたロールがある場合、 true を返します。

例:  hasRole('admin')

デフォルトでは、指定されたロールが「ROLE_」で始まらない場合は追加されます。これは、 DefaultWebSecurityExpressionHandler の  defaultRolePrefix を変更することによりカスタマイズできます。

hasAnyRole(String…​ roles)

現在のプリンシパルが提供されたロールのいずれかを持っている場合(文字列のコンマ区切りリストとして与えられた場合)  true を返します。

例:  hasAnyRole('admin', 'user')

デフォルトでは、指定されたロールが「ROLE_」で始まらない場合は追加されます。これは、 DefaultWebSecurityExpressionHandler の  defaultRolePrefix を変更することによりカスタマイズできます。

hasAuthority(String authority)

現在のプリンシパルが指定された権限を持っている場合、 true を返します。

例:  hasAuthority('read')

hasAnyAuthority(String…​ authorities)

現在のプリンシパルに提供された権限のいずれかがある場合、 true を返する (文字列のコンマ区切りリストとして与えられます)

例:  hasAnyAuthority('read', 'write')

principal

現在のユーザーを表すプリンシパルオブジェクトへの直接アクセスを許可する

authentication

SecurityContext から取得した現在の  Authentication オブジェクトへの直接アクセスを許可する

permitAll

常に  true に評価されます

denyAll

常に  false に評価されます

isAnonymous()

現在のプリンシパルが匿名ユーザーの場合、 true を返する

isRememberMe()

現在のプリンシパルが remember-me ユーザーである場合、 true を返する

isAuthenticated()

ユーザーが匿名でない場合、 true を返する

isFullyAuthenticated()

ユーザーが匿名ユーザーでも覚えのないユーザーでもない場合は、 true を返します。

hasPermission(Object target, Object permission)

ユーザーが指定された許可に対して提供されたターゲットにアクセスできる場合、 true を返します。例:  hasPermission(domainObject, 'read')

hasPermission(Object targetId, String targetType, Object permission)

ユーザーが指定された許可に対して提供されたターゲットにアクセスできる場合、 true を返します。例:  hasPermission(1, 'com.example.domain.Message', 'read')


11.3.2 Web セキュリティ式

式を使用して個々の URL を保護するには、最初に  <http> 要素の  use-expressions 属性を  true に設定する必要があります。Spring Security は、 <intercept-url> 要素の  access 属性に Spring EL 式が含まれることを期待します。式はブール値に評価され、アクセスを許可するかどうかを定義する必要があります。例:

<http>
    <intercept-url pattern="/admin*"
        access="hasRole('admin') and hasIpAddress('192.168.1.0/24')"/>
    ...
</http>

ここで、アプリケーションの「admin」領域(URL パターンで定義)は、付与された権限「admin」を持ち、IP アドレスがローカルサブネットと一致するユーザーのみが使用できるように定義しました。前のセクションでビルトイン  hasRole 式を見てきました。式  hasIpAddress は、Web セキュリティに固有の追加の組み込み式です。これは、 WebSecurityExpressionRoot クラスによって定義され、そのインスタンスは、Web アクセス式を評価するときに式ルートオブジェクトとして使用されます。このオブジェクトは、 request という名前で  HttpServletRequest オブジェクトを直接公開しているため、式でリクエストを直接呼び出すことができます。式が使用されている場合、 WebExpressionVoter がネームスペースで使用される  AccessDecisionManager に追加されます。そのため、ネームスペースを使用しておらず、式を使用する場合は、これらのいずれかを構成に追加する必要があります。

Web セキュリティ式で Bean を参照する

使用可能な式を継承したい場合は、公開する Spring Bean を簡単に参照できます。例: 次のメソッドシグネチャーを含む  webSecurity という名前の Bean があると仮定します。

public class WebSecurity {
        public boolean check(Authentication authentication, HttpServletRequest request) {
                ...
        }
}

以下を使用してメソッドを参照できます。

<http>
    <intercept-url pattern="/user/**"
        access="@webSecurity.check(authentication,request)"/>
    ...
</http>

または Java 構成

http
        .authorizeRequests()
                .antMatchers("/user/**").access("@webSecurity.check(authentication,request)")
                ...

Web セキュリティ式のパス変数

URL 内のパス変数を参照できると便利な場合があります。例:  /user/{userId} 形式の URL パスから id でユーザーを検索する RESTful アプリケーションを検討します。

パターンに配置することで、パス変数を簡単に参照できます。例: 次のメソッドシグネチャーを含む  webSecurity という名前の Bean がある場合:

public class WebSecurity {
        public boolean checkUserId(Authentication authentication, int id) {
                ...
        }
}

以下を使用してメソッドを参照できます。

<http>
    <intercept-url pattern="/user/{userId}/**"
        access="@webSecurity.checkUserId(authentication,#userId)"/>
    ...
</http>

または Java 構成

http
    .authorizeRequests(authorizeRequests ->
        authorizeRequests
            .antMatchers("/user/{userId}/**").access("@webSecurity.checkUserId(authentication,#userId)")
            ...
    );

どちらの構成でも、一致する URL はパス変数を渡して(そして変換して)checkUserId メソッドに渡します。例: URL が  /user/123/resource の場合、渡される ID は  123 になります。

11.3.3 メソッドセキュリティ式

メソッドのセキュリティは、単純な許可または拒否ルールよりも少し複雑です。Spring Security 3.0 は、式の使用を包括的にサポートできるようにするために、いくつかの新しいアノテーションを導入しました。

@Pre および @Post アノテーション

式属性をサポートする 4 つのアノテーションがあり、呼び出し前および呼び出し後の認可チェックを可能にし、送信されたコレクション引数または戻り値のフィルタリングもサポートします。それらは  @PreAuthorize @PreFilter @PostAuthorize @PostFilter です。それらの使用は、 global-method-security 名前空間要素を介して有効になります。

<global-method-security pre-post-annotations="enabled"/>
@PreAuthorize および @PostAuthorize を使用したアクセス制御

最も明らかに有用なアノテーションは、メソッドを実際に呼び出すことができるかどうかを決定する  @PreAuthorize です。たとえば (「連絡先」サンプルアプリケーションから)

@PreAuthorize("hasRole('USER')")
public void create(Contact contact);

つまり、アクセスはロール「ROLE_USER」を持つユーザーにのみ許可されます。明らかに、従来の構成と必要なロールの単純な構成属性を使用して、同じことを簡単に実現できます。しかし、どうですか:

@PreAuthorize("hasPermission(#contact, 'admin')")
public void deletePermission(Contact contact, Sid recipient, Permission permission);

ここでは、式の一部としてメソッド引数を実際に使用して、現在のユーザーに特定の連絡先の「管理者」権限があるかどうかを判断しています。以下に示すように 、ビルトイン  hasPermission() 式は、アプリケーションコンテキストを介して Spring Security ACL モジュールにリンクされ ます 。式の変数として名前でメソッドの引数にアクセスできます。

Spring Security がメソッドの引数を解決できる方法はいくつかあります。Spring Security は、 DefaultSecurityParameterNameDiscoverer を使用してパラメーター名を検出します。デフォルトでは、メソッド全体に対して次のオプションが試行されます。

  • Spring Security の  @P アノテーションがメソッドへの単一の引数に存在する場合、値が使用されます。これは、パラメーター名に関する情報を含まない JDK 8 より前の JDK でコンパイルされたインターフェースに役立ちます。例:

    import org.springframework.security.access.method.P;
    
    ...
    
    @PreAuthorize("#c.name == authentication.name")
    public void doSomething(@P("c") Contact contact);

    バックグラウンドでは、 AnnotationParameterNameDiscoverer を使用してこの使用を実装し、指定されたアノテーションの value 属性をサポートするようにカスタマイズできます。

  • Spring Data の  @Param アノテーションがメソッドの少なくとも 1 つのパラメーターに存在する場合、値が使用されます。これは、パラメーター名に関する情報を含まない JDK 8 より前の JDK でコンパイルされたインターフェースに役立ちます。例:

    import org.springframework.data.repository.query.Param;
    
    ...
    
    @PreAuthorize("#n == authentication.name")
    Contact findContactByName(@Param("n") String name);

    バックグラウンドでは、 AnnotationParameterNameDiscoverer を使用してこの使用を実装し、指定されたアノテーションの value 属性をサポートするようにカスタマイズできます。

  • -parameters 引数を使用してソースをコンパイルするために JDK 8 が使用され、Spring 4+ が使用されている場合、標準の JDK リフレクション API を使用してパラメーター名が検出されます。これはクラスとインターフェースの両方で機能します。
  • 最後に、コードがデバッグシンボルを使用してコンパイルされた場合、パラメーター名はデバッグシンボルを使用して検出されます。インターフェースにはパラメーター名に関するデバッグ情報がないため、これは機能しません。インターフェースには、アノテーションまたは JDK 8 アプローチを使用する必要があります。

式内で任意の Spring-EL 機能を使用できるため、引数のプロパティにアクセスすることもできます。例: 連絡先のユーザー名と一致するユーザー名を持つユーザーのみにアクセスを許可する特定の方法が必要な場合は、次のように記述できます。

@PreAuthorize("#contact.name == authentication.name")
public void doSomething(Contact contact);

ここでは、セキュリティコンテキストに格納されている  Authentication である別の組み込み式  authentication にアクセスしています。式  principal を使用して、その「プリンシパル」プロパティに直接アクセスすることもできます。多くの場合、値は  UserDetails インスタンスであるため、 principal.username や  principal.enabled などの式を使用できます。

通常、Less では、メソッドが呼び出された後にアクセス制御チェックを実行することができます。これは、 @PostAuthorize アノテーションを使用して実現できます。メソッドからの戻り値にアクセスするには、式で組み込み名  returnObject を使用します。

@PreFilter および @PostFilter を使用したフィルタリング

すでにご存じかもしれませんが、Spring Security はコレクションと配列のフィルタリングをサポートしており、式を使用してこれを実現できるようになりました。これは、メソッドの戻り値で最も一般的に実行されます。例:

@PreAuthorize("hasRole('USER')")
@PostFilter("hasPermission(filterObject, 'read') or hasPermission(filterObject, 'admin')")
public List<Contact> getAll();

@PostFilter アノテーションを使用する場合、Spring Security は返されたコレクションを反復処理し、指定された式が false である要素を削除します。 filterObject という名前は、コレクション内の現在のオブジェクトを指します。 @PreFilter を使用して、メソッド呼び出しの前にフィルタリングすることもできますが、これはあまり一般的な要件ではありません。構文はまったく同じですが、コレクション型である引数が複数ある場合は、このアノテーションの  filterTarget プロパティを使用して名前で 1 つ選択する必要があります。

フィルタリングは、データ取得クエリをチューニングするための代替ではないことに注意してください。大規模なコレクションをフィルタリングし、多くのエントリを削除する場合、これは非効率的である可能性があります。

組み込み式

メソッドのセキュリティに固有の組み込み式がいくつかありますが、これはすでに上記で使用されているものです。 filterTarget と  returnValue の値は非常に単純ですが、 hasPermission() 式を使用することで、より詳細に見ることができます。

PermissionEvaluator インターフェース

hasPermission() 式は  PermissionEvaluator のインスタンスに委譲されます。式システムと Spring Security の ACL システムを橋渡しすることを目的としており、抽象的な認可に基づいて、ドメインオブジェクトの認可制約を指定できます。ACL モジュールへの明示的な依存関係はないため、必要に応じて別の実装に交換できます。インターフェースには 2 つのメソッドがあります。

boolean hasPermission(Authentication authentication, Object targetDomainObject,
                            Object permission);

boolean hasPermission(Authentication authentication, Serializable targetId,
                            String targetType, Object permission);

最初の引数( Authentication オブジェクト)が指定されていないことを除いて、使用可能な式のバージョンに直接マップします。1 つ目は、アクセスが制御されているドメインオブジェクトがすでにロードされている状況で使用されます。次に、現在のユーザーがそのオブジェクトに対して与えられた許可を持っている場合、式は true を返します。2 番目のバージョンは、オブジェクトがロードされていないが、その識別子がわかっている場合に使用されます。ドメインオブジェクトの抽象的な「型」指定子も必要です。これにより、正しい ACL 権限をロードできます。これは伝統的にオブジェクトの Java クラスでしたが、パーミッションのロードメソッドと一貫している限り、そうである必要はありません。

hasPermission() 式を使用するには、アプリケーションコンテキストで  PermissionEvaluator を明示的に構成する必要があります。これは次のようになります。

<security:global-method-security pre-post-annotations="enabled">
<security:expression-handler ref="expressionHandler"/>
</security:global-method-security>

<bean id="expressionHandler" class=
"org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
    <property name="permissionEvaluator" ref="myPermissionEvaluator"/>
</bean>

myPermissionEvaluator は、 PermissionEvaluator を実装する Bean です。通常、これは  AclPermissionEvaluator と呼ばれる ACL モジュールからの実装になります。詳細については、「連絡先」サンプルアプリケーションの構成を参照してください。

メソッドセキュリティメタアノテーション

メソッドのセキュリティのためにメタアノテーションを使用して、コードを読みやすくすることができます。これは、コードベース全体で同じ複雑な式を繰り返している場合に特に便利です。例: 次のことを考慮してください。

@PreAuthorize("#contact.name == authentication.name")

これをどこでも繰り返す代わりに、代わりに使用できるメタアノテーションを作成できます。

@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("#contact.name == authentication.name")
public @interface ContactPermission {}

メタアノテーションは、Spring Security メソッドのセキュリティアノテーションのいずれにも使用できます。仕様への準拠を維持するため、JSR-250 アノテーションはメタアノテーションをサポートしていません。

11.4 リクエストを承認する

この例では、ユーザーの認証のみが必要であり、アプリケーションのすべての URL で認証されています。 http.authorizeRequests() メソッドに複数の子を追加することにより、URL のカスタム要件を指定できます。例:

protected void configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests(authorizeRequests ->                                        1
            authorizeRequests
                .antMatchers("/resources/**", "/signup", "/about").permitAll()         2
                .antMatchers("/admin/**").hasRole("ADMIN")                             3
                .antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")   4
                .anyRequest().authenticated()                                          5
        )
        .formLogin(withDefaults());
}

1

http.authorizeRequests() メソッドには複数の子があり、各マッチャーは宣言された順に考慮されます。

2

すべてのユーザーがアクセスできる複数の URL パターンを指定しました。具体的には、URL が「/resources/」で始まる、「/signup」に等しい、または「/about」に等しい場合、どのユーザーもリクエストにアクセスできます。

3

「/admin/」で始まる URL は、ロール「ROLE_ADMIN」を持つユーザーに制限されます。 hasRole メソッドを呼び出しているため、「ROLE_」プレフィックスを指定する必要はありません。

4

「/db/」で始まる URL では、ユーザーに「ROLE_ADMIN」と「ROLE_DBA」の両方が必要です。 hasRole 式を使用しているため、「ROLE_」プレフィックスを指定する必要はありません。

5

まだ一致していない URL には、ユーザーの認証のみが必要です

11.5 メソッドのセキュリティ

バージョン 2.0 以降、Spring Security はサービスレイヤーメソッドにセキュリティを追加するためのサポートを大幅に改善しました。JSR-250 アノテーションセキュリティおよびフレームワークの元の  @Secured アノテーションのサポートを提供します。3.0 から、新しい式ベースのアノテーションを利用することもできます。 intercept-methods 要素を使用して Bean 宣言を装飾し、単一の Bean にセキュリティを適用するか、AspectJ スタイルのポイントカットを使用してサービスレイヤー全体で複数の Bean を保護できます。

11.5.1 EnableGlobalMethodSecurity

@Configuration インスタンスで  @EnableGlobalMethodSecurity アノテーションを使用して、アノテーションベースのセキュリティを有効にできます。例: 以下は、Spring Security の  @Secured アノテーションを有効にします。

@EnableGlobalMethodSecurity(securedEnabled = true)
public class MethodSecurityConfig {
// ...
}

(クラスまたはインターフェース上の)メソッドにアノテーションを追加すると、それに応じてそのメソッドへのアクセスが制限されます。Spring Security のネイティブアノテーションサポートは、メソッドの属性セットを定義します。これらは、AccessDecisionManager に渡され、実際の決定が行われます。

public interface BankService {

@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account readAccount(Long id);

@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account[] findAccounts();

@Secured("ROLE_TELLER")
public Account post(Account account, double amount);
}

JSR-250 アノテーションのサポートは、次を使用して有効にできます。

@EnableGlobalMethodSecurity(jsr250Enabled = true)
public class MethodSecurityConfig {
// ...
}

これらは標準ベースであり、単純なロールベースの制約を適用できますが、Spring Security のネイティブアノテーションはありません。新しい式ベースの構文を使用するには、次を使用します

@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig {
// ...
}

同等の Java コードは

public interface BankService {

@PreAuthorize("isAnonymous()")
public Account readAccount(Long id);

@PreAuthorize("isAnonymous()")
public Account[] findAccounts();

@PreAuthorize("hasAuthority('ROLE_TELLER')")
public Account post(Account account, double amount);
}

11.5.2 GlobalMethodSecurityConfiguration

@EnableGlobalMethodSecurity アノテーションで許可されるよりも複雑な操作を実行する必要がある場合があります。これらのインスタンスでは、 GlobalMethodSecurityConfiguration を継承して、 @EnableGlobalMethodSecurity アノテーションがサブクラスに存在することを確認できます。例: カスタム  MethodSecurityExpressionHandler を提供する場合、次の構成を使用できます。

@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
    @Override
    protected MethodSecurityExpressionHandler createExpressionHandler() {
        // ... create and return custom MethodSecurityExpressionHandler ...
        return expressionHandler;
    }
}

オーバーライドできるメソッドの詳細については、 GlobalMethodSecurityConfiguration Javadoc を参照してください。

11.5.3 <global-method-security> 要素

この要素は、(要素に適切な属性を設定することにより)アプリケーションでアノテーションベースのセキュリティを有効にし、アプリケーションコンテキスト全体に適用されるセキュリティポイントカット宣言をグループ化するために使用されます。 <global-method-security> 要素を 1 つだけ宣言する必要があります。次の宣言により、Spring Security の  @Secured のサポートが有効になります。

<global-method-security secured-annotations="enabled" />

(クラスまたはインターフェース上の)メソッドにアノテーションを追加すると、それに応じてそのメソッドへのアクセスが制限されます。Spring Security のネイティブアノテーションサポートは、メソッドの属性セットを定義します。これらは、実際の決定を行うために  AccessDecisionManager に渡されます。

public interface BankService {

@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account readAccount(Long id);

@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account[] findAccounts();

@Secured("ROLE_TELLER")
public Account post(Account account, double amount);
}

JSR-250 アノテーションのサポートは、次を使用して有効にできます。

<global-method-security jsr250-annotations="enabled" />

これらは標準ベースであり、単純なロールベースの制約を適用できますが、Spring Security のネイティブアノテーションはありません。新しい式ベースの構文を使用するには、次を使用します

<global-method-security pre-post-annotations="enabled" />

同等の Java コードは

public interface BankService {

@PreAuthorize("isAnonymous()")
public Account readAccount(Long id);

@PreAuthorize("isAnonymous()")
public Account[] findAccounts();

@PreAuthorize("hasAuthority('ROLE_TELLER')")
public Account post(Account account, double amount);
}

式ベースのアノテーションは、ユーザーの権限リストに対するロール名のチェックにとどまらない単純なルールを定義する必要がある場合に適しています。

[Note] メモ

=== アノテーション付きメソッドは、Spring Bean として定義されているインスタンスに対してのみ保護されます(メソッドセキュリティが有効になっている同じアプリケーションコンテキスト内)。Spring によって作成されていないインスタンスを保護する場合(たとえば、 new オペレーターを使用)、AspectJ を使用する必要があります。===

[Note] メモ

=== 同じアプリケーションで複数のタイプのアノテーションを有効にできますが、他の方法では動作が適切に定義されないため、インターフェースまたはクラスには 1 つのタイプのみを使用する必要があります。特定のメソッドに適用される 2 つのアノテーションが見つかった場合、そのうちの 1 つだけが適用されます。===

11.5.4 protect-pointcut を使用したセキュリティポイントカットの追加

protect-pointcut の使用は、単純な宣言のみで多くの Bean にセキュリティを適用できるため、特に強力です。次の例を考えてみましょう。

<global-method-security>
<protect-pointcut expression="execution(* com.mycompany.*Service.*(..))"
    access="ROLE_USER"/>
</global-method-security>

これにより、クラスが  com.mycompany パッケージにあり、クラス名が「Service」で終わるアプリケーションコンテキストで宣言された Bean のすべてのメソッドが保護されます。 ROLE_USER ロールを持つユーザーのみがこれらのメソッドを呼び出すことができます。URL マッチングと同様に、最初のマッチング表現が使用されるため、ポイントカットのリストで最も具体的な一致が最初に来る必要があります。セキュリティアノテーションはポイントカットよりも優先されます。

11.6 ドメインオブジェクトセキュリティ (ACL)

11.6.1 概要

複雑なアプリケーションでは、多くの場合、単純に Web リクエストまたはメソッド呼び出しレベルではなく、アクセス認可を定義する必要があります。代わりに、セキュリティの決定には、誰( Authentication)、どこ( MethodInvocation)および何( SomeDomainObject)の両方を含める必要があります。言い換えると、認可の決定では、メソッド呼び出しの実際のドメインオブジェクトインスタンスサブジェクトも考慮する必要があります。

ペットクリニック用のアプリケーションを設計しているとします。Spring ベースのアプリケーションのユーザーには、ペットクリニックのスタッフとペットクリニックの顧客の 2 つのメイングループがあります。スタッフはすべてのデータにアクセスできますが、顧客は自分の顧客レコードのみを見ることができます。もう少し面白くするために、顧客は、「子犬幼稚園」のメンターやローカル「ポニークラブ」の社長など、他のユーザーに顧客レコードの閲覧を許可できます。Spring Security を基盤として使用すると、使用できるアプローチがいくつかあります。

  • セキュリティを強化するためのビジネスメソッドを記述します。 Customer ドメインオブジェクトインスタンス内のコレクションを参照して、アクセスできるユーザーを判断できます。 SecurityContextHolder.getContext().getAuthentication() を使用すると、 Authentication オブジェクトにアクセスできます。
  • AccessDecisionVoter を記述して、 Authentication オブジェクトに格納されている  GrantedAuthority[] からセキュリティを強化します。これは、 AuthenticationManager が、プリンシパルがアクセスできる  Customer ドメインオブジェクトインスタンスのそれぞれを表すカスタム  GrantedAuthority[] を  Authentication に取り込む必要があることを意味します。
  • AccessDecisionVoter を記述してセキュリティを強化し、ターゲット  Customer ドメインオブジェクトを直接開きます。これは、投票者が  Customer オブジェクトを取得できる DAO にアクセスする必要があることを意味します。次に、 Customer オブジェクトの承認済みユーザーのコレクションにアクセスし、適切な決定を下します。

これらのアプローチはいずれも完全に正当です。ただし、最初のステップでは、認可チェックをビジネスコードに結合します。これに関する主な問題には、単体テストの難易度の向上と、 Customer 認証ロジックを他の場所で再利用することがより困難になるという事実が含まれます。 Authentication オブジェクトから  GrantedAuthority[] を取得することもできますが、多数の  Customer に拡張することはできません。ユーザーが 5,000  Customer にアクセスできる場合(この場合はほとんどありませんが、大規模なポニークラブで人気の獣医だと想像してください! )、 Authentication オブジェクトの構築に必要なメモリ量と時間は望ましくありません。 Customer を外部コードから直接開く最後のメソッドは、おそらく 3 つのうちの最良のメソッドです。関心事の分離を実現し、メモリまたは CPU サイクルを誤用しませんが、 AccessDecisionVoter と最終的なビジネスメソッド自体の両方が  Customer オブジェクトの取得を担当する DAO への呼び出しを実行するという点で、依然として非効率的です。メソッド呼び出しごとに 2 回アクセスすることは明らかに望ましくありません。さらに、リストされているすべてのアプローチで、独自のアクセス制御リスト(ACL)の永続性とビジネスロジックをゼロから作成する必要があります。

幸いなことに、別の選択肢があります。これについては以下で説明します。

11.6.2 主なコンセプト

Spring Security の ACL サービスは  spring-security-acl-xxx.jar で提供されます。Spring Security のドメインオブジェクトインスタンスのセキュリティ機能を使用するには、この JAR をクラスパスに追加する必要があります。

Spring Security のドメインオブジェクトインスタンスのセキュリティ機能は、アクセス制御リスト(ACL)の概念に基づいています。システム内のすべてのドメインオブジェクトインスタンスには独自の ACL があり、ACL はそのドメインオブジェクトを操作できる人と操作できない人の詳細を記録します。これを念頭に置いて、Spring Security は、アプリケーションに 3 つの主要な ACL 関連機能を提供します。

  • すべてのドメインオブジェクトの ACL エントリを効率的に取得する方法 (それらの ACL の変更)
  • メソッドが呼び出される前に、特定のプリンシパルがオブジェクトで機能することを許可する方法
  • メソッドが呼び出された後、特定のプリンシパルがオブジェクト(またはオブジェクトが返すもの)で動作することを許可する方法

最初の箇条書きで示されるように、Spring Security ACL モジュールの主な機能の 1 つは、ACL を取得する高性能なメソッドを提供することです。この ACL リポジトリ機能は非常に重要です。システム内のすべてのドメインオブジェクトインスタンスには複数のアクセス制御エントリがあり、各 ACL はツリー状の構造の他の ACL から継承する可能性があるためです(これは Spring Security ですぐにサポートされます。および非常に一般的に使用されています)。Spring Security の ACL 機能は、プラグ可能なキャッシュ、デッドロックを最小限に抑えるデータベース更新、ORM フレームワークからの独立性(JDBC を直接使用)、適切なカプセル化、透過的なデータベース更新とともに、ACL の高性能検索を提供するように注意深く設計されています。

データベースが ACL モジュールの操作の中心であることを前提に、実装でデフォルトで使用される 4 つのメインテーブルを見てみましょう。表は、典型的な Spring Security ACL デプロイのサイズの順に以下に示されており、最も多くの行を持つ表が最後にリストされています。

  • ACL_SID を使用すると、システム内のプリンシパルまたは機関を一意に識別できます(「SID」は「セキュリティ ID」を表します)。唯一の列は、ID、SID のテキスト表現、テキスト表現がプリンシパル名または  GrantedAuthority のどちらを参照しているかを示すフラグです。一意のプリンシパルまたは  GrantedAuthority ごとに 1 つの行があります。許可を受け取るという文脈で使用される場合、SID は一般に「受信者」と呼ばれます。
  • ACL_CLASS を使用すると、システム内のドメインオブジェクトクラスを一意に識別できます。唯一の列は、ID と Java クラス名です。ACL のアクセス許可を保存する一意のクラスごとに 1 つの行があります。
  • ACL_OBJECT_IDENTITY は、システム内の一意の各ドメインオブジェクトインスタンスの情報を格納します。列には、ID、ACL_CLASS テーブルへの外部キー、一意の識別子が含まれているため、どの ACL_CLASS インスタンスに情報を提供しているか、親、ドメインオブジェクトインスタンスの所有者を表す ACL_SID テーブルへの外部キー、ACL エントリが親 ACL から継承できるようにするかどうか。ACL 権限を保存するドメインオブジェクトインスタンスごとに 1 つの行があります。
  • 最後に、ACL_ENTRY は各受信者に割り当てられた個々のアクセス許可を保存します。列には、ACL_OBJECT_IDENTITY への外部キー、受信者(ACL_SID への外部キー)、監査するかどうか、実際に許可または拒否される許可を表す整数ビットマスクが含まれます。ドメインオブジェクトを操作する許可を受け取るすべての受信者に対して 1 つの行があります。

最後の段落で記述されていたように、ACL システムは整数ビットマスキングを使用します。心配不要です。ACL システムを使用するためのビットシフトの細かい点を意識する必要はありませんが、オンまたはオフに切り替えることができる 32 ビットがあると言えば十分です。これらの各ビットは許可を表し、デフォルトでは許可は読み取り(ビット 0)、書き込み(ビット 1)、作成(ビット 2)、削除(ビット 3)および管理(ビット 4)です。他のアクセス許可を使用する場合は、独自の  Permission インスタンスを簡単に実装できます。ACL フレームワークの残りの部分は、拡張機能の知識がなくても動作します。

システム内のドメインオブジェクトの数は、整数ビットマスキングを使用することを選択したという事実とはまったく関係がないことを理解することが重要です。許可に 32 ビットを使用できますが、数十億のドメインオブジェクトインスタンス(ACL_OBJECT_IDENTITY およびおそらく ACL_ENTRY の数十億行を意味する)を持つことができます。これは、潜在的なドメインオブジェクトごとにビットが必要であると人々が誤って信じていることがあるためです。

ACL システムの機能と、テーブル構造での表示の基本的な概要を説明したため、次に主要なインターフェースを見てみましょう。主なインターフェースは次のとおりです。

  • Acl: すべてのドメインオブジェクトには  Acl オブジェクトが 1 つだけあり、 Acl オブジェクトは内部で  AccessControlEntry を保持し、 Acl の所有者を知っています。Acl はドメインオブジェクトを直接参照せず、代わりに  ObjectIdentity を参照します。 Acl は ACL_OBJECT_IDENTITY テーブルに保存されます。
  • AccessControlEntry Acl は、複数の  AccessControlEntry を保持します。これらは、フレームワークでは ACE と略されることがよくあります。各 ACE は、 Permission Sid Acl の特定のタプルを参照します。ACE は、許可または非許可であり、監査設定を含むこともできます。ACE は ACL_ENTRY テーブルに保存されます。
  • Permission: 許可は、特定の不変のビットマスクを表し、ビットマスキングと情報の出力に便利な機能を提供します。上記の基本的なアクセス許可(ビット 0 〜 4)は、 BasePermission クラスに含まれています。
  • Sid: ACL モジュールは、プリンシパルと  GrantedAuthority[] を参照する必要があります。間接レベルは、「セキュリティ ID」の略語である  Sid インターフェースによって提供されます。一般的なクラスには、 PrincipalSid ( Authentication オブジェクト内のプリンシパルを表す)および  GrantedAuthoritySid が含まれます。セキュリティ ID 情報は ACL_SID テーブルに保存されます。
  • ObjectIdentity: 各ドメインオブジェクトは、 ObjectIdentity によって ACL モジュール内で内部的に表されます。デフォルトの実装は  ObjectIdentityImpl と呼ばれます。
  • AclService: 指定された  ObjectIdentity に適用可能な  Acl を取得します。含まれる実装( JdbcAclService)では、検索操作は  LookupStrategy に委譲されます。 LookupStrategy は、バッチ検索  (BasicLookupStrategy を使用して ACL 情報を取得するための高度に最適化された戦略を提供し、マテリアライズドビュー、階層クエリ、同様のパフォーマンス中心の非 ANSI SQL 機能を活用するカスタム実装をサポートします。
  • MutableAclService: 永続化のために、変更された  Acl を提示できます。希望しない場合、このインターフェースを使用することは必須ではありません。

すぐに使える AclService および関連するデータベースクラスはすべて ANSI SQL を使用することに注意してください。これはすべての主要なデータベースで機能するはずです。執筆時点では、システムは Hypersonic SQL、PostgreSQL、Microsoft SQL Server、Oracle を使用して正常にテストされていました。

Spring Security には、ACL モジュールを示す 2 つのサンプルが付属しています。1 つ目は Contacts サンプルで、もう 1 つは Document Management System(DMS)サンプルです。例としてこれらを確認することをお勧めします。

11.6.3 入門

Spring Security の ACL 機能の使用を開始するには、ACL 情報をどこかに保存する必要があります。これには、Spring を使用した  DataSource のインスタンス化が必要です。次に、 DataSource は  JdbcMutableAclService および  BasicLookupStrategy インスタンスに注入されます。後者は高性能 ACL 検索機能を提供し、前者はミューテーター機能を提供します。構成例については、Spring Security に同梱されているサンプルのいずれかを参照してください。また、最後のセクションにリストされている 4 つの ACL 固有のテーブルをデータベースに追加する必要があります(適切な SQL ステートメントについては ACL サンプルを参照してください)。

必要なスキーマを作成して  JdbcMutableAclService をインスタンス化したら、次にドメインモデルが Spring Security ACL パッケージとの相互運用性をサポートしていることを確認する必要があります。 ObjectIdentityImpl が使用できる多くのメソッドを提供するため、 ObjectIdentityImpl で十分であることを願っています。ほとんどの人は、 public Serializable getId() メソッドを含むドメインオブジェクトを持っています。戻り値の型が長い場合、または long と互換性がある場合(int など)、 ObjectIdentity の課題をさらに考慮する必要はありません。ACL モジュールの多くの部分は、長い識別子に依存しています。long(または int、byte など)を使用していない場合、多くのクラスを再実装する必要がある可能性が非常に高くなります。Spring Security の ACL モジュールでは、最も一般的な識別子データ型であるすべてのデータベースシーケンスとすでに互換性があり、すべての一般的な使用シナリオに対応するのに十分な長さがあるため、長い識別子をサポートするつもりはありません

次のコードは、 Acl を作成する方法、または既存の  Acl を変更する方法を示しています。

// Prepare the information we'd like in our access control entry (ACE)
ObjectIdentity oi = new ObjectIdentityImpl(Foo.class, new Long(44));
Sid sid = new PrincipalSid("Samantha");
Permission p = BasePermission.ADMINISTRATION;

// Create or update the relevant ACL
MutableAcl acl = null;
try {
acl = (MutableAcl) aclService.readAclById(oi);
} catch (NotFoundException nfe) {
acl = aclService.createAcl(oi);
}

// Now grant some permissions via an access control entry (ACE)
acl.insertAce(acl.getEntries().length, p, sid, true);
aclService.updateAcl(acl);

上記の例では、ID 番号 44 の「Foo」ドメインオブジェクトに関連付けられた ACL を取得しています。次に、ACE を追加して、「Samantha」というプリンシパルがオブジェクトを「管理」できるようにします。コードの断片は、insertAce メソッドを除き、比較的自明です。insertAce メソッドの最初の引数は、Acl のどの位置に新しいエントリを挿入するかを決定します。上記の例では、新しい ACE を既存の ACE の最後に配置しています。最後の引数は、ACE が許可するか拒否するかを示すブール値です。ほとんどの場合、許可されます(true)が、拒否している場合(false)は、アクセス許可が事実上ブロックされています。

Spring Security は、DAO またはリポジトリ操作の一部として ACL を自動的に作成、更新、または削除するための特別な統合を提供しません。代わりに、個々のドメインオブジェクトに対して上記のようなコードを記述する必要があります。サービスレイヤーで AOP を使用して、ACL 情報をサービスレイヤーの操作に自動的に統合することを検討する価値があります。これは過去に非常に効果的なアプローチであることがわかりました。

上記の手法を使用していくつかの ACL 情報をデータベースに保存したら、次のステップは、認可決定ロジックの一部として実際に ACL 情報を使用することです。ここには多くの選択肢があります。メソッド呼び出しの前後にそれぞれ起動する独自の  AccessDecisionVoter または  AfterInvocationProvider を作成できます。このようなクラスは、 AclService を使用して関連する ACL を取得し、 Acl.isGranted(Permission[] permission, Sid[] sids, boolean administrativeMode) を呼び出して認可を認可するか拒否するかを決定します。または、 AclEntryVoter AclEntryAfterInvocationProvider、または  AclEntryAfterInvocationCollectionFilteringProvider クラスを使用できます。これらのクラスはすべて、実行時に ACL 情報を評価するための宣言ベースのアプローチを提供するため、コードを記述する必要がありません。これらのクラスの使用方法については、サンプルアプリケーションを参照してください。

現行バージョンへ切り替える