12.2.2. カスタムの LoginModule の例

UsernamePasswordLoginModule を拡張し、JNDI ルックアップからユーザーのパスワードとロール名を取得するカスタムの Login Module の例を作成するときに、次の情報が役立ちます。
password/<username> (<username> が認証されている現在のユーザーである場合) の形式の名前を使用してコンテキストで検索を実行する場合に、ユーザーのパスワードを返すカスタムの JNDI コンテキストログインモジュールを作成することができるように本項で説明していきます。同様に、roles/<username> の形式の検索は、要求されたユーザーのロールを返します。
例12.16「JndiUserAndPass カスタムのログインモジュール」JndiUserAndPass カスタムのログインモジュールのソースコードを示しています。
これは JBoss UsernamePasswordLoginModule を拡張するため、JndiUserAndPass が行うことは JNDI ストアからユーザーのパスワードとロールを取得することだけです。JndiUserAndPass は JAAS LoginModule 動作には関係しません。

例12.16 JndiUserAndPass カスタムのログインモジュール

package org.jboss.book.security.ex2;
                    
import java.security.acl.Group;
import java.util.Map;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.LoginException;

import org.jboss.security.SimpleGroup;
import org.jboss.security.SimplePrincipal;
import org.jboss.security.auth.spi.UsernamePasswordLoginModule;

/** 
 *  An example custom login module that obtains passwords and roles
 *  for a user from a JNDI lookup.
 *     
 *  @author Scott.Stark@jboss.org
 *  @version $Revision: 1.4 $
*/
public class JndiUserAndPass 
    extends UsernamePasswordLoginModule
{
    /** The JNDI name to the context that handles the password/username lookup */
    private String userPathPrefix;
    /** The JNDI name to the context that handles the roles/ username lookup */
    private String rolesPathPrefix;
    
    /**
     * Override to obtain the userPathPrefix and rolesPathPrefix options.
     */
    public void initialize(Subject subject, CallbackHandler callbackHandler,
                           Map sharedState, Map options)
    {
        super.initialize(subject, callbackHandler, sharedState, options);
        userPathPrefix = (String) options.get("userPathPrefix");
        rolesPathPrefix = (String) options.get("rolesPathPrefix");
    }
    
    /**
     *  Get the roles the current user belongs to by querying the
     * rolesPathPrefix + '/' + super.getUsername() JNDI location.
     */
    protected Group[] getRoleSets() throws LoginException
    {
        try {
            InitialContext ctx = new InitialContext();
            String rolesPath = rolesPathPrefix + '/' + super.getUsername();

            String[] roles = (String[]) ctx.lookup(rolesPath);
            Group[] groups = {new SimpleGroup("Roles")};
            log.info("Getting roles for user="+super.getUsername());
            for(int r = 0; r < roles.length; r ++) {
                SimplePrincipal role = new SimplePrincipal(roles[r]);
                log.info("Found role="+roles[r]);
                groups[0].addMember(role);
            }
            return groups;
        } catch(NamingException e) {
            log.error("Failed to obtain groups for
                        user="+super.getUsername(), e);
            throw new LoginException(e.toString(true));
        }
    }
                    
    /** 
     * Get the password of the current user by querying the
     * userPathPrefix + '/' + super.getUsername() JNDI location.
     */
    protected String getUsersPassword() 
        throws LoginException
    {
        try {
            InitialContext ctx = new InitialContext();
            String userPath = userPathPrefix + '/' + super.getUsername();
            log.info("Getting password for user="+super.getUsername());
            String passwd = (String) ctx.lookup(userPath);
            log.info("Found password="+passwd);
            return passwd;
        } catch(NamingException e) {
            log.error("Failed to obtain password for
                        user="+super.getUsername(), e);
            throw new LoginException(e.toString(true));
        }
    }   
}
JNDI ストアの詳細は org.jboss.book.security.ex2.service.JndiStore MBean に記載されています。このサービスは javax.naming.Context プロキシを返す ObjectFactory を JNDI にバインドします。プロキシは、passwordroles に対して検索名のプレフィックスを確認することで、検索動作を処理します。
名前が password で始まる場合は、ユーザーのパスワードが要求されています。名前が roles で始まる場合は、ユーザーのロールが要求されています。サンプルの実装はユーザー名に関係なく常に theduke のパスワードと {"TheDuke", "Echo"} に等しいロール名の配列を返します。希望であれば他の実装で試すことも可能です。
例のコードにはカスタムのログインモジュールをテストする簡易セッション Bean が含まれています。この例を構築、デプロイ、実行するには、例のディレクトリで次のコマンドを実行します。
[examples]$ ant -Dchap=security -Dex=2 run-example
...
run-example2:
     [echo] Waiting for 5 seconds for deploy...
     [java] [INFO,ExClient] Login with user name=jduke, password=theduke
     [java] [INFO,ExClient] Looking up EchoBean2
     [java] [INFO,ExClient] Created Echo
     [java] [INFO,ExClient] Echo.echo('Hello') = Hello
ユーザーのサーバー側の認証に JndiUserAndPass カスタムのログインモジュールを使用するという選択は、例のセキュリティドメインのログイン設定により決まります。EJB JAR META-INF/jboss.xml 記述子がセキュリティドメインを設定します。
<?xml version="1.0"?>
<jboss>
   <security-domain>security-ex2</security-domain>
</jboss>
SAR META-INF/login-config.xml 記述子はログインモジュール設定を定義します。
<application-policy name = "security-ex2">
   <authentication>
      <login-module code="org.jboss.book.security.ex2.JndiUserAndPass" flag="required">
         <module-option name="userPathPrefix">/security/store/password</module-option>
         <module-option name = "rolesPathPrefix">/security/store/roles</module-option>
      </login-module>
   </authentication>
</application-policy>