最新の安定バージョンについては、Spring Security 6.4.5 を使用してください! |
式ベースのアクセス制御
概要
Spring Security は式のサポートに SpEL を使用しており、トピックをより深く理解することに興味がある場合は、それがどのように機能するかを確認する必要があります。式は、評価コンテキストの一部として「ルートオブジェクト」を使用して評価されます。Spring Security は、ルートオブジェクトとして Web およびメソッドのセキュリティ用の特定のクラスを使用して、組み込みの式と、現在のプリンシパルなどの値へのアクセスを提供します。
一般的な組み込み式
式のルートオブジェクトの基本クラスは SecurityExpressionRoot
です。これにより、Web セキュリティとメソッドセキュリティの両方で使用できるいくつかの一般的な式が提供されます。
式 | 説明 |
---|---|
| 現在のプリンシパルに指定されたロールがある場合、 サンプル: デフォルトでは、提供されたロールが |
| 現在のプリンシパルが提供されたロールのいずれかを持っている場合(文字列のコンマ区切りリストとして与えられた場合) サンプル: デフォルトでは、提供されたロールが |
| 現在のプリンシパルが指定された権限を持っている場合、 サンプル: |
| 現在のプリンシパルに指定された権限のいずれかがある場合(文字列のコンマ区切りリストとして指定)、 サンプル: |
| 現在のユーザーを表すプリンシパルオブジェクトへの直接アクセスを許可します。 |
|
|
| 常に |
| 常に |
| 現在のプリンシパルが匿名ユーザーの場合、 |
| 現在のプリンシパルが remember-me ユーザーの場合、 |
| ユーザーが匿名でない場合は |
| ユーザーが匿名ユーザーではなく、remember-me ユーザーでない場合は、 |
| ユーザーが指定された権限に対して指定されたターゲットにアクセスできる場合は、 |
| ユーザーが指定された権限に対して指定されたターゲットにアクセスできる場合は、 |
Web セキュリティ式
式を使用して個々の URL を保護するには、最初に <http>
要素の use-expressions
属性を true
に設定する必要があります。次に、Spring Security は、<intercept-url>
要素の access
属性に SpEL 式が含まれていることを想定しています。各式はブール値に評価され、アクセスを許可するかどうかを定義する必要があります。次のリストは例を示しています。
<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
オブジェクトも直接公開しているため、式で直接リクエストを呼び出すことができます。式が使用されている場合、名前空間で使用される AccessDecisionManager
に WebExpressionVoter
が追加されます。名前空間を使用せずに式を使用する場合は、これらのいずれかを構成に追加する必要があります。
Web セキュリティ式で Bean を参照する
使用可能な式を継承したい場合は、公開している Spring Bean を簡単に参照できます。例: 次のメソッドシグネチャーを含む webSecurity
という名前の Bean があると仮定すると、次を使用できます。
Java
Kotlin
public class WebSecurity {
public boolean check(Authentication authentication, HttpServletRequest request) {
...
}
}
class WebSecurity {
fun check(authentication: Authentication?, request: HttpServletRequest?): Boolean {
// ...
}
}
次に、次のようにメソッドを参照できます。
Java
XML
Kotlin
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/user/**").access(new WebExpressionAuthorizationManager("@webSecurity.check(authentication,request)"))
...
)
<http>
<intercept-url pattern="/user/**"
access="@webSecurity.check(authentication,request)"/>
...
</http>
http {
authorizeRequests {
authorize("/user/**", "@webSecurity.check(authentication,request)")
}
}
Web セキュリティ式のパス変数
URL 内のパス変数を参照できると便利な場合があります。例: /user/{userId}
の形式の URL パスから ID でユーザーを検索する RESTful アプリケーションについて考えてみます。
パターンに配置することで、パス変数を簡単に参照できます。例: 次のメソッドシグネチャーを含む webSecurity
という名前の Bean がある場合は、次を使用できます。
Java
Kotlin
public class WebSecurity {
public boolean checkUserId(Authentication authentication, int id) {
...
}
}
class WebSecurity {
fun checkUserId(authentication: Authentication?, id: Int): Boolean {
// ...
}
}
次に、次のようにメソッドを参照できます。
Java
XML
Kotlin
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/user/{userId}/**").access(new WebExpressionAuthorizationManager("@webSecurity.checkUserId(authentication,#userId)"))
...
);
<http>
<intercept-url pattern="/user/{userId}/**"
access="@webSecurity.checkUserId(authentication,#userId)"/>
...
</http>
http {
authorizeRequests {
authorize("/user/{userId}/**", "@webSecurity.checkUserId(authentication,#userId)")
}
}
この構成では、一致する URL がパス変数を渡して(そしてそれを変換して) checkUserId
メソッドに変換します。例: URL が /user/123/resource
の場合、渡される ID は 123
になります。
メソッドセキュリティ式
メソッドのセキュリティは、単純な許可または拒否のルールよりも少し複雑です。Spring Security 3.0 は、式の使用を包括的にサポートできるようにするために、いくつかの新しいアノテーションを導入しました。
@Pre および @Post アノテーション
式属性をサポートする 4 つのアノテーションがあり、呼び出し前および呼び出し後の認可チェックを可能にし、送信されたコレクション引数または戻り値のフィルタリングもサポートします。@PreAuthorize
、@PreFilter
、@PostAuthorize
、@PostFilter
です。それらの使用は、global-method-security
名前空間要素を介して有効になります。
<global-method-security pre-post-annotations="enabled"/>
@PreAuthorize および @PostAuthorize を使用したアクセス制御
最も明らかに有用なアノテーションは @PreAuthorize
であり、メソッドを実際に呼び出すことができるかどうかを決定します。次の例(「連絡先」サンプルアプリケーション [GitHub] (英語) から)では、@PreAuthorize
アノテーションを使用しています。
Java
Kotlin
@PreAuthorize("hasRole('USER')")
public void create(Contact contact);
@PreAuthorize("hasRole('USER')")
fun create(contact: Contact?)
これは、アクセスが ROLE_USER
ロールを持つユーザーにのみ許可されることを意味します。明らかに、必要なロールに従来の構成と単純な構成属性を使用することで、同じことを簡単に実現できます。ただし、次の例を検討してください。
Java
Kotlin
@PreAuthorize("hasPermission(#contact, 'admin')")
public void deletePermission(Contact contact, Sid recipient, Permission permission);
@PreAuthorize("hasPermission(#contact, 'admin')")
fun deletePermission(contact: Contact?, recipient: Sid?, permission: Permission?)
ここでは、実際に式の一部としてメソッド引数を使用して、現在のユーザーが特定の連絡先に対して admin
権限を持っているかどうかを判断します。組み込みの hasPermission()
式は、このセクションの後半で説明するように、アプリケーションコンテキストを介して Spring Security ACL モジュールにリンクされます。式変数として、名前で任意のメソッド引数にアクセスできます。
Spring Security は、さまざまな方法でメソッド引数を解決できます。Spring Security は、DefaultSecurityParameterNameDiscoverer
を使用してパラメーター名を検出します。デフォルトでは、メソッドに対して次のオプションが試行されます。
Spring Security の
@P
アノテーションがメソッドの単一の引数に存在する場合、その値が使用されます。これは、JDK 8 より前の JDK でコンパイルされたインターフェース(パラメーター名に関する情報が含まれていない)に役立ちます。次の例では、@P
アノテーションを使用しています。Java
Kotlin
import org.springframework.security.access.method.P; ... @PreAuthorize("#c.name == authentication.name") public void doSomething(@P("c") Contact contact);
import org.springframework.security.access.method.P ... @PreAuthorize("#c.name == authentication.name") fun doSomething(@P("c") contact: Contact?)
バックグラウンドでは、これは
AnnotationParameterNameDiscoverer
を使用して実装されます。これは、指定されたアノテーションの value 属性をサポートするようにカスタマイズできます。Spring Data の
@Param
アノテーションがメソッドの少なくとも 1 つのパラメーターに存在する場合、その値が使用されます。これは、パラメーター名に関する情報が含まれていない JDK8 より前の JDK でコンパイルされたインターフェースに役立ちます。次の例では、@Param
アノテーションを使用しています。Java
Kotlin
import org.springframework.data.repository.query.Param; ... @PreAuthorize("#n == authentication.name") Contact findContactByName(@Param("n") String name);
import org.springframework.data.repository.query.Param ... @PreAuthorize("#n == authentication.name") fun findContactByName(@Param("n") name: String?): Contact?
バックグラウンドでは、これは
AnnotationParameterNameDiscoverer
を使用して実装されます。これは、指定されたアノテーションの value 属性をサポートするようにカスタマイズできます。JDK 8 を使用して
-parameters
引数を使用してソースをコンパイルし、Spring 4 + を使用している場合は、標準の JDK リフレクション API を使用してパラメーター名を検出します。これは、クラスとインターフェースの両方で機能します。最後に、コードがデバッグシンボルを使用してコンパイルされた場合、パラメーター名はデバッグシンボルを使用して検出されます。インターフェースにはパラメーター名に関するデバッグ情報がないため、これはインターフェースでは機能しません。インターフェースの場合、アノテーションまたは JDK8 アプローチを使用する必要があります。
式内では任意の SpEL 機能を使用できるため、引数のプロパティにアクセスすることもできます。例: ユーザー名が連絡先のユーザー名と一致するユーザーにのみアクセスを許可する特定のメソッドが必要な場合は、次のように記述できます。
Java
Kotlin
@PreAuthorize("#contact.name == authentication.name")
public void doSomething(Contact contact);
@PreAuthorize("#contact.name == authentication.name")
fun doSomething(contact: Contact?)
@PreFilter および @PostFilter を使用したフィルタリング
Spring Security は、式を使用したコレクション、配列、マップ、ストリームのフィルタリングをサポートしています。これは、メソッドの戻り値に対して最も一般的に実行されます。次の例では、@PostFilter
を使用しています。
Java
Kotlin
@PreAuthorize("hasRole('USER')")
@PostFilter("hasPermission(filterObject, 'read') or hasPermission(filterObject, 'admin')")
public List<Contact> getAll();
@PreAuthorize("hasRole('USER')")
@PostFilter("hasPermission(filterObject, 'read') or hasPermission(filterObject, 'admin')")
fun getAll(): List<Contact?>
@PostFilter
アノテーションを使用する場合、Spring Security は返されたコレクションまたはマップを反復処理し、指定された式が false である要素をすべて削除します。配列の場合、フィルタリングされた要素を含む新しい配列インスタンスが返されます。filterObject
は、コレクション内の現在のオブジェクトを参照します。マップを使用する場合、現在の Map.Entry
オブジェクトを参照します。これにより、式で filterObject.key
または filterObject.value
を使用できます。@PreFilter
を使用して、メソッド呼び出しの前にフィルタリングすることもできますが、これはあまり一般的ではない要件です。構文は同じです。ただし、コレクション型である引数が複数ある場合は、このアノテーションの filterTarget
プロパティを使用して名前で引数を選択する必要があります。
フィルタリングは明らかに、データ取得クエリの調整に代わるものではないことに注意してください。大規模なコレクションをフィルタリングして多くのエントリを削除する場合、これは非効率的である可能性があります。
組み込み式
メソッドのセキュリティに固有の組み込み式がいくつかありますが、これは以前に使用されていました。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 モジュールからの実装です。詳細については、Contacts
[GitHub] (英語) サンプルアプリケーション構成を参照してください。
メソッドセキュリティメタアノテーション
メソッドのセキュリティにメタアノテーションを利用して、コードを読みやすくすることができます。これは、コードベース全体で同じ複雑な式を繰り返す場合に特に便利です。例: 次のことを考慮してください。
@PreAuthorize("#contact.name == authentication.name")
これをどこでも繰り返す代わりに、メタアノテーションを作成できます。
Java
Kotlin
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("#contact.name == authentication.name")
public @interface ContactPermission {}
@Retention(AnnotationRetention.RUNTIME)
@PreAuthorize("#contact.name == authentication.name")
annotation class ContactPermission
Spring Security メソッドのセキュリティアノテーションには、メタアノテーションを使用できます。仕様への準拠を維持するために、JSR-250 アノテーションはメタアノテーションをサポートしていません。