12.2. カスタムモジュール
JaasSecurityManager には Subject プリンシパルのセットの特定の使用パターンが必要です。JAAS Subject クラスの情報ストレージの機能および JaasSecurityManager と動作するログインモジュールを書くためのこうした機能の必要な使用方法を理解しておく必要があります。
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 アイデンティティとロールに関しては、JBossSX は getPrincipals() と getPrincipals(java.lang.Class) により取得されるプリンシパルのセットという最も論理的な選択をしました。使用パターンは以下のとおりです。
- ユーザーアイデンティティ (例えばユーザー名、ソーシャルセキュリティ番号、従業員 ID) は
SubjectPrincipalsセットのjava.security.Principalオブジェクトとして保存されます。ユーザーアイデンティティを表すPrincipal実装は、プリンシパルの名前に関する比較と等値で始める必要があります。適切な実装はorg.jboss.security.SimplePrincipalクラスとして使用できます。他のPrincipalインスタンスは必要に応じてSubjectPrincipalsセットに追加できます。 - 割り当てられたユーザーロールも
Principalsセットに保存され、java.security.acl.Groupインスタンスを使用して名前付きロールセットでグループ化されます。GroupインターフェースはPrincipal/Groupの集まりを定義し、java.security.Principalのサブインターフェースとなります。 Subjectに割り当てるロールセットの数はいくつでも構いません。
- JBossSX フレームワークは
RolesとCallerPrincipalという名前のよく知られた 2 つのロールセットを使用します。RolesグループはSubjectが認証されたアプリケーションドメインで知られた名前付きロールに対するPrincipalの集まりです。このロールセットはEJBContext.isCallerInRole(String)のようなメソッドで使用され、EJB は現在の呼び出し側が名前付きアプリケーションドメインロールに属しているか確認するためにこれを使用できます。メソッドパーミッションの確認を実行するセキュリティインターセプタのロジックもこのロールセットを使用します。CallerPrincipalGroupはアプリケーションドメインのユーザーに割り当てられた単一のPrincipalアイデンティティで構成されます。EJBContext.getCallerPrincipal()メソッドはCallerPrincipalを使用して、アプリケーションドメインが動作環境アイデンティティからアプリケーションに適したユーザーアイデンティティにマップすることができます。SubjectにCallerPrincipalGroupがない場合は、アプリケーションアイデンティティは動作環境アイデンティティと同じです。
12.2.1. Subject の使用パターンのサポート
Subject 使用パターンの正しい実装を簡略化するために、JBossSX には Subject を強制的に正しく使用するテンプレートパターンを使用して認証された Subject を生成するログインモジュールが含まれています。
2 つのうち最も汎用的なクラスは org.jboss.security.auth.spi.AbstractServerLoginModule クラスです。
javax.security.auth.spi.LoginModule インターフェースの実装を提供し、動作環境セキュリティインフラストラクチャに固有の主要なタスクに対し抽象メソッドが可能になります。そのクラスに関する重要な詳細は 例12.14「AbstractServerLoginModule クラスの一部」 で強調表示されています。JavaDoc コメントがサブクラスの役割を詳しく説明します。
重要
loginOk インスタンス変数は極めて重要です。ログインが成功する場合はtrue に、そうでない場合はログインメソッドを上書きするサブクラスにより false に設定されます。この変数が正しく設定されていないと、コミットメソッドはサブジェクトを正しく更新しません。
例12.14 AbstractServerLoginModule クラスの一部
package org.jboss.security.auth.spi;
/**
* This class implements the common functionality required for a JAAS
* server-side LoginModule and implements the JBossSX 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;
}
カスタムのログインモジュールに適する2つ目の抽象ベースログインモジュールは org.jboss.security.auth.spi.UsernamePasswordLoginModule です。
char[] パスワードを認証資格情報として強制することで、カスタムのログインモジュール実装をさらに簡略化します。また、ロールを持たないプリンシパルへの匿名ユーザー (null ユーザー名とパスワードで表示) のマッピングにも対応しています。クラスに関する重要な詳細は次のクラスの一部で強調表示されています。JavaDoc コメントがサブクラスの役割を詳しく説明しています。
例12.15 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;
}
作成中のログインモジュールの認証技術に対して文字列ベースのユーザー名と資格情報の使用が可能であるかによって AbstractServerLoginModule と UsernamePasswordLoginModule のどちらをサブクラス化するか選択します。文字列ベースのセマンティックが有効な場合、サブクラスは UsernamePasswordLoginModule となり、有効でない場合は AbstractServerLoginModule となります。
カスタムのログインモジュールが実行しなければならない手順は、選択するベースのログインモジュールクラスにより異なります。セキュリティインフラストラクチャと統合するカスタムのログインモジュールを書く場合、AbstractServerLoginModule または UsernamePasswordLoginModule をサブクラス化することから始めて、ログインモジュールが認証された Principal 情報を JBossSX セキュリティマネージャーが要求する形式で提供するようにします。
AbstractServerLoginModule をサブクラス化する場合は、次を上書きする必要があります。
void initialize(Subject, CallbackHandler, Map, Map): 解析するカスタムのオプションがある場合。boolean login(): 認証アクティビティを実行するためです。ログインに成功する場合、loginOkインスタンス変数を true に設定するようにします。ログインに失敗する場合は false となるようにします。Principal getIdentity():log()のステップで認証されるユーザーのPrincipalオブジェクトを返すためです。Group[] getRoleSets():login()の間に認証されるPrincipalに割り当てられるロールが含まれるRolesという名前のGroupを最低でも 1 つ返すためです。2 番目の共通のGroupはCallerPrincipalという名前で、セキュリティドメインアイデンティティではなく、ユーザーのアプリケーションアイデンティティを提供します。
UsernamePasswordLoginModule をサブクラス化する場合は、次を上書きする必要があります。
void initialize(Subject, CallbackHandler, Map, Map): 解析するカスタムのオプションがある場合。Group[] getRoleSets():login()の間に認証されるPrincipalに割り当てられるロールが含まれるRolesという名前のGroupを最低でも 1 つ返すためです。2 番目の共通のGroupはCallerPrincipalという名前で、セキュリティドメインアイデンティティではなく、ユーザーのアプリケーションアイデンティティを提供します。String getUsersPassword():getUsername()メソッドで取得できる現在のユーザー名に必要なパスワードを返すためです。getUsersPassword()メソッドはcallbackhandlerがユーザー名と候補パスワードを返した後にlogin()内から呼び出されます。