SeamFramework.org Community Documentation

- 前のページ
- 次のページ

## 第15章 セキュリティ

## 15.1. 概要

SeamのセキュリティAPIはSeamベースのアプリケーションに種々のセキュリティ関連機能を提供します。 これらの機能には、以下のようなものがあります。

- 認証ー種々のセキュリティプロバイダによるユーザー認証を可能にする、拡張可能なJAASベースの認証層を提供します

- ID管理ーSeamアプリケーション動作時にユーザーとロールを管理するAPIを提供します

- 認可ーユーザーロール、永続的なルールをベースにしたパーミッションや、カスタマイズ可能でセキュリティロジックを簡単に実装できるプラグイン可能な許可リゾルバーをサポートする、非常に包括的な、認可フレームワーク

- パーミッション管理ーアプリケーションのセキュリティポリシーを容易に管理する事を可能にする一連のSeam組み込みコンポーネント

- CAPCHAのサポートーSeamで作られたサイトを自動検索プログラムスクリプトによる予期しない動作から保護するための支援をします

- 等々

この章ではこれらの機能の詳細について説明します

## 15.2. セキュリティの無効化

ユニットテスト時のように、状況によりSeamのセキュリティを無効化する必要がある場合があります。 たとえば、ユニットテストや、ネイティブのJAASのような別のセキュリティへのアプローチをする場合です。このような場合には、静的メソッド `Identity.setSecurityEnabled(false)` を呼ぶことによりセキュリティチェックを無効にすることができます。 もちろん、アプリケーションを設定したいときにスタティックメソッドを呼び出すのはあまり便利ではありません。そこで、別のアプローチとして、次のように component.xml で設定を制御することができます。

- エンティティのセキュリティ

- Hibernateセキュリティインタセプタ

- Seamセキュリティインタセプタ

- ページ単位の制約

- サーブレット API へのセキュリティの統合

Seam セキュリティが提供する機能を利用する計画があると仮定して、この章の残りは、セキュリティモデルの見地からユーザーにアイデンティティを与えたり (認証) 、制約を設定することでアプリケーションに鍵をかける (認可) のための、大量のオプションをドキュメント化します。認証の仕事はセキュリティモデルの基盤となるので、そこから始めましょう。

## 15.3. 認証

Seam セキュリティの提供する認証機構は JAAS (Java Authentication and Authorization Service) の上に構築されており、 ユーザー認証のための堅牢で設定の自由度の高い API を提供しています。 しかしながら、 Seamで は JAAS の複雑さを隠蔽したより単純化された認証機構も提供しています。

### 15.3.1. 認証コンポーネントの設定

## 注意

この章で解説するSeamのID管理機能を使用するのであれば、authenticator（認証）コンポーネントを作成する必要はありませんのでこの章を飛ばしても結構です。

単純な認証機構ではSeamアプリケーションのコンポーネントに認証を委ねる`SeamLoginModule`（これは、Seamに内蔵されているJAASのログインモジュールです）を使います。 このログインモジュールはSeamのデフォルトのアプリケーションポリシーとして予め設定されていますので、新たに設定に追加する事なく使用することができます。 また、作成したアプリケーションのエンティティクラスを利用して、認証メソッドを記述したり、 サード―パーティのプロバイダを使った認証をする事ができます。 この「単純な認証機構」を利用するためには`components.xml`に下記のように`identity`コンポーネントを設定する必要があります。

    <components xmlns="http://jboss.com/products/seam/components"

                xmlns:core="http://jboss.com/products/seam/core"

                xmlns:security="http://jboss.com/products/seam/security"

                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

                xsi:schemaLocation=

                    "http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.2.xsd

                     http://jboss.com/products/seam/security http://jboss.com/products/seam/security-2.2.xsd">

        <security:identity authenticate-method="#{authenticator.authenticate}"/>

    </components

    >

EL式 `#{authenticator.authenticate}` は`authenticator`コンポーネントの`authenticate`メソッドを使って、ユーザーの認証を行うことを示すメソッドバインディングです。

### 15.3.2. 認証メソッドの記述

`components.xml` 中の`identity`の`authenticate-method`プロパティで`SeamLoginModule`にユーザーの認証に使うメソッドを指定します。 このメソッドはパラメータを取らず、認証が成功したか否かのboolean型を返します。 ユーザーのusernameとpasswordは`Credentials.getUsername()` と `Credentials.getPassword()`からそれぞれ取得します（また、`Identity.instance().getCredentials()`から`credentials`コンポーネントを参照する事もできます）。 ユーザーがメンバーとして参加するロールは`Identity.addRole()`により指定される必要があります。 以下にPOJOコンポーネント中の認証メソッドの完全な例を示します。

    @Name("authenticator")

    public class Authenticator {

       @In EntityManager entityManager;

       @In Credentials credentials;

       @In Identity identity;

       public boolean authenticate() {

          try {

             User user = (User) entityManager.createQuery(

                "from User where username = :username and password = :password")

                .setParameter("username", credentials.getUsername())

                .setParameter("password", credentials.getPassword())

                .getSingleResult();

             if (user.getRoles() != null) {

                for (UserRole mr : user.getRoles())

                   identity.addRole(mr.getName());

             }

             return true;

          }

          catch (NoResultException ex) {

             return false;

          }

       }

    }

上記の例では、`User`と`UserRole`はアプリケーション独自のエンティティBeanとなっています。 パラメータ `roles` は "admin", "user" の様に文字列として、`Set`に追加されてゆく必要があります。 この例の場合、userが見付からずに`NoResultException`が投げられた場合には、認証メソッドは`false`を返して、認証が失敗したことを示します。

## ティップ

認証メソッドを記述する場合、副次的な影響を受けない、あるいは影響が最小になるようにすることが重要です。 これは、セキュリティAPIにより何回認証メソッドが呼び出されるのか保証が無く、一回の要求で複数回実行されることもあるからです。 このため、認証が成功あるいは失敗した時に実行されるコードはイベントオブザーバを利用して記述されるべきです。 Seamのセキュリティにより発生するイベントについての詳細はこの章の後半に記載されています。

#### 15.3.2.1. Identity.addRole()

`Identity.addRole()`メソッドは現在のユーザーが認証されているか否かで動作が異なります。 認証されていないセッションの場合には、`addRole()`は認証過程でのみ呼び出され、指定されたロールは、認証されていないロールの仮のリストに登録され、認証が成功すると、仮のロールから実際のロールに移行し、このロールで`Identity.hasRole()`が実行され、trueが返されます。 以下のシークエンス図に、仮の認証ロールリストの認証プロセスにおける役割について示します。

カレントセッションがすでに認証されている場合に`Identity.addRole()` を呼ぶと指定されたロールが即座に現在のユーザーに付与されます。

#### 15.3.2.2. セキュリティ関連のイベントのためのオブザーバの記述

例として、ログインに成功する度にユーザーの統計データを更新する場合を考えてみましょう。 これは、`org.jboss.seam.security.loginSuccessful`イベントのオブザーバを記述する事により、以下のように実現できます。

       @In UserStats userStats;

       @Observer("org.jboss.seam.security.loginSuccessful")

       public void updateUserStats()

       {

          userStats.setLastLoginDate(new Date());

          userStats.incrementLoginCount();

       }

このオブザーバメソッドは、Authenticatorコンポーネントを含め、どこにおいても構いません。 セキュリティ関連のイベントについてこの章でさらに見てゆきます。

### 15.3.3. ログインフォームの記述

`credentials` コンポーネントは`username`および`password`属性を保持しており、一般的な認証処理に対応できるようになっています。 これらの属性はログインフォームのusernameとpasswordフィールドに直接バインドする事が可能です。 これらの属性が設定されてしまえば、後は`identity.login()`を呼び出すことにより、保持されているusernameとpasswordによるユーザーの認証が行われます。簡単なログインフォームの例を示します。

    <div>

        <h:outputLabel for="name" value="Username"/>

        <h:inputText id="name" value="#{credentials.username}"/>

    </div>

    <div>

        <h:outputLabel for="password" value="Password"/>

        <h:inputSecret id="password" value="#{credentials.password}"/>

    </div>

    <div>

        <h:commandButton value="Login" action="#{identity.login}"/>

    </div

    >

（loginと）同様にログアウトも、`#{identity.logout}`を呼び出すことにより実行されます。ログアウトを実行することにより、現在認証されているユーザーのセキュリティの状態をクリアし、セッションは無効化されます。

### 15.3.4. 設定のまとめ

まとめとして、認証システムを設定するためには、以下の三つのステップが必要となります。

- 認証メソッドを`components.xml`に設定する。

- 認証メソッドを記述する。

- ユーザーを認証するためのログインフォームを記述する。

### 15.3.5. Remember Me （覚えておいて！）

