16.2. カスタムモジュール

EAP セキュリティーフレームワークでバンドルされたログインモジュールがセキュリティー環境で動作しない場合、独自のカスタムログインモジュール実装を記述できます。AuthenticationManagerSubject プリンシパルセットの特定の使用パターンを必要とします。AuthenticationManager と動作するログインモジュールを書くには、JAAS サブジェクトクラスの情報ストレージ機能と、これらの機能の想定される使用方法を理解する必要があります。
本項ではこの要件を検証し、カスタムログインモジュールの実装に役立つ 2 つの抽象ベースの LoginModule 実装を紹介します。
以下のメソッドを使用すると Subject に関連するセキュリティー情報を取得できます。
java.util.Set getPrincipals()
java.util.Set getPrincipals(java.lang.Class c)
java.util.Set getPrivateCredentials()
java.util.Set getPrivateCredentials(java.lang.Class c)
java.util.Set getPublicCredentials()
java.util.Set getPublicCredentials(java.lang.Class c)
Subject アイデンティティーおよびロールに対し、EAP は getPrincipals() および getPrincipals(java.lang.Class) から取得したプリンシパルセットを選択します。使用パターンは次のとおりです。
  • ユーザーアイデンティティー (例: ユーザー名、従業員 ID など) は java.security.Principal オブジェクトとして SubjectPrincipals セットに保存されます。ユーザーアイデンティティーを示す Principal 実装は、プリンシパルの名前に基づいた比較および等価が必要になります。適切な実装は org.jboss.security.SimplePrincipal クラスとして使用可能です。必要な場合は、他の Principal インスタンスを SubjectPrincipals に追加できます。
  • 割り当てられたユーザーロールも Principals セットに保存され、java.security.acl.Group インスタンスを使用して名前付きロールセットにグループ化されます。Group インターフェースは java.security.Principal のサブインスタンスで、PrincipalGroup のコレクションを定義します。
  • 任意の数のロールセットを Subject に割り当てできます。
  • EAP セキュリティーフレームワークは、Roles および CallerPrincipal という名前の 2 つのロールセットを使用します。
    • Roles グループは、Subject が認証されたアプリケーションドメインで知られる名前付きロールの Principal のコレクションです。このロールセットは、現在の呼び出し側が名前付きアプリケーションドメインロールに属するかどうかを確認するために EJB が使用できる EJBContext.isCallerInRole(String) などのメソッドによって使用されます。メソッドパーミッションチェックを実行するセキュリティーインターセプターロジックもこのロールセットを使用します。
    • CallerPrincipal Group は、アプリケーションドメインのユーザーに割り当てられた単一の Principal アイデンティティーで構成されます。EJBContext.getCallerPrincipal() メソッドは CallerPrincipal を使用して、アプリケーションドメインが操作環境アイデンティティーからアプリケーションに適したユーザーアイデンティティーへマップできるようにします。SubjectCallerPrincipal Group がない場合、アプリケーションアイデンティティーは操作環境アイデンティティーと同じになります。

16.2.1. サブジェクト使用パターンのサポート

「カスタムモジュール」に記載されている Subject 使用パターンを正しく簡単に実装するため、適切に Subject を使用できるようにするテンプレートパターンを認証された Subject に追加するログインモジュールが EAP に含まれています。
AbstractServerLoginModule

2 つのログインモジュールでより汎用的なのが org.jboss.security.auth.spi.AbstractServerLoginModule クラスです。

これは、javax.security.auth.spi.LoginModule の実装を提供し、操作環境セキュリティーインフラストラクチャー固有の主要タスクに対して抽象メソッドを提供します。このクラスの主な詳細は、例16.20「AbstractServerLoginModule クラスの一部」を参照してください。JavaDoc のコメントでサブクラスの役割が説明されています。

重要

loginOk インスタンス変数が極めて重要になります。ログインに成功した場合はこれを true に設定する必要があります。ログインに失敗した場合は、ログインメソッドをオーバーライドするサブクラスによって false に設定する必要があります。この変数が適切に設定されないと、コミットメソッドは適切にサブジェクトを更新しません。
フェーズの結果でログを追跡すると、ログインモジュールを制御フラグとチェーンできるようになります。これらの制御フラグは、認証プロセスの一部としてログインモジュールの成功を必要としません。

例16.20 AbstractServerLoginModule クラスの一部

