第12章 セキュリティ

SeamのセキュリティAPIはシームプロジェクトのドメイン或はページに対して認証と承認機能を提供するAPIです。

12.1. 概要

Seamのセキュリティ機能は下の二つの操作モードを提供します。

  • 単純なモード - 認証と、ロールに基づくセキュリティチェックを提供します

  • より高度なモード - 上記の機能に加えてJBoss Ruleを利用したルールベースのセキュリティチェック機能を提供します。

12.1.1. どちらのモードを使うのが適切か?

アプリケーションの必要性に応じて、ログインしたユーザ、或は特定のロールを有するユーザに対してのみページ閲覧や操作の実行を許可したい場合のような、単純なセキュリティ機能が必要な場合には、「単純なモード」で十分でしょう。 このモデルでは、設定が簡単であることや、必要なライブラリが少なくメモリの消費量も少ないメリットがあります。

一方、複雑なビジネスルールや、コンテクストの状態によるセキュリティのチェックが必要となる場合には、「より高度なモード」を使う必要があるでしょう。

12.2. 要求条件

Seamセキュリティの「より高度なモード」を利用する場合には、下記のjarファイルをモジュールとしてapplication.xmlに設定する必要があります。 一方、「単純なモード」であれば、これらのファイルは必要ありません。

  • drools-compiler-3.0.5.jar

  • drools-core-3.0.5.jar

  • commons-jci-core-1.0-406301.jar

  • commons-jci-janino-2.4.3.jar

  • commons-lang-2.1.jar

  • janino-2.4.3.jar

  • stringtemplate-2.3b6.jar

  • antlr-2.7.6.jar

  • antlr-3.0ea8.jar

webベースのセキュリティを実装する場合にはjboss-seam-ui.jarがアプリケーションの warファイルに含まれている必要があります。 また、EL式でセキュリティを利用する場合にはSeamFaceletViewHandlerの設定が必要となりますので、faces-config.xml に下の様に設定してください。

<application>
    <view-handler>org.jboss.seam.ui.facelet.SeamFaceletViewHandler</view-handler>
</application>

12.3. 認証

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

12.3.1. 設定

単純な認証機構では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/core http://jboss.com/products/seam/core-1.2.xsd 
                 http://jboss.com/products/seam/components http://jboss.com/products/seam/components-1.2.xsd
                 http://jboss.com/products/seam/drools http://jboss.com/products/seam/drools-1.2.xsd"
                 http://jboss.com/products/seam/security http://jboss.com/products/seam/security-1.2.xsd">                
        
    <security:identity authenticate-method="#{authenticator.authenticate}"/>
    
</components>

ルールに基づく承認のような、「より高度なセキュリティ機構」を実装す場合には、Drools (JBoss Rules) のjarファイル群をクラスパスに含め、後述のようにコンフィグレーションファイルに若干の設定を加えることが必要となります。

EL式 #{authenticator.authenticate}authenticatorコンポーネントのauthenticateメソッドを使って、ユーザの認証を行うことを示しています。

12.3.2. 認証メソッドの記述

components.xml中で、identityに特定されている(紐付けされている)authenticate-methodSeamLoginModuleがユーザの認証をするときに使用するメソッドを規定します。このメソッドはパラメータをとらず、認証の可否を示すブール値を返すよう期待されています。ユーザのusernameやpasswordは其々、Identity.instance().getUsername()Identity.instance().getPassword()で取得することができます。 また、ユーザが属するロールについてはIdentity.instance().addRole()により、付与することができます。 下に、Java Beanコンポーネント中の認証メソッドの完全な例を示します。

@Name("authenticator")
public class Authenticator {
   @In EntityManager entityManager;
   
   public boolean authenticate() {
      try
      {
         User user = (User) entityManager.createQuery(
            "from User where username = :username and password = :password")
            .setParameter("username", Identity.instance().getUsername())
            .setParameter("password", Identity.instance().getPassword())
            .getSingleResult();

         if (user.getRoles() != null)
         {
            for (UserRole mr : user.getRoles())
               Identity.instance().addRole(mr.getName());
         }

         return true;
      }
      catch (NoResultException ex)
      {
         FacesMessages.instance().add("Invalid username/password");
         return false;
      }
      
   }
   
}

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

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