Seamのセキュリティ機能ではオンラインのWEBアプリケーションで一般的に提供されている"Remember me"(覚えておいてね）機能をサポートしています。 この機能は、二つの異なったモードをサポートしており、一つはusernameをユーザーのブラウザにクッキーとして保存し、ブラウザからpasswordの入力を促すものです（この場合でも、最近のほとんどのブラウザは、passwordを記憶しています）。

第2のモードはユニークなトークンをクッキーとして記憶しておいて、そのサイトに入ると、passwordの入力をする事なく自動的にユーザーの認証をする機能をサポートするものです。

## 警告

クライアント側の永続的なクッキーによる自動的な認証（第2のモード）は危険で、ユーザーへの利便性は向上しますが、クロスサイトスクリプティングに対するセキュリティホールの影響を通常より遥かに重大な物としてしまいます。 認証のためのクッキーでなければ、XSSにより攻撃者に盗まれるクッキーは、現在のセッションのユーザーのクッキーという事になります。 これは、ユーザーがセッションを開いていなければ攻撃が有効でないことを意味し、攻撃可能な時間が非常に短い事を意味します。 攻撃者が自動認証をサポートする永続的なRemember meクッキーを盗む可能性があるとすれば、それはたいへん危険なことです。 この機能の利用の危険性は、システムがXSS攻撃に対してどれだけ防御できているのかに依存し、XSS攻撃に対して100％の防御を保証している必要がありますが、入力内容をWEBページに表示するようなサイトにとって、これは簡単ではありません。

ブラウザのベンダーはこの問題を認識しており、最近のブラウザでは新たに導入されたRemember Passwordをサポートしています。 この場合は、ブラウザは特定のドメイン、ウェブサイトに対してのusernameとpasswordを記憶しており、ウェブサイトとのセッションがアクティブでない状態で、ログインフォームのusernameとpasswordを自動的に埋めてゆきます。 ウェブデザイナの立場であれば、ログインのためのショートカットキーを設定しておけば、Remember Meと同様にユーザーの利便性を向上させることができます。 OS-xのSafariなど一部のブラウザでは、OSのキーチェインに暗号化したログインのフォームを記憶させています。 また、ブラウザのクッキーは一般に同期化させることはできませんが、ネットワーク環境ではこのキーチェインはラップトップからデスクトップへと移動させることができます。

まとめ：自動認証をする永続的なRemember Meの使用は一般化してしまっていますが、セキュリティ上、不適切であり使用すべきではありません。 ログイン時のusernameのみを記憶するクッキーを使用することには問題はありません。

デフォルトのRemember me(usernameのみ）機能を使用するためには、特に設定は必要ありません。 下の例のように、ログインフォームにremember meチェックボックスを入れて、これを`rememberMe.enabled`とバインドするだけです。

      <div>

        <h:outputLabel for="name" value="User name"/>

        <h:inputText id="name" value="#{credentials.username}"/>

      </div>

      <div>

        <h:outputLabel for="password" value="Password"/>

        <h:inputSecret id="password" value="#{credentials.password}" redisplay="true"/>

      </div

    >

      <div class="loginRow">

        <h:outputLabel for="rememberMe" value="Remember me"/>

        <h:selectBooleanCheckbox id="rememberMe" value="#{rememberMe.enabled}"/>

      </div

    >

#### 15.3.5.1. トークンベースのremember me認証

トークンベースの自動認証機能の remember me を使用するためには、まずトークンの記憶場所を設定する必要があります。 Seamでもサポートしていますが、このトークンの記憶場所としてはデータベースが一般的です。 しかし、`org.jboss.seam.security.TokenStore` インタフェースを実装して独自の記憶場所を設定することも可能です。 この章では標準で提供されている`JpaTokenStore`実装を使用して認証トークンをデータベーステーブルに記憶させることを前提としています。

まず最初に、トークンを保持する新たなエンティティを作ります。 以下に、一般的なエンティティの構造を示します。

    @Entity

    public class AuthenticationToken implements Serializable {

       private Integer tokenId;

       private String username;

       private String value;

       @Id @GeneratedValue

       public Integer getTokenId() {

          return tokenId;

       }

       public void setTokenId(Integer tokenId) {

          this.tokenId = tokenId;

       }

       @TokenUsername

       public String getUsername() {

          return username;

       }

       public void setUsername(String username) {

          this.username = username;

       }

       @TokenValue

       public String getValue() {

          return value;

       }

       public void setValue(String value) {

          this.value = value;

       }

    }

このコードから分かるように、エンティティのusernameとトークンのプロパティには`@TokenUsername` と`@TokenValue`という特別なアノテーションが使われており、これらは認証トークンを含むエンティティに必須です。

次に、このエンティティBeanに対して認証トークンの出し入れをするために、 `JpaTokenStore`を設定します。 これは、 `components.xml` に`token-class`属性を指定することにより行います。

      <security:jpa-token-store token-class="org.jboss.seam.example.seamspace.AuthenticationToken"/>

ここまでが終了したら、最後は`components.xml` に`RememberMe`コンポーネントの設定をする事です。 `mode`は`autoLogin`に設定されていなければなりません。

      <security:remember-me mode="autoLogin"/>

これで、remember meをチェックしているユーザーがサイトを再訪した時に、自動的に認証されるようになります。

ユーザーがサイトを再訪した時に確実に自動的に認証される様にするために、`components.xml`に以下が含まれている必要があります。

      <event type="org.jboss.seam.security.notLoggedIn">

        <action execute="#{redirect.captureCurrentView}"/>

        <action execute="#{identity.tryLogin()}"/>

      </event>

      <event type="org.jboss.seam.security.loginSuccessful">

        <action execute="#{redirect.returnToCapturedView}"/>

      </event

    >

### 15.3.6. セキュリティ例外の処理

セキュリティエラーでユーザーがデフォルトのエラーページを受け取らないようにするために、`pages.xml`にセキュリティエラーに対応した、もう少し見栄えのするページにリダイレクトするよう設定する事が推奨されます。 セキュリティAPIの発生させる例外には主として二つのタイプがあります。

- `NotLoggedInException` - ユーザーがログインすることなく、特定のページ閲覧、或は特定の操作を実行しようとしたときに投げられます。

- `AuthorizationException` - ユーザーが既にログインしていて、当該ユーザーが許可されていないページの閲覧、或は操作を行おうとしたときに投げられます。

`NotLoggedInException`の場合、ユーザーがログインできるよう、ユーザーをログインページかユーザー登録ページへ誘導する事が推奨されます。 一方、`AuthorizationException`の場合にはユーザーをエラーページに誘導した方が良いでしょう。 以下の例では、この二つのセキュリティ例外によるリダイレクトを処理している`pages.xml`を示しています。

    <pages>

        ...

        <exception class="org.jboss.seam.security.NotLoggedInException">

            <redirect view-id="/login.xhtml">

                <message

    >You must be logged in to perform this action</message>

            </redirect>

        </exception>

        <exception class="org.jboss.seam.security.AuthorizationException">

            <end-conversation/>

            <redirect view-id="/security_error.xhtml">

                <message

    >You do not have the necessary security privileges to perform this action.</message>

            </redirect>

        </exception>

    </pages

    >

ほとんどのwebアプリケーションでは、より洗練されたログインリダイレクトを必要としますが、Seamではこの様なケースに対応できるような機能も持たせています。

### 15.3.7. ログインリダイレクト

認証されていないユーザーが特定のビュー（或はワイルドカードで指定された複数のビュー）の閲覧をしようとした時に、Seamがユーザーをログイン画面にリダイレクトするようにするためには (`pages.xml`に) 下のように記述します。

    <pages login-view-id="/login.xhtml">

        <page view-id="/members/*" login-required="true"/>

        ...

    </pages

    >

## ティップ

これは、前項までの例外処理に比べて少々ぶっきらぼうさを抑えた処理ですが、例外処理によるリダイレクトと組み合わせて使用すると良いでしょう

ユーザーがログインした後で、再度ログインし直したい場合に自動的に最初のページ（ユーザーが入ってきたページ）に戻したいような状況を考えてみましょう。 下の様にイベントリスナーを`components.xml`に記述すると、ログインせずに制限されたページの閲覧をした（閲覧に失敗した）ことを記憶させておいて、ユーザーが再ログインして成功したときに、当初の要求時のページパラメータをもと当該ページにリダイレクトさせることができます。

    <event type="org.jboss.seam.security.notLoggedIn">

        <action execute="#{redirect.captureCurrentView}"/>

    </event>

    <event type="org.jboss.seam.security.postAuthenticate">

        <action execute="#{redirect.returnToCapturedView}"/>

    </event

    >

ログインリダイレクトは対話スコープで実装されていますので、`authenticate()`の中で対話を終了させてはいけません。

### 15.3.8. HTTP認証

推奨されませんが、どうしても必要であれば、Seamは（RFC2617）のHTTPBasicあるいはHTTPDigestメソッドを認証に使用する事ができます。 これらの認証フォームを使用する場合にはcomponents.xmlで `authentication-filter`が使用可能に設定されている必要があります。

      <web:authentication-filter url-pattern="*.seam" auth-type="basic"/>

ベーシックな認証フィルタを使用する場合、`auth-type`に`basic`を設定し、ダイジェスト認証を使用する場合には、`digest`を設定します。 ダイジェスト認証を使用する場合には `key` と `realm`も設定する必要があります。

      <web:authentication-filter url-pattern="*.seam" auth-type="digest" key="AA3JK34aSDlkj" realm="My App"/>

`key`は任意の文字列です。 `realm`はユーザーが認証される時にユーザーに提供される認証レルムです。

#### 15.3.8.1. ダイジェスト認証の記述

ダイジェスト認証を使用する場合は`org.jboss.seam.security.digest.DigestAuthenticator`アブストラクトクラスを拡張して、`validatePassword()`メソッドによりユーザーのプレーンテキストのパスワードとダイジェスト要求を照合する必要があります。 以下はコード例です。

       public boolean authenticate()

       {

          try

          {

             User user = (User) entityManager.createQuery(

                "from User where username = :username")

                .setParameter("username", identity.getUsername())

                .getSingleResult();

             return validatePassword(user.getPassword());

          }

          catch (NoResultException ex)

          {

             return false;

          }

       }

### 15.3.9. 高度な認証機能

ここでは、より高度なセキュリティ要求に応えられる、セキュリティAPIで提供されているさらに高度な機能について紹介します。

#### 15.3.9.1. 使用しているコンテナのJAASの設定

もしSeamのセキュリティAPIが提供する簡素化されたJAAS設定を使用したくなければ、`components.xml`に`jaas-config-name` プロパティを設定する事によりシステムのデフォルトのJAAS設定にすることができます。 例として、JBossASをアプリケーションサーバーとして使用していて `other`ポリシー（JBossASの提供する `UsersRolesLoginModule`ログインモジュールを使用する）を使用したい場合には、`components.xml`に以下のような記述をします。

    <security:identity jaas-config-name="other"/>

これは単にSeamのセキュリティに対して設定されたJAASセキュリティポリシーに基づいて認証を行うように指示をしているだけで、Seamアプリケーションが配置されているどのコンテナに対してもユーザーの認証がなされたわけではない事に留意してください。

## 15.4. IDの管理

ID管理は、バックエンドのIDストアの種類（データベース、LDAP等）に依存しない、Seamのユーザーとロールの管理のための標準APIを提供します。 ID管理APIの中心は`identityManager`コンポーネントで、新規ユーザーの作成、変更、削除、ロールの追加、無効化、パスワードの変更、ユーザーアカウントの有効化、無効化、ユーザーの認証、ユーザーとロールの一覧等の機能のためのメソッドを提供します。

使用する前に`identityManager`に一つ以上の`IdentityStore`sを設定する必要があります。 これらのコンポーネントが実際にバックにあるデータベース、LDAP、その他のセキュリティプロバイダと協調して仕事をします。

### 15.4.1. IDマネージャの設定

`identityManager` コンポーネントに認証と許可について別々のIDストアを設定する事が可能で、例えば、LDAPディレクトリを使用してユーザーの認証をし、RDBからこのユーザーのロール情報を得て使用する事ができます。

Seamは`IdentityStore`として二つの`IdentityStore`の実装を提供しています。 ひとつはRDBを使用してユーザーとロールの情報を保持する`JpaIdentityStore`で、デフォルトのIDストアとして設定されており、`identityManager` コンポーネントの設定をすることなく使用する事ができます。 もう一つは`LdapIdentityStore`で、LDAPディレクトリを使用してユーザーとロールを保持します。

`identityManager` コンポーネントには`identityStore` と `roleIdentityStore`の二つの設定可能なプロパティがあります。これらの値は、`IdentityStore`インタフェースを実装したSeamコンポーネントを参照するEL式である必要があります。 既に言及したように、設定がされていない場合にはデフォルトの `JpaIdentityStore`が使用され、また、`identityStore`のみが設定された場合には`roleIdentityStore` に同じ値が設定されたものとして処理されます。 例えば、`components.xml`で`LdapIdentityStore`を`identityManager`に使用するように設定した場合には、ユーザーに関するものと、ロールに関するものの両方に`identityManager`が使用されます。

      <security:identity-manager identity-store="#{ldapIdentityStore}"/>

下記の例ではユーザーに関しては`LdapIdentityStore`を、またロールに関する処理には`JpaIdentityStore`を使用するよう`identityManager` を設定しています。

      <security:identity-manager

        identity-store="#{ldapIdentityStore}"

        role-identity-store="#{jpaIdentityStore}"/>

以下の章ではこれらのIDストアのインプリメンテーションの詳細について説明します

### 15.4.2. JpaIdentityStore

このIDストアはユーザーおよびロールをリレーショナルデータベースに保存する事を可能としています。 また、データベースのスキーマ設計にはできる限り制約を作らないように設計されており、使用するテーブルの構造に大幅な自由度を認めています。 これはユーザーおよびロールのレコード用のエンティティBeanに特別のアノテーションを使用する事により実現しています。

#### 15.4.2.1. JpaIdentityStoreの設定

`JpaIdentityStore` は `user-class` 属性と`role-class`属性を設定する必要があります。 これらの属性は、それぞれユーザーとロールのレコードを保存するエンティティクラスを参照している必要があります。 下の例では、サンプルソースのSeamSpaceの`components.xml`の該当部分を示しています。

      <security:jpa-identity-store

        user-class="org.jboss.seam.example.seamspace.MemberAccount"

        role-class="org.jboss.seam.example.seamspace.MemberRole"/>

#### 15.4.2.2. エンティティの設定

先に述べたように、特定のアノテーションを使用してユーザーとロールを保持するエンティティBeanを設定します。 下の表に、これらのアノテーションとその詳細な説明について示します。

表 15.1. ユーザーエンティティアノテーション

アノテーション | 状態 | 詳細
--- | --- | ---
`@UserPrincipal` | 要求条件 | このアノテーションでユーザーのusernameを保持しているフィールドあるいはメソッドをマークします。
`@UserPassword` | 要求条件 | このアノテーションは、アノテートされたフィールドあるいはメソッドにユーザーのpasswordがある事を示しています。 passwordのハッシュアルゴリズムを`hash`で指定することが可能で、指定できる値には`md5`, `sha` と `none`があります。
    @UserPassword(hash = "md5")

    public String getPasswordHash() {

      return passwordHash;

    }
Seamが標準でサポートしていないハッシュアルゴリズムを使用する場合には、`PasswordHash`を拡張して必要なハッシュアルゴリズムを実装してください。
`@UserFirstName` | オプション | ユーザーのファーストネームを保持しているフィールドあるいはメソッドをマークします。
`@UserLastName` | オプション | ユーザーのラストネームを保持しているフィールドあるいはメソッドをマークします。
`@UserEnabled` | オプション | このアノテーションは、アノテートされたフィールドあるいはメソッドがユーザーが不活化されているか否かを示していることを示しています。 ここでアノテートされるフィールドあるいはメソッドの属性はbooleanでなければなりません。 また、もしこのアノテーションが無ければ、すべてのユーザーが不活化されていないことになります。
`@UserRoles` | 要求条件 | このアノテーションは、アノテートされたフィールドあるいはメソッドにユーザーのロールがある事を示しています。 この属性については、以下により詳細に記述します。

表 15.2. エンティティのロールのアノテーション

アノテーション | 状態 | 詳細
--- | --- | ---
`@RoleName` | 要求条件 | ユーザーのロール名を保持しているフィールドあるいはメソッドをマークします。
`@RoleGroups` | オプション | ロールのグループメンバーを保持しているフィールドあるいはメソッドをマークします。
`@RoleConditional` | オプション | ロールが条件付きか否かを示すフィールドあるいはメソッドをマークします。

#### 15.4.2.3. エンティティ Beanの例

既に示したように`JpaIdentityStore`はデータベースのユーザーとロールに関するテーブルのスキーマのデザインができるだけ自由にできるように設計されています。 ここでは、ユーザーとロールを保持するいくつかのデータベースのスキーマについてみてゆきます。

##### 15.4.2.3.1. 最小のスキーマの例

この単純な例では、クロス参照テーブル`UserRoles`を通じてmany-to-many関連でリンクされているuserとroleのテーブルで構成されています。

    @Entity

    public class User {

      private Integer userId;

      private String username;

      private String passwordHash;

      private Set<Role

    > roles;

      @Id @GeneratedValue

      public Integer getUserId() { return userId; }

      public void setUserId(Integer userId) { this.userId = userId; }

      @UserPrincipal

      public String getUsername() { return username; }

      public void setUsername(String username) { this.username = username; }

      @UserPassword(hash = "md5")

      public String getPasswordHash() { return passwordHash; }

      public void setPasswordHash(String passwordHash) { this.passwordHash = passwordHash; }

      @UserRoles

      @ManyToMany(targetEntity = Role.class)

      @JoinTable(name = "UserRoles",

        joinColumns = @JoinColumn(name = "UserId"),

        inverseJoinColumns = @JoinColumn(name = "RoleId"))

      public Set<Role

    > getRoles() { return roles; }

      public void setRoles(Set<Role

    > roles) { this.roles = roles; }

    }

    @Entity
    public class Role {
      private Integer roleId;
      private String rolename;

      @Id @Generated
      public Integer getRoleId() { return roleId; }
      public void setRoleId(Integer roleId) { this.roleId = roleId; }

      @RoleName
      public String getRolename() { return rolename; }
      public void setRolename(String rolename) { this.rolename = rolename; }
    }

##### 15.4.2.3.2. 複雑なスキーマの例

この例では、前の最少機能の例にすべてのオプションフィールドと、ロールにグループメンバーを許可する機能を追加しています。

    @Entity

    public class User {

      private Integer userId;

      private String username;

      private String passwordHash;

      private Set<Role

    > roles;

      private String firstname;

      private String lastname;

      private boolean enabled;

      @Id @GeneratedValue

      public Integer getUserId() { return userId; }

      public void setUserId(Integer userId) { this.userId = userId; }

      @UserPrincipal

      public String getUsername() { return username; }

      public void setUsername(String username) { this.username = username; }

      @UserPassword(hash = "md5")

      public String getPasswordHash() { return passwordHash; }

      public void setPasswordHash(String passwordHash) { this.passwordHash = passwordHash; }

      @UserFirstName

      public String getFirstname() { return firstname; }

      public void setFirstname(String firstname) { this.firstname = firstname; }

      @UserLastName

      public String getLastname() { return lastname; }

      public void setLastname(String lastname) { this.lastname = lastname; }

      @UserEnabled

      public boolean isEnabled() { return enabled; }

      public void setEnabled(boolean enabled) { this.enabled = enabled; }

      @UserRoles

      @ManyToMany(targetEntity = Role.class)

      @JoinTable(name = "UserRoles",

        joinColumns = @JoinColumn(name = "UserId"),

        inverseJoinColumns = @JoinColumn(name = "RoleId"))

      public Set<Role

    > getRoles() { return roles; }

      public void setRoles(Set<Role

    > roles) { this.roles = roles; }

    }

    @Entity
    public class Role {
      private Integer roleId;
      private String rolename;
      private boolean conditional;

      @Id @Generated
      public Integer getRoleId() { return roleId; }
      public void setRoleId(Integer roleId) { this.roleId = roleId; }

      @RoleName
      public String getRolename() { return rolename; }
      public void setRolename(String rolename) { this.rolename = rolename; }

      @RoleConditional
      public boolean isConditional() { return conditional; }
      public void setConditional(boolean conditional) { this.conditional = conditional; }

      @RoleGroups
      @ManyToMany(targetEntity = Role.class)
      @JoinTable(name = "RoleGroups",
        joinColumns = @JoinColumn(name = "RoleId"),
        inverseJoinColumns = @JoinColumn(name = "GroupId"))
      public Set<Role
    > getGroups() { return groups; }
      public void setGroups(Set<Role
    > groups) { this.groups = groups; }

    }

#### 15.4.2.4. JpaIdentityStore関連のイベント

`IdentityManager`のIDストアの実装として`JpaIdentityStore`を使用する場合、特定の `IdentityManager`メソッドを起動するとイベントが発生します。

##### 15.4.2.4.1. JpaIdentityStore.EVENT_PRE_PERSIST_USER

このイベントは`IdentityManager.createUser()`を呼ぶと発生します。 ユーザーエンティティがデータベースに保持される直前にエンティティインスタンスをイベントパラメータとしたイベントが発生します。 このエンティティは`JpaIdentityStore`に設定した `user-class`のインスタンスです。

エンティティに標準の`createUser()`で設定されない追加的なフィールド値を設定するためにこのイベントに対応するオブザーバを利用する事ができます。

##### 15.4.2.4.2. JpaIdentityStore.EVENT_USER_CREATED

このイベントは`IdentityManager.createUser()`を呼ぶことによっても発生します。 しかし、このイベントはユーザーエンティティの内容がデータベースに保持された後に発生します。 このイベントも`EVENT_PRE_PERSIST_USER`と同様、イベントパラメータとしてエンティティのインスタンスを渡します。 ユーザーエンティティを参照する、コンタクトの詳細や当該ユーザー特有のデータのエンティティをデータベースに保存する必要がある場合には、このイベントを観察しておくのが有効です。

##### 15.4.2.4.3. JpaIdentityStore.EVENT_USER_AUTHENTICATED

このイベントは`IdentityManager.authenticate()`を呼んだ時に発生します。 このイベントはエンティティインスタンスをイベントパラメータとして渡しますので、認証されたユーザーのその他の属性を読みだす時に有効に使用できます。

### 15.4.3. LdapIdentityStore

このIDストアの実装はLDAPディレクトリをユーザーレコードとして使用するよう設計されています。 この実装は、ユーザーとロールのディレクトリへの保存の方法の設定の自由度が高くなっています。 以下のセクションでは、このIIDストアの設定オプションについて説明し、いくつかのサンプル設定を示します。

#### 15.4.3.1. LdapIdentiyStoreの設定

以下の表に`components.xml` で設定できる`LdapIdentityStore`の属性について示します。

表 15.3. LdapIdentityStore設定可能属性

プロパティ | デフォルト値 | 詳細
--- | --- | ---
`server-address` | `localhost` | LDAPサーバのアドレス
`server-port` | `389` | LDAPサーバが使用しているポートの番号
`user-context-DN` | `ou=Person,dc=acme,dc=com` | ユーザーレコードを含むコンテキストの識別名（DN)
`user-DN-prefix` | `uid=` | この値がユーザーレコードの位置指定するためにusernameの前に前置されます。
`user-DN-suffix` | `,ou=Person,dc=acme,dc=com` | この値がユーザーレコードの位置指定するためにusernameの後ろに追加されます。
`role-context-DN` | `ou=Role,dc=acme,dc=com` | ロールレコードを含むコンテキストの識別子（DN)
`role-DN-prefix` | `cn=` | この値がロール名の前に前置され、ロールレコードを位置指定するための識別子として使用されます。
`role-DN-suffix` | `,ou=Roles,dc=acme,dc=com` | この値がロール名の後ろに追加され、ロールレコードを位置指定するための識別子として使用されます。
`bind-DN` | `cn=Manager,dc=acme,dc=com` | LDAPサーバとバインドするために使用するコンテキスト
`bind-credentials` | `secret` | LDAPサーバとバインドするときに使用されるクレデンシャル（パスワード）
`user-role-attribute` | `roles` | ユーザーがメンバーであるロールのリストを含んでいるユーザーレコード中の属性の名前
`role-attribute-is-DN` | `true` | このブール値はユーザーレコード中のロール属性が識別名か否かを示しています。
`user-name-attribute` | `uid` | ユーザーレコードのどの属性がusernameに該当するのかを示しています。
`user-password-attribute` | `userPassword` | ユーザーレコードのどの属性がpasswordに該当するのかを示しています。
`first-name-attribute` | `null` | ユーザーレコードのどの属性がfirst nameに該当するのかを示しています。
`last-name-attribute` | `sn` | ユーザーレコードのどの属性がlast nameに該当するのかを示しています。
`full-name-attribute` | `cn` | ユーザーレコードのどの属性がユーザーのフルネームに該当するのかを示しています。
`enabled-attribute` | `null` | ユーザーレコードのどの属性がユーザーが不活化されていないかを示しています。
`role-name-attribute` | `cn` | ロールレコードのどの属性がロール名に該当するのかを示しています。
`object-class-attribute` | `objectClass` | ディレクトリ中でオブジェクトのクラスを決定している属性を示しています。
`role-object-classes` | `organizationalRole` | 新規のロールレコードの作成のためのオブジェクトクラスの配列
`user-object-classes` | `person,uidObject` | 新規のユーザーレコード作成のためのオブジェクトクラスの配列

