ドメインオブジェクトセキュリティ (ACL)
このセクションでは、Spring Security がアクセス制御リスト(ACL)を使用してドメインオブジェクトのセキュリティを提供する方法について説明します。
複雑なアプリケーションでは、多くの場合、Web リクエストまたはメソッド呼び出しレベルを超えてアクセス認可を定義する必要があります。代わりに、セキュリティの決定には、誰(Authentication
)、どこ(MethodInvocation
)、何(SomeDomainObject
)を含める必要があります。つまり、認可の決定では、メソッド呼び出しの実際のドメインオブジェクトインスタンスのサブジェクトも考慮する必要があります。
ペットクリニックのアプリケーションを設計していると想像してください。Spring ベースのアプリケーションのユーザーには、主に 2 つのグループがあります。ペットクリニックのスタッフとペットクリニックの顧客です。スタッフはすべてのデータにアクセスできる必要がありますが、顧客は自分の顧客レコードのみを表示できる必要があります。もう少し面白くするために、顧客は、「子犬の幼稚園」のメンターやローカル「ポニークラブ」の社長など、他のユーザーに顧客の記録を見せることができます。Spring Security を基盤として使用する場合、いくつかの可能なアプローチがあります。
セキュリティを強化するためのビジネスメソッドを記述します。
Customer
ドメインオブジェクトインスタンス内のコレクションを調べて、どのユーザーがアクセスできるかを判別できます。SecurityContextHolder.getContext().getAuthentication()
を使用すると、Authentication
オブジェクトにアクセスできます。AccessDecisionVoter
を記述して、Authentication
オブジェクトに保管されているGrantedAuthority[]
インスタンスからのセキュリティを実施します。つまり、AuthenticationManager
は、プリンシパルがアクセスできるCustomer
ドメインオブジェクトインスタンスのそれぞれを表すために、Authentication
にカスタムGrantedAuthority[]
オブジェクトを設定する必要があります。AccessDecisionVoter
を記述してセキュリティを強化し、ターゲットCustomer
ドメインオブジェクトを直接開きます。これは、投票者がCustomer
オブジェクトを取得できる DAO にアクセスする必要があることを意味します。次に、Customer
オブジェクトの承認されたユーザーのコレクションにアクセスし、適切な決定を下すことができます。
これらのアプローチのそれぞれは完全に正当です。ただし、最初のメソッドでは、認証チェックをビジネスコードに結合します。これに関する主な問題には、単体テストの難易度の向上と、Customer
認証ロジックを他の場所で再利用することがより困難になるという事実が含まれます。Authentication
オブジェクトから GrantedAuthority[]
インスタンスを取得することも問題ありませんが、多数の Customer
オブジェクトに拡張することはできません。ユーザーが 5,000 Customer
オブジェクトにアクセスできる場合(この場合はありそうにありませんが、大規模なポニークラブで人気のある獣医であると想像してください! )、消費されるメモリの量と Authentication
オブジェクトの構築に必要な時間は望ましくありません。最後のメソッドである Customer
を外部コードから直接開くメソッドは、おそらく 3 つのうちで最良のメソッドです。関心の分離を実現し、メモリや CPU サイクルを誤用しませんが、AccessDecisionVoter
と最終的なビジネスメソッド自体の両方が Customer
オブジェクトの取得を担当する DAO への呼び出しを実行するという点で依然として非効率的です。メソッド呼び出しごとに 2 回アクセスすることは、明らかに望ましくありません。さらに、リストされているすべてのアプローチで、独自のアクセス制御リスト(ACL)の永続性とビジネスロジックを最初から作成する必要があります。
幸い、別の方法がありますが、これについては後で説明します。
主なコンセプト
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
: すべてのドメインオブジェクトには、AccessControlEntry
オブジェクトを内部的に保持し、Acl
の所有者を知っているAcl
オブジェクトが 1 つだけあります。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[]
インスタンスを参照する必要があります。間接レベルのレベルは、Sid
インターフェースによって提供されます。( "SID" は "SecurityIDentity" の略語です)一般的なクラスには、PrincipalSid
(Authentication
オブジェクト内のプリンシパルを表すため)およびGrantedAuthoritySid
が含まれます。セキュリティ ID 情報は、ACL_SID
テーブルに保管されます。ObjectIdentity
: 各ドメインオブジェクトは、ObjectIdentity
によって ACL モジュール内で内部的に表されます。デフォルトの実装はObjectIdentityImpl
と呼ばれます。AclService
: 特定のObjectIdentity
に適用可能なAcl
を取得します。含まれている実装(JdbcAclService
)では、取得操作はLookupStrategy
に委譲されます。LookupStrategy
は、バッチ検索(BasicLookupStrategy
)を使用し、マテリアライズドビュー、階層クエリ、同様のパフォーマンス中心の非 ANSI SQL 機能を使用するカスタム実装をサポートする、ACL 情報を取得するための高度に最適化された戦略を提供します。MutableAclService
: 変更されたAcl
を永続化のために提示できます。このインターフェースの使用はオプションです。
AclService
および関連するデータベースクラスはすべて ANSISQL を使用していることに注意してください。これはすべての主要なデータベースで機能するはずです。このドキュメントの記載時点では、システムは Hypersonic SQL、PostgreSQL、Microsoft SQL Server、および Oracle で正常にテストされていました。
ACL モジュールを示す 2 つのサンプルが Spring Security に付属しています。1 つは連絡先サンプル [GitHub] (英語) で、もう 1 つはドキュメント管理システム(DMS)サンプル [GitHub] (英語) です。これらの例をご覧になることをお勧めします。
入門
Spring Security の ACL 機能を開始するには、ACL 情報をどこかに保存する必要があります。これには、Spring での DataSource
のインスタンス化が必要です。次に、DataSource
が JdbcMutableAclService
および BasicLookupStrategy
インスタンスに注入されます。前者はミューテーター機能を提供し、後者は高性能 ACL 取得機能を提供します。構成例については、Spring Security に付属しているサンプル [GitHub] (英語) の 1 つを参照してください。また、前のセクションにリストされている 4 つの ACL 固有のテーブルをデータベースに入力する必要があります(適切な SQL ステートメントについては ACL サンプルを参照してください)。
必要なスキーマを作成して JdbcMutableAclService
をインスタンス化したら、ドメインモデルが Spring Security ACL パッケージとの相互運用性をサポートしていることを確認する必要があります。ObjectIdentityImpl
は、さまざまなメソッドで使用できるため、十分に機能することを願っています。ほとんどの人は、public Serializable getId()
メソッドを含むドメインオブジェクトを持っています。戻り値の型が long
であるか、long
と互換性がある場合(int
など)、ObjectIdentity
の課題をさらに考慮する必要がない場合があります。ACL モジュールの多くの部分は、長い識別子に依存しています。long
(または int
、byte
など)を使用しない場合は、おそらくいくつかのクラスを再実装する必要があります。long はすでにすべてのデータベースシーケンスと互換性があり、最も一般的な識別子データ型であり、すべての一般的な使用シナリオに対応するのに十分な長さであるため、Spring Security の ACL モジュールで非 long 識別子をサポートする予定はありません。
次のコードの断片は、Acl
を作成する方法、または既存の Acl
を変更する方法を示しています。
Java
Kotlin
// 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);
val oi: ObjectIdentity = ObjectIdentityImpl(Foo::class.java, 44)
val sid: Sid = PrincipalSid("Samantha")
val p: Permission = BasePermission.ADMINISTRATION
// Create or update the relevant ACL
var acl: MutableAcl? = null
acl = try {
aclService.readAclById(oi) as MutableAcl
} catch (nfe: NotFoundException) {
aclService.createAcl(oi)
}
// Now grant some permissions via an access control entry (ACE)
acl!!.insertAce(acl.entries.size, p, sid, true)
aclService.updateAcl(acl)
前の例では、識別子番号 44 の Foo
ドメインオブジェクトに関連付けられた ACL を取得します。次に、"Samantha" という名前のプリンシパルがオブジェクトを「管理」できるように ACE を追加します。コードフラグメントは、insertAce
メソッドを除いて、比較的自明です。insertAce
メソッドの最初の引数は、新しいエントリが挿入される Acl 内の位置を決定します。前の例では、既存の ACE の最後に新しい ACE を配置しました。最後の引数は、ACE が許可するか拒否するかを示すブール値です。ほとんどの場合、許可します(true
)。ただし、拒否した場合(false
)、アクセス許可は事実上ブロックされています。
Spring Security は、DAO またはリポジトリ操作の一部として ACL を自動的に作成、更新、削除するための特別な統合を提供していません。代わりに、個々のドメインオブジェクトについて、前の例に示したものと同様のコードを作成する必要があります。ACL 情報をサービス層の操作と自動的に統合するには、サービス層で AOP を使用することを検討する必要があります。このアプローチが効果的であることがわかりました。
ここで説明する手法を使用して ACL 情報をデータベースに保存したら、次のステップは、認可決定ロジックの一部として ACL 情報を実際に使用することです。ここにはいくつかの選択肢があります。メソッド呼び出しの前または後に(それぞれ)起動する独自の AccessDecisionVoter
または AfterInvocationProvider
を作成できます。このようなクラスは、AclService
を使用して関連する ACL を取得し、Acl.isGranted(Permission[] permission, Sid[] sids, boolean administrativeMode)
を呼び出して、アクセス認可が認可されているか拒否されているかを判断します。または、AclEntryVoter
、AclEntryAfterInvocationProvider
、AclEntryAfterInvocationCollectionFilteringProvider
クラスを使用することもできます。これらのクラスはすべて、実行時に ACL 情報を評価するための宣言ベースのアプローチを提供し、コードを記述する必要がなくなります。
これらのクラスの使用方法については、サンプルアプリケーション [GitHub] (英語) を参照してください。