最新の安定バージョンについては、Spring Security 6.3.1 を使用してください! |
ドメインオブジェクトセキュリティ (ACL)
概要
複雑なアプリケーションでは、多くの場合、単純に 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)の永続性とビジネスロジックをゼロから作成する必要があります。
幸いなことに、別の選択肢があります。これについては以下で説明します。
主なコンセプト
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 を使用して正常にテストされていました。
ACL モジュールを示す 2 つのサンプルが Spring Security に付属しています。1 つは連絡先サンプル [GitHub] (英語) で、もう 1 つはドキュメント管理システム(DMS)サンプル [GitHub] (英語) です。例として、これらを確認することをお勧めします。
入門
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 など)を使用していない場合は、多くのクラスを再実装する必要がある可能性が非常に高くなります。long はすでにすべてのデータベースシーケンス、最も一般的な識別子のデータ型と互換性があり、すべての一般的な使用シナリオに対応するのに十分な長さがあるため、Spring Security の ACL モジュールで非ロング識別子をサポートするつもりはありません。
次のコードは、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)
上記の例では、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 情報を評価するための宣言ベースのアプローチを提供するため、コードを記述する必要がありません。これらのクラスの使用方法については、サンプルアプリケーションを参照してください。