#### 15.4.3.2. LdapIdentityStore設定例

下の設定例では、擬似ホスト `directory.mycompany.com`上で動作しているLDAPディレクトリに対応する`LdapIdentityStore` の設定を示しています。 ユーザーは、このディレクトリ配下に`ou=Person,dc=mycompany,dc=com`というコンテキストで保持され、usernameに対応する`uid`属性により識別されます。 ロールはロール用のコンテキスト`ou=Roles,dc=mycompany,dc=com`に保持され、ユーザーのエントリから`roles`属性を通じて参照されます。 ロールのエントリはロールの名前に対応するロールの一般名（`cn` 属性）により識別されます。 この例では、ユーザーは`enabled`属性をfalseにする事により、使用不可にする事ができます。

      <security:ldap-identity-store

        server-address="directory.mycompany.com"

        bind-DN="cn=Manager,dc=mycompany,dc=com"

        bind-credentials="secret"

        user-DN-prefix="uid="

        user-DN-suffix=",ou=Person,dc=mycompany,dc=com"

        role-DN-prefix="cn="

        role-DN-suffix=",ou=Roles,dc=mycompany,dc=com"

        user-context-DN="ou=Person,dc=mycompany,dc=com"

        role-context-DN="ou=Roles,dc=mycompany,dc=com"

        user-role-attribute="roles"

        role-name-attribute="cn"

        user-object-classes="person,uidObject"

        enabled-attribute="enabled"

        />

### 15.4.4. 自分のIDストアを記述する

Seamによって標準でサポートされていないセキュリティプロバイダを使った認証やID管理を行う場合には、`org.jboss.seam.security.management.IdentityStore`を実装する、一つのクラスの実装を記述するだけ実現できます。

`IdentityStore`の実装するメソッドの詳細については該当するJavaDocを参照してください。

### 15.4.5. ID管理による認証

SeamアプリケーションでID管理機能を使っている場合には、認証コンポーネント（認証の項参照）による認証を行う必要はありません。`components.xml`の`identity`設定から`authenticator-method`を削除してください。 これで、特別な設定をすることなく`SeamLoginModule` はデフォルトの`IdentityManager`を使用してユーザーの認証を行うようになります。

### 15.4.6. IDマネージャの使用

`IdentityManager`にアクセスできるようにするには、下のようにSeamコンポーネントにインジェクトします。

      @In IdentityManager identityManager;

あるいは、静的な`instance()`メソッド経由でアクセスします。

      IdentityManager identityManager = IdentityManager.instance();

下のテーブルに`IdentityManager`のAPIのメソッドを示します。

表 15.4. ID管理のAPI

メソッド | 戻り値 | 詳細
--- | --- | ---
`createUser(String name, String password)` | `ブール型` | 指定されたusernameとpasswordで新規ユーザーのアカウントを作成します。 もし作成が成功すれば`true`を、さもなくば`false`を返します。
`deleteUser(String name)` | `ブール型` | 指定された名前のユーザーを削除します。 もし成功すれば`true`を、さもなくば`false`を返します。
`createRole(String role)` | `ブール型` | 指定された名前で新規のロールを作成します。 もし作成が成功すれば`true`を、さもなくば`false`を返します。
`deleteRole(String name)` | `ブール型` | 指定された名前のロールを削除します。 もし作成が成功すれば`true`を、さもなくば`false`を返します。
`enableUser(String name)` | `ブール型` | 指定された名前のユーザーアカウントを活性化します。 活性化されていないアカウントは認証の対象とはなりません。もし成功すれば`true`を、さもなくば`false`を返します
`disableUser(String name)` | `ブール型` | 指定された名前のユーザーアカウントを不活化します。 もし成功すれば`true`を、さもなくば`false`を返します。
`changePassword(String name, String password)` | `ブール型` | 指定された名前のユーザーアカウントのpasswordの変更をします。 もし成功すれば`true`を、さもなくば`false`を返します。
`isUserEnabled(String name)` | `ブール型` | もし、指定されたユーザーのアカウントが活性化されていれば `true`を、さもなくば`false`を返します。
`grantRole(String name, String role)` | `ブール型` | 特定のロールをユーザーやロールに権限を付与します。 ロールは既に存在していることが必要です。 ロールの付与が成功した場合には`true`を返し、もしそのロールがユーザーに既に付与されていた場合には`false`を返します。
`revokeRole(String name, String role)` | `ブール型` | 特定のユーザーあるいはロールから指定したロールを取り消します。 ユーザーが当該のロールのメンバーであり、かつ取り消しが成功した場合には`true`を返し、ユーザーが当該ロールのメンバーでなければ`false`を返します。
`userExists(String name)` | `ブール型` | もし、当該のユーザーが存在すれば`true`、さもなくば`false`を返します。
`listUsers()` | `リスト` | ABC順にソートされたすべてのユーザー名の一覧を返します。
`listUsers(String filter)` | `リスト` | 指定されたパラメータでフィルタしたユーザー名のリストをABC順にソートして返します
`listRoles()` | `リスト` | すべてのロール名の一覧を返します
`getGrantedRoles(String name)` | `リスト` | 指定されたユーザーに明示的に認められたロール名の一覧を返します
`getImpliedRoles(String name)` | `リスト` | 指定されたユーザー名に対して暗示的に付与されているすべてのロール名のリストを返します。 暗示的に付与されているロールとは、ユーザーに直接付与されているロールではなく、ユーザーが所属するロールに対して付与されているロールを言います。 例えば、`admin`ロールは`user`ロールのメンバーであり、ユーザーが`admin`ロールのメンバーであれば、このユーザーには暗示的に`admin`ロールと `user`ロールが付与されている。
`authenticate(String name, String password)` | `ブール型` | 設定されたIDストアを使ってusernameとpasswordを認証します。 認証が成功すれば`true`、失敗すれば`false`を返します。 認証が成功しても、このメソッドの戻り値以外は何も変化しませんし、`Identity`コンポーネントの状態も変化しません。 loginを適切に行うためには`Identity.login()`が使用されなければなりません。
`addRoleToGroup(String role, String group)` | `ブール型` | 特定のロールを指定したグループのメンバーに追加します。 操作が成功した場合にtrueを返します。
`removeRoleFromGroup(String role, String group)` | `ブール型` | 指定されたロールを指定されたグループから削除します。 もし成功すればtrueを返します。
`listRoles()` | `リスト` | すべてのロール名のリスト

