クライアント認証プログラミング

認証のために WebSphere® eXtreme Scale は、クライアントからサーバー・サイドに資格情報を送信するランタイムを提供し、次にオーセンティケーター・プラグインを呼び出してユーザーを認証します。

WebSphere eXtreme Scale のユーザーは、 認証を実行するために以下のプラグインを実装する必要があります。

Credential および CredentialGenerator プラグイン

eXtreme Scale クライアント は、認証を必要とするサーバーに接続するときにはクライアント資格情報を提示する 必要があります。 クライアントの資格情報は、com.ibm.websphere.objectgrid.security.plugins.Credential インターフェースによって表されます。 クライアント資格情報には、ユーザー名とパスワードのペア、Kerberos チケット、クライアント証明書、またはクライアントとサーバーが同意する任意の形式でのデータがあります。 このインターフェースでは、equals(Object) メソッドおよび hashCode メソッドを定義します。 Credential オブジェクトをサーバー・サイドの鍵として使用することによって認証済み Subject オブジェクトがキャッシュされるため、この 2 つのメソッドは重要です。さらに、WebSphere eXtreme Scale は資格情報を生成するプラグインを提供します。 このプラグインは、com.ibm.websphere.objectgrid.security.plugins.CredentialGenerator インターフェースによって示され、資格情報に期限がある場合に役立ちます。 この場合は、getCredential メソッドが呼び出されて資格情報が更新されます。

Credential インターフェースでは、equals(Object) メソッドおよび hashCode メソッドを明示的に定義します。 Credential オブジェクトをサーバー・サイドの鍵として使用することによって認証済み Subject オブジェクトがキャッシュされるため、この 2 つのメソッドは重要です。

また、資格情報を生成するために提供されたプラグインも使用することができます。このプラグインは、com.ibm.websphere.objectgrid.security.plugins.CredentialGenerator インターフェースによって示され、資格情報に期限がある場合に役立ちます。 この場合は、getCredential メソッドが呼び出されて資格情報が更新されます。 詳しくは、CredentialGenerator インターフェースを参照してください。

資格情報インターフェース用として次の 3 つのデフォルトの実装が提供されています。

  • com.ibm.websphere.objectgrid.security.plugins.builtins.UserPasswordCredential 実装 は、ユーザー ID とパスワードのペアを含みます。
  • com.ibm.websphere.objectgrid.security.plugins.builtins.WSTokenCredential 実装 は、WebSphere Application Server 固有の認証および許可トークンを含みます。 これらのトークンを使用すると、同じセキュリティー・ドメイン内の アプリケーション・サーバーにセキュリティー属性を伝搬することができます。

さらに、WebSphere eXtreme Scale は資格情報を生成するプラグインを提供します。 このプラグインは、com.ibm.websphere.objectgrid.security.plugins.CredentialGenerator インターフェースによって表されます。WebSphere eXtreme Scale は、次に示す 2 つのデフォルト組み込み実装を提供します。

  • com.ibm.websphere.objectgrid.security.plugins.builtins.UserPasswordCredentialGenerator コンストラクターは、ユーザー ID およびパスワードを取ります。getCredential メソッドは、呼び出されると、 ユーザー ID およびパスワードが含まれている UserPasswordCredential オブジェクトを返します。
  • com.ibm.websphere.objectgrid.security.plugins.builtins.WSTokenCredentialGenerator は、WebSphere Application Server で実行中の資格情報 (セキュリティー・トークン) 生成プログラムを表します。getCredential メソッドを呼び出すと、 現在のスレッドに関連した Subject が取得されます。その後、この Subject オブジェクトのセキュリティー情報が WSTokenCredential オブジェクトに変換されます。定数 WSTokenCredentialGenerator.RUN_AS_SUBJECT または WSTokenCredentialGenerator.CALLER_SUBJECT を使用して、スレッドから runAs サブジェクトか呼び出し元サブジェクトのいずれを検索するかを指定できます。

UserPasswordCredential および UserPasswordCredentialGenerator

テストの目的で、WebSphere eXtreme Scale は以下のプラグイン実装を提供します。

  1. com.ibm.websphere.objectgrid.security.plugins.builtins.UserPasswordCredential
  2. com.ibm.websphere.objectgrid.security.plugins.builtins.UserPasswordCredentialGenerator

ユーザー・パスワードの資格情報では、ユーザー ID とパスワードを保管します。 次にユーザー・パスワードの資格情報生成プログラムは、このユーザー ID とパスワードを収容します。

これら 2 つのプラグインを実装する方法を、以下のコード例で示します。

UserPasswordCredential.java
// This sample program is provided AS IS and may be used, executed, copied and modified
// without royalty payment by customer
// (a) for its own instruction and study,
// (b) in order to develop applications designed to run with an IBM WebSphere product,
// either for customer's own internal use or for redistribution by customer, as part of such an
// application, in customer's own products.
// Licensed Materials - Property of IBM
// 5724-J34 © COPYRIGHT International Business Machines Corp. 2007
package com.ibm.websphere.objectgrid.security.plugins.builtins;

import com.ibm.websphere.objectgrid.security.plugins.Credential;

/**
 * This class represents a credential containing a user ID and password.
 *
 * @ibm-api
 * @since WAS XD 6.0.1
 *
 * @see Credential
 * @see UserPasswordCredentialGenerator#getCredential()
 */
public class UserPasswordCredential implements Credential {

    private static final long serialVersionUID = 1409044825541007228L;

    private String ivUserName;

    private String ivPassword;

    /**
     * Creates a UserPasswordCredential with the specified user name and
     * password.
     *
     * @param userName the user name for this credential
     * @param password the password for this credential
     *
     * @throws IllegalArgumentException if userName or password is <code>null</code>
     */
    public UserPasswordCredential(String userName, String password) {
        super();
        if (userName == null || password == null) {
            throw new IllegalArgumentException("User name and password cannot be null.");
        }
        this.ivUserName = userName;
        this.ivPassword = password;
    }

