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

式ベースのアクセス制御

概要

Spring Security は式のサポートに SpEL を使用しており、トピックをより深く理解することに興味がある場合は、それがどのように機能するかを確認する必要があります。式は、評価コンテキストの一部として「ルートオブジェクト」を使用して評価されます。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()

ユーザーが匿名ユーザーではなく、remember-me ユーザーでない場合は、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 属性に 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?)

ここでは、セキュリティコンテキストに格納されている Authentication である別の組み込み式 authentication にアクセスします。principal 式を使用して、principal プロパティに直接アクセスすることもできます。多くの場合、値は UserDetails インスタンスであるため、principal.username や principal.enabled などの式を使用できます。

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