ID管理APIを使うためには、ユーザーはそのメソッドを呼び出す適切な権限を持っている必要があります。 以下の表に`IdentityManager`にある個々のメソッドの起動に必要な権限の一覧を示します。 権限はリテラル文字列で指定します。

表 15.5. ID管理 セキュリティパーミッション

メソッド | パーミッションの対象 | パーミッションのアクション
--- | --- | ---
`createUser()` | `seam.user` | `作成`
`deleteUser()` | `seam.user` | `削除`
`createRole()` | `seam.role` | `作成`
`deleteRole()` | `seam.role` | `削除`
`enableUser()` | `seam.user` | `更新`
`disableUser()` | `seam.user` | `更新`
`changePassword()` | `seam.user` | `更新`
`isUserEnabled()` | `seam.user` | `読み込み`
`grantRole()` | `seam.user` | `更新`
`revokeRole()` | `seam.user` | `更新`
`userExists()` | `seam.user` | `読み込み`
`listUsers()` | `seam.user` | `読み込み`
`listRoles()` | `seam.role` | `読み込み`
`addRoleToGroup()` | `seam.role` | `更新`
`removeRoleFromGroup()` | `seam.role` | `更新`

下の例では、`admin`ロールのメンバーすべてが、すべてのID管理関連のメソッドへのアクセス権を付与されているセキュリティルールを示しています。

    rule ManageUsers
      no-loop
      activation-group "permissions"
    when
      check: PermissionCheck(name == "seam.user", granted == false)
      Role(name == "admin")
    then
      check.grant();
    end

    rule ManageRoles
      no-loop
      activation-group "permissions"
    when
      check: PermissionCheck(name == "seam.role", granted == false)
      Role(name == "admin")
    then
      check.grant();
    end

## 15.5. エラーメッセージ

セキュリティAPIはセキュリティ関連イベントに対応するいくつかのデフォルトのフェースメッセージを発生します。 以下の表には、リソースファイル`message.properties`で、これらを上書きするためのメッセージキーを一覧にしています。 もし、これらのメッセージを出さないようにするのであれば、リソースファイルで対応するキーの値をブランクにしてください。

表 15.6. セキュリティメッセージキー

メッセージキー | 詳細
--- | ---
`org.jboss.seam.loginSuccessful` | セキュリティAPIを通して、無事ログインできたときに生成されます。
`org.jboss.seam.loginFailed` | ユーザーネーム、パスワードの組み合わせ、或は何らかの認証のエラーにより、ユーザーがログインに失敗したときに生成されます。
`org.jboss.seam.NotLoggedIn` | ユーザーが認証されずにセキュリティチェックが必要な操作、あるいはページへのアクセスを試みたときに生成されます。
`org.jboss.seam.AlreadyLoggedIn` | このメッセージは既に認証されたユーザーが再度ログインを試みた時に生成されます。

## 15.6. 認可

SeamのセキュリティAPIは、コンポーネント、コンポーネントのメソッド、それにページに対して多くの認可機能を提供します。 ここでは、それぞれの機能について説明します。 ここで説明するような高度なセキュリティ機能（ルールベースの認可のような）を使用する場合には`components.xml`に前述のような設定を記述しておかなければならない、ということに留意してください。

### 15.6.1. 核となる概念

Seamのセキュリティは必要なセキュリティ権限を持たないユーザーの操作を行わせないように、ロールとパーミッションによりユーザーの操作を制限する事を前提として設計されています。 SeamセキュリティAPIの提供する認可メカニズムは、ロールとパーミッションによるユーザー管理の概念に基づいて作られており、複数のアプリケーションリソース保護の方法を提供する拡張可能なフレームワークを提供しています。

#### 15.6.1.1. ロールとは

ロールとは、アプリケーションの特定の操作を実施する特権を付与されてるユーザーのグループあるいはタイプを言い、"admin"、 "user"、 "customer"等の名前により構成されています。 これらのロールはユーザーに対して（あるいは場合により他のロールに対し）付与され、特定のアプリケーションの操作に対する特権を持つユーザーの論理的なグループを作成する事を容易にしています。

#### 15.6.1.2. パーミッションとは？

パーミッションとは、一つの特定の操作を実行するための特権（時として1回限りの）を言います。 パーミッションのみを使ってアプリケーションを組むことも可能ですが、ユーザーのグループに対して特定の特権を付与する事により、セキュリティ管理をより容易にすることができます。 これらはロールよりも、構造上若干複雑で、 ”対象(target)”、”操作(action)” と”受益者(receipient)”の三つの要素から構成されます。 パーミッションの対象は特定の受益者（ユーザー）により特定の操作が行われるオブジェクト（あるいは、任意の名前、クラス）です。 例として、ユーザーBobは顧客オブジェクトを削除するパーミッションを持つ、場合を考えてみると、 パーミッションの対象は「顧客」、パーミッションの操作は「削除」、そして受益者はBobという事になります。

このドキュメント中では権限は、実際には常に必要な受益者を省略して`target:action` という形式で表示されています。

### 15.6.2. コンポーネントの安全性を確保する

それでは、もっとも簡単な形式の認可、コンポーネントのセキュリティについて`@Restrict`アノテーションから見てゆきましょう。

## @Restrictアノテーション とタイプセーフなセキュリティアノテーション

`@Restrict`アノテーションを使うとEL式をサポートしていることもあり、コンポーネントのメソッドに対して強力かつフレキシブルなセキュリティを付与する事ができますが、コンパイル時の安全性等から、タイプセーフな同様の方法（後述）が推奨されます。

#### 15.6.2.1. @Restrictアノテーション

`@Restrict`アノテーションにより、Seamのコンポーネントにはクラスあるいはメソッドレベルでのセキュリティを付与する事ができます。 もし、クラスとその中のメソッドの両方に`@Restrict`アノテーションがあった場合には、メソッドレベルの制限が優先され、クラスレベルの制限は結果として適用されません。 もし、メソッドの起動がセキュリティチェックで失敗した場合には、`Identity.checkRestriction()`単位で例外が発生します。 コンポーネントレベルでの`@Restrict`アノテーションは、そのコンポーネントのすべてのメソッドに`@Restrict`をアノテートしたのと同じことになります。

空の`@Restrict`は`component:methodName`を意味します。 下のようなコンポーネントの例を見てみましょう。

    @Name("account")

    public class AccountAction {

        @Restrict public void delete() {

          ...

        }

    }

この例では、`delete()`を呼び出すためには`account:delete`という権限が必要な事を暗黙的に示しています。 同様の内容は`@Restrict("#{s:hasPermission('account','delete')}")`と記述する事もできます。他の例についても見てゆきましょう。

    @Restrict @Name("account")

    public class AccountAction {

        public void insert() {

          ...

        }

        @Restrict("#{s:hasRole('admin')}")

        public void delete() {

          ...

        }

    }

ここでは、コンポーネントクラスに`@Restrict`とアノテーションが付記されています。これは、`Restrictがオーバーライドされない限り、パーミッションのチェックが暗示的に要求されることを示しています。この例の場合、`insert()`は`account:insert`のパーミッションを必要とし、`delete()`はユーザーが`admin`ロールに属していることが必要な事を示しています。

先に進む前に、上の例で見た `#{s:hasRole()}` 式について見てみましょう。 `s:hasRole` も `s:hasPermission`もEL式であり、 `Identity`クラスの同様の名前のメソッドに対応します。 これらセキュリティAPIのすべてについてEL式の中で使う事ができます。

EL式とすることで、`@Restrict`アノテーションは、Seamコンテキスト中のどのようなオブジェクトの値でも参照することができるようになります。 これは、特定のオブジェクトのインスタンスをチェックしてパーミッションを決定する場合に非常に有効な方法です。下の例を見てみましょう。

    @Name("account")

    public class AccountAction {

        @In Account selectedAccount;

        @Restrict("#{s:hasPermission(selectedAccount,'modify')}")

        public void modify() {

            selectedAccount.modify();

        }

    }

ここで興味深いのは、`hasPermission()`というファンクション中で`selectedAccout`を参照している事です。 この変数の値はSeamのコンテキスト中で検索され、`Identity`の`hasPermission()`に渡され、この例の場合、特定の`Account`のオブジェクトに対する変更許可を持っているかを決定しています。

#### 15.6.2.2. インラインによる制約

時として、`@Restrict`アノテーションを使わずに、コードでセキュリティチェックを行いたい場合があるかもしれません。この様な場合には、下のように`Identity.checkRestriction()`を使って、セキュリティ式を評価することができます。

    public void deleteCustomer() {

        Identity.instance().checkRestriction("#{s:hasPermission(selectedCustomer,'delete')}");

    }

もし式が`true`と評価されなかった場合には、

- ユーザーがログインしていなかったのであれば、NotLoggedInExceptionが投げられ、

- ユーザーがログインしていた場合には、AuthorizationExceptionが投げられます。

また、下のようにJavaコードから直接`hasRole()`や`hasPermission()`メソッドを呼ぶこともできます。

    if (!Identity.instance().hasRole("admin"))

         throw new AuthorizationException("Must be admin to perform this action");

    if (!Identity.instance().hasPermission("customer", "create"))

         throw new AuthorizationException("You may not create new customers");

### 15.6.3. ユーザーインタフェースのセキュリティ

適切なユーザーインタフェースのデザインの一つとして、ユーザーが使用する権限を有しないオプションの表示をしないようにすることがあります。 Seamのセキュリティはユーザーの権限に応じて、コンポーネントのセキュリティで使用したのと同様にEL式を使用する事により1）ページ単位 2）個々のコントロール単位 で描画を管理する事ができます。

インタフェースのセキュリティの例について見てゆきましょう。 まず最初に、ログインしていないユーザーの時だけ表示されるログインフォームについて考えてみましょう。 `identity.isLoggedIn()`属性を使えば下のように記述できます。

    <h:form class="loginForm" rendered="#{not identity.loggedIn}"

    >

もしユーザーがログインしていなければ、ログインフォームが表示されます（実に単純ですね）。 次に、`manager`ロールを持っている人達だけがアクセス可能なメニューが必要だと仮定しましょう。 このような場合の一つの方法として、下に例を示したあります。

    <h:outputLink action="#{reports.listManagerReports}" rendered="#{s:hasRole('manager')}">

        Manager Reports

    </h:outputLink

    >

これも、たいへんシンプルで、ユーザーが`manager`ロールを持っていなければ、outputLinkは描画されません。`rendered`属性は一般に制御そのものに使われたり、`<s:div>` や `<s:span>`の中で制御の目的に使われます。

次にもう少し複雑な例： `h:dataTable`の制御用のアクションリンクの表示非表示をユーザーの権限により制御する事を考えます。 EL式`s:hasPermission`により、ユーザーが必要な権限を持っているか否かを決定するために必要なオブジェクトをパラメータとして渡すことができます。 以下に、セキュリティを向上させたリンクを持たせた`h:dataTable`の例を示します。

    <h:dataTable value="#{clients}" var="cl">

        <h:column>

            <f:facet name="header"

    >Name</f:facet>

            #{cl.name}

        </h:column>

        <h:column>

            <f:facet name="header"

    >City</f:facet>

            #{cl.city}

        </h:column>

        <h:column>

            <f:facet name="header"

    >Action</f:facet>

            <s:link value="Modify Client" action="#{clientAction.modify}"

                    rendered="#{s:hasPermission(cl,'modify')"/>

            <s:link value="Delete Client" action="#{clientAction.delete}"

                    rendered="#{s:hasPermission(cl,'delete')"/>

        </h:column>

    </h:dataTable

    >

### 15.6.4. ページ単位のセキュリティ

ページレベルのセキュリティはアプリケーションが`pages.xml`を使用していることが必要ですが、設定自身は非常に簡単です。単に保護したいページの `page`要素に`<restrict/>`を追加するだけです。 明示的に`restrict`で制限をしない場合、 当該ページに対してGET要求でアクセスが試みられると `/viewId.xhtml:render` がチェックされ、またJSFポストバック（フォームのサブミッション）に対しては `/viewId.xhtml:restore`権限がチェックされます。 これ以外の場合には、指定した制限について通常のセキュリティ式評価が行われます。 以下にいくつかの例を示します。

    <page view-id="/settings.xhtml">

        <restrict/>

    </page

    >

このページは暗黙的に、GET要求に対して`/settings.xhtml:render`権限を要求し、フェース要求に対しては`/settings.xhtml:restore`権限を要求しています。

    <page view-id="/reports.xhtml">

        <restrict

    >#{s:hasRole('admin')}</restrict>

    </page

    >

このページに対するfacesあるいはnon-facesな要求はユーザーが`admin`ロールのメンバーである事が必要です。

### 15.6.5. エンティティのセキュリティ

Seamのセキュリティは、エンティティ単位でのread,insert,updateおよびdelete操作に対してのセキュリティ制約をかけることを可能にしています。