    /**
     * Gets the user name for this credential.
     *
     * @return   the user name argument that was passed to the constructor
     *           or the <code>setUserName(String)</code>
     *           method of this class
     *
     * @see #setUserName(String)
     */
    public String getUserName() {
        return ivUserName;
    }

    /**
     * Sets the user name for this credential.
     *
     * @param userName the user name to set.
     *
     * @throws IllegalArgumentException if userName is <code>null</code>
     */
    public void setUserName(String userName) {
        if (userName == null) {
            throw new IllegalArgumentException("User name cannot be null.");
        }
        this.ivUserName = userName;
    }

    /**
     * Gets the password for this credential.
     *
     * @return   the password argument that was passed to the constructor
     *           or the <code>setPassword(String)</code>
     *           method of this class
     *
     * @see #setPassword(String)
     */
    public String getPassword() {
        return ivPassword;
    }

    /**
     * Sets the password for this credential.
     *
     * @param password the password to set.
     *
     * @throws IllegalArgumentException if password is <code>null</code>
     */
    public void setPassword(String password) {
        if (password == null) {
            throw new IllegalArgumentException("Password cannot be null.");
        }
        this.ivPassword = password;
    }

    /**
     * Checks two UserPasswordCredential objects for equality.
     * <p>
     * Two UserPasswordCredential objects are equal if and only if their user names
     * and passwords are equal.
     *
     * @param o the object we are testing for equality with this object.
     *
     * @return <code>true</code> if both UserPasswordCredential objects are equivalent.
     *
     * @see Credential#equals(Object)
     */
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o instanceof UserPasswordCredential) {
            UserPasswordCredential other = (UserPasswordCredential) o;
            return other.ivPassword.equals(ivPassword) && other.ivUserName.equals(ivUserName);
        }

        return false;
    }

    /**
     * Returns the hashcode of the UserPasswordCredential object.
     *
     * @return the hash code of this object
     *
     * @see Credential#hashCode()
     */
    public int hashCode() {
        return ivUserName.hashCode() + ivPassword.hashCode();
    }
}
UserPasswordCredentialGenerator.java
// This sample program is provided AS IS and may be used, executed, copied and modified
// without royalty payment by customer
// (a) for its own instruction and study,
// (b) in order to develop applications designed to run with an IBM WebSphere product,
// either for customer's own internal use or for redistribution by customer, as part of such an
// application, in customer's own products.
// Licensed Materials - Property of IBM
// 5724-J34 © COPYRIGHT International Business Machines Corp. 2007
package com.ibm.websphere.objectgrid.security.plugins.builtins;

import java.util.StringTokenizer;

import com.ibm.websphere.objectgrid.security.plugins.Credential;
import com.ibm.websphere.objectgrid.security.plugins.CredentialGenerator;

/**
 * This credential generator creates <code>UserPasswordCredential</code> objects.
 * <p>
 * UserPasswordCredentialGenerator has a one to one relationship with
 * UserPasswordCredential because it can only create a UserPasswordCredential
 * representing one identity.
 *
 * @since WAS XD 6.0.1
 * @ibm-api
 *
 * @see CredentialGenerator
 * @see UserPasswordCredential
 */
public class UserPasswordCredentialGenerator implements CredentialGenerator {

    private String ivUser;

    private String ivPwd;

    /**
     * Creates a UserPasswordCredentialGenerator with no user name or password.
     *
     * @see #setProperties(String)
     */
    public UserPasswordCredentialGenerator() {
        super();
    }

    /**
     * Creates a UserPasswordCredentialGenerator with a specified user name and
     * password
     *
     * @param user the user name
     * @param pwd the password
     */
    public UserPasswordCredentialGenerator(String user, String pwd) {
        ivUser = user;
        ivPwd = pwd;
    }

    /**
     * Creates a new <code>UserPasswordCredential</code> object using this
     * object's user name and password.
     *
     * @return a new <code>UserPasswordCredential</code> instance
     *
     * @see CredentialGenerator#getCredential()
     * @see UserPasswordCredential
     */
    public Credential getCredential() {
        return new UserPasswordCredential(ivUser, ivPwd);
    }

    /**
     * Gets the password for this credential generator.
     *
     * @return   the password argument that was passed to the constructor
     */
    public String getPassword() {
        return ivPwd;
    }

    /**
     * Gets the user name for this credential.
     *
     * @return   the user argument that was passed to the constructor
     *           of this class
     */
    public String getUserName() {
        return ivUser;
    }
    /**
     * Sets additional properties namely a user name and password.
     *
     * @param properties a properties string  with a user name and
     *                   a password separated by a blank.
     *
     * @throws IllegalArgumentException if the format is not valid
     */
    public void setProperties(String properties) {
        StringTokenizer token = new StringTokenizer(properties, " ");
        if (token.countTokens() != 2) {
            throw new IllegalArgumentException(
                "The properties should have a user name and password and separated by a blank.");
        }

        ivUser = token.nextToken();
        ivPwd = token.nextToken();
    }
    /**
     * Checks two UserPasswordCredentialGenerator objects for equality.
     * <p>
     * Two UserPasswordCredentialGenerator objects are equal if and only if
     * their user names and passwords are equal.
     *
     * @param obj the object we are testing for equality with this object.
     *
     * @return <code>true</code> if both UserPasswordCredentialGenerator objects
     *         are equivalent.
     */
    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        }

        if (obj != null && obj instanceof UserPasswordCredentialGenerator) {
            UserPasswordCredentialGenerator other = (UserPasswordCredentialGenerator) obj;

            boolean bothUserNull = false;
            boolean bothPwdNull = false;

            if (ivUser == null) {
                if (other.ivUser == null) {
                    bothUserNull = true;
                } else {
                    return false;
                }
            }

            if (ivPwd == null) {
                if (other.ivPwd == null) {
                    bothPwdNull = true;
                } else {
                    return false;
                }
            }

            return (bothUserNull || ivUser.equals(other.ivUser)) && (bothPwdNull || ivPwd.equals(other.ivPwd));
        }

        return false;
    }

    /**
     * Returns the hashcode of the UserPasswordCredentialGenerator object.
     *
     * @return the hash code of this object
     */
    public int hashCode() {

        return ivUser.hashCode() + ivPwd.hashCode();
    }

}