Identityコンポーネントはusernamepasswordプロパティを提供することにより、ほとんどの認証プロセスに対応することができます。 これらのプロパティはログインフォームのusernameとpasswordのフィールドに直接バインドすることができますので、これらのプロパティがセットされた後にidentity.login()メソッドを呼び出すことにより、設計者が組み込んだ認証システムに基ずきユーザの認証をすることができます。 簡単なログインフォームの例を示します。

<div>
    <h:outputLabel for="name" value="Username"/>
    <h:inputText id="name" value="#{identity.username}"/>
</div>

<div>
    <h:outputLabel for="password" value="Password"/>
    <h:inputSecret id="password" value="#{identity.password}"/>
</div>

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

(loginと)同様にログアウトも、#{identity.logout}を呼び出すことにより実行されます。ログアウトを実行することにより、現在まで認証されていたユーザのセキュリティの状態は破棄されます。

12.3.4. 簡単な設定 ー まとめ

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

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

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

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

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

セキュリティエラーが出た場合にユーザがデフォルトのエラーページを見ることのない様 pages.xmlを設定して、もう少しましなセキュリティエラーページにリダイレクトすることが推奨されます。 セキュリティAPIが投げる主だった例外には下の2種類があります。

  • 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ではこの様なケースに対応出来るような機能も持たせています。

12.3.6. ログインリダイレクト

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

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

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

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

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

<event type="org.jboss.seam.notLoggedIn">
    <action expression="#{redirect.captureCurrentView}"/>
</event>
    
<event type="org.jboss.seam.postAuthenticate">
    <action expression="#{redirect.returnToCapturedView}"/>
</event>

ログインリダイレクトは対話スコープの中で実装されていますので、当該の対話スコープをauthenticate()メソッドで終了させないように注意してください。

12.3.7. 高度な認証機能

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

12.3.7.1. コンテナのJAAS設定を利用する

SeamのセキュリティAPIが提供する単純化されたJAASの設定を使用しないのであれば、components.xmljaasConfigNameプロパティを設定して、デフォールトのJAASの設定にセキュリティを任せることができます。 例えば、JBossASを使用していて、JBossASの提供するUsersRolesLoginModuleを利用しotherポリシーを利用したいのであれば、下のようにcomponents.xmlを記述してください。

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

12.4. エラーメッセージ

セキュリティAPIはセキュリティに関するイベントについて、多くのデフォルトのフェースメッセージを生成します。下のメッセージキーの一覧表は、リソースファイルのmessage.propertiesで特定のメッセージを置き換えるのに利用できます。

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

org.jboss.seam.loginSuccessful

セキュリティAPIを通して、無事ログイン出来たときに生成されます。

org.jboss.seam.loginFailed

ユーザネーム、パスワードの組み合わせ、或は何らかの認証のエラーにより、ユーザがログインに失敗したときに生成されます。

org.jboss.seam.NotLoggedIn

ユーザが認証されずにセキュリティチェックが必要な操作、あるいはページへのアクセスを試みたときに生成されます。

12.5. 認可

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

12.5.1. 核となる概念

Seamの提供するセキュリティAPIによる認可のメカニズムは、ロールと許可の組み合わせに基づいてユーザに認可を与えるというコンセプトに基づいて作られています。 ロールとは、ユーザの属するgroupあるいはtypeを意味し、そこに属していることにより、アプリケーション中で特定の操作をすることが認められます。 それに対し、パーミッション(許可)とは特定の一つの操作を、時には1回だけ、許可される事を意味します。 勿論、パーミッションだけを使って、アプリケーションを組むことは可能ですが、ロールはユーザの特定のグループに対する特権を、より簡単に、高次元のレベルで与えることができます。

ロールとは単純に、admin、userとかcustomer といったような名前で与えられます。 一方、パーミッションは、名前と操作の組み合わせで与えられ、ここの説明ではname:action(例えば、customer:delete, customer.insert)といった形式で表現しています。

