最新の安定バージョンについては、Spring Security 6.3.1 を使用してください!

式ベースのアクセス制御

概要

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

一般的な組み込み式

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

表 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')

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 などの式を使用できます。

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

@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 アノテーションはメタアノテーションをサポートしていません。