UserPasswordCredential クラスには、2 つの属性、ユーザー名およびパスワードが含まれています。 UserPasswordCredentialGenerator は、UserPasswordCredential オブジェクトが含まれるファクトリーとしてサービス提供します。

WSTokenCredential および WSTokenCredentialGenerator

WebSphere eXtreme Scale クライアント およびサーバーがすべて WebSphere Application Server に デプロイされている場合、クライアント・アプリケーションは、以下の条件が満たされている場合は、これら 2 つの組み込み実装を使用 することができます。

  1. WebSphere Application Server グローバル・セキュリティーがオンになっている。
  2. すべての WebSphere eXtreme Scale クライアントおよびサーバーが WebSphere Application Server Java 仮想マシンで実行されている。
  3. アプリケーション・サーバーが、同じセキュリティー・ドメインにある。
  4. クライアントが WebSphere Application Server で既に認証されている。

この場合、クライアントはcom.ibm.websphere.objectgrid.security.plugins.builtins.WSTokenCredentialGenerator クラスを使用して、資格情報を生成できます。サーバーでは、WSAuthenticator 実装クラス を使用して、資格情報を認証します。

このシナリオは、eXtreme Scale クライアントが 既に認証済みであるという事実を利用します。サーバーがあるアプリケーション・サーバーが、クライアントを格納するアプリケーション・サーバーと同じセキュリティー・ドメインにあるため、クライアントからサーバーにセキュリティー・トークンを伝搬することができます。これにより、同じユーザー・レジストリーを再認証する必要がなくなります。

注: CredentialGenerator が常に同じ資格情報を生成するわけではありません。 有効期限があるリフレッシュ可能な資格情報の場合、CredentialGenerator は、認証が確実に成功するようにするため、最新の有効な資格情報を生成できなければ なりません。Credential オブジェクトとして Kerberos チケットを 使用することが、1 つの例です。Kerberos チケットがリフレッシュされると、CredentialGenerator は、 CredentialGenerator.getCredential が呼び出されたときに、 リフレッシュ後のチケットを取得しなければなりません。

Authenticator プラグイン

eXtreme Scale クライアントが CredentialGenerator オブジェクトを使用して Credential オブジェクトを取得すると、このクライアント Credential オブジェクトがクライアント要求とともに eXtreme Scale サーバーに送信されます。 サーバーは、要求の処理前に Credential オブジェクトの認証を行います。 Credential オブジェクトが正常に認証されると、このクライアントを表す Subject オブジェクトが戻されます。

そうすると、この Subject オブジェクトはキャッシュされますが、存続時間がセッション・タイムアウト値に達すると有効期限が切れます。 ログイン・セッション・タイムアウト値は、クラスター XML ファイル内にある loginSessionExpirationTime プロパティーを使用して設定できます。 例えば、loginSessionExpirationTime="300" と設定すると、Subject オブジェクトの有効期限は 300 秒で切れます。

この Subject オブジェクトは、後で示すように、要求の認可に使用されます。eXtreme Scale サーバーは、Authenticator プラグインを使用して、Credential オブジェクトの認証を行います。 詳しくは、オーセンティケーターを参照してください。

Authenticator プラグインは、eXtreme Scale ランタイムがクライアント・ユーザー・レジストリー (例えば、Lightweight Directory Access Protocol (LDAP) サーバー) からの Credential オブジェクトを認証する所です。

WebSphere eXtreme Scale は即時に使用可能なユーザー・レジストリー構成を提供するわけではありません。 ユーザー・レジストリーの構成と管理は、単純化と柔軟性のため、WebSphere eXtreme Scale の外部に 残されています。このプラグインはユーザー・レジストリーへの接続と認証時に実装されます。 例えば、Authenticator の実装では、 資格情報からユーザー ID とパスワードを抽出し、その情報を使用して LDAP サーバーに接続し、検証します。 この認証の結果として、Subject オブジェクトが作成されます。 この実装で、JAAS ログイン・モジュールを使用する可能性があります。 認証の結果として、Subject オブジェクトが戻されます。

このメソッドでは、2 つの例外 InvalidCredentialException および ExpiredCredentialException が作成されることに注意してください。InvalidCredentialException 例外は、資格情報が無効であることを示します。ExpiredCredentialException 例外は、資格情報の期限が切れていることを示します。認証メソッドの結果としてこの 2 つの例外のいずれかが発生した場合、例外はクライアントに送り返されます。ただし、クライアント・ランタイムによって、この 2 つの例外は別々に処理されます。

  • エラーが InvalidCredentialException 例外である場合は、クライアント・ランタイムにこの例外が表示されます。 ご使用のアプリケーションで例外を処理する必要があります。CredentialGenerator を修正するなどして、操作を再試行します。
  • エラーが ExpiredCredentialException 例外であり、再試行数 0 以外の場合は、 クライアント・ランタイムによって、CredentialGenerator.getCredential メソッドが再度呼び出され、新しい Credential オブジェクトがサーバーに送信されます。 新しい資格情報認証が成功すると、サーバーは要求を処理します。 新しい資格情報認証が失敗すると、クライアントに例外が送り返されます。 認証の再試行回数が許可値に達しても、クライアントがまだ ExpiredCredentialException 例外 を受け取る場合は、ExpiredCredentialException 例外となります。ご使用のアプリケーションでエラーを処理する必要があります。

Authenticator インターフェースは、柔軟性に優れています。Authenticator インターフェースは、独自の方法で実装することができます。例えば、2 種類のユーザー・レジストリーをサポートするように、このインターフェースを実装することもできます。