12.5.2. セキュリティコンポーネント

それでは、@Restrictアノテーションを使った、簡単なコンポーネントのセキュリティから始めましょう。

12.5.2.1. @Restrictアノテーション

Seamのコンポーネントは、@Restrictアノテーションを使って、メソッドあるいはクラスのレベルでセキュリティを確保します。 もし、クラスと、そこに含まれるメソッドの両方に@Restrictアノテーションがあった場合には、メソッドの制約が優先され、クラスに対する制約は適用されません。 もし、メソッドがセキュリティのチェックで失敗した場合には、Idetity.checkRestriction()により、例外エラーが投げられます。(Inline Restriction参照)。 クラスに対する@Restrictは、そこに含まれる全てのメソッドに@Restrictをつけた事と同しことです。

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

@Name("account")
public class AccountAction {
    @Restrict public void delete() {
      ...
    }
}

この例の場合、delete()の実行に必要なパーミッションはacount:deleteとなります。同じ事は@Restrict("#{s:hasPermission('account','delete',null)}")と明示的に記述することができます。 更に、別の例を見てみましょう。

@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:hasRoles:hasPermissionもEL関数で、対応するIdentityクラスのメソッドを示しています。 これらの関数は全てのセキュリティAPIのEL式で使用することができます。

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

@Name("account")
public class AccountAction {
    @In Account selectedAccount;
    @Restrict("#{s:hasPermission('account','modify',selectedAccount)}")
    public void modify() {
        selectedAccount.modify();
    }
}

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

12.5.2.2. インラインによる制約

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

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

もし、評価の結果が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", null))
     throw new AuthorizationException("You may not create new customers");

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

うまくデザインされたユーザインターフェースでは、当該ユーザが利用する権限を持たない物は見せないようにします。 Seamのセキュリティ機構は、コンポーネントのセキュリティで使用したのと同様のEL式を使って、ページの部分あるいは個別の操作について、表示/非表示をコントロールすることができます。

インターフェースのセキュリティの例についてみてみましょう。 まず、ユーザがログインしていない場合に限ってログイン画面が表示される場合を考えてみます。 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はオブジェクトをパラメータとして受け付ける機能を持っており、それにより特定のオブジェクトに対するユーザの権限を評価することが可能となります。 下に、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('client','modify',cl)"/>
        <s:link value="Delete Client" action="#{clientAction.delete}"
                rendered="#{s:hasPermission('client','delete',cl)"/>
    </h:column>
</h:dataTable>

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

ページ単位のセキュリティのコントロールはアプリケーションがpages.xmlを利用していないと使えませんが、設定は非常に簡単です。 pages.xml中で、セキュリティを確保したいpageエレメントに対し<restrict/><restrict></restrict>要素を加えるだけです。 restrict要素に対し値が与えられていない場合には、{viewId}:render がデフォルトとして評価され、アクセスの可否を決定します。 値が与えれれた場合には、通常のセキュリティの評価と同様に評価されます。下に、いくつかの例を示します。

<page view-id="/settings.xhtml">
    <restrict/>
</page>
        
<page view-id="/reports.xhtml">    
    <restrict>#{s:hasRole('admin')}</restrict>
</page>

上の1番目の例では、/settings.xhtml:renderという暗黙の制約がかけられており、また、2番目の例ではユーザがadminのロールに属するか否かのチェックが行われています。

12.5.5. エンティティのセキュリティ

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

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

@Entity
@Name("customer")
@Restrict
public class Customer {
  ...
}

もし、@Restrictが評価式無しで付記されていれば、デフォルトとしてentityName:actionがチェックされます。ここで、entityNameは当該エンティティの名前(あるいは@Entityアノテーションがなければ、クラスの名前)で、actionread, insert, update あるいは deleteのいずれかです。

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

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

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

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

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

以下に、あるエンティティへの全てのinsert操作に対してセキュリティチェックを実行する為の設定の例を示します。この例では、セキュリティについて「どのようにアノテーションを付記するのか」を示しているだけですので、メソッドは何もしていない事に留意してください。

  @PrePersist @Restrict
  public void prePersist() {}      
   