package org.jboss.security.auth.spi;
/**
 *  This class implements the common functionality required for a JAAS
 *  server-side LoginModule and implements the PicketBox standard
 *  Subject usage pattern of storing identities and roles. Subclass
 *  this module to create your own custom LoginModule and override the
 *  login(), getRoleSets(), and getIdentity() methods.
 */
public abstract class AbstractServerLoginModule
    implements javax.security.auth.spi.LoginModule
{
    protected Subject subject;
    protected CallbackHandler callbackHandler;
    protected Map sharedState;
    protected Map options;
    protected Logger log;

    /** Flag indicating if the shared credential should be used */
    protected boolean useFirstPass;
    /** 
     * Flag indicating if the login phase succeeded. Subclasses that
     * override the login method must set this to true on successful
     * completion of login
     */
    protected boolean loginOk;
                
    // ...
    /**
     * Initialize the login module. This stores the subject,
     * callbackHandler and sharedState and options for the login
     * session. Subclasses should override if they need to process
     * their own options. A call to super.initialize(...)  must be
     * made in the case of an override.
     *
     * <p>
     * The options are checked for the  <em>password-stacking</em> parameter.
     * If this is set to "useFirstPass", the login identity will be taken from the
     * <code>javax.security.auth.login.name</code> value of the sharedState map,
     * and the proof of identity from the
     * <code>javax.security.auth.login.password</code> value of the sharedState map.
     *
     * @param subject the Subject to update after a successful login.
     * @param callbackHandler the CallbackHandler that will be used to obtain the
     * the user identity and credentials.
     * @param sharedState a Map shared between all configured login module instances
     * @param options the parameters passed to the login module.
     */
    public void initialize(Subject subject,
                           CallbackHandler callbackHandler,
                           Map sharedState,
                           Map options)
    {
        // ...
    }
    

    /**
     *  Looks for javax.security.auth.login.name and
     *  javax.security.auth.login.password values in the sharedState
     *  map if the useFirstPass option was true and returns true if
     *  they exist. If they do not or are null this method returns
     *  false.  
     *  Note that subclasses that override the login method
     *  must set the loginOk var to true if the login succeeds in
     *  order for the commit phase to populate the Subject. This
     *  implementation sets loginOk to true if the login() method
     *  returns true, otherwise, it sets loginOk to false.
     */
    public boolean login() 
        throws LoginException
    {
        // ...
    }
    
    /**
     *  Overridden by subclasses to return the Principal that
     *  corresponds to the user primary identity.
     */
    abstract protected Principal getIdentity();
                
    /**
     *  Overridden by subclasses to return the Groups that correspond
     *  to the role sets assigned to the user. Subclasses should
     *  create at least a Group named "Roles" that contains the roles
     *  assigned to the user.  A second common group is
     *  "CallerPrincipal," which provides the application identity of
     *  the user rather than the security domain identity.
     * 
     *  @return Group[] containing the sets of roles
     */
    abstract protected Group[] getRoleSets() throws LoginException;
}
UsernamePasswordLoginModule

カスタムログインモジュールに適している 2 つ目の抽象ベースログインモジュールは org.jboss.security.auth.spi.UsernamePasswordLoginModule です。

このログインモジュールは、文字別ベースのユーザー名をユーザーアイデンティティーとし、char[] パスワードを認証クレデンシャルとすることで、カスタムログインモジュールの実装をさらに簡素化します。また、このログインモジュールは、匿名ユーザー (null のユーザー名とパスワードによって示される) をロールを持たないプリンシパルへマップすることをサポートします。クラスの主な詳細は以下を参照してください。JavaDoc のコメントでサブクラスの役割が説明されています。

例16.21 UsernamePasswordLoginModule クラスの一部

package org.jboss.security.auth.spi;

/**
 *  An abstract subclass of AbstractServerLoginModule that imposes a
 *  an identity == String username, credentials == String password
 *  view on the login process. Subclasses override the
 *  getUsersPassword() and getUsersRoles() methods to return the
 *  expected password and roles for the user.
 */