WebSphere eXtreme Scale には、 サンプルのオーセンティケーター・プラグイン実装があります。WebSphere Application Server オーセンティケーター・プラグイン の場合を除いて、他の実装はテスト目的のサンプルに過ぎません。

KeyStoreLoginAuthenticator

この例では、テストとサンプルを目的とする eXtreme Scale 組み込み実装である KeyStoreLoginAuthenticator を使用しています (鍵ストアは単純なユーザー・レジストリーであり、実動環境には使用しないようにしてください)。 このクラスは、オーセンティケーターの実装方法の説明が目的で表示されていることに注意してください。

KeyStoreLoginAuthenticator.java
// This sample program is provided AS IS and may be used, executed, copied and modified
// without royalty payment by customer
// (a) for its own instruction and study,
// (b) in order to develop applications designed to run with an IBM WebSphere product,
// either for customer's own internal use or for redistribution by customer, as part of such an
// application, in customer's own products.
// Licensed Materials - Property of IBM
// 5724-J34 © COPYRIGHT International Business Machines Corp. 2007

package com.ibm.websphere.objectgrid.security.plugins.builtins;

import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;

import com.ibm.websphere.objectgrid.security.plugins.Authenticator;
import com.ibm.websphere.objectgrid.security.plugins.Credential;
import com.ibm.websphere.objectgrid.security.plugins.ExpiredCredentialException;
import com.ibm.websphere.objectgrid.security.plugins.InvalidCredentialException;
import com.ibm.ws.objectgrid.Constants;
import com.ibm.ws.objectgrid.ObjectGridManagerImpl;
import com.ibm.ws.objectgrid.security.auth.callback.UserPasswordCallbackHandlerImpl;

/**
 * This class is an implementation of the <code>Authenticator</code> interface
 * when a user name and password are used as a credential.
 * <p>
 * When user ID and password authentication is used, the credential passed to the
 * <code>authenticate(Credential)</code> method is a UserPasswordCredential object.
 * <p>
 * This implementation will use a <code>KeyStoreLoginModule</code> to authenticate
 * the user into the key store using the JAAS login module "KeyStoreLogin". The key
 * store can be configured as an option to the <code>KeyStoreLoginModule</code>
 * class. Please see the <code>KeyStoreLoginModule</code> class for more details
 * about how to set up the JAAS login configuration file.
 * <p>
 * This class is only for sample and quick testing purpose. Users should
 * write your own Authenticator implementation which can fit better into
 * the environment.
 *
 * @ibm-api
 * @since WAS XD 6.0.1
 *
 * @see Authenticator
 * @see KeyStoreLoginModule
 * @see UserPasswordCredential
 */
public class KeyStoreLoginAuthenticator implements Authenticator {

    /**
     * Creates a new KeyStoreLoginAuthenticator.
     */
    public KeyStoreLoginAuthenticator() {
        super();
    }

    /**
     * Authenticates a <code>UserPasswordCredential</code>.
     * <p>
     * Uses the user name and password from the specified UserPasswordCredential
     * to login to the KeyStoreLoginModule named "KeyStoreLogin".
     *
     * @throws InvalidCredentialException if credential isn't a
     *         UserPasswordCredential or some error occurs during processing
     *         of the supplied UserPasswordCredential
     *
     * @throws ExpiredCredentialException if credential is expired.  This exception
     *         is not used by this implementation
     *
     * @see Authenticator#authenticate(Credential)
     * @see KeyStoreLoginModule
     */
    public Subject authenticate(Credential credential) throws InvalidCredentialException, 
			ExpiredCredentialException {

        if (credential == null) {
            throw new InvalidCredentialException("Supplied credential is null");
        }

        if (! (credential instanceof UserPasswordCredential) ) {
            throw new InvalidCredentialException("Supplied credential is not a UserPasswordCredential");
        }

        UserPasswordCredential cred = (UserPasswordCredential) credential;
        LoginContext lc = null;
        try {
            lc = new LoginContext("KeyStoreLogin",
                    new UserPasswordCallbackHandlerImpl(cred.getUserName(), cred.getPassword().toCharArray()));

            lc.login();

            Subject subject = lc.getSubject();

            return subject;
        }
        catch (LoginException le) {
            throw new InvalidCredentialException(le);
        }
        catch (IllegalArgumentException ile) {
            throw new InvalidCredentialException(ile);
        }
    }
}
KeyStoreLoginModule.java
// This sample program is provided AS IS and may be used, executed, copied and modified
// without royalty payment by customer
// (a) for its own instruction and study,
// (b) in order to develop applications designed to run with an IBM WebSphere product,
// either for customer's own internal use or for redistribution by customer, as part of such an
// application, in customer's own products.
// Licensed Materials - Property of IBM
// 5724-J34 © COPYRIGHT International Business Machines Corp. 2007
package com.ibm.websphere.objectgrid.security.plugins.builtins;

import java.io.File;
import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
import javax.security.auth.x500.X500Principal;
import javax.security.auth.x500.X500PrivateCredential;

import com.ibm.websphere.objectgrid.ObjectGridRuntimeException;
import com.ibm.ws.objectgrid.Constants;
import com.ibm.ws.objectgrid.ObjectGridManagerImpl;
import com.ibm.ws.objectgrid.util.ObjectGridUtil;

/**
 * A KeyStoreLoginModule is keystore authentication login module based on 
 * JAAS authentication.
 * <p>
 * A login configuration should provide an option "<code>keyStoreFile</code>" to 
 * indicate where the keystore file is located. If the <code>keyStoreFile</code> 
 * value contains a system property in the form, <code>${system.property}</code>,
 * it will be expanded to the value of the system property.
 * <p>
 * If an option "<code>keyStoreFile</code>" is not provided, the default keystore
 * file name is <code>"${java.home}${/}.keystore"</code>.
 * <p>
 * Here is a Login module configuration example: 
 * <pre><code>
 *    KeyStoreLogin {
 *        com.ibm.websphere.objectgrid.security.plugins.builtins.KeystoreLoginModule required
 *            keyStoreFile="${user.dir}${/}security${/}.keystore";
 *    };
 * </code></pre>
 * 
 * @ibm-api
 * @since WAS XD 6.0.1  
 * 
 * @see LoginModule
 */
