9.3. 認可¶
9.3.1. Overview¶
本節では、Spring Securityが提供している認可機能について説明する。
認可処理は、アプリケーションの利用者がアクセスできるリソースを制御するための処理である。 利用者がアクセスできるリソースを制御するためのもっとも標準的な方法は、 リソース(又はリソースの集合)毎にアクセスポリシーを定義してき、利用者がリソースにアクセスしようとした時にアクセスポリシーを調べて制御する方法である。
アクセスポリシーには、どのリソースにどのユーザーからのアクセスを許可するかを定義する。 Spring Securityでは、以下の3つのリソースに対してアクセスポリシーを定義することができる。
- Webリソース
- Javaメソッド
- ドメインオブジェクト [1]
- JSPの画面項目
本節では、「Webリソース」「Javaメソッド」「JSPの画面項目」のアクセスに対して認可処理を適用するための実装例(定義例)を紹介しながら、Spring Securityの認可機能について説明する。
| [1] | ドメインオブジェクトのアクセスに対する認可処理については、 Spring Security Reference -Domain Object Security (ACLs)-を参照されたい。 | 
9.3.1.1. 認可処理のアーキテクチャ¶
Spring Securityは、以下のような流れで認可処理を行う。
| 項番 | 説明 | 
|---|---|
| (1) | クライアントは、任意のリソースにアクセスする。 | 
| (2) | FilterSecurityInterceptorクラスは、AccessDecisionManagerインタフェースのメソッドを呼び出し、リソースへのアクセス権の有無をチェックする。 | 
| (3) | AffirmativeBasedクラス(デフォルトで使用されるAccessDecisionManagerの実装クラス)は、AccessDecisionVoterインタフェースのメソッドを呼び出し、アクセス権の有無を投票させる。 | 
| (4) | FilterSecurityInterceptorは、AccessDecisionManagerによってアクセス権が付与された場合に限り、リソースへアクセスする。 | 
9.3.1.1.1. ExceptionTranslationFilter¶
ExceptionTranslationFilterは、認可処理(AccessDecisionManager)で発生した例外をハンドリングし、クライアントへ適切なレスポンスを行うためのSecurity Filterである。
デフォルトの実装では、未認証ユーザーからのアクセスの場合は認証を促すレスポンス、認証済みのユーザーからのアクセスの場合は認可エラーを通知するレスポンスを返却する。
9.3.1.1.2. FilterSecurityInterceptor¶
FilterSecurityInterceptorは、HTTPリクエストに対して認可処理を適用するためのSecurity Filterで、実際の認可処理はAccessDecisionManagerに委譲する。
AccessDecisionManagerインタフェースのメソッドを呼び出す際には、クライアントがアクセスしようとしたリソースに指定されているアクセスポリシーを連携する。
9.3.1.1.3. AccessDecisionManager¶
AccessDecisionManagerは、アクセスしようとしたリソースに対してアクセス権があるかチェックを行うためのインタフェースである。
Spring Securityが提供する実装クラスは3種類存在するが、いずれもAccessDecisionVoterというインタフェースのメソッドを呼び出してアクセス権を付与するか否かを判定させている。
AccessDecisionVoterは「付与」「拒否」「棄権」のいずれかを投票し、AccessDecisionManagerの実装クラスが投票結果を集約して最終的なアクセス権を判断する。
アクセス権がないと判断した場合は、AccessDeniedExceptionを発生させアクセスを拒否する。
なお、すべての投票結果が「棄権」であった場合、Spring Securityのでデフォルトでは、「アクセス権なし」と判定される。
| クラス名 | 説明 | 
|---|---|
| AffirmativeBased | AccessDecisionVoterに投票させ、「付与」が1件投票された時点でアクセス権を与える実装クラス。デフォルトで使用される実装クラス。 | 
| ConsensusBased | 全ての AccessDecisionVoterに投票させ、「付与」の投票数が多い場合にアクセス権を与える実装クラス。「付与」「拒否」が1件以上、且つ同数の場合、Spring Securityのデフォルトでは、「アクセス権あり」と判定される。 | 
| UnanimousBased | AccessDecisionVoterに投票させ、「拒否」が1件投票された時点で アクセス権を与えない 実装クラス。 | 
Note
AccessDecisionVoterの選択
使用するAccessDecisionVoterが1つの場合はどの実装クラスを使っても動作に違いはない。
複数のAccessDecisionVoterを使用する場合は、要件に合わせて実装クラスを選択されたい。
9.3.1.1.4. AccessDecisionVoter¶
AccessDecisionVoterは、アクセスしようとしたリソースに指定されているアクセスポリシーを参照してアクセス権を付与するかを投票するためのインタフェースである。
Spring Securityが提供する主な実装クラスは以下の通り。
| クラス名 | 説明 | 
|---|---|
| WebExpressionVoter | SpEL経由で認証情報( Authentication)が保持する権限情報とリクエスト情報(HttpServletRequest)を参照して投票を行う実装クラス。 | 
| RoleVoter | 利用者が持つロールを参照して投票を行う実装クラス。 | 
| RoleHierarchyVoter | 利用者が持つ階層化されたロールを参照して投票を行う実装クラス。 | 
| AuthenticatedVoter | 認証状態を参照して投票を行う実装クラス。 | 
Note
デフォルトで適用されるAccessDecisionVoter
デフォルトで適用されるAccessDecisionVoterインタフェースの実装クラスは、Spring Security 4.0からWebExpressionVoterに統一されている。
WebExpressionVoterは、RoleVoter、RoleHierarchyVoter、AuthenticatedVoterを使用した時と同じことが実現できるため、
本ガイドラインでも、デフォルトのWebExpressionVoterを使って認可処理を行う前提で説明を行う。
9.3.2. How to use¶
認可機能を使用するために必要となるbean定義例(アクセスポリシーの指定方法)や実装方法について説明する。
9.3.2.1. アクセスポリシーの記述方法¶
アクセスポリシーの記述方法を説明する。
Spring Securityは、アクセスポリシーを指定する記述方法としてSpring Expression Language(SpEL)をサポートしている。 SpELを使わない方法もあるが、本ガイドラインではExpressionを使ってアクセスポリシーを指定する方法で説明を行う。 SpELの使い方については本節でも紹介するが、より詳しい使い方を知りたい場合は Spring Framework Reference Documentation -Spring Expression Language (SpEL)-を参照されたい。
9.3.2.1.1. Built-InのCommon Expressions¶
Spring Securityが用意している共通的なExpressionは以下の通り。
| Expression | 説明 | 
|---|---|
| hasRole(String role) | ログインユーザーが、引数に指定したロールを保持している場合に trueを返却する。 | 
| hasAnyRole(String... roles) | ログインユーザー、が引数に指定したロールのいずれかを保持している場合に trueを返却する。 | 
| isAnonymous() | ログインしていない匿名ユーザーの場合に trueを返却する。 | 
| isRememberMe() | Remember Me認証によってログインしたユーザーの場合に trueを返却する。 | 
| isAuthenticated() | ログイン中の場合に trueを返却する。 | 
| isFullyAuthenticated() | Remember Me認証ではなく通常の認証プロセスによってログインしたユーザーの場合に trueを返却する。 | 
| permitAll | 常に trueを返却する。 | 
| denyAll | 常に falseを返却する。 | 
| principal | 認証されたユーザーのユーザー情報( UserDetailsインタフェースを実装したクラスのオブジェクト)を返却する。 | 
| authentication | 認証されたユーザーの認証情報( Authenticationインタフェースを実装したクラスのオブジェクト)を返却する。 | 
Note
Expressionを使用した認証情報へのアクセス
Expressionとしてprincipalやauthenticationを使用すると、ログインユーザーのユーザー情報や認証情報を参照することができるため、ロール以外の属性を使ってアクセスポリシーを設定することが可能になる。
Note
ロール名のプレフィックス
Spring Security 3.2までは、ロール名には"ROLE_" プレフィックスを指定する必要があったが、Spring Security 4.0から"ROLE_" プレフィックスの指定が不要となっている。
例)
- Spring Secuirty 3.2以前 : hasRole('ROLE_USER')
- Spring Security 4.0以降 : hasRole('USER')
9.3.2.1.2. Built-InのWeb Expressions¶
Spring Securityが用意しているWebアプリケーション向けExpressionは以下の通り。
| Expression | 説明 | 
|---|---|
| hasIpAddress(String ipAddress) | リクエスト元のIPアドレスが、引数に指定したIPアドレス体系に一致する場合に trueを返却する。 | 
9.3.2.1.3. 演算子の使用¶
演算子を使用した判定も行うことができる。 以下の例では、ロールと、リクエストされたIPアドレス両方に合致した場合、アクセス可能となる。
- spring-security.xmlの定義例 - <sec:http> <sec:intercept-url pattern="/admin/**" access="hasRole('ADMIN') and hasIpAddress('192.168.10.1')"/> <!-- omitted --> </sec:http> - 使用可能な演算子一覧 - 演算子 - 説明 - [式1] and [式2]式1、式2が、どちらも真の場合に、真を返す。- [式1] or [式2]いずれかの式が、真の場合に、真を返す。- ![式]式が真の場合は偽を、偽の場合は真を返す。
9.3.2.2. Webリソースへの認可¶
Spring Securityは、サーブレットフィルタの仕組みを利用してWebリソース(HTTPリクエスト)に対して認可処理を行う。
9.3.2.2.1. 認可処理の適用¶
Webリソースに対して認可処理を適用する場合は、以下のようなbean定義を行う。
- spring-security.xmlの定義例
<sec:http>
    <!-- omitted -->
    <sec:intercept-url pattern="/**" access="isAuthenticated()" />  <!-- (1) -->
    <!-- omitted -->