public abstract class UsernamePasswordLoginModule
    extends AbstractServerLoginModule
{
    /** The login identity */
    private Principal identity;
    /** The proof of login identity */
    private char[] credential;
    /** The principal to use when a null username and password are seen */
    private Principal unauthenticatedIdentity;

    /**
     * The message digest algorithm used to hash passwords. If null then
     * plain passwords will be used. */
    private String hashAlgorithm = null;

    /**
     *  The name of the charset/encoding to use when converting the
     * password String to a byte array. Default is the platform's
     * default encoding.
     */
     private String hashCharset = null;

    /** The string encoding format to use. Defaults to base64. */
    private String hashEncoding = null;
                
    // ...
                
    /** 
     *  Override the superclass method to look for an
     *  unauthenticatedIdentity property. This method first invokes
     *  the super version.
     *
     *  @param options,
     *  @option unauthenticatedIdentity: the name of the principal to
     *  assign and authenticate when a null username and password are
     *  seen.
     */
    public void initialize(Subject subject,
                           CallbackHandler callbackHandler,
                           Map sharedState,
                           Map options)
    {
        super.initialize(subject, callbackHandler, sharedState,
                         options);
        // Check for unauthenticatedIdentity option.
        Object option = options.get("unauthenticatedIdentity");
        String name = (String) option;
        if (name != null) {
            unauthenticatedIdentity = new SimplePrincipal(name);
        }
    }
                
    // ...
                
    /**
     *  A hook that allows subclasses to change the validation of the
     *  input password against the expected password. This version
     *  checks that neither inputPassword or expectedPassword are null
     *  and that inputPassword.equals(expectedPassword) is true;
     *
     *  @return true if the inputPassword is valid, false otherwise.
     */
    protected boolean validatePassword(String inputPassword,
                                       String expectedPassword)
    {
        if (inputPassword == null || expectedPassword == null) {
            return false;
        }
        return inputPassword.equals(expectedPassword);
    }
    
    /**
     *  Get the expected password for the current username available
     * via the getUsername() method. This is called from within the
     * login() method after the CallbackHandler has returned the
     * username and candidate password.
     *
     * @return the valid password String
     */
    abstract protected String getUsersPassword()
        throws LoginException;
}
ログインモジュールのサブクラス化

文字別ベースのユーザー名とクレデンシャルが、作成中のカスタムログインモジュールの認証技術で使用できるかどうかを基に AbstractServerLoginModuleUsernamePasswordLoginModule のどちらをサブクラス化するかを決定します。文字別ベースのセマンティックが有効な場合は UsernamePasswordLoginModule をサブクラス化し、その他の場合は AbstractServerLoginModule をサブクラスします。

サブクラス化の手順

カスタムログインモジュールが実行する手順は、選択するベースログインモジュールクラスによって異なります。セキュリティーインフラストラクチャーと統合するカスタムログインモジュールを作成する場合は、EAP セキュリティーマネージャーが想定する形式の認証された Principal 情報がログインモジュールによって提供されるようにするため、最初に AbstractServerLoginModule または UsernamePasswordLoginModule をサブクラス化します。

AbstractServerLoginModule をサブクラス化する場合は、以下をオーバーライドする必要があります。
  • void initialize(Subject, CallbackHandler, Map, Map): 解析するカスタムオプションがある場合。
  • boolean login(): 認証を行うため。ログインに成功した場合は必ず loginOk インスタンス変数を true に設定します。失敗した場合は false に設定します。
  • Principal getIdentity(): log() 手順によって認証されたユーザーの Principal オブジェクトを返します。
  • Group[] getRoleSets(): 最低でも、login() の間に認証された Principal へ割り当てられたロールが含まれる Roles という名前の Group を返します。次に一般的な Group の名前は CallerPrincipal で、セキュリティードメインアイデンティティーではなくユーザーのアプリケーションアイデンティティーを提供します。
UsernamePasswordLoginModule をサブクラス化する場合は、以下をオーバーライドする必要があります。
  • void initialize(Subject, CallbackHandler, Map, Map): 解析するカスタムオプションがある場合。
  • Group[] getRoleSets(): 最低でも、login() の間に認証された Principal へ割り当てられたロールが含まれる Roles という名前の Group を返します。次に一般的な Group の名前は CallerPrincipal で、セキュリティードメインアイデンティティーではなくユーザーのアプリケーションアイデンティティーを提供します。
  • String getUsersPassword(): getUsername() メソッドより使用可能な現在のユーザー名の想定されるパスワードを返します。callbackhandler がユーザー名と候補のパスワードを返した後、getUsersPassword() メソッドは login() 内から呼び出されます。