ここでは、seamspaceサンプルコードから、認証されたユーザが新規のMemberBlogを挿入することが許可されているのか、をチェックするエンティティパーミッションルールの例を見てみます。 セキュリティチェックの対象となるエンティティ(この例の場合)MemberBlogが自動的にワーキングメモリにアサートされます。

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

このルールではPrincipalで示される認証されているユーザが、ブログのエントリを作成したメンバーと同じ名前であれば、memberBlog:insertのパーミッションを与えます。"name : name"という構造は、Principalファクトやそのほかの所でも見られる変数結合で、name変数にPrincipalnameプロパティを結合します。 変数結合は、次の行の、メンバーのusernameとPrincipalの比較(マッチング)の様な所で、値を参照することを可能にします。 詳細については、JBoss Rules のドキュメンテーションを参照してください。

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

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

EJB3エンティティビーンのセキュリティチェックは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>

12.5.5.2. Hibernateでのエンティティセキュリティ

SeamでHibernateのSessionFactoryを使っているのであれば、エンティティセキュリティの為に特別な設定をする必要はありません。

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

今まで、いろいろとパーミッションについて述べてきましたが、このパーミッションについて、どのように定義するのか、また受容されるのかについては述べてきませんでした。 この項ではどのようにパーミッションのチェックが行われるのか、またSeamアプリケーションにどのようにパーミッションを実装するのかについて説明し、セキュリティの章の最後とします。

12.6.1. パーミッションについての概要

特定のユーザがcustomer:modifyのパーミッションを持っているのか否か、セキュリティAPIはどのようにして知るのでしょうか? SeamのセキュリティはJBoss Ruleに基づいた大変ユニークな方法で、ユーザのパーミッションを決定しています。 ルールエンジンを使うことのメリットは、1)個々のユーザのパーミッションを決定しているビジネスロジックを集中管理することが出来る、2)複雑な条件の基においてもJBossRuleは効率的にスピーディに処理することが出来る、事にあります。

12.6.2. ルールファイルの設定

Seamセキュリティはパーミッションのチェックに使用するためのsecurityRulesと呼ばれるRuleBaseを検索して使用します。 このファイルはcomponents.xmlに下のように記述、設定します。

<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-1.2.xsd 
                 http://jboss.com/products/seam/components http://jboss.com/products/seam/components-1.2.xsd
                 http://jboss.com/products/seam/drools http://jboss.com/products/seam/drools-1.2.xsd"
                 http://jboss.com/products/seam/security http://jboss.com/products/seam/security-1.2.xsd">                 
        
   <drools:rule-base name="securityRules">
       <drools:rule-files>
           <value>/META-INF/security.drl</value>
       </drools:rule-files>
   </drools:rule-base>    
   
</components>

RuleBaseが設定できましたら、セキュリティルールの記述に進みましょう。

12.6.3. セキュリティルールファイルの作成

まづ、アプリケーションの/META-INFの下にsecurity.drlというファイルを作成してください。 実際には、このファイル名はsecurity.drlである必要は無く、components.xmlの記述と一致していれば、何でもかまいません。

次に設定ファイルの内容について。この時点では、JBoss Ruleのドキュメントから拝借してくるのが手っ取り早いでしょうが、ここでは、下の様な最も単純な例から始めてみましょう。

package MyApplicationPermissions;

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

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

このサンプルについて一行づつ見てゆきましょう。 まず、パッケージ宣言。JBossRuleのパッケージはルールの集まりで、ルールベース以外の所には関係してきませんので、どのようなパッケージ名であってもかまいません。

次に、PermissionCheckとRoleクラスのインポートで、これらは、ルールエンジンに対して、ルールの中でこれらのクラスを参照することを表わしています。

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

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

ルールのLHS部分を見てみると、二つの条件が記述されています。 まづ、最初の部分について詳細に見てみましょう。

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