public class KeyStoreLoginModule implements LoginModule {

    private static final String CLASS_NAME = KeyStoreLoginModule.class.getName();

    /** 
     * Key store file property name 
     */
    public static final String KEY_STORE_FILE_PROPERTY_NAME = "keyStoreFile";

    /**
     * Key store type. Only JKS is supported 
     */
    public static final String KEYSTORE_TYPE = "JKS";

    /**
     * The default key store file name 
     */
    public static final String DEFAULT_KEY_STORE_FILE = "${java.home}${/}.keystore";

    private CallbackHandler handler;

    private Subject subject;

    private boolean debug = false;

    private Set principals = new HashSet();

    private Set publicCreds = new HashSet();

    private Set privateCreds = new HashSet();

    protected KeyStore keyStore;

    /**
     * Creates a new KeyStoreLoginModule.
     */
    public KeyStoreLoginModule() {
    }

    /**
     * Initializes the login module.
     * 
     * @see LoginModule#initialize(Subject, CallbackHandler, Map, Map)
     */
    public void initialize(Subject sub, CallbackHandler callbackHandler,
            Map mapSharedState, Map mapOptions) {

        // initialize any configured options
        debug = "true".equalsIgnoreCase((String) mapOptions.get("debug"));

        if (sub == null)
            throw new IllegalArgumentException("Subject is not specified"); 

        if (callbackHandler == null)
            throw new IllegalArgumentException(
            "CallbackHander is not specified"); 

        // Get the key store path
        String sKeyStorePath = (String) mapOptions
            .get(KEY_STORE_FILE_PROPERTY_NAME);

        // If there is no key store path, the default one is the .keystore
        // file in the java home directory
        if (sKeyStorePath == null) {
            sKeyStorePath = DEFAULT_KEY_STORE_FILE;
        }

        // Replace the system enviroment variable
        sKeyStorePath = ObjectGridUtil.replaceVar(sKeyStorePath);

        File fileKeyStore = new File(sKeyStorePath);

        try {
            KeyStore store = KeyStore.getInstance("JKS");
            store.load(new FileInputStream(fileKeyStore), null);

            // Save the key store
            keyStore = store;

            if (debug) {
                System.out.println("[KeyStoreLoginModule] initialize: Successfully loaded key store");
            }
        }
        catch (Exception e) {
            ObjectGridRuntimeException re = new ObjectGridRuntimeException(
                    "Failed to load keystore: " + fileKeyStore.getAbsolutePath()); 
            re.initCause(e);
            if (debug) {
                System.out.println("[KeyStoreLoginModule] initialize: Key store loading failed with exception "
                        + e.getMessage());
            }
        }

        this.subject = sub;
        this.handler = callbackHandler;
    }

    /**
     * Authenticates a user based on the keystore file.
     * 
     * @see LoginModule#login()
     */
    public boolean login() throws LoginException {

        if (debug) {
            System.out.println("[KeyStoreLoginModule] login: entry");
        }

        String name = null;
        char pwd[] = null;

        if (keyStore == null || subject == null || handler == null) {
            throw new LoginException("Module initialization failed"); 
        }

        NameCallback nameCallback = new NameCallback("Username:");
        PasswordCallback pwdCallback = new PasswordCallback("Password:", false);

        try {
            handler.handle(new Callback[] { nameCallback, pwdCallback });
        }
        catch (Exception e) {
            throw new LoginException("Callback failed: " + e); 
        }

        name = nameCallback.getName();
        char[] tempPwd = pwdCallback.getPassword();

        if (tempPwd == null) {
            // treat a NULL password as an empty password
            tempPwd = new char[0];
        }
        pwd = new char[tempPwd.length];
        System.arraycopy(tempPwd, 0, pwd, 0, tempPwd.length);

        pwdCallback.clearPassword();

        if (debug) {
            System.out.println("[KeyStoreLoginModule] login: "
                    + "user entered user name: " + name);
        }

        // Validate the user name and password
        try {
            validate(name, pwd);
        }
        catch (SecurityException se) {
            principals.clear();
            publicCreds.clear();
            privateCreds.clear();
            LoginException le = new LoginException(
            "Exception encountered during login");
            le.initCause(se);

            throw le;
        }

        if (debug) {
            System.out.println("[KeyStoreLoginModule] login: exit");
        }
        return true;
    }

    /**
     * Indicates the user is accepted. 
     * <p>
     * This method is called only if the user is authenticated by all modules in
     * the login configuration file. The principal objects will be added to the 
     * stored subject.
     * 
     * @return false if for some reason the principals cannot be added; true
     *         otherwise
     * 
     * @exception LoginException
     *                LoginException is thrown if the subject is readonly or if
     *                any unrecoverable exceptions is encountered.
     * 
     * @see LoginModule#commit()
     */
    public boolean commit() throws LoginException {
        if (debug) {
            System.out.println("[KeyStoreLoginModule] commit: entry");
        }

        if (principals.isEmpty()) {
            throw new IllegalStateException("Commit is called out of sequence");
        }

        if (subject.isReadOnly()) {
            throw new LoginException("Subject is Readonly");
        }

        subject.getPrincipals().addAll(principals);
        subject.getPublicCredentials().addAll(publicCreds);
        subject.getPrivateCredentials().addAll(privateCreds);

        principals.clear();
        publicCreds.clear();
        privateCreds.clear();

        if (debug) {
            System.out.println("[KeyStoreLoginModule] commit: exit");
        }
        return true;
    }

    /**
     * Indicates the user is not accepted
     * 
     * @see LoginModule#abort()
     */
    public boolean abort() throws LoginException {
        boolean b = logout();
        return b;
    }

