最新の安定バージョンについては、Spring Security 6.3.1 を使用してください! |
式ベースのアクセス制御
概要
Spring Security は、式のサポートに Spring EL を使用します。トピックをより深く理解することに興味がある場合は、それがどのように機能するかを確認してください。式は、評価コンテキストの一部として「ルートオブジェクト」で評価されます。Spring Security は、組み込み式と現在のプリンシパルなどの値へのアクセスを提供するために、Web およびメソッドセキュリティの特定のクラスをルートオブジェクトとして使用します。
一般的な組み込み式
式ルートオブジェクトの基本クラスは SecurityExpressionRoot
です。これにより、Web セキュリティとメソッドセキュリティの両方で使用できる一般的な式が提供されます。
式 | 説明 |
---|---|
| 現在のプリンシパルに指定されたロールがある場合、 例: デフォルトでは、指定されたロールが "ROLE_" で始まらない場合は追加されます。これは、 |
| 現在のプリンシパルが提供されたロールのいずれかを持っている場合(文字列のコンマ区切りリストとして与えられた場合) 例: デフォルトでは、指定されたロールが "ROLE_" で始まらない場合は追加されます。これは、 |
| 現在のプリンシパルが指定された権限を持っている場合、 例: |
| 現在のプリンシパルに提供された権限のいずれかがある場合、 例: |
| 現在のユーザーを表すプリンシパルオブジェクトへの直接アクセスを許可します |
|
|
| 常に |
| 常に |
| 現在のプリンシパルが匿名ユーザーの場合、 |
| 現在のプリンシパルが remember-me ユーザーである場合、 |
| ユーザーが匿名でない場合、 |
| ユーザーが匿名ユーザーでも覚えのないユーザーでもない場合は、 |
| ユーザーが指定された許可に対して提供されたターゲットにアクセスできる場合、 |
| ユーザーが指定された許可に対して提供されたターゲットにアクセスできる場合、 |
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 があると仮定します。
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
.antMatchers("/user/**").access("@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
.antMatchers("/user/{userId}/**").access("@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] (英語) サンプルアプリケーションから)
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?)
ここでは、実際に式の一部としてメソッド引数を使用して、現在のユーザーが特定の連絡先に対する「管理者」権限を持っているかどうかを判断しています。組み込みの hasPermission()
式は、以下に示すように、アプリケーションコンテキストを介して Spring Security ACL モジュールにリンクされます。式変数として、名前で任意のメソッド引数にアクセスできます。
Spring Security がメソッドの引数を解決できる方法はいくつかあります。Spring Security は、DefaultSecurityParameterNameDiscoverer
を使用してパラメーター名を検出します。デフォルトでは、メソッド全体に対して次のオプションが試行されます。
Spring Security の
@P
アノテーションがメソッドへの単一の引数に存在する場合、値が使用されます。これは、パラメーター名に関する情報を含まない JDK 8 より前の JDK でコンパイルされたインターフェースに役立ちます。例: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
を使用して実装されます。AnnotationParameterNameDiscoverer
は、指定されたアノテーションの値属性をサポートするようにカスタマイズできます。Spring Data の
@Param
アノテーションがメソッドの少なくとも 1 つのパラメーターに存在する場合、値が使用されます。これは、パラメーター名に関する情報を含まない JDK 8 より前の JDK でコンパイルされたインターフェースに役立ちます。例: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
を使用して実装されます。AnnotationParameterNameDiscoverer
は、指定されたアノテーションの値属性をサポートするようにカスタマイズできます。JDK 8 を使用して -parameters 引数を使用してソースをコンパイルし、Spring 4 + を使用している場合は、標準の JDK リフレクション API を使用してパラメーター名を検出します。これは、クラスとインターフェースの両方で機能します。
最後に、コードがデバッグシンボルを使用してコンパイルされた場合、パラメーター名はデバッグシンボルを使用して検出されます。インターフェースにはパラメーター名に関するデバッグ情報がないため、これは機能しません。インターフェースには、アノテーションまたは JDK 8 アプローチを使用する必要があります。
式内で任意の Spring-EL 機能を使用できるため、引数のプロパティにアクセスすることもできます。例: 連絡先のユーザー名と一致するユーザー名を持つユーザーのみにアクセスを許可する特定の方法が必要な場合は、次のように記述できます。
Java
Kotlin
@PreAuthorize("#contact.name == authentication.name")
public void doSomething(Contact contact);
@PreAuthorize("#contact.name == authentication.name")
fun doSomething(contact: Contact?)
ここでは、セキュリティコンテキストに格納されている Authentication
である別の組み込み式 authentication
にアクセスしています。式 principal
を使用して、その「プリンシパル」プロパティに直接アクセスすることもできます。多くの場合、値は UserDetails
インスタンスであるため、principal.username
や principal.enabled
などの式を使用できます。
@PreFilter および @PostFilter を使用したフィルタリング
Spring Security は、式を使用したコレクション、配列、マップ、ストリームのフィルタリングをサポートしています。これは、メソッドの戻り値に対して最も一般的に実行されます。例:
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
プロパティを使用して名前で 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 モジュールからの実装になります。詳細については、連絡先 [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 アノテーションはメタアノテーションをサポートしていません。