この条件は、ワーキングメモリ中でnameプロパティがcustomerであり、actionプロパティがdeleteであるPermissionCheckオブジェクトが存在していなければならない事を示しています。 ワーキングメモリとは、セッションスコープのオブジェクトで、ルールエンジンがパーミッションチェックをするために必要なコンテキスト情報を所有している物の事を言います。hasPermission()メソッドが呼ばれる度にPermissionCheckオブジェクト、(あるいはFact?)がワーキングメモリ中に保持されます。 このPermissionCheckはチェックされるパーミッションに完全に対応します。 即ち、haPermission("account", "create", null)が呼ばれると、パーミッションのチェックが終了するまでの間、nameがaccountでactionがcreateであるPermissionCheckオブジェクトがワーキングメモリに保持されます。

ワーキングメモリには、パーミッションチェックが実行される間保持されるPermissionCheck以外に、認証されたユーザが存在する間、存在する小さなオブジェクトがあります。これらには、認証プロセスで生成されたjava.security.Principalや、当該ユーザが属するロールについてのorg.jboss.seam.security.Role(複数)があります。 また、パラメータとしてIdentity.instance().getSecurityContext().assertObject()にオブジェクトを渡すことにより、他のオブジェクトをワーキングメモリ中に保持することもできます。

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

Role(name == "admin")

これは、単純にワーキングメモリ中のRoleオブジェクトにadminという名前のnameがなければならない事を示しています。 先に述べたように、ユーザのロールはワーキングメモリ中に保持されています。 従って、これらの条件を合わせると「貴方が、adminロールのメンバーで、且つcustomer:deleteについての許可を持っているかチェックします」と言っていることになります。

そして、ルールが適用された結果を、ルールのRHSの部分に見てゆきましょう。

c.grant()

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

12.6.3.1. ワイルドカードのパーミッションチェック

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

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

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

12.7. SSLによるセキュリティ

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

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

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

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

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

全てのページに対してschemaをデフォルトとして設定することも可能です。これは、若干のページにだけHTTPSを設定したい場合等には、特に重要です。 もし、デフォルトの設定がされていないと、現在のschemaが以後継承されてゆくために、必要のないページまでがHTTPSプロトコルを使うようになるからです。 この様な問題を忌避するために、デフォールトのscehmaをワイルドカードを使って、"*"として設定しておくことをお勧めします。

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

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

12.8. キャプチャテストの実装

厳密にはセキュリティAPIの一部ではありませんが、アプリケーションを自動ロボットとの不必要なインターアクションから守るために、キャプチャ(Completely Automated Public Turing test to tell Computers and Humans Apart)を実装することは、新規ユーザ登録や、公開ブログ、フォーラム等の場合には意味のあることです。 Seamは非常に優れた、キャプチャチャレンジを生成するライブラリーJCaptchaとの統合を実現しています。 キャプチャ機能を利用する場合には、Seamのライブラリのディレクトリから、jcaptcha-*.jarをアプリケーションにコピーして、application.xmlにJava モジュールとして登録してください。

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

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

<servlet>
    <servlet-name>Seam Resource Servlet</servlet-name>
    <servlet-class>org.jboss.seam.servlet.ResourceServlet</servlet-class>
</servlet>
    
<servlet-mapping>
    <servlet-name>Seam Resource Servlet</servlet-name>
    <url-pattern>/seam/resource/*</url-pattern>
</servlet-mapping>

12.8.2. ページにキャプチャを追加する

ページにキャプチャチャレンジを追加するのはいたって簡単です。 Seamはcaptchaというページスコープのコンポーネントを提供しており、キャプチャのバリデーションを含むキャプチャ処理に必要な全ての機能を提供しています。

<div>
    <h:graphicImage value="/seam/resource/captcha?#{captcha.id}"/>
</div>
  
<div>
    <h:outputLabel for="verifyCaptcha">Enter the above letters</h:outputLabel>
    <h:inputText id="verifyCaptcha" value="#{captcha.response}" required="true"/>
    <div class="validationError"><h:message for="verifyCaptcha"/></div>
</div>

上の様に設定することにより、graphicImageがキャプチャチャレンジの表示をコントロールし、inputTextがユーザからの回答を受け取り、フォームが送信された時に自動的にキャプチャと照合されます。