    /**
     * Logs the user out. Clear all the maps.
     * 
     * @see LoginModule#logout()
     */
    public boolean logout() throws LoginException {


        // Clear the instance variables
        principals.clear();
        publicCreds.clear();
        privateCreds.clear();

        // clear maps in the subject
        if (!subject.isReadOnly()) {
            if (subject.getPrincipals() != null) {
                subject.getPrincipals().clear();
            }

            if (subject.getPublicCredentials() != null) {
                subject.getPublicCredentials().clear();
            }

            if (subject.getPrivateCredentials() != null) {
                subject.getPrivateCredentials().clear();
            }
        }
        return true;
    }

    /**
     * Validates the user name and password based on the keystore.
     * 
     * @param userName user name
     * @param password password
     * @throws SecurityException if any exceptions encountered
     */
    private void validate(String userName, char password[])
        throws SecurityException {

        PrivateKey privateKey = null;

        // Get the private key from the keystore
        try {
            privateKey = (PrivateKey) keyStore.getKey(userName, password);
        }
        catch (NoSuchAlgorithmException nsae) {
            SecurityException se = new SecurityException();
            se.initCause(nsae);
            throw se;
        }
        catch (KeyStoreException kse) {
            SecurityException se = new SecurityException();
            se.initCause(kse);
            throw se;
        }
        catch (UnrecoverableKeyException uke) {
            SecurityException se = new SecurityException();
            se.initCause(uke);
            throw se;
        }

        if (privateKey == null) {
            throw new SecurityException("Invalid name: " + userName); 
        }

        // Check the certificats
        Certificate certs[] = null;
        try {
            certs = keyStore.getCertificateChain(userName);
        }
        catch (KeyStoreException kse) {
            SecurityException se = new SecurityException();
            se.initCause(kse);
            throw se;
        }

        if (debug) {
            System.out.println("  Print out the certificates:");
            for (int i = 0; i < certs.length; i++) {
                System.out.println("  certificate " + i);
                System.out.println("    " + certs[i]);
            }
        }

        if (certs != null && certs.length > 0) {

            // If the first certificate is an X509Certificate
            if (certs[0] instanceof X509Certificate) {
                try {
                    // Get the first certificate which represents the user
                    X509Certificate certX509 = (X509Certificate) certs[0];

                    // Create a principal
                    X500Principal principal = new X500Principal(certX509
                            .getIssuerDN()
                            .getName());
                    principals.add(principal);

                    if (debug) {
                        System.out.println("  Principal added: " + principal);
                    }
                    // Create the certification path object and add it to the 
                    // public credential set
                    CertificateFactory factory = CertificateFactory
                        .getInstance("X.509");
                    java.security.cert.CertPath certPath = factory
                        .generateCertPath(Arrays.asList(certs));
                    publicCreds.add(certPath);

                    // Add the private credential to the private credential set
                    privateCreds.add(new X500PrivateCredential(certX509,
                            privateKey, userName));

                }
                catch (CertificateException ce) {
                    SecurityException se = new SecurityException();
                    se.initCause(ce);
                    throw se;
                }
            }
            else {
                // The first certificate is not an X509Certificate
                // We just add the certificate to the public credential set
                // and the private key to the private credential set.
                publicCreds.add(certs[0]);
                privateCreds.add(privateKey);
            }
        }
    }
}

LDAP オーセンティケーター・プラグインの使用

LDAP サーバーに対するユーザー名およびパスワード認証を処理するために、com.ibm.websphere.objectgrid.security.plugins.builtins.LDAPAuthenticator のデフォルトの実装が用意されています。この実装では、LDAPLogin ログイン・モジュールを使用して、ユーザーを Lightweight Directory Access Protocol (LDAP) サーバーにログインさせます。 以下のスニペットでは、認証メソッドの実装方法を説明しています。

/**
* @see com.ibm.ws.objectgrid.security.plugins.Authenticator#
* authenticate(LDAPLogin)
*/
public Subject authenticate(Credential credential) throws
InvalidCredentialException, ExpiredCredentialException {

    UserPasswordCredential cred = (UserPasswordCredential) credential;
    LoginContext lc = null;
    try {
        lc = new LoginContext("LDAPLogin",
            new UserPasswordCallbackHandlerImpl(cred.getUserName(),
            cred.getPassword().toCharArray()));

        lc.login();

        Subject subject = lc.getSubject();

        return subject;
    }
    catch (LoginException le) {
        throw new InvalidCredentialException(le);
    }
    catch (IllegalArgumentException ile) {
        throw new InvalidCredentialException(ile);
    }
}

また、この目的のため、eXtreme Scale には、ログイン・モジュール com.ibm.websphere.objectgrid.security.plugins.builtins.LDAPLoginModule が同梱されています。JAAS ログイン構成ファイルに以下の 2 つのオプションを指定する必要があります。

LDAPLoginModule モジュールは、com.ibm.websphere.objectgrid.security.plugins.builtins.LDAPAuthentcationHelper.authenticate メソッドを呼び出します。 以下のコード・スニペットは、LDAPAuthenticationHelper の認証メソッドを実装する方法を示しています。

/**
* Authenticate the user to the LDAP directory.
* @param user the user ID, e.g., uid=xxxxxx,c=us,ou=bluepages,o=ibm.com
* @param pwd the password
*
* @throws NamingException
*/
public String[] authenticate(String user, String pwd)
throws NamingException {
    Hashtable env = new Hashtable();
    env.put(Context.INITIAL_CONTEXT_FACTORY, factoryClass);
    env.put(Context.PROVIDER_URL, providerURL);
    env.put(Context.SECURITY_PRINCIPAL, user);
    env.put(Context.SECURITY_CREDENTIALS, pwd);
    env.put(Context.SECURITY_AUTHENTICATION, "simple");

    InitialContext initialContext = new InitialContext(env);

    // Look up for the user
    DirContext dirCtx = (DirContext) initialContext.lookup(user);

    String uid = null;
    int iComma = user.indexOf(",");
    int iEqual = user.indexOf("=");
    if (iComma > 0 && iComma > 0) {
        uid = user.substring(iEqual + 1, iComma);
    }
    else {
        uid = user;
    }

    Attributes attributes = dirCtx.getAttributes("");

    // Check the UID
    String thisUID = (String) (attributes.get(UID).get());

    String thisDept = (String) (attributes.get(HR_DEPT).get());

    if (thisUID.equals(uid)) {
        return new String[] { thisUID, thisDept };
    }
    else {
        return null;
    }
}