</sec:http>
| 項番 | 説明 | 
|---|---|
| (1) | <sec:intercept-url>タグに、HTTPリクエストに対してアクセスポリシーを定義する。ここでは、SpELを使用して「Webアプリケーション配下の全てのリクエストに対して認証済みのユーザーのみアクセスを許可する」というアクセスポリシーを定義している。 | 
Note
use-expressionsのデフォルト定義
Spring Security 4.0から、<sec:http> タグのuse-expressions属性のデフォルト値がtrueに変更になっているため、trueを使用する場合に明示的な記述は不要となった。
9.3.2.2.2. アクセスポリシーの定義¶
bean定義ファイルを使用して、Webリソースに対してアクセスポリシーを定義する方法について説明する。
9.3.2.2.2.1. アクセスポリシーを適用するWebリソースの指定¶
まず、アクセスポリシーを適用するリソース(HTTPリクエスト)を指定する。
アクセスポリシーを適用するリソースの指定は、<sec:intercept-url>タグの以下の属性を使用する。
| 属性名 | 説明 | 
|---|---|
| pattern | Ant形式又は正規表現で指定したパスパターンに一致するリソースを適用対象にするための属性。 | 
| method | 指定したHTTPメソッド(GET,POSTなど)を使ってアクセスがあった場合に適用対象にするための属性。 | 
| requires-channel | 「http」、もしくは「https」を指定する。指定したプロトコルでのアクセスを強制するための属性。 指定しない場合、どちらでもアクセス可能である。 | 
上記以外の属性については、<intercept-url>を参照されたい。
- <sec:intercept-url>タグ- pattern属性の定義例(spring-security.xml)
<sec:http >
    <sec:intercept-url pattern="/admin/accounts/**" access="..."/>
    <sec:intercept-url pattern="/admin/**" access="..."/>
    <sec:intercept-url pattern="/**" access="..."/>
    <!-- omitted -->
</sec:http>
Spring Securityは定義した順番でリクエストとのマッチング処理を行い、最初にマッチした定義を適用する。 そのため、bean定義ファイルを使用してアクセスポリシーを指定する場合も定義順番には注意が必要である。
Tip
パスパターンの解釈
Spring Securityのデフォルトの動作では、パスパターンはAnt形式で解釈する。
パスパターンを正規表現で指定したい場合は、<sec:http>タグのrequest-matcher属性に
"regex"を指定すること。
<sec:http request-matcher="regex"> <sec:intercept-url pattern="/admin/accounts/.*" access=hasRole('ACCOUNT_MANAGER')" /> <!-- omitted --> </sec:http>
Warning
Spring MVCとSpring Securityでは、リクエストとのマッチングの仕組みが厳密には異なっており、この差異を利用してSpring Securityの認可機能を突破し、ハンドラメソッドにアクセスできる脆弱性が存在する。 本事象の詳細は「CVE-2016-5007 Spring Security / MVC Path Matching Inconsistency」を参照されたい。
本事象は、trimTokens プロパティに false を設定した org.springframework.util.AntPathMatcher のBeanをSpring MVCに適用することで回避することができる。
<mvc:annotation-driven> <mvc:path-matching path-matcher="pathMatcher" /> </mvc:annotation-driven> <bean id="pathMatcher" class="org.springframework.util.AntPathMatcher"> <property name="trimTokens" value="false" /> </bean>
上記の対策をTERASOLUNA Server Framework for Javaで提供するブランクプロジェクトでは設定しているが、 設定を外すと脆弱性にさらされてしまうので注意する必要がある。
また、特定のURLに対してアクセスポリシーを設ける(pattern属性に*や**などのワイルドカード指定を含めない)場合、
拡張子を付けたパターンとリクエストパスの末尾に/を付けたパターンに対するアクセスポリシーの追加が必須である。
下記の設定例は、/restrictに対して「ROLE_ADMIN」ロールを持つユーザからのアクセスのみを許可している。
<sec:http> <sec:intercept-url pattern="/restrict.*" access="hasRole('ADMIN')" /> <!-- (1) --> <sec:intercept-url pattern="/restrict/" access="hasRole('ADMIN')" /> <!-- (2) --> <sec:intercept-url pattern="/restrict" access="hasRole('ADMIN')" /> <!-- (3) --> <!-- omitted --> </sec:http>
項番 説明 /restrictに拡張子を付けたパターン(/restrict.jsonなど)のアクセスポリシーを定義する。/restrictにリクエストパスの末尾に/を付けたパターン(/restrict/など)のアクセスポリシーを定義する。/restrictに対するアクセスポリシーを定義する。
Warning
Spring SecurityとSpring MVCではアクセスされたURLを取得する方法が異なっているため、この差異を利用してSpring Securityの認可機能を突破しハンドラメソッドにアクセスできる脆弱性が存在する。 本事象は、APサーバまたはバージョンによって発生状況が異なる。 詳細は「CVE-2016-9879 Encoded “/” in path variables」を参照されたい。
対策として、本事象への対策が行われているTERASOLUNA Server Framework for Java 5.3.0.RELEASE以降にバージョンアップされたい。
9.3.2.2.2.2. アクセスポリシーの指定¶
つぎに、アクセスポリシーを指定する。
アクセスポリシーの指定は、<sec:intercept-url>タグのaccess属性に指定する。
- <sec:intercept-url>タグ- access属性の定義例(- spring-security.xml)- <sec:http> <sec:intercept-url pattern="/admin/accounts/**" access="hasRole('ACCOUNT_MANAGER')"/> <sec:intercept-url pattern="/admin/configurations/**" access="hasIpAddress('127.0.0.1') and hasRole('CONFIGURATION_MANAGER')" /> <sec:intercept-url pattern="/admin/**" access="hasRole('ADMIN')" /> <!-- omitted --> </sec:http> - アクセスポリシーを指定するための属性¶ - 属性名 - 説明 - accessSpELでのアクセス制御式や、アクセス可能なロールを指定する。
- <sec:intercept-url>タグ- pattern属性の定義例(spring-security.xml)- <sec:http> <sec:intercept-url pattern="/reserve/**" access="hasAnyRole('USER','ADMIN')" /> <!-- (1) --> <sec:intercept-url pattern="/admin/**" access="hasRole('ADMIN')" /> <!-- (2) --> <sec:intercept-url pattern="/**" access="denyAll" /> <!-- (3) --> <!-- omitted --> </sec:http> - 項番 - 説明 (1)「/reserve/**」にアクセスするためには、「ROLE_USER」もしくは「ROLE_ADMIN」ロールが必要である。- hasAnyRoleについては、後述する。(2)「/admin/**」にアクセスするためには、「ROLE_ADMIN」ロールが必要である。- hasRoleについては、後述する。(3)- denyAllを全てのパターンに設定し、権限設定が記述されていないURLに対してはどのユーザーもアクセス出来ない設定としている。- denyAllについては、後述する。- Note - URLパターンの記述順序について - クライアントからのリクエストに対して、intercept-urlで記述されているパターンに、上から順にマッチさせ、マッチしたパターンに対してアクセス認可を行う。 そのため、パターンの記述は、必ず、より限定されたパターンから記述すること。 
Spring Securiyではデフォルトで、SpELが有効になっている。
access属性に記述したSpELは真偽値で評価され、式が真の場合に、アクセスが認可される。
以下に使用例を示す。
- spring-security.xmlの定義例 - <sec:http> <sec:intercept-url pattern="/admin/**" access="hasRole('ADMIN')"/> <!-- (1) --> <!-- omitted --> </sec:http> - 項番 - 説明 (1)- hasRole('ロール名')を指定することで、ログインユーザーが指定したロールを保持していれば真を返す。
使用可能な主なExpressionは、アクセスポリシーの記述方法 を参照されたい。
9.3.2.3. メソッドへの認可¶
Spring Securityは、Spring AOPの仕組みを利用してDIコンテナで管理しているBeanのメソッド呼び出しに対して認可処理を行う。
メソッドに対する認可処理は、ドメイン層(サービス層)のメソッド呼び出しに対して行うことを想定して提供されている。 メソッドに対する認可処理を使用すると、ドメインオブジェクトのプロパティを参照することができるため、きめの細かいアクセスポリシーの定義を行うことが可能になる。
9.3.2.3.1. AOPの有効化¶
メソッドへの認可処理を使用する場合は、メソッド呼び出しに対して認可処理を行うためのコンポーネント(AOP)を有効化する必要がある。 AOPを有効化すると、アクセスポリシーをメソッドのアノテーションに定義できるようになる。
Spring Securityは、以下のアノテーションをサポートしている。
- @PreAuthorize、- @PostAuthorize、- @PreFilter、- @PostFilter
- JSR-250 (javax.annotation.securityパッケージ)のアノテーション(@RolesAllowedなど)
- @Secured
本ガイドラインでは、アクセスポリシーをExpressionで使用することができる@PreAuthorize、@PostAuthorizeを使用する方法を説明する。
- spring-security.xmlの定義例
<sec:global-method-security pre-post-annotations="enabled" /> <!-- (1) (2) -->
| 項番 | 説明 | 
|---|---|
| (1) | <sec:global-method-security>タグを付与すると、メソッド呼び出しに対する認可処理を行うAOPが有効になる。 | 
| (2) | pre-post-annotations属性にtrueを指定する。pre-post-annotations属性にtrueを指定すると、Expressionを指定してアクセスポリシーを定義できるアノテーションが有効になる。 | 
9.3.2.3.2. 認可処理の適用¶
メソッドに対して認可処理を適用する際は、アクセスポリシーを指定するアノテーションを使用して、メソッド毎にアクセスポリシーを定義する。
9.3.2.3.3. アクセスポリシーの定義¶
9.3.2.3.3.1. メソッド実行前に適用するアクセスポリシーの指定¶
メソッドの実行前に適用するアクセスポリシーを指定する場合は、@PreAuthorizeを使用する。
@PreAuthorizeのvalue属性に指定したExpressionの結果がtrueになるとメソッドの実行が許可される。
下記例では、管理者以外は、他人のアカウント情報にアクセスできないように定義している。
- @PreAuthorizeの定義例
// (1) (2)
@PreAuthorize("hasRole('ADMIN') or (#username == principal.username)")
public Account findOne(String username) {
    return accountRepository.findOne(username);
}
| 項番 | 説明 | 
|---|---|
| (1) | 認可処理を適用したいメソッドに、 @PreAuthorizeを付与する。 | 
| (2) | value属性に、メソッドに対してアクセスポリシーを定義する。ここでは、「管理者の場合は全てのアカウントへのアクセスを許可する」「管理者以外の場合は自身のアカウントへのアクセスのみ許可する」というアクセスポリシーを定義している。 | 
ここでポイントになるのは、Expressionの中からメソッドの引数にアクセスしている部分である。
具体的には、「#username」の部分が引数にアクセスしている部分である。
Expression内で「# + 引数名」形式のExpressionを指定することで、メソッドの引数にアクセスすることができる。
Tip
引数名を指定するアノテーション
Spring Securityは、クラスに出力されているデバッグ情報から引数名を解決する仕組み
になっているが、アノテーション(@org.springframework.security.access.method.P)
を使用して明示的に引数名を指定することもできる。
以下のケースにあてはまる場合は、アノテーションを使用して明示的に変数名を指定する。
- クラスに変数のデバッグ情報を出力しない 
- Expressionの中から実際の変数名とは別の名前を使ってアクセスしたい (例えば短縮した名前) - @PreAuthorize("hasRole('ADMIN') or (#username == principal.username)") public Account findOne(@P("username") String username) { return accountRepository.findOne(username); } 
なお、#usernameと、メソッドの引数である usernameの名称が一致している場合は @Pを省略することが可能である。
ただし、Spring Securityは引数名の解決を、実装クラスの引数名を使用して行っているため @PreAuthorize アノテーションをインターフェースに定義している場合には、
実装クラスの引数名を、 @PreAuthorize 内で指定した #username と一致させる必要がある ので、注意されたい。
JDK 8 から追加されたコンパイルオプション(-parameters)を使用すると、メソッドパラメータにリフレクション用のメタデータが生成されるため、アノテーションを指定しなくても引数名が解決される。
9.3.2.3.3.2. メソッド実行後に適用するアクセスポリシーの指定¶
メソッドの実行後に適用するアクセスポリシーを指定する場合は、@PostAuthorizeを使用する。
@PostAuthorizeのvalue属性に指定したExpressionの結果がtrueになるとメソッドの実行結果が呼び出し元に返却される。
下記例では、所属する部署が違うユーザーのアカウント情報にアクセスできないように定義している。
- @PostAuthorizeの定義例
@PreAuthorize("...")
@PostAuthorize("(returnObject == null) " +
        "or (returnObject.departmentCode == principal.account.departmentCode)")
public Account findOne(String username) {
    return accountRepository.findOne(username);
}
ここでポイントになるのは、Expressionの中からメソッドの返り値にアクセスしている部分である。
具体的には、「returnObject.departmentCode」の部分が返り値にアクセスしている部分である。
Expression内で「returnObject」を指定すると、メソッドの返り値にアクセスすることができる。
9.3.2.4. JSPの画面項目への認可¶
Spring Securityは、JSPタグライブラリを使用してJSPの画面項目に対して認可処理を適用することができる。
ここでは最もシンプルな定義を例に、JSPの画面項目のアクセスに対して認可処理を適用する方法について説明する。
9.3.2.4.1. アクセスポリシーの定義¶
JSPタグライブラリを使用してJSPの画面項目に対してアクセスポリシーを定義する際は、表示を許可する条件(アクセスポリシー)をJSPに定義する。
- アクセスポリシー定義例
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
<!-- (1) -->
<sec:authorize access="hasRole('ADMIN')"> <!-- (2) -->
    <h2>Admin Menu</h2>
    <!-- omitted -->
</sec:authorize>
| 項番 | 説明 | 
|---|---|
| (1) | アクセスポリシーを適用したい部分を <sec:authorize>タグで囲む。 | 
| (2) | access属性にアクセスポリシーを定義する。ここでは、「管理者の場合は表示を許可する」というアクセスポリシーを定義している。 | 
9.3.2.4.2. Webリソースに指定したアクセスポリシーとの連動¶
ボタンやリンクなど(サーバーへのリクエストを伴う画面項目)に対してアクセスポリシーを定義する際は、リクエスト先のWebリソースに定義されているアクセスポリシーと連動させる。
Webリソースに指定したアクセスポリシーと連動させる場合は、<sec:authorize>タグのurl属性を使用する。
url属性に指定したWebリソースにアクセスできる場合に限り<sec:authorize>タグの中に実装したJSPの処理が実行される。
- Webリソースに定義されているアクセスポリシーとの連携例
<ul>
    <!-- (1) -->
    <sec:authorize url="/admin/accounts"> <!-- (2) -->
        <li>
            <a href="<c:url value='/admin/accounts' />">Account Management</a>
        </li>
    </sec:authorize>
</ul>
| 項番 | 説明 | 
|---|---|
| (1) | ボタンやリンクを出力する部分を <sec:authorize>タグで囲む。 | 
| (2) | <sec:authorize>タグのurl属性にWebリソースへアクセスするためのURLを指定する。ここでは、「 "/admin/accounts"というURLが割り振られているWebリソースにアクセス可能な場合は表示を許可する」というアクセスポリシーを定義しており、Webリソースに定義されているアクセスポリシーを直接意識する必要がない。 | 
Note
HTTPメソッドによるポリシーの指定
Webリソースのアクセスポリシーの定義をする際に、HTTPメソッドによって異なるアクセスポリシーを指定している場合は、<sec:authorize>タグのmethod属性を指定して、連動させる定義を特定すること。
Warning
表示制御に関する留意点
ボタンやリンクなどの表示制御を行う場合は、必ずWebリソースに定義されているアクセスポリシーと連動させること。
ボタンやリンクに対して直接アクセスポリシーの指定を行い、Webリソース自体にアクセスポリシーを定義していないと、 URLを直接してアクセスするような不正なアクセスを防ぐことができない。
9.3.2.4.3. 認可処理の判定結果を変数に格納¶
<sec:authorize>タグを使って呼び出した認可処理の判定結果は、変数に格納して使いまわすことができる。
- JSPの実装例
<sec:authorize url="/admin/accounts"
               var="hasAccountsAuthority"/> <!-- (1) -->
<c:if test="${hasAccountsAuthority}"> <!-- (2) -->
    <!-- omitted -->
</c:if>
| 項番 | 説明 | 
|---|---|
| (1) | var属性に判定結果を格納するための変数名を指定する。アクセスが許可された場合は、変数に trueが設定される。 | 
| (2) | 変数の値を参照して表示処理を実装する。 | 
9.3.2.5. 認可エラー時のレスポンス¶
Spring Securityは、リソースへのアクセスを拒否した場合、以下のような流れでエラーをハンドリングしてレスポンスの制御を行う。
| 項番 | 説明 | 
|---|---|
| (1) | Spring Securityは、リソースやメソッドへのアクセスを拒否するために、 AccessDeniedExceptionを発生させる。 | 
| (2) | ExceptionTranslationFilterクラスは、AccessDeniedExceptionをキャッチし、AccessDeniedHandlerまたはAuthenticationEntryPointインタフェースのメソッドを呼び出してエラー応答を行う。 | 
| (3) | 認証済みのユーザーからのアクセスの場合は、 AccessDeniedHandlerインタフェースのメソッドを呼び出してエラー応答を行う。 | 
| (4) | 未認証のユーザーからのアクセスの場合は、 AuthenticationEntryPointインタフェースのメソッドを呼び出してエラー応答を行う。 | 
9.3.2.5.1. AccessDeniedHandler¶
AccessDeniedHandlerインタフェースは、認証済みのユーザーからのアクセスを拒否した際のエラー応答を行うためのインタフェースである。
Spring Securityは、AccessDeniedHandlerインタフェースの実装クラスとして以下のクラスを提供している。
| クラス名 | 説明 | 
|---|---|
| AccessDeniedHandlerImpl | HTTPレスポンスコードに403(Forbidden)を設定し、指定されたエラーページに遷移する。 エラーページの指定がない場合は、HTTPレスポンスコードに403(Forbidden)を設定してエラー応答( HttpServletResponse#sendError)を行う。 | 
| InvalidSessionAccessDeniedHandler | InvalidSessionStrategyインタフェースの実装クラスに処理を委譲する。このクラスは、CSRF対策とセッション管理機能を使用してセッションタイムアウトを検知する設定を有効にした際に、CSRFトークンがセッションに存在しない(つまりセッションタイムアウトが発生している)場合に使用される。 | 
| DelegatingAccessDeniedHandler | AccessDeniedExceptionとAccessDeniedHandlerインタフェースの実装クラスのマッピングを行い、発生したAccessDeniedExceptionに対応するAccessDeniedHandlerインタフェースの実装クラスに処理を委譲する。InvalidSessionAccessDeniedHandlerはこの仕組みを利用して呼び出されている。 | 
Spring Securityのデフォルトの設定では、エラーページの指定がないAccessDeniedHandlerImplが使用される。
9.3.2.5.2. AuthenticationEntryPoint¶
AuthenticationEntryPointインタフェースは、未認証のユーザーからのアクセスを拒否した際のエラー応答を行うためのインタフェースである。
Spring Securityは、AuthenticationEntryPointインタフェースの実装クラスとして以下のクラスを提供している。
| クラス名 | 説明 | 
|---|---|
| LoginUrlAuthenticationEntryPoint | フォーム認証用のログインフォームを表示する。 | 
| BasicAuthenticationEntryPoint | Basic認証用のエラー応答を行う。 具体的には、HTTPレスポンスコードに401(Unauthorized)を、レスポンスヘッダとしてBasic認証用の「 WWW-Authenticate」ヘッダを設定してエラー応答(HttpServletResponse#sendError)を行う。 | 
| DigestAuthenticationEntryPoint | Digest認証用のエラー応答を行う。 具体的には、HTTPレスポンスコードに401(Unauthorized)を、レスポンスヘッダとしてDigest認証用の「 WWW-Authenticate」ヘッダを設定してエラー応答(HttpServletResponse#sendError)を行う。 | 
| Http403ForbiddenEntryPoint | HTTPレスポンスコードに403(Forbidden)を設定してエラー応答( HttpServletResponse#sendError)を行う。 | 
| DelegatingAuthenticationEntryPoint | RequestMatcherとAuthenticationEntryPointインタフェースの実装クラスのマッピングを行い、HTTPリクエストに対応するAuthenticationEntryPointインタフェースの実装クラスに処理を委譲する。 | 
Spring Securityのデフォルトの設定では、認証方式に対応するAuthenticationEntryPointインタフェースの実装クラスが使用される。
9.3.2.5.3. 認可エラー時の遷移先¶
Spring Securityのデフォルトの設定だと、認証済みのユーザーからのアクセスを拒否した際は、アプリケーションサーバのエラーページが表示される。 アプリケーションサーバーのエラーページを表示してしまうと、システムのセキュリティを低下させる要因になるのため、適切なエラー画面を表示することを推奨する。 エラーページの指定は、以下のようなbean定義を行うことで可能である。
- spring-security.xmlの定義例
<sec:http>
    <!-- omitted -->
    <sec:access-denied-handler
        error-page="/WEB-INF/views/common/error/accessDeniedError.jsp" /> <!-- (1) -->
    <!-- omitted -->
</sec:http>
| 項番 | 説明 | 
|---|---|
| (1) | <sec:access-denied-handler>タグのerror-page属性に認可エラー用のエラーページを指定する。 | 
Tip
サーブレットコンテナのエラーページ機能の利用
認可エラーのエラーページは、サーブレットコンテナのエラーページ機能を使って指定することもできる。
サーブレットコンテナのエラーページ機能を使う場合は、web.xmlの<error-page>タグを使用してエラーページを指定する。
<error-page> <error-code>403</error-code> <location>/WEB-INF/views/common/error/accessDeniedError.jsp</location> </error-page>
9.3.3. How to extend¶
本節では、Spring Securityが用意しているカスタマイズポイントや拡張方法について説明する。
Spring Securityは、多くのカスタマイズポイントを提供しているため、すべてのカスタマイズポイントは紹介しない。 本節では代表的なカスタマイズポイントに絞って説明を行う。
9.3.3.1. 認可エラー時のレスポンス (認証済みユーザー編)¶
ここでは、認証済みユーザーからのアクセスを拒否した際の動作をカスタマイズする方法を説明する。
9.3.3.1.1. AccessDeniedHandlerの適用¶
Spring Securityが提供しているデフォルトの動作をカスタマイズする仕組みだけでは要件をみたせない場合は、AccessDeniedHandlerインタフェースの実装クラスを直接適用することができる。
例えば、Ajaxのリクエスト(REST APIなど)で認可エラーが発生した場合は、エラーページ(HTML)ではなくJSON形式でエラー情報を応答することが求められるケースがある。
そのような場合は、AccessDeniedHandlerインタフェースの実装クラスを作成してSpring Securityに適用することで実現することができる。
- AccessDeniedHandlerインタフェースの実装クラスの作成例
public class JsonDelegatingAccessDeniedHandler implements AccessDeniedHandler {
    private final RequestMatcher jsonRequestMatcher;
    private final AccessDeniedHandler delegateHandler;
    public JsonDelegatingAccessDeniedHandler(
            RequestMatcher jsonRequestMatcher, AccessDeniedHandler delegateHandler) {
        this.jsonRequestMatcher = jsonRequestMatcher;
        this.delegateHandler = delegateHandler;
    }
    public void handle(HttpServletRequest request, HttpServletResponse response,
                       AccessDeniedException accessDeniedException)
            throws IOException, ServletException {
        if (jsonRequestMatcher.matches(request)) {
            // response error information of JSON format
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            // omitted
        } else {
            // response error page of HTML format
            delegateHandler.handle(
                    request, response, accessDeniedException);
        }
    }
}
- spring-security.xmlの定義例
<!-- (1) -->
<bean id="accessDeniedHandler"
      class="com.example.web.security.JsonDelegatingAccessDeniedHandler">
    <constructor-arg>
        <bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
            <constructor-arg value="/api/**"/>
        </bean>
    </constructor-arg>
    <constructor-arg>
        <bean class="org.springframework.security.web.access.AccessDeniedHandlerImpl">
            <property name="errorPage"
                      value="/WEB-INF/views/common/error/accessDeniedError.jsp"/>
        </bean>
    </constructor-arg>
</bean>
<sec:http>
    <!-- omitted -->
    <sec:access-denied-handler ref="accessDeniedHandler" />  <!-- (2) -->
    <!-- omitted -->
</sec:http>
| 項番 | 説明 | 
|---|---|
| (1) | AccessDeniedHandlerインタフェースの実装クラスをbean定義してDIコンテナに登録する。 | 
| (2) | <sec:access-denied-handler>タグのref属性にAccessDeniedHandlerのbeanを指定する。 | 
9.3.3.2. 認可エラー時のレスポンス (未認証ユーザー編)¶
ここでは、未認証ユーザーからのアクセスを拒否した際の動作をカスタマイズする方法を説明する。
9.3.3.2.1. リクエスト毎にAuthenticationEntryPointを適用¶
認証済みユーザーと同様に、Ajaxのリクエスト(REST APIなど)で認可エラーが発生した場合は、ログインページ(HTML)ではなくJSON形式でエラー情報を応答することが求められるケースがある。
そのような場合は、リクエストのパターン毎にAuthenticationEntryPointインタフェースの実装クラスをSpring Securityに適用することで実現することができる。
- spring-security.xmlの定義例
<!-- (1) -->
<bean id="authenticationEntryPoint"
      class="org.springframework.security.web.authentication.DelegatingAuthenticationEntryPoint">
    <constructor-arg>
        <map>
            <entry>
                <key>
                    <bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
                        <constructor-arg value="/api/**"/>
                    </bean>
                </key>
                <bean class="com.example.web.security.JsonAuthenticationEntryPoint"/>
            </entry>
        </map>
    </constructor-arg>
    <property name="defaultEntryPoint">
        <bean class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
            <constructor-arg value="/login"/>
        </bean>
    </property>
</bean>
<sec:http entry-point-ref="authenticationEntryPoint"> <!-- (2) -->
    <!-- omitted -->
</sec:http>
| 項番 | 説明 | 
|---|---|
| (1) | AuthenticationEntryPointインタフェースの実装クラスをbean定義してDIコンテナに登録する。ここでは、Spring Securityが提供している DelegatingAuthenticationEntryPointクラスを利用して、リクエストのパターン毎にAuthenticationEntryPointインタフェースの実装クラスを適用している。 | 
| (2) | <sec:http>タグのentry-point-ref属性にAuthenticationEntryPointのbeanを指定する。 | 
Note
デフォルトで適用されるAuthenticationEntryPoint
リクエストに対応するAuthenticationEntryPointインタフェースの実装クラスの指定がない場合は、Spring Securityがデフォルトで定義するAuthenticationEntryPointインタフェースの実装クラスが使用される仕組みになっている。
認証方式としてフォーム認証を使用する場合は、LoginUrlAuthenticationEntryPointクラスが使用されログインフォームが表示される。
9.3.3.3. ロールの階層化¶
認可処理では、ロールに階層関係を設けることができる。
上位に指定したロールは、下位のロールにアクセスが許可されているリソースにもアクセスすることができる。 ロールの関係が複雑な場合は、階層関係も設けることも検討されたい。
例えば、「ROLE_ADMIN」が上位ロール、「ROLE_USER」が下位ロールという階層関係を設けた場合、
下記のようアクセスポリシーを設定すると、「ROLE_ADMIN」権限を持つユーザーは、
"/user"配下のパス(「ROLE_USER」権限を持つユーザーがアクセスできるパス)にアクセスすることができる。
- spring-security.xmlの定義例
<sec:http>
    <sec:intercept-url pattern="/user/**" access="hasAnyRole('USER')" />
    <!-- omitted -->
</sec:http>
9.3.3.3.1. 階層関係の設定¶
ロールの階層関係は、org.springframework.security.access.hierarchicalroles.RoleHierarchyインタフェースの実装クラスで解決する。
- spring-security.xmlの定義例
<bean id="roleHierarchy"
    class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl"> <!-- (1) -->
    <property name="hierarchy"> <!-- (2) -->
        <value>
            ROLE_ADMIN > ROLE_STAFF
            ROLE_STAFF > ROLE_USER
        </value>
    </property>
</bean>
| 項番 | 説明 | 
|---|---|
| (1) | org.springframework.security.access.hierarchicalroles.RoleHierarchyImplクラスを指定する。RoleHierarchyImplは、Spring Securityが提供するデフォルトの実装クラスである。 | 
| (2) | hierarchyプロパティに階層関係を定義する。書式: [上位ロール] > [下位ロール] 上記例では、 STAFFは、USERに認可されたリソースにもアクセス可能である。 ADMINは、USERとSTAFFに認可されたリソースにもアクセス可能である。 | 
9.3.3.3.2. Webリソースの認可処理への適用¶
ロールの階層化を、WebリソースとJSPの画面項目に対する認可処理に適用する方法を説明する。
- spring-security.xmlの定義例
<!-- (1) -->
<bean id="webExpressionHandler"
    class="org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler">
    <property name="roleHierarchy" ref="roleHierarchy"/>  <!-- (2) -->
</bean>
<sec:http>
    <!-- omitted -->
    <sec:expression-handler ref="webExpressionHandler" />  <!-- (3) -->
</sec:http>
| 項番 | 説明 | 
|---|---|
| (1) | org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandlerのBeanを定義する。 | 
| (2) | roleHierarchyプロパティにRoleHierarchyインタフェースの実装クラスのBeanを指定する。 | 
| (3) | <sec:expression-handler>タグのref属性に、org.springframework.security.access.expression.SecurityExpressionHandlerインタフェースの実装クラスのBeanを指定する。 | 
9.3.3.3.3. メソッドの認可処理への適用¶
ロールの階層化を、Javaメソッドに対する認可処理に適用する方法を説明する。
- spring-security.xmlの定義例
<bean id="methodExpressionHandler"
    class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler"> <!-- (1) -->
    <property name="roleHierarchy" ref="roleHierarchy"/> <!-- (2) -->
</bean>
<sec:global-method-security pre-post-annotations="enabled">
    <sec:expression-handler ref="methodExpressionHandler" /> <!-- (3) -->
</sec:global-method-security>
| 項番 | 説明 | 
|---|---|
| (1) | org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandlerのBeanを定義する。 | 
| (2) | roleHierarchyプロパティにRoleHierarchyインタフェースの実装クラスのBeanを指定する。 | 
| (3) | <sec:expression-handler>タグのref属性に、org.springframework.security.access.expression.SecurityExpressionHandlerインタフェースの実装クラスのBeanを指定する。 | 