エンティティクラスのアクション全部に対してセキュリティをかけたいのであれば、下のようにクラスに`@Restrict`アノテーションを付記します。

    @Entity

    @Name("customer")

    @Restrict

    public class Customer {

      ...

    }

もし、`@Restrict`が評価式無しで付記されていれば、デフォルトとして`entity:action`のパーミッションがチェックされます。 ここで、パーミッションの対象はエンティティのインスタンスで、`action` は `read`, `insert`, `update` あるいは `delete`のいずれかです。

また、下のようにエンティティのライフサイクルに`@Restrict` アノテーションを付記することにより、特定の操作だけに制約を課すことができます。

- `@PostLoad` - エンティティのインスタンスがデータベースからロードされた後に呼び出されます。このメソッドは`read` パーミッションの設定に使用してください。

- `@PrePersist` - エンティティの新規のインスタンスが (データベースに）挿入される前に呼び出されます。 このメソッドは`insert` パーミッションの設定に使用してください。

- `@PreUpdate` - エンティティが更新される前に呼び出されます。 このメソッドは`update`パーミッションの設定に使用してください。

- `@PreRemove` - エンティティが削除される前に呼び出されます。 このメソッドは`delete`パーミッションの設定に使用してください。

ここでは`insert`操作に対してのセキュリティチェックをするためのエンティティの設定方法を示しています。 ここで注意していただきたいのは、メソッドの内容はセキュリティと関係なく、アノテーションの仕方が重要な事です。

      @PrePersist @Restrict

      public void prePersist() {}

## `/META-INF/orm.xml`の使用

`/META-INF/orm.xml`にコールバックメソッドを指定する事もできます:

    <?xml version="1.0" encoding="UTF-8"?>

    <entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"

                     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

                     xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_1_0.xsd"

                     version="1.0">

       <entity class="Customer">

          <pre-persist method-name="prePersist" />

       </entity>

    </entity-mappings

    >

もちろん この場合も`Customer`の`prePersist()` メソッドに`@Restrict`アノテーションは必要です。

これは、認証されているユーザーが新規に`MemberBlog`レコードを追加する事ができるか否かをチェックする、エンティティ権限ルールの例（サンプルソースのseamspaceのコードから）です。 セキュリティチェックの対象となるエンティティは自動的にワーキングメモリー（この場合、`MemberBlog`）に挿入されます。

    rule InsertMemberBlog
      no-loop
      activation-group "permissions"
    when
      principal: Principal()
      memberBlog: MemberBlog(member : member -> (member.getUsername().equals(principal.getName())))
      check: PermissionCheck(target == memberBlog, action == "insert", granted == false)
    then
      check.grant();
    end;

このルールは`Principal`ファクトで示される現在の認証ユーザーがブログのエントリを作成したメンバーと同じ名前であれば`memberBlog:insert`パーミッションを付与します。 例示したコード中にある、構造体 "`principal: Principal()`" は認証の過程で挿入された ワーキングメモリ中の`Principal`オブジェクトへの変数結合で、変数`principal`と命名されています。 変数結合にする事により、他の場所で値が参照可能となり、下のように`Principal`名とメンバーのユーザー名を比較する事ができます。 詳細は、JBoss Rules ドキュメントを参照してください。

最後に、JPAプロバイダをSeamセキュリティと統合するために、リスナークラスをインストールします。

#### 15.6.5.1. JPAでのエンティティセキュリティ

EJB3エンティティBeanのセキュリティチェックは`EntityListener`により行われ、下記のような`META-INF/orm.xml`の設定でリスナーをインストールすることができます。

    <?xml version="1.0" encoding="UTF-8"?>

    <entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"

                     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

                     xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_1_0.xsd"

                     version="1.0">

        <persistence-unit-metadata>

            <persistence-unit-defaults>

                <entity-listeners>

                    <entity-listener class="org.jboss.seam.security.EntitySecurityListener"/>

                </entity-listeners>

            </persistence-unit-defaults>

        </persistence-unit-metadata>

    </entity-mappings

    >

#### 15.6.5.2. 管理されたHibernateセッションにおけるエンティティのセキュリティ

Seamで設定されるHibernateの`SessionFactory`とアノテーションを使用している場合、あるいは`orm.xml`を使用している場合にはエンティティセキュリティ機能を使用するために特別な設定をする必要はありません。

### 15.6.6. タイプセーフなアクセス権のアノテーション

Seamは`@Restrict`に代わるアノテーションをいくつか持っており、これらを使う事により`@Restrict`とは異なりEL式の評価を行わないので、コンパイル時の安全性を提供します。

Seamには標準のCRUD操作に関するパーミッション用のアノテーションが提供されていますが、独自のアノテーションを作成する事もできます。 以下は`org.jboss.seam.annotations.security`パッケージで配布されているアノテーションです。

- @Insert

- @Read

- @Update

- @Delete

これらのアノテーションを使うには、単にセキュリティチェックを行いたいメソッドやパラメータをアノテートするだけです。 メソッドがアノテートされた場合には、アクセス権のチェックの対象となるターゲットクラスも指定する必要があります。 以下の例を見てください。

      @Insert(Customer.class)
      public void createCustomer() {
        ...
      }

この例ではユーザーが新規の`Customer`オブジェクトを作成する権限があるか否かパーミッションチェックを行います。 パーミッションチェックの対象は`Customer.class`（`java.lang.Class`インスタンスそのもの）で、アクションはアノテーション名の小文字に変換されたもの、ここでは`insert`、となります。

同様にコンポーネントのメソッドのパラメータに対してもアノテートする事ができます。 これを行った場合には、パラメータの値がアクセス権チェックの対象ですから、アクセス権のターゲットを指定する必要はありません。

      public void updateCustomer(@Update Customer customer) {
        ...
      }

下のように、独自のセキュリティアノテーションを作る場合には、単に`@PermissionCheck`とアノテートするだけです。

    @Target({METHOD, PARAMETER})

    @Documented

    @Retention(RUNTIME)

    @Inherited

    @PermissionCheck

    public @interface Promote {

       Class value() default void.class;

    }

もしデフォルトのアクセス権アクション名（アノテーション名の小文字版）を他の値で上書きする必要があれば、`@PermissionCheck`アノテーション内にその値を指定することができます。

    @PermissionCheck("upgrade")

### 15.6.7. タイプセーフなロールのアノテーション

タイプセーフなパーミッションのアノテーションをサポートするのに加えて、Seamセキュリティはタイプセーフなロールのアノテーションを提供し、認証されているユーザーがどのロールのメンバーに属しているかに基づいてコンポーネントのメソッドへのアクセスを制限する事を可能にしています。 Seamはこのようなアノテーションとして、`admin`ロール（アプリケーションにこのロールが設定されていれば）のメンバーに属しているユーザーのみにアクセスを制限する`org.jboss.seam.annotations.security.Admin`を提供しています。 独自のロールのアノテーションを作成するためには、下の例のように`org.jboss.seam.annotations.security.RoleCheck`でメタアノテートします。

    @Target({METHOD})
    @Documented
    @Retention(RUNTIME)
    @Inherited
    @RoleCheck
    public @interface User {
    }

上の例に示されているような`@User`アノテーションを持つメソッドは、呼ばれる度に自動的にインタセプトされ対応するロール名（アノテーション名を小文字に書き換えた名前ーこの場合は`user`）のメンバーにユーザーが含まれるかチェックされます。

### 15.6.8. パーミッション許可モデル

Seamセキュリティはアプリケーションに対するパーミッションの決定に対して拡張可能なフレームワークを提供します。 下のクラスダイアグラム図にはSeamの提供するパーミッションフレームワークの主要コンポーネントについて示しています。

関連するクラスについての詳細を以下のセクションに示します

#### 15.6.8.1. パーミッションリゾルバー

実際には、これは個々のオブジェクトのアクセス権を決定するメソッドを提供するインタフェースです。 Seamは以下の`PermissionResolver`を内蔵しています。 それぞれの詳細はこの章の後半で説明します。

- `RuleBasedPermissionResolver` - このパーミッションリゾルバーはDroolsを使ってルールベースのパーミッションチェックを行います

- `PersistentPermissionResolver` - このパーミッションリゾルバーはデータベース等にパーミッションオブジェクトを保持します。

##### 15.6.8.1.1. 独自のパーミッションリゾルバーの記述

独自のパーミッションリゾルバを作成するためには、下の表にある`PermissionResolver`インタフェースに定義されている二つのメソッドを実装します。 独自の`PermissionResolver`実装をSeamプロジェクトにデプロイする事により、プロジェクトがデプロイされる時（立ち上がり時）に自動的にスキャンされ`ResolverChain`に組み込まれます。

表 15.7. パーミッションリゾルバーインタフェース

戻り値の型 | メソッド | 詳細
--- | --- | ---
`ブール型` | `hasPermission(Object target, String action)` | このメソッドは`Identity.getPrincipal()`を呼ぶ事により得られる現在の認証されたユーザーが、パラメータで渡された`target`と`action`に対してパーミッションを有するか否かを解決し、パーミッションを有すればtrueを、無ければfalseを返します。
`void` | `filterSetByAction(Set<Object> targets, String action)` | このメソッドは指定されたセットから任意のオブジェクトを削除し、もし`hasPermission()`メソッドに同様の`action`をパラメータ値として渡した場合には、 `true` を返します。

## 注意

それらはユーザーのセッションにキャッシュされるので、任意のカスタム `PermissionResolver` 実装はいくつかの制限事項に注意を払う必要があります。最初に、それらがセッションスコープよりも細かな粒度の状態を含むことは許されません (コンポーネントのスコープはアプリケーションまたはセッション)。次に、複数スレッドから同時にアクセスされるかもしれないので依存性注入は禁止です。つまり、パフォーマンス上の理由から、Seam インタセプタスタック全部をバイバスするために `@BypassInterceptors` のアノテーションを付加することが推奨されます。

#### 15.6.8.2. リゾルバーチェーン

`ResolverChain`は`PermissionResolver`sを順番に並べたリストを持っており、このリストに従い、特定のオブジェクトクラス、あるいはパーミッション対象についてのパーミッションを解決します。

デフォルト `ResolverChain` は、アプリケーションデプロイメント間に発見されるすべての 権限リゾルバ (permission resolver) から構成されます。`org.jboss.seam.security.defaultResolverChainCreated` イベント (とイベントパラメータとして渡される`ResolverChain` インスタンスは)、デフォルトの `ResolverChain`が生成されるときに発行されます。このことは、なんらかの理由からデプロイメントが追加されるときに発見されなかったリゾルバやチェインの中で順序を入れ替えたり、削除したりするリゾルバを追加可能にします。

下のシークエンス図にパーミッションチェック時のパーミッションフレームワーク内のコンポーネント相互の作用を示します。 パーミッションチェックは、例えば、セキュリティインタセプタ、EL式`s:hasPermission`、あるいはAPI`Identity.checkPermission`を呼び出す等、複数の方法により呼び出されます。

- 1 パーミッションチェックはコードあるいはEL式評価により`Identity.hasPermission()`が呼び出されることにより実行されます。

- 1.1. `Identity`は解決対象のパーミッションを`PermissionMapper.resolvePermission()`を渡して呼び出します。

- 1.1.1. `PermissionMapper`はクラスによりキー付けされた`ResolverChain`インスタンスの`Map`を維持しており、パーミッションの対象オブジェクトに対応して適切な`ResolverChain`を選択するように管理しています。 適切な`ResolverChain`を見つければ、`ResolverChain.getResolvers()`を呼び出し、管理している`PermissionResolver`sを読み込みます。

- 1.1.2. `ResolverChain`中の個々の`PermissionResolver`について`PermissionMapper`はパーミッションチェックの対象をパラメータとして渡して`hasPermission()`メソッドを呼び出します。 いずれかの`PermissionResolver`s が`true`を返せば、パーミッションチェックが成功したと見なし`PermissionMapper`が`Identity`に対して`true`を返します。 いずれの`PermissionResolver`sも`true`を返さなければ、 パーミッションチェックは失敗したことになります。

### 15.6.9. ルールベースのパーミッションリゾルバー

Seamに内蔵されているパーミッションリゾルバーの一つ`RuleBasedPermissionResolver`は、Drools(JBoss Rules)によるセキュリティルールに基づいたパーミッションの評価を受け付けます。 ルールエンジンを使う事の利点は； 1）ユーザーパーミッションの評価に使用されるビジネスロジックを一か所にまとめることができる 2）スピードーDroolは効率の良いアルゴリズムを使用し、多くの条件の元に多くの複雑なルールを評価することが可能になっています。

#### 15.6.9.1. 要求条件

Seamセキュリティの提供するルールベースのアクセス権を使用する場合には、Droolに必要な下記のjarファイルをディストリビューション含める必要があります。

- drools-api.jar

- drools-compiler.jar

- drools-core.jar

- drools-decisiontables.jar

- drools-templates.jar

- janino.jar

- antlr-runtime.jar

- mvel2.jar

#### 15.6.9.2. 設定

`RuleBasedPermissionResolver`を設定するためには、`components.xml`にDroolのルールベースが設定されている必要があります。 このルールベースは下の例のように、デフォルトで`securityRules`と命名されていることを仮定しています。

    <components xmlns="http://jboss.com/products/seam/components"

                  xmlns:core="http://jboss.com/products/seam/core"

                  xmlns:security="http://jboss.com/products/seam/security"

                  xmlns:drools="http://jboss.com/products/seam/drools"

                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

                  xsi:schemaLocation=

                      "http://jboss.com/products/seam/core http://jboss.com/products/seam/core-2.2.xsd

                       http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.2.xsd

                       http://jboss.com/products/seam/drools http://jboss.com/products/seam/drools-2.2.xsd

                       http://jboss.com/products/seam/security http://jboss.com/products/seam/security-2.2.xsd">

         <drools:rule-base name="securityRules">

             <drools:rule-files>

                 <value

    >/META-INF/security.drl</value>

             </drools:rule-files>

         </drools:rule-base>

      </components

    >

デフォルトのルールベースの名前は`RuleBasedPermissionResolver`の`security-rules`属性で上書きする事ができます。

      <security:rule-based-permission-resolver security-rules="#{prodSecurityRules}"/>

`RuleBase`コンポーネントを設定したら、次にセキュリティルールの記述をします。

#### 15.6.9.3. セキュリティルールの記述

セキュリティルールを作成するためには、まずアプリケーションのjarファイルの`/META-INF`ディレクトリ下に新規のルールファイルを作ります。 通常このファイルは`security.drl`のように命名されますが、`components.xml`に別途指定しておけば、どのようにでも命名する事ができます。

セキュリティルールの内容は？ この段階ではDroolsのドキュメントから適当に拝借してくるのが良いかもしれませんが、ここでは非常に単純な例から始めてみる事にしましょう

    package MyApplicationPermissions;

      import org.jboss.seam.security.permission.PermissionCheck;
      import org.jboss.seam.security.Role;

      rule CanUserDeleteCustomers
      when
        c: PermissionCheck(target == "customer", action == "delete")
        Role(name == "admin")
      then
        c.grant();
      end

では、一つづつ見てゆきましょう。 最初はパッケージ宣言です。 Droolのパッケージはルールの集まりで、ルールベースの範囲外とは何ら関わりが無いので、パッケージの名前は任意で構いません。

つぎに、`PermissionCheck`クラスと`Role`クラスに関するいくつかのインポート文があります。 これらのインポート文は、これから使うルールでこれらのクラスを参照する事をルールエンジンに対して伝えています。

そして、ルールの記述コード。それぞれのルールは、ルールごとにユニークな名前が与えられている必要があります (通常は、ルールの目的をルールの名前にします。) この例の場合、`CanUserDeleteCustomers`がルールの名前で、読んで字の如く、顧客レコードの削除をできるかできないかのチェックに使用します。

ルールの記述が二つの部分から成っている事がわかります。ルールは左部分 (LHS) と右部分 (RHS) として知られている部分から成り立っています。LHSはルールの条件部分 (即ち、ルールが実行されるために満たさなければならない条件のリスト) を規定しています。 LHSは`when`で表されるセクションにあり、また、RHSはLHSが満たされた場合に実行されるアクション、あるいは結果を記述しています。 RHSは`then`以降の部分に記述します。 また、ルールの最後は`end`で終了します。

ルールについてLHSを見ると、二つの条件がある事が分かります。 まず、最初の条件を見てみましょう。

    c: PermissionCheck(target == "customer", action == "delete")

この条件は、簡単な英語で「ワーキングメモリ中に、`target`属性として"customer"を持ち、`target`属性として"delete"を持つ`PermissionCheck`オブジェクトが存在しなければならない」と示しています。

ワーキングメモリって何？ Droolｓの技術用語で、ルールエンジンがパーミッションチェックをするために必要なコンテキスト情報を保持しているセッションスコープのオブジェクトの事を「ワーキングメモリ」と呼びます。 `hasPermission()`メソッドが呼ばれる都度、仮の`PermissionCheck`オブジェクト、あるいはファクト(Fact)がワーキングメモリに挿入されます。 この`PermissionCheck`は今からチェックされるパーミッションに対応しており、例えば`hasPermission("account", "create")`を呼び出すと、`target`属性が "account"で`action`属性が"create"である`PermissionCheck`オブジェクトがワーキングメモリに挿入され、パーミッションチェックが終了するまで存在します。

`PermissionCheck`ファクトの他に、認証されたユーザーが所属するロールの`org.jboss.seam.security.Role`ファクトがあります。 これらの `Role`ファクトはパーミッションチェックの開始の都度ユーザーの認証されたロールと同期されます。 従って、パーミッションチェックに使用されたワーキングメモリ中の`Role`オブジェクトは、もし認証されたユーザーがそのロールに所属していなければ、次回のパーミッションチェックの前に削除されます。 ワーキングメモリには`PermissionCheck`と `Role`ファクトの他に認証の過程で作成された`java.security.Principal`オブジェクトが保持されています。

これ以外にパラメータとしてオブジェクトを渡し`RuleBasedPermissionResolver.instance().getSecurityContext().insert()`を呼び出すことにより、追加でワーキングメモリ中に長期に生存するファクトを挿入する事ができます。 例外として、先に説明したように`Role`オブジェクトはパーミッションチェックの都度同期されるために、はワーキングメモリ中に長期に生存するファクトとする事はできません。

先の例に戻り、LHSが`c:`で始まっていることに気がつくと思います。 これは、変数結合を表しており、条件のマッチングに利用されるオブジェクトへの参照を意味しています（この例の場合は`PermissionCheck`）。 LHSの2行目には下の記述があります。

    Role(name == "admin")

この条件はワーキングメモリ中に"admin"という`name`の`Role`オブジェクトが存在しなければならない事を示しています。 先述したように、ユーザーのロールはパーミッションチェックの開始の都度ワーキングメモリに挿入されますので、この条件は結果として「`admin`ロールに所属するユーザーで`customer:delete`の許可を求めているのであれば、これを認めます」という事を示しています。

ルールが適用されると、何が起こるのでしょうか？ 次にルールのRHS側を見てみましょう。

    c.grant()

RHSはJavaコードから成っており、この例の場合は`c`というオブジェクト (既に述べたように、`PermissionCheck`オブジェクトへの変数結合) の`grant()`メソッドが起動されます。`PermissionCheck`オブジェクトの`name`と`action`プロパティ以外に`false`に初期設定された`granted`プロパティが存在します。`PermissionCheck`の`grant()`を呼ぶことにより、`granted`プロパティは`true`にセットされ、パーミッションのチェックが成功し、ユーザーはパーミッションで決められたアクションについて実行することができるようになります。

#### 15.6.9.4. 非文字列のパーミッションターゲット

ここまで文字列型のパーミッションターゲットのチェックについて見てきました。 しかし、もっと複雑なパーミッションターゲットのセキュリティルールを記述することも可能です。 例えば、ユーザーがブログにコメントを作成する事を可能にするセキュリティルールを記述する場合を考えてみましょう。 これは以下のように、パーミッションチェックの対象が`MemberBlog`インスタンスで、現在の認証されたユーザーが`user`ロールのメンバーであることが必要である、と表現されます。

    rule CanCreateBlogComment
      no-loop
      activation-group "permissions"
    when
      blog: MemberBlog()
      check: PermissionCheck(target == blog, action == "create", granted == false)
      Role(name == "user")
    then
      check.grant();
    end

#### 15.6.9.5. ワイルドカードによるパーミッションチェック

ワイルドカードを使ってパーミッションチェックを設定することも可能で、これはあるパーミッションに対してすべての操作を許可します。下のようにルールの`PermissionCheck`の`action`制約を省略することにより、実装できます。

    rule CanDoAnythingToCustomersIfYouAreAnAdmin
    when
      c: PermissionCheck(target == "customer")
      Role(name == "admin")
    then
      c.grant();
    end;

上記のルールでは、`admin`ロールを持つユーザーは、どの`customer`に対しても、任意の操作が可能なパーミッションチェックになっています。

### 15.6.10. 永続的パーミッションリゾルバー

Seamに内蔵されているパーミッションリゾルバーにはこれ以外に`PersistentPermissionResolver`があり、これはリレーショナルデータベースのような永続的保存場所からパーミッションを読み込むことが可能で、ACLスタイルのインスタンスベースのセキュリティを提供しており、個別のユーザーとロールに対して特定のパーミッションを指定する事ができます。 また、任意の名前のパーミッションターゲットを指定して保存する事が可能です。

#### 15.6.10.1. 設定

使用するためには`components.xml`に、有効な`PersistentPermissionResolver`を設定した`PermissionStore`を記述する必要があります。 設定していない場合、デフォルトのパーミッションストア`JpaIdentityStore`の使用を試みます。 デフォルト以外のパーミッションストアを使用する場合には`permission-store`属性を下のように記述します。

      <security:persistent-permission-resolver permission-store="#{myCustomPermissionStore}"/>

#### 15.6.10.2. パーミッションストアについて

`PersistentPermissionResolver`は、パーミッションを保存しているバックエンドの保存場所との接続のためにパーミッションストアを必要とします。 Seamは標準で一つの`PermissionStore`実装`JpaPermissionStore`を提供しており、リレーショナルデータベースにパーミッションを保存します。 下記のメソッドを定義している`PermissionStore`インタフェースを実装することにより、独自のパーミッションストアを作成する事も可能です。

表 15.8. パーミッションストアのインタフェース

戻り値の型 | メソッド | 詳細
--- | --- | ---
`List<Permission>` | `listPermissions(Object target)` | このメソッドは対象のオブジェクトに付与されているすべての権限を表す`Permission`オブジェクトの`List`を返します。
`List<Permission>` | `listPermissions(Object target, String action)` | このメソッドは対象のオブジェクトに付与されている特定のアクションに対するすべての権限を表す`Permission`オブジェクトの`List`を返します。
`List<Permission>` | `listPermissions(Set<Object> targets, String action)` | このメソッドは対象の一連のオブジェクトに付与されている特定のアクションに対するすべての権限を表す`Permission`オブジェクトの`List`を返します
`ブール型` | `grantPermission(Permission)` | このメソッドは特定の`Permission`オブジェクトをバックエンドのストレージに保持し、成功すればtrueを返します。
`ブール型` | `grantPermissions(List<Permission> permissions)` | このメソッドは指定された `List`にある、すべての`Permission`オブジェクトをバックエンドのストレージに保持し、成功すればtrueを返します。
`ブール型` | `revokePermission(Permission permission)` | このメソッドは指定された`Permission`オブジェクトをストレージから削除します。
`ブール型` | `revokePermissions(List<Permission> permissions)` | このメソッドは指定されたリストにあるすべての`Permission`オブジェクトをストレージから削除します。
`List<String>` | `listAvailableActions(Object target)` | このメソッドは指定された対象オブジェクトクラスに対して可能なアクション（文字列型）のリストを返します。 特定のクラスのパーミッションを付与するためのユーザーインタフェースを作成するためにパーミッション管理と共に使用されます。

#### 15.6.10.3. JpaPermissionStoreについて

これはデフォルトの（また、Seamが提供する唯一の）`PermissionStore`の実装で、パーミッションの保存にリレーショナルデータベースを使用しています。 使用するためにはユーザーとロールのパーミッションの保存に係る一つないし二つのエンティティクラスの設定が必要になります。 これらのエンティティクラスは保存されているレコードとエンティティの属性がパーミッションのどれに対応するのかを設定するための特別なセキュリティに関するアノテーションでアノテートされている必要があります。

もしユーザーパーミッションとロールパーミッションに同一のエンティティ（一つのDBテーブル）を使うのであれば、`user-permission-class`属性を設定します。 ユーザーパーミッションとロールパーミッションを別々のテーブルに保持するのであれば、`user-permission-class`属性に加えて`role-permission-class`属性を設定する必要があります。

例えば、ユーザーとロールのパーミッションを一つのエンティティクラスに保存するよう設定する場合は次のようになります。

      <security:jpa-permission-store user-permission-class="com.acme.model.AccountPermission"/>

ユーザーパーミッションとロールパーミッションを別のエンティティクラスに保存する場合の設定は次のようになります。

      <security:jpa-permission-store user-permission-class="com.acme.model.UserPermission"

        role-permission-class="com.acme.model.RolePermission"/>

##### 15.6.10.3.1. パーミッション アノテーション

先述のように、ユーザーとロールのパーミッションを保持するエンティティクラスは`org.jboss.seam.annotations.security.permission`パッケージにある特別なアノテーションを設定されている必要があります。 下の表にこれらのアノテーションと、その使用方法の説明を示します。

表 15.9. エンティティ パーミッション アノテーション

アノテーション | ターゲット | 詳細
--- | --- | ---
`@PermissionTarget` | `フィールド、メソッド` | このアノテーションはパーミッションの対象を含んでいるエンティティの属性を示します。 この属性は`java.lang.String`でなければなりません。
`@PermissionAction` | `フィールド、メソッド` | このアノテーションはパーミッションアクションを含んでいるエンティティの属性を示します。 この属性は`java.lang.String`でなければなりません。
`@PermissionUser` | `フィールド、メソッド` | このアノテーションはパーミッションの受益ユーザーを含んでいるエンティティの属性を示します。 この属性は`java.lang.String`で、かつユーザーのusernameを含んでいなければなりません。
`@PermissionRole` | `フィールド、メソッド` | このアノテーションはパーミッションの受益ロールを含んでいるエンティティの属性を示します。 この属性は`java.lang.String`で、かつロール名を含んでいなければなりません。
`@PermissionDiscriminator` | `フィールド、メソッド` | このアノテーションはユーザーとロールパーミッションを同じエンティティ（テーブル）に保存する場合に使用します。 エンティティのユーザーとロールパーミッション属性の識別のために使用します。 デフォルトで、`user`ストリングリテラルを含むカラムをユーザーパーミッションのカラムと見なし、`role`ストリングリテラルを含むカラムをロールパーミッションのカラムと見なします。 アノテーション中で`userValue`と`roleValue`属性を指定する事により、これらのデフォルト値は上書きする事ができます。 例えば、`user`の代わりに`u`を、`role`の代わりに`r`を使うとすると、アノテーションは下のようになります。
      @PermissionDiscriminator(userValue = "u", roleValue = "r")

##### 15.6.10.3.2. エンティティの例

この例ではユーザーとロールパーミッションが一つのエンティティクラスに保持されています。 下に示したクラスはサンプルのSeamSpaceからのものです。

    @Entity

    @Name("message")

    @Scope(EVENT)

    public class Message implements Serializable

    {

       private Long id;

       private String title;

       private String text;

       private boolean read;

       private Date datetime;

       @Id @GeneratedValue

       public Long getId() {

          return id;

       }

       public void setId(Long id) {

          this.id = id;

       }

       @NotNull @Length(max=100)

       public String getTitle() {

          return title;

       }

       public void setTitle(String title) {

          this.title = title;

       }

       @NotNull @Lob

       public String getText() {

          return text;

       }

       public void setText(String text) {

          this.text = text;

       }

       @NotNull

       public boolean isRead() {

          return read;

       }

       public void setRead(boolean read) {

          this.read = read;

       }

       @NotNull

       @Basic @Temporal(TemporalType.TIMESTAMP)

       public Date getDatetime() {

          return datetime;

       }

       public void setDatetime(Date datetime) {

          this.datetime = datetime;

       }

    }

上の例に見るように、`getDiscriminator()`メソッドが `@PermissionDiscriminator`でアノテートされ、どのレコードがユーザーのパーミッションを示し、どのレコードがロールパーミッションを示しているかを`JpaPermissionStore`に示しています。 さらに、`getRecipient()`メソッドが`@PermissionUser`と`@PermissionRole`でアノテートされています。 これは、間違いではなく `discriminator`属性の値により、エンティティの`recipient`属性の内容をユーザーの名前、あるいはロールの名前として処理する事を示しています。

##### 15.6.10.3.3. クラスに特定されたパーミッションの設定

これ以外のクラス特有のアノテーションを使用して対象クラスに対して特定のパーミッションを設定する事ができます。 これらのパーミッションは`org.jboss.seam.annotation.security.permission`パッケージにあります。

表 15.10. クラス パーミッション アノテーション

アノテーション | ターゲット | 詳細
--- | --- | ---
`@Permissions` | `タイプ` | コンテナのアノテーション、 このアノテーションは`@Permission`の配列を含んでいます。
`@Permission` | `タイプ` | このアノテーションでは対象クラスに対して一つのパーミッションアクションを認めています。 `action`属性が指定されていなければなりません。 また、ビットマスク値によるパーミッションアクションを指定する場合には `mask`が指定されていなければなりません。

この例では、上のアノテーションを使っています。 下のクラスはサンプルのSeamSpaceにもあります。

    @Permissions({

       @Permission(action = "view"),

       @Permission(action = "comment")

    })

    @Entity

    public class MemberImage implements Serializable {

この例では`view`と`comment`の二つのパーミッションアクションを`MemberImage`エンティティクラスに対して宣言する方法を示しています。

##### 15.6.10.3.4. パーミッション マスク

デフォルトでは、一つの対象オブジェクトと受益者に対する複数のパーミッションは、一つのデータベースレコードとして`action`属性（DBではカラム）に複数のアクションをコンマで区切って記述され、保持されます。 大量のパーミッション情報を、物理的な保存領域を抑えてデータベースに保存するために、パーミッションアクションにコンマ区切りの文字列リストの代わりに、整数のビットマスク値を使用する事ができます。

例えば、受益者 "Bob"が特定の`MemberImage`（エンティティBean）インスタンスに対して `view`と`comment`のパーミッションが付与されていた場合、パーミッションエンティティの`action`属性は、二つのパーミッションアクションを付与されていることを示し"`view,comment`"を含んでいます。 代わりに、ビットマスクをパーミッションアクションに使用すると下のようになります。

    @Permissions({

       @Permission(action = "view", mask = 1),

       @Permission(action = "comment", mask = 2)

    })

    @Entity

    public class MemberImage implements Serializable {

`action`属性は、この場合単に"3"（bit 1 と 2 がonの状態）となります。 特定の対象クラスに対する大量のアクション許可を記述する場合には、アクションにビットマスクを使用する事により、明らかにパーミッションレコードの保存に必要な容量を圧縮する事ができます。

`mask`の値が2のｎ乗になっている事は明らかに重要です。

##### 15.6.10.3.5. 識別子ポリシー

`JpaPermissionStore`は、パーミッションを保存したり、参照したりするときに対象のインスタンスのパーミッションについて効果的に操作を行うために、対象インスタンスを一意に特定できる必要があります。 これを実現するために、ユニークなIDを生成するよう個々の対象となるクラスに対してidentifier strategyがアサインされます。 それぞれのID戦略実装により、クラスのタイプに応じてユニークなIDの生成が行われます。

`IdentifierStrategy`インタフェースはたいへんに単純で、二つのメソッドを宣言しているだけです。

    public interface IdentifierStrategy {

       boolean canIdentify(Class targetClass);

       String getIdentifier(Object target);

    }

最初のメソッド`canIdentify()`は、識別子ストラテジーが指定されたターゲットクラスに対してユニークな識別子を生成可能な場合に`true`を返します。 第2のメソッド`getIdentifier()`は指定されたターゲットオブジェクトに対してユニークな識別子の値を返します。

Seamは二つの`IdentifierStrategy`実装、`ClassIdentifierStrategy`と`EntityIdentifierStrategy`を提供しています（詳細は次のセクション）。

特定のクラスに対して、特別のID戦略を使用するよう明示的に設定するには、`org.jboss.seam.annotations.security.permission.Identifier`アノテーションがされ、`IdentifierStrategy` インタフェースの実装に値が設定されている必要があります。 オプションとして`name`属性を指定する事も可能ですが、この指定が及ぼす効果は `IdentifierStrategy`の実際の実装に依存します。

##### 15.6.10.3.6. クラス識別子ストラテジー

ID戦略はクラスに対してユニークなIDを生成するために使用し、指定してあれば`@Identifier`アノテーション中の `name`の値が使用されます。 もし、`name`属性が指定されていない場合には、Seamのコンポーネントであれば対象のクラスのコンポーネント名を使用するか、さもなくばパッケージ名を除くクラスの名前を使用します。 下の例にあるクラスのIDは"`customer`"となります。

    @Identifier(name = "customer")

    public class Customer {

以下のクラスの識別子は"`customerAction`"となります:

    @Name("customerAction")

    public class CustomerAction {

最終的に、以下のクラスの識別子は "`Customer`"となります:

    public class Customer {

##### 15.6.10.3.7. エンティティ識別子ストラテジー

このID戦略はエンティティBean毎にユニークなIDを割り当てる方法で、エンティティのプライマリキーを示す文字列とエンティティの名前をつなぎ合わせて、IDを生成しています。 IDの名前セクションの生成ルールは`ClassIdentifierStrategy`と同様です。 プライマリキー値 (即ち、エンティティのid ) は `PersistenceProvider`コンポーネントを使って取得する事ができ、アプリケーションでどの永続性実装を使用しているかに依存せずに値を決める事ができます。 `@Entity`でアノテートされていないエンティティについては, エンティティクラス自身に下のように明示的にID戦略を設定する事が必要です。

    @Identifier(value = EntityIdentifierStrategy.class)

    public class Customer {

生成される識別子の例として、下のようなエンティティクラスを考えてみましょう。

    @Entity

    public class Customer {

      private Integer id;

      private String firstName;

      private String lastName;

      @Id

      public Integer getId() { return id; }

      public void setId(Integer id) { this.id = id; }

      public String getFirstName() { return firstName; }

      public void setFirstName(String firstName) { this.firstName = firstName; }

      public String getLastName() { return lastName; }

      public void setLastName(String lastName) { this.lastName = lastName; }

    }

`id`が`1`の`Customer`のインスタンスに対する識別子は"`Customer:1`"となります。 もし、エンティティクラスに明示的な識別子名のアノテーションがあれば、

    @Entity

    @Identifier(name = "cust")

    public class Customer {

結果として、`id`が`123`の`Customer`は "`cust:123`"という識別子を持つことになります。

## 15.7. パーミッションの管理

Seamセキュリティでユーザーとロールの管理のために提供しているID管理APIと同様に、永続的なユーザーのアクセス権の管理のためのパーミッションマネージメントAPIを `PermissionManager`により提供しています。

### 15.7.1. パーミッションマネージャ

`PermissionManager`コンポーネントはパーミッションを管理するための多くのメソッドを持つアプリケーションスコープのSeamのコンポーネントです。 使用する前にパーミッションストアを設定する必要があります（デフォルトで、`JpaPermissionStore`が存在すれば、これを使うようになります）。 明示的に別のパーミッションストアを設定する場合には、`components.xml`に`permission-store`を設定します。

    <security:permission-manager permission-store="#{ldapPermissionStore}"/>

以下の表に`PermissionManager`の提供するメソッドの詳細を示します。

表 15.11. パーミッションマネージャAPIのメソッド

戻り値の型 | メソッド | 詳細
--- | --- | ---
`List<Permission>` | `listPermissions(Object target, String action)` | 指定されたターゲットとアクションに対する承認されたすべてのパーミッションを示す`Permission`オブジェクトを返します。
`List<Permission>` | `listPermissions(Object target)` | 指定されたターゲットとアクションに対する承認されたすべてのパーミッションを示す`Permission`オブジェクトを返します。
`ブール型` | `grantPermission(Permission permission)` | バックエンドのパーミッションストアに指定した`Permission`sをセーブする。 操作に成功した場合trueを返す。
`ブール型` | `grantPermissions(List<Permission> permissions)` | バックエンドのパーミッションストアに指定した複数の`Permission`sをセーブする。 操作に成功した場合trueを返す。
`ブール型` | `revokePermission(Permission permission)` | バックエンドのパーミッションストアから指定した`Permission`sを取り除く（削除する）。操作に成功した場合trueを返す。
`ブール型` | `revokePermissions(List<Permission> permissions)` | バックエンドのパーミッションストアから指定した複数の`Permission`sを取り除く（削除する）。 操作に成功した場合trueを返す。
`List<String>` | `listAvailableActions(Object target)` | 対象ターゲットに対する適用可能なアクションのリストを返す。 返されるアクションはターゲットオブジェクトクラスに設定されている`@Permission` アノテーションに依存する。

### 15.7.2. パーミッションマネージャ操作のためのパーミッションチェック

`PermissionManager`メソッドを起動する場合には現在の認証ユーザーが当該管理操作をするために必要な適切なパーミッションを持っている必要があります。 下の表に、現在のユーザーが持っていなければならないパーミッションの一覧を示します。

表 15.12. パーミッション管理 セキュリティパーミッション

メソッド | パーミッションの対象 | パーミッションのアクション
--- | --- | ---
`listPermissions()` | 特定の`target` | `seam.read-permissions`
`grantPermission()` | ターゲットの特定の`Permission`、あるいは個別のターゲットの`Permission`sリスト（どちらのメソッドを呼ぶのかにより異なる） | `seam.grant-permission`
`grantPermission()` | ターゲットの特定の`Permission` | `seam.grant-permission`
`grantPermissions()` | それぞれのターゲットの特定の`Permission`s | `seam.grant-permission`
`revokePermission()` | ターゲットの特定の`Permission` | `seam.revoke-permission`
`revokePermissions()` | それぞれのターゲットの特定の`Permission`s | `seam.revoke-permission`

## 15.8. SSLによるセキュリティ

SeamはHTTPSプロトコルによるpageのセキュリティを基本的な部分についてサポートしています。 この機能は、`pages.xml`で必要なページについて`scheme`を指定することにより簡単に設定することができます。 下の例では`/login.xhtml`でHTTPSを使う様に設定しています。

    <page view-id="/login.xhtml" scheme="https"/>

また、この設定は自動的にJSFの`s:link`や`s:button`にも引き継がれ (`view`で指定した場合) 、リンクも正しいプロトコルで描画されます。前述の例の場合、下のようなリンクも`/login.xhtml`がHTTPSを使うように設定されているために、`s:link`先の`login.xhmtl`にもHTTPSがプロトコルとして使用されます。

    <s:link view="/login.xhtml" value="Login"/>

指定されたプロトコル以外 (正しくないプロトコル) を使って、ページを見ようとすると、正しいプロトコルを使って、指定のページへリダイレクトされます。 `schema="https"`が指定されているページにhttpでアクセスしようとすると、そのページにhttpsを使ってリダイレクトされます。

すべてのページに対してデフォルトのschemeを設定することも可能で、一部のページに対してHTTPSを使用したい場合などに有効です。 デフォルトスキーマが設定されていない場合には、現在のスキーマを継承します。 従って、ユーザーがHTTPSを必要とするページにアクセスすると、それ以降はHTTPSを必要としないページに対してもHTTPSを使ったアクセスとなります（これは、セキュリティ上は良いのですが、パフォーマンス上は問題があります）。 HTTPをデフォルトの`scheme`として指定する場合には次の一行を`pages.xml`に追加してください。

    <page view-id="*" scheme="http" />

もちろん、HTTPSを使う必要がなければ、デフォルトのschemaを指定する必要もありません。

以下のように、`components.xml`に設定することにより、スキーマが変さらになるたびに現在のHTTPセッションを自動的に無効にする事ができます。

    <web:session invalidate-on-scheme-change="true"/>

このオプションはHTTPSのページから、HTTPのページへの重要データの漏れや、セッションIDの盗聴に対する脆弱性を減少させます。

### 15.8.1. デフォルトのポートの上書き

もし個別にHTTPとHTTPSの使用を設定する必要があるのであれば、`pages.xml` の`pages` 要素に`http-port` あるいは `https-port`を設定することにより行えます。

    <pages xmlns="http://jboss.com/products/seam/pages"

           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

           xsi:schemaLocation="http://jboss.com/products/seam/pages http://jboss.com/products/seam/pages-2.2.xsd"

           no-conversation-view-id="/home.xhtml"

           login-view-id="/login.xhtml"

           http-port="8080"

           https-port="8443"

    >

## 15.9. キャプチャ

厳密にはセキュリティAPIの一部ではありませんが、SeamはCAPCHA(Completely Automated Public Turing test to tell Computers and Humans Apart)アルゴリズムを内蔵しており、ウェブ上の自動処理プログラムによりアプリケーションが動作しないようにする事を可能にしています。

### 15.9.1. キャプチャ サーブレットの設定

キャプチャを起動して走らせるためには、Seamのリソースサーブレットを下のように、`web.xml`に設定する必要があります。これにより、アプリケーションのページにキャプチャチャレンジのイメージを提供するようになります。

    <servlet>

        <servlet-name

    >Seam Resource Servlet</servlet-name>

        <servlet-class

    >org.jboss.seam.servlet.SeamResourceServlet</servlet-class>

    </servlet>

    <servlet-mapping>

        <servlet-name

    >Seam Resource Servlet</servlet-name>

        <url-pattern

    >/seam/resource/*</url-pattern>

    </servlet-mapping

    >

### 15.9.2. キャプチャのフォームへの追加

キャプチャチャレンジをフォームに追加するのは以下のようにいたって簡単です:

    <h:graphicImage value="/seam/resource/captcha"/>

    <h:inputText id="verifyCaptcha" value="#{captcha.response}" required="true">

       <s:validate />

    </h:inputText>

    <h:message for="verifyCaptcha"/>

これですべてです。 `graphicImage` がキャプチャチャレンジの表示を制御し、`inputText`がユーザーからの入力を受け付けます。 ユーザーの入力はフォームが送信された時に自動的にキャプチャと照合されます。

### 15.9.3. キャプチャアルゴリズムのカスタム化

内蔵コンポーネントをオーバーライドする事により、キャプチャのアルゴリズムをカスタマイズすることができます。

    @Name("org.jboss.seam.captcha.captcha")

    @Scope(SESSION)

    public class HitchhikersCaptcha extends Captcha

    {

       @Override @Create

       public void init()

       {

          setChallenge("What is the answer to life, the universe and everything?");

          setCorrectResponse("42");

       }

       @Override

       public BufferedImage renderChallenge()

       {

           BufferedImage img = super.renderChallenge();

           img.getGraphics().drawOval(5, 3, 60, 14); //add an obscuring decoration

           return img;

       }

    }

## 15.10. セキュリティ イベント

以下の表に、セキュリティ関連のイベントによりSeamのセキュリティで発生するイベント（章 6. イベント、インタセプタ、例外処理）を一覧にしました。

表 15.13. セキュリティ イベント

イベントキー | 詳細
--- | ---
`org.jboss.seam.security.loginSuccessful` | ログインに成功した時に発生
`org.jboss.seam.security.loginFailed` | ログインに失敗した時に発生
`org.jboss.seam.security.alreadyLoggedIn` | ユーザーがすでに認証されていて再度ログインした時に発生
`org.jboss.seam.security.notLoggedIn` | ユーザーがログインしていないためにセキュリティチェックに失敗した時に発生
`org.jboss.seam.security.notAuthorized` | ユーザーがログインできても十分な特権が無くセキュリティチェックに失敗した時に発生
`org.jboss.seam.security.preAuthenticate` | ユーザーが認証される直前に発生
`org.jboss.seam.security.postAuthenticate` | ユーザーが認証された直後に発生
`org.jboss.seam.security.loggedOut` | ユーザーがログアウトした後に発生
`org.jboss.seam.security.credentialsUpdated` | ユーザーのクレデンシャル（信用情報）が変更された時に発生
`org.jboss.seam.security.rememberMe` | IdentityのrememberMeプロパティが変更された時に発生

## 15.11. 自分とは別の権限での実行

場合により、上位権限で処理することが必要な場合があります（たとえば、認証されていないユーザーが、新しいユーザーアカウントを作成する場合）。 Seamはこのような機能を`RunAsOperation`クラスで提供しています。 このクラスは、限定された一組の操作に対して `Principal`、`Subject`、あるいはユーザーのロールを一時的に上書きする事を可能にします。

以下のコード例では`RunAsOperation`の使われ方について、`addRole()` メソッドを呼び出して操作終了まで特定のロールを付与する方法を示します。 `execute()` メソッドはより上位の特権で実行するためのコードを持っています。

        new RunAsOperation() {

           public void execute() {

              executePrivilegedOperation();

           }

        }.addRole("admin")

         .run();

同様に`getPrincipal()` や `getSubject()`メソッドは`Principal`インスタンスおよび `Subject`インスタンスを実行中に上書きする事ができます。 最終的に`RunAsOperation`を実行するために`run()`メソッドを使用します。

## 15.12. IDコンポーネント（Identity component）の拡張

アプリケーションが特別なセキュリティを要求する場合には、Identityコンポーネントを拡張する必要がある場合があります。以下の例（説明のためのもので、通常、クレデンシャルは`Credentials`コンポーネントにより処理されます）では`companyCode` フィールドを追加した拡張Identityコンポーネントを示しています。`APPLICATION`により、拡張Identityが内蔵Identityよりも優先されてインストールされることを保証しています。

    @Name("org.jboss.seam.security.identity")

    @Scope(SESSION)

    @Install(precedence = APPLICATION)

    @BypassInterceptors

    @Startup

    public class CustomIdentity extends Identity

    {

       private static final LogProvider log = Logging.getLogProvider(CustomIdentity.class);

       private String companyCode;

       public String getCompanyCode()

       {

          return companyCode;

       }

       public void setCompanyCode(String companyCode)

       {

          this.companyCode = companyCode;

       }

       @Override

       public String login()

       {

          log.info("###### CUSTOM LOGIN CALLED ######");

          return super.login();

       }

    }

## 警告

`SESSION`コンテキストが開始されると同時に利用可能とするために、`Identity`コンポーネントは`@Startup`とアノテートされている必要があることに留意してください。 これが行われていないと、Seamのいくつかの機能が動作しないことがあります。

## 15.13. OpenID

OpenID は外部でウェブベースの認証を行うためのコミュニティ標準です。基本的な考えは、ウェブアプリケーションは外部の OpenID サーバーへ責任を委譲することによって認証のローカルでの処理を補う (または置き換える) ことが可能ということです。この利点は、ユーザーにとってはウェブアプリケーションごとに名前とパスワードを覚えておく必要がなくなるということと、開発者にとっては複雑な認証システムを保守する重荷から解放されるということです。

OpenID を使うとき、ユーザーは OpenID プロバイダを選択し、プロバイダはユーザーに OpenID を割り当てます。その ID は、`http://maximoburrito.myopenid.com` のような URLの形式を取る。しかし、サイトにログインするとき、IDの一部となる `http://` の部分は省いてアクセスできます。ウェブアプリケーション (OpenIDの世界では relying party として知られます） は、認証のためにユーザーがどのリモートサイトへ接続したり、リダイレクトしたりするのかを決定します。認証に成功したら、ユーザーには IDを証明する(暗号化されてセキュアな) トークンが与えられ、最初のウェブアプリケーションの元へリダイレクトされます。次に、ローカルのウェブアプリケーションは、ユーザーが提示した OpenID を制御するそのアプリケーションにアクセス可能なことを確認します。

この時点で認証は認可を意味することではないことを理解しておくことは重要です。ウェブアプリケーションはその情報を使う方法を決定する必要があります。ウェブアプリケーションはそのユーザにすぐにログインさせシステムへのフルアクセスをさせることもできますし、提示された OpenID からローカルユーザーのアカウントへのマップを試みることもできます。もしそのユーザーが登録されていなければ登録を促します。Open IDを処理するどの方法を選択するかはローカルアプリケーションの設計に委ねられます。

### 15.13.1. OpenID の設定

Seam は openid4java パッケージを使い、Seamと統合するために追加の JAR ファイル群を要求します。それらは、`htmlparser.jar`、 `openid4java.jar`、 `openxri-client.jar` そして `openxri-syntax.jar` です。

OpenID の処理は `OpenIdPhaseListener` を使うことを要求します。それは `faces-config.xml` ファイルに追加すべきです。そのフェーズリスナーは、ローカルアプリケーションへの再入を可能にして、 OpenID プロバイダからのコールバックを処理します。

    <lifecycle>

        <phase-listener>org.jboss.seam.security.openid.OpenIdPhaseListener</phase-listener>

    </lifecycle>

この構成をすれば、OpenID サポートが利用できます。OpenID サポートコンポーネント、すなわち `org.jboss.seam.security.openid.openid` 、は openid4java クラスがクラスパス上にあれば自動的にインストールされます。

### 15.13.2. OpenIdDLogin フォームの記述

OpenID ログインを開始するには、ユーザーに対してユーザーの OpenID を問い合わせる簡単なフォームを提示します。`#{openid.id}` の値は、ユーザーの OpenID を受け入れ、 `#{openid.login}` アクションは認可の要求を開始します。

    <h:form>

        <h:inputText value="#{openid.id}" />

        <h:commandButton action="#{openid.login}" value="OpenID Login"/>

    </h:form>

ユーザがログインフォームを提出したとき、そのユーザーの OpenID プロバイダへリダイレクトされます。ユーザーは、最終的には Seam の擬似ビューである（`OpenIdPhaseListener` によって提供される) `/openid.xhtml` を介してアプリケーションに戻ります。アプリケーションは、 `pages.xml` ナビゲーションフォームを用いることによって、あたかもそのユーザーがアプリケーションを決して離れなかったかのように、そのビューから OpenID の応答を処理することができます。

### 15.13.3. 直ちにログインする

最も簡単な戦略は、単純にユーザーに直ちにログインすることです。次のナビゲーションルールは `#{openid.loginImmediately()}` アクションを使ってこれを処理する方法について示します。

    <page view-id="/openid.xhtml">

        <navigation evaluate="#{openid.loginImmediately()}">

            <rule if-outcome="true">

                <redirect view-id="/main.xhtml">

                    <message>OpenID login successful...</message>

                </redirect>

            </rule>

            <rule if-outcome="false">

                <redirect view-id="/main.xhtml">

                    <message>OpenID login rejected...</message>

                </redirect>

            </rule>

        </navigation>

    </page>

`loginImmediately()` アクションは OpenID が有効かどうかを調べます。もし有効なら、OpenIDPrincipal を identity コンポーネントへ追加し、そのユーザーがログイン済みとマークして (言い換えれば `#{identity.loggedIn}` が true を返す)、true を返します。OpenID が有効でなかったなら、そのメソッドは false を返し、ユーザーはアプリケーションに認証されていない状態で再入します。もしユーザーの OpenIDが有効ならば、 それは 式 `#{openid.validatedId}` と `#{openid.valid}` を使ってアクセス可能になるでしょう。

### 15.13.4. 遅延ログイン

ユーザーがアプリケーションに直ぐにログインすることを望まないかもしれません。その場合、ナビゲーションは `#{openid.valid}` プロパティを調べて、ユーザーをローカルの登録または処理のページにリダイレクトすべきです。ここで取るアクションとしては、もっと情報を求めるか、ローカルユーザーアカウントを作成するか、プログラムによる登録を避けるためにキャプチャ (captcha) を提示することでしょう。処理を終了したとき、ユーザが入ったことをログに記録したいのならば、前に見てきたようにELを使うか、あるいは `org.jboss.seam.security.openid.OpenId` コンポーネントを直接呼び出すことによって、`loginImmediately` メソッドを呼び出すことができます。もちろん、もっとカスタマイズされた振る舞いをさせるために自分自身で Seam Identifty コンポーネントとやりとりするためのカスタムコードを書くことを妨げるものは何もありません。

### 15.13.5. ログアウト

ログアウト (これによってOpenIDとの関連を忘れます) は、`#{openid.logout}` を呼び出すことによって実行されます。Seam セキュリティを使わないのであれば、このメソッドを直接呼び出すことができます。Seam セキュリティを使わないのであれば、 `#{identity.logout}` を使い続け、ログアウトイベントを捕捉するために OpenID の logout メソッドを呼び出すイベントハンドラをインストールすべきです。

    <event type="org.jboss.seam.security.loggedOut">

        <action execute="#{openid.logout}" />

    </event>

これを放置しないことは重要です。さもないと、ユーザーは同じセッションにおいて再びログインできなくなってしまうでしょう。

- 前のページ第14章 Seam と JBoss Rules
- 上に戻る
- ホーム
- 次のページ第16章 国際化と地域化およびテーマ