認証が成功した場合、ID とパスワードは有効であるとみなされます。次に、ログイン・モジュールは、 この認証メソッドから ID 情報および部門情報を取得します。 ログイン・モジュールでは、2 つのプリンシパル SimpleUserPrincipal および SimpleDeptPrincipal が作成されます。 認証済みサブジェクトを使用して、グループ許可 (ここでは、部門がグループです) および個々の許可を行うことができます。

以下に、LDAP サーバーへのログインに使用されるログイン・モジュールの構成例を示します。

LDAPLogin { com.ibm.websphere.objectgrid.security.plugins.builtins.LDAPLoginModule required
    providerURL="ldap://directory.acme.com:389/"
    factoryClass="com.sun.jndi.ldap.LdapCtxFactory";
};

前の構成では、LDAP サーバーは ldap://directory.acme.com:389/server を指しています。この設定をご使用の LDAP サーバーに変更します。このログイン・モジュールでは、指定された ID およびパスワードを使用して、LDAP サーバーに接続します。この実装は、テスト目的のみです。

WebSphere Application Server オーセンティケーター・プラグインの 使用

さらに、eXtreme Scale には、WebSphere Application Server セキュリティー・インフラストラクチャーを 使用するための com.ibm.websphere.objectgrid.security.plugins.builtins.WSTokenAuthenticator 組み込み実装も 用意されています。この組み込み実装は、以下の条件が満たされている場合に使用できます。

  1. WebSphere Application Server グローバル・セキュリティーがオンになっている。
  2. すべての eXtreme Scale クライアントおよびサーバーが WebSphere Application Server JVM で起動している。
  3. これらのアプリケーション・サーバーが、同じセキュリティー・ドメインにある。
  4. eXtreme Scale クライアントが、WebSphere Application Server で認証済みである。

クライアントはcom.ibm.websphere.objectgrid.security.plugins.builtins.WSTokenCredentialGenerator クラスを使用して、資格情報を生成できます。サーバーでは、Authenticator 実装クラス を使用して、資格情報を認証します。トークンが正常に認証されると、Subject オブジェクトが戻されます。

このシナリオの利点は、クライアントが既に認証済みであることです。サーバーがあるアプリケーション・サーバーが、クライアントを格納するアプリケーション・サーバーと同じセキュリティー・ドメインにあるため、クライアントからサーバーにセキュリティー・トークンを伝搬することができます。これにより、同じユーザー・レジストリーを再認証する必要がなくなります。

Tivoli® Access Manager オーセンティケーター・プラグインの使用

Tivoli Access Manager は、 セキュリティー・サーバーとして幅広く使用されています。Tivoli Access Manager が提供する ログイン・モジュールを使用して、オーセンティケーターを 実装することもできます。

Tivoli Access Manager でユーザーを認証するには、com.tivoli.mts.PDLoginModule ログイン・モジュールを適用します。このモジュールの場合、呼び出し側アプリケーションが以下の情報を 提供する必要があります。

  1. 短縮名または X.500 名 (DN) として指定されたプリンシパル名
  2. パスワード

ログイン・モジュールはプリンシパルを 認証し、Tivoli Access Manager 資格情報を返します。ログイン・モジュールは、 呼び出し側アプリケーションによって以下の情報が提供されると想定しています。

  1. javax.security.auth.callback.NameCallback オブジェクトを通してのユーザー名
  2. javax.security.auth.callback.PasswordCallback オブジェクトを通してのパスワード

Tivoli Access Manager 資格情報が 正常に取得されると、JAAS LoginModule によって Subject および PDPrincipal が 作成されます。Tivoli Access Manager 認証用の組み込みは、PDLoginModule モジュールで 使用されるだけなので、用意されていません。詳しくは、「IBM® Tivoli Access Manager Authorization Java Classes デベロッパーズ・リファレンス」を参照してください。

WebSphere eXtreme Scale へのセキュアな接続

eXtreme Scale クライアントを サーバーにセキュアに接続するには、ObjectGridManager インターフェースで、ClientSecurityConfiguration オブジェクトを使用する connect メソッドを使用します。以下に簡単な例を示します。

public ClientClusterContext connect(String catalogServerEndpoints, 
		 ClientSecurityConfiguration securityProps,
     URL overRideObjectGridXml) throws ConnectException;

このメソッドは、クライアント・セキュリティー構成を表すインターフェースである ClientSecurityConfiguration タイプ のパラメーターを使用します。 com.ibm.websphere.objectgrid.security.config.ClientSecurityConfigurationFactory public API を使用して、このインターフェースのインスタンスをデフォルト値で作成するか、 または WebSphere eXtreme Scale クライアント・プロパティー・ファイルを渡してインスタンスを作成します。このファイルには、認証に関連した以下のプロパティーが含まれています。 正符号 (+) でマークされた値はデフォルトです。

  • securityEnabled (true, false+): このプロパティーは、セキュリティーが有効かどうかを示します。クライアントがサーバーに接続されている場合、クライアント・サイドとサーバー・サイドの securityEnabled 値は、両方とも true または false である必要があります。例えば、 接続されるサーバーのセキュリティーが有効な場合、 クライアントはこのプロパティーを true に設定してサーバーに接続する必要があります。
  • authenticationRetryCount (an integer value, 0+): このプロパティーでは、資格情報の期限が切れている場合のログインの再試行回数を決定します。値が 0 の場合、再試行は行われません。 認証再試行は、資格情報の期限が切れている場合にのみ適用されます。資格情報が無効な場合、 再試行は行われません。操作の再試行は、ご使用のアプリケーションで対応する必要があります。

com.ibm.websphere.objectgrid.security.config.ClientSecurityConfiguration オブジェクトを作成した後、以下のメソッドを使用して、クライアントに credentialGenerator オブジェクトを設定します。

/**
* Set the {@link CredentialGenerator} object for this client.
* @param generator the CredentialGenerator object associated with this client
*/
void setCredentialGenerator(CredentialGenerator generator);
以下のように、WebSphere eXtreme Scale クライアント・プロパティー・ファイルにも CredentialGenerator オブジェクトを設定できます。
  • credentialGeneratorClass: CredentialGenerator オブジェクトのクラス実装名。 デフォルトのコンストラクターを指定する必要があります。
  • credentialGeneratorProps: CredentialGenerator クラスのプロパティー。この値がヌル以外の場合、このプロパティーは、setProperties(String) メソッドを使用して、構成済みの CredentialGenerator オブジェクトに設定されます。

以下に、ClientSecurityConfiguration のインスタンスを生成し、このインスタンスを使用してサーバーに接続する例を示します。

/**
* Get a secure ClientClusterContext
* @return a secure ClientClusterContext object
*/
protected ClientClusterContext connect() throws ConnectException {
ClientSecurityConfiguration csConfig = ClientSecurityConfigurationFactory
.getClientSecurityConfiguration("/properties/security.ogclient.props");

UserPasswordCredentialGenerator gen= new
UserPasswordCredentialGenerator("manager", "manager1");

csConfig.setCredentialGenerator(gen);

return objectGridManager.connect(csConfig, null);
}

接続が呼び出されると、WebSphere eXtreme Scale クライアントは、CredentialGenerator.getCredential メソッドを 呼び出してクライアント資格情報を取得します。この資格情報は、 接続要求とともにサーバーに送信されて、認証されます。

セッションごとに異なる CredentialGenerator インスタンスの使用

WebSphere eXtreme Scale クライアント は 1 つのクライアント ID を表す場合もあれば、複数の ID を表す場合も あります。以下に、複数の ID を表す場合の例を示します。 この例では、WebSphere eXtreme Scale クライアントは、Web サーバーで作成され、共有されます。この Web サーバー のすべてのサーブレットで、この 1 つの WebSphere eXtreme Scale クライアントが使用されます。各サーブレットが 異なる Web クライアントを表すため、WebSphere eXtreme Scale サーバーへ要求を送信するときは、異なる資格情報を使用します。

WebSphere eXtreme Scale は、 セッション・レベルでの資格情報の変更に対応しています。各セッションでは、 個別の CredentialGenerator オブジェクトを使用できます。したがって、前のシナリオは、 サーブレットで個別の CredentialGenerator オブジェクトを使用してセッションを取得することにより 実装されます。以下の例は、ObjectGridManager インターフェースの ObjectGrid.getSession(CredentialGenerator) メソッドを示しています。

/**
     * Get a session using a <code>CredentialGenerator</code>.
     * <p>
     * This method can only be called by the ObjectGrid client in an ObjectGrid
     * client server environment. If ObjectGrid is used in a local model, that is,
     * within the same JVM with no client or server existing, <code>getSession(Subject)</code>
     * or the <code>SubjectSource</code> plugin should be used to secure the ObjectGrid.
     *
     * <p>If the <code>initialize()</code> method has not been invoked prior to
     * the first <code>getSession</code> invocation, an implicit initialization
     * will occur.  This ensures that all of the configuration is complete
     * before any runtime usage is required.</p>
     *
     * @param credGen A <code>CredentialGenerator</code> for generating a credential
     *                for the session returned.
     *
     * @return An instance of <code>Session</code>
     *
     * @throws ObjectGridException if an error occurs during processing
     * @throws TransactionCallbackException if the <code>TransactionCallback</code>
     *         throws an exception
     * @throws IllegalStateException if this method is called after the
     *         <code>destroy()</code> method is called.
     *
     * @see #destroy()
     * @see #initialize()
     * @see CredentialGenerator
     * @see Session
     * @since WAS XD 6.0.1
 */
Session getSession(CredentialGenerator credGen) throws
ObjectGridException, TransactionCallbackException;

以下に例を示します。

ObjectGridManager ogManager = ObjectGridManagerFactory.getObjectGridManager();

CredentialGenerator credGenManager = new UserPasswordCredentialGenerator("manager", "xxxxxx");
CredentialGenerator credGenEmployee = new UserPasswordCredentialGenerator("employee", "xxxxxx");

ObjectGrid og = ogManager.getObjectGrid(ctx, "accounting");

// Get a session with CredentialGenerator;
Session session = og.getSession(credGenManager );

// Get the employee map
ObjectMap om = session.getMap("employee");

// start a transaction.
session.begin();

Object rec1 = map.get("xxxxxx");

session.commit();

// Get another session with a different CredentialGenerator;
session = og.getSession(credGenEmployee );

// Get the employee map
om = session.getMap("employee");

// start a transaction.
session.begin();

Object rec2 = map.get("xxxxx");

session.commit();

ObjectGird.getSession メソッドを使用して Session オブジェクトを取得する場合、 このセッションでは、ClientConfigurationSecurity オブジェクトに設定されている CredentialGenerator オブジェクトを使用します。ObjectGrid.getSession(CredentialGenerator) メソッドは、ClientSecurityConfiguration オブジェクトに 設定されている CredentialGenerator をオーバーライドします。

Session オブジェクトを再使用できる場合は、パフォーマンスが向上します。 ただし、ObjectGrid.getSession(CredentialGenerator) メソッドの呼び出しにかかるコストは、さほど高くありません。 主なオーバーヘッドは、増加したオブジェクト・ガーベッジ・コレクション時間となります。Session オブジェクトの完了後には、 必ず参照を解放してください。一般的に、Session オブジェクトで ID を共有できる 場合は、Session オブジェクトを再使用してください。そうでない場合は、ObjectGrid.getSession(CredentialGenerator) メソッドを使用してください。