Glassfish や Payara の embedded サーバではログイン認証は動きません(そのままでは)

f:id:Naotsugu:20170707201305p:plain

payara-embedded や payara-micro では普通にログイン認証作っても動きません。

login.conf を明示的に指定しないといけません。


embedded サーバを使うには

payara-embedded-all (や glassfish-embedded-all) で Java EE をサクッと動かすのはとても簡単です。

以下のように Programmatic にアプリサーバを起動できます。

BootstrapProperties bootstrapProperties = new BootstrapProperties();
GlassFishRuntime runtime = GlassFishRuntime.bootstrap(bootstrapProperties);

GlassFishProperties glassfishProperties = new GlassFishProperties();
glassfishProperties.setPort("http-listener", 9090);
GlassFish glassfish = runtime.newGlassFish(glassfishProperties);

glassfish.start();

File war = ...
Deployer deployer = glassfish.getDeployer();
deployer.deploy(war);

Payara Micro だとこんな感じ。

File war = ...
PayaraMicro micro = PayaraMicro.getInstance();
PayaraMicroRuntime instance = micro.bootStrap();
instance.deploy(war);

fileRealm とか jdbcRealm が動かない

前述のように embedded サーバ起動した場合、認証やSSL関連などが動かないものがあります。

例えば、jdbcRealm は user テーブルと user_authgroup があったとして、以下のように glassfish の CLI コマンドでコンフィグレーションすることができます。

CommandResult result = glassfish.getCommandRunner().run(
        "create-auth-realm",
        "--classname", "com.sun.enterprise.security.auth.realm.jdbc.JDBCRealm",
        "--property", "jaas-context=jdbcRealm"
                    + ":datasource-jndi=\"java:app/primaryDS\""
                    + ":user-table=user"
                    + ":user-name-column=username"
                    + ":password-column=password"
                    + ":group-table=user_authgroup"
                    + ":group-name-column=groupname"
                    + ":digest-algorithm=SHA-256",
        "appJdbcRealm");
logger.info(glassfish.getCommandRunner().run("list-auth-realms").getOutput());

しかしレルム作成して、jdbc 設定や web.xml 設定を正しく行ったとしても、ログイン時には LoginModules が無いというエラーになります。

WARNING: WEB9102: Web Login Failed: com.sun.enterprise.security.auth.login.common.LoginException: Login failed: No LoginModules configured for jdbcRealm
WARNING: StandardWrapperValve[code.javaee.starter.RsApplication]: Servlet.service() for servlet code.javaee.starter.RsApplication threw exception
javax.servlet.ServletException: Login failed
        at org.apache.catalina.authenticator.AuthenticatorBase.doLogin(AuthenticatorBase.java:983)
        at org.apache.catalina.authenticator.AuthenticatorBase.login(AuthenticatorBase.java:963)

日本語だとの場合は以下のようなエラーですね。

警告: WEB9102: Web Login Failed: com.sun.enterprise.security.auth.login.common.LoginException: Login failed: jdbcRealm用に構成されたLoginModulesはありません

どこでエラーになるか

具体的には、

  • RealmAdapter#authenticate()
    • LoginContextDriver#login()
      • LoginContextDriver#doPasswordLogin()

と辿って、new LoginContext(jaasCtx, s, dummyCallback) とLoginContextをインスタンス化する際に例外が発生します。

// get the LoginModules configured for this application
AppConfigurationEntry[] entries = config.getAppConfigurationEntry(name);
if (entries == null) {
    ...
    entries = config.getAppConfigurationEntry(OTHER);
    if (entries == null) {
        MessageFormat form = new MessageFormat(ResourcesMgr.getString
                        ("No.LoginModules.configured.for.name"));
        Object[] source = {name};
        throw new LoginException(form.format(source));
    }
}

No.LoginModules.configured.for.name の箇所ですね。

ここで取得しようとしている AppConfigurationEntry とは、JAASログイン構成ファイルで、ソースで言うと以下になります。

package javax.security.auth.login;

public class AppConfigurationEntry {

    private String loginModuleName;
    private LoginModuleControlFlag controlFlag;
    private Map<String,?> options;
    ...
}

JAASログイン構成ファイルとは

JAAS のドキュメントから抜粋すると、以下の構成ファイルのことです。

ログイン構成ファイルは、1つ以上のエントリで構成され、各エントリには、特定のアプリケーションで使用される基礎となる認証技術が指定されます。
各エントリの構造を、次に示します。

<entry name> { 
    <LoginModule> <flag> <LoginModule options>;
    <LoginModule> <flag> <LoginModule options>;
    // ...
    };

このように、各ログイン構成ファイル・エントリは、ログイン・モジュールの名前とログイン・モジュール固有の1つ以上の項目で構成されます。
ログイン・モジュール固有の項目は、ログイン・モジュール、フラグ値、ログイン・モジュールに渡されるオプションを指定します。
ログイン・モジュール固有の個々の項目の末尾にはセミコロンを付け、項目のグループ全体を1組の中カッコで囲みます。
各構成ファイルのエントリはセミコロンで終わります。

この構成ファイルを指定するにはシステムプロパティを指定するかJREのlib/security ディレクトリ内の java.security ファイルに指定します。

payara embedded で JAASログイン構成ファイル を指定する

payara-embedded の jar の中には config ディレクトリの中に login.conf があります。

fileRealm {
    com.sun.enterprise.security.auth.login.FileLoginModule required;
};

ldapRealm {
    com.sun.enterprise.security.auth.login.LDAPLoginModule required;
};

solarisRealm {
    com.sun.enterprise.security.auth.login.SolarisLoginModule required;
};

jdbcRealm {
    com.sun.enterprise.security.ee.auth.login.JDBCLoginModule required;
};
jdbcDigestRealm {
       com.sun.enterprise.security.ee.auth.login.JDBCDigestLoginModule required;
};
pamRealm {
        com.sun.enterprise.security.auth.login.PamLoginModule required;
};

この構成ファイルが読み込めずに構成が無いというエラーになります。

コマンド行引数 -Djava.security.auth.login.config で指定しても良いですが、Glassfish 起動前に以下のようにシステムプロパティを設定するのが簡単です。

String instanceRootStr = System.getProperty("com.sun.aas.instanceRoot");
File configDir = new File(instanceRootStr, "config");

if (System.getProperty("java.security.auth.login.config") == null) {
    System.setProperty("java.security.auth.login.config",
            new File(configDir.getAbsolutePath(), "login.conf").getAbsolutePath());
}

これでめでたくログイン認証が動くようになります。

まとめ

embedded 系の Glassfish を Programmatic に動かした場合、システムプロパティから読み込んでいる設定ファイルが読み込まれないものがあるため、事前にシステムプロパティの設定が必要になることがあります。

他にも SSL 関連など、必要に応じてシステムプロパティを設定しておくと良いでしょう

  • java.security.policy server.policy
  • javax.net.ssl.keyStore keystore.jks
  • javax.net.ssl.trustStore cacerts.jks


3年前にも同じことで悩んでたのを忘れていて、2度目の無駄な時間使ってしまったので、少し詳しく残しといたよ。