Web Performer の認証機能を独自実装する

こんにちは。エヌデーデーの関口です。
Web Performer でアプリケーションを作成する場合、一番基本的な認証処理はデータベースに認証用テーブルを用意するという方法でしょう。
しかし、構築するシステムのポリシーによっては社内にある Active Directory や LDAP などのディレクトリサーバーの情報で認証しなければならないケースもあるでしょう。
今回はそのようなケースに対応するための実装方法をご紹介します。

想定される読者

  • Web Performer は導入済み
  • Web Performer の認証にディレクトリサーバーを利用する必要がある
  • Java での拡張開発経験がある

Web Performer の認証方法の紹介

それでは、Web Performer で認証する場合の手順を簡単にまとめてみます。
その前に、定義ガイド 30 章で紹介されている基本的なデータベース認証の方法と、SAML認証の方法、そして独自のログイン画面を作成する方法の概略を示し、その次に今回の独自認証の概略を紹介します。

基本的なデータベース認証

基本的なデータベース認証の手順は以下の通りです。

  1. データベースに認証用のテーブルを用意する
  2. Web Performer のプロジェクトにログインのためのデータベース接続の設定を行う
    • src/JavaWebApp/{アプリケーション名}/WEB-INF/login.conf を作成
    • login.conf に接続先データベースの設定を行う
    • login.conf に認証用テーブルに対する SQL 文を定義する
    • login.conf にロールを取得するための SQL 文を定義する

独自のログイン画面を作成した認証

ログイン画面に複雑な動作や独自のレイアウトなどを用いたいというケースの場合、独自のログイン画面を作成する方法が紹介されています。次のような手順が必要です。

  1. 独自ログイン画面の JSP を用意し、JSP 内にログインのボタンを押された時の認証手段を実装する
  2. Filter クラスを継承した独自の ログイン判定クラスを実装し、ログイン状態の判別ロジックを実装する
  3. 2 で作成したクラスを Filter として登録するために、web.xml.base を編集する

Web Performer に限らず、Java の Web アプリケーションを作成したことがある方なら比較的理解しやすい方法です。

SAML認証

SAML認証は、ユーザーが所属するドメインとは異なるドメインのWebシステムへのシングルサインオンを実現するためのプロトコルです。

SAML認証を利用するためには、前提として利用するサービス(Web アプリケーション)の他に、ログインのアカウントとパスワードなどを管理するための IdP(Identity Provider) が必要となります。これに対して、Web Performer で構築する Web アプリケーションなどは SP(Service Provider) と呼ばれ、IdP で認証されたアカウントを信頼して、機能を提供する側となります。

なお、IdP の機能を提供するサービスや製品の例としては、Active Directory Federation Service(AD FS), Azure AD, AWS SSO, GCP の Cloud Identity, Onelogin などがあります。

Web Performer で SAML認証を行うためには、次のような手順が必要です。

  1. Web Performer のプロジェクトに SAML認証を利用するということを宣言・設定する
    • src/JavaWebApp/{アプリケーション名}/WEB-INF/login.conf を作成
    • login.conf に認証モードを SAML認証であると宣言
    • login.conf に JKS のキーストアに関する情報を定義
    • Web アプリケーションが SP として動作する URL を定義する
    • Web アプリケーションがログイン ID と認識する認証情報の属性を定義する
    • Web アプリケーションがロールとして認識する認証情報の属性を定義する
  2. IdP からメタデータの xml ファイルを取得し、Web Performer プロジェクトに配置する
  3. JKS 形式のキーストアファイルを作成して、Web Performer プロジェクトに配置する
  4. Web アプリケーションを起動して、SP としての X.509 証明書を発行する
  5. IdP に SP の情報(URL や X.509 証明書)を登録する

一見面倒なように見えますが、SAML認証を行う方法としては一般的な手順と変わりません。

今回のケース、AD(LDAP)認証

「独自のログイン画面を作成した認証」で示した方法で、AD(LDAP)認証を実現することも出来ますが、標準で提供されるログイン画面を利用しつつ、できるだけカスタマイズ部分を少なくしたいという場合に、今回のケースは利用出来ます。手順の概要は次の通りです。

  1. Web Performer のプロジェクトに独自ログインの設定を行う
    • src/JavaWebApp/{アプリケーション名}/WEB-INF/login.conf を作成
    • login.conf に独自認証クラスの完全修飾名を定義する
    • login.conf にロールを取得するための SQL 文を定義する
    • login.conf に必要に応じて、独自認証クラスで使用したいパラメータを定義する
  2. 独自認証クラスを実装する

今回はロールの取得については、SQL で実行することにしています。
(AD に設定した値からロールを決定する方法もあるのですが、AD の管理ユーザーを用意したり、プログラムが複雑になるため今回は説明を省略しています)

具体的な実装例

さて、具体的に上記に示した手順に沿って、実装例を見てみましょう。なお、実装例を見て行くにあたり、Web Performer のプロジェクト、アプリケーションは既に定義済みのものとします。

login.conf の編集

login.conf ファイルを、src/JavaWebApp/{アプリケーション名}/WEB-INF/ に作成します。
内容は次のようにしてください。なお、下記の例のエンコードは Windows-31J としています。

conf.encoding=Windows-31J

#ページデザイン
title=ログイン
message=ログインしてください。
error.message=ユーザIDまたはパスワードが違います。
field=user
field.user.label = ユーザID
field=password
field.password.label = パスワード
field.password.type=password

#アプリケーション内で認証後のユーザーIDを設定する値
user=${field.user.value}

#独自認証クラスの完全修飾名
authenticator=sample.CustomAuthenticator

#独自認証クラスで使用するパラメータ
authenticator.param.input=${field.password.value}
#AD認証の場合は、ドメイン名\ユーザーID
authenticator.param.match=DOMAIN\\${field.user.value}
#LDAP認証の場合は、DNを構成する文字列
#authenticator.param.match=CN=${field.user.value}\,DC=DOMAIN\,DC=local
authenticator.param.url=ldaps://ad.example.local

#DB設定
db.type=jdbc
db.driver=org.apache.derby.client.ClientAutoloadedDriver
db.url=jdbc:derby://localhost:1527/sample
db.user=sample
db.password=sample

#ロールを取得するためのSQL文
roleMapper.param.roles=SQL{ SELECT ROLE FROM ROLES WHERE USERID='${field.user.value}'}

17行目に定義している、authenticator という定義で、どの独自認証クラスを利用するのかということを決定しています。
続く authenticator.param.inputauthenticator.param.match という設定値は独自認証クラスで使用するパラメータとなります。

20行目の authenticator.param.input は通常パスワードのフィールドを指すように設定します。

22行目の authenticator.param.match は SQL 文を設定すると、ユーザー ID で SQL を実行してパスワードを取得するために使うのですが、今回は SQL 以外の方法で独自認証クラスで使用できるように値を変えています。独自認証クラスの実装と併せて後述します。
24行目には、コメントしていますが、LDAPで認証をマッチさせるための設定があります。22行目との違いは、AD認証の場合は、{ドメイン名}\{アカウント} という文字列が実際の認証で用いられる ID であるのに対し、LDAP認証の場合は、DN という形式の文字列で ID を特定するために、参考例を示しています。なお、設定値にカンマが含まれる場合、カンマと\(バックスラッシュ)をエスケープするために\を使っています。

また、25行目には定義ガイドには記載の無い authenticator.param.url というものもありますが、これも後述します。

最終行の roleMapper.param.roles は データベースからロールを取得するための SQL文を SQL{ } で囲って記述します。

独自認証クラスの実装

続いて独自認証クラスを実装していきます。このクラスは、jp.co.canon_soft.wp.runtime.auth.DefaultAuthenticator というクラスを継承し、authenticate というメソッドを実装しておく必要があります。

今回の実装例のクラスは、 src/JavaWebApp/{アプリケーション名}/WEB-INF/ src/sample/CustomAuthenticator.java というファイルで実装しています。

package sample;

import java.util.Hashtable;
import java.util.Map;

import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;

import org.apache.commons.collections.ExtendedProperties;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import jp.co.canon_soft.wp.runtime.auth.AuthenticationException;
import jp.co.canon_soft.wp.runtime.auth.DefaultAuthenticator;

public class CustomAuthenticator extends DefaultAuthenticator {
    private static Log log = LogFactory.getLog(CustomAuthenticator.class);

    @Override
    @SuppressWarnings("rawtypes")
    public void authenticate(ExtendedProperties ext, Map params) throws AuthenticationException {

        // パスワード
        String input = StringUtils.trim((String) params.get("input"));
        // 認証用IDの文字列
        // ADの場合は、{ドメイン名}\{ユーザID} 例:DOMAINNAME\account1
        // LDAPの場合は、DNを構成する文字列 例:CN=account1,O=Users,DC=DOMAINNAME,DC=local
        String match = StringUtils.trim((String) params.get("match"));
        // 接続先URL
        String url = StringUtils.trim((String) params.get("url"));

        log.trace("input[" + input + "]");
        log.trace("match[" + match + "]");
        log.trace("url[" + url + "]");

        Hashtable<String, String> env = new Hashtable<String, String>();

        // LDAP接続のためのContextファクトリ
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
        env.put(Context.PROVIDER_URL, url);
        // セキュリティレベル
        env.put(Context.SECURITY_AUTHENTICATION, "simple");
        env.put(Context.SECURITY_PRINCIPAL, match);
        env.put(Context.SECURITY_CREDENTIALS, input);
        DirContext dirContext = null;
        try {
            dirContext = new InitialDirContext(env);

        } catch (Exception e) {
            // 認証エラーの場合は、AuthenticationException を投げる
            throw new AuthenticationException("");
        } finally {
            try {
                dirContext.close();
            } catch (Exception e) {
                // do nothing.
            }
        }
    }
}

AD(LDAP)認証では、少なくとも以下の3つの要素が必要となります。そして、それぞれ前述した login.conf の中で定義をしています。

  • ユーザーのパスワード (authenticator.param.input)
  • LDAP でユーザーを特定するための文字列 (authenticator.param.match)
  • AD(LDAP)サーバーの URL (authenticator.param.url)

これらの値は、26行目〜33行目で、params.get() という処理で取得しています。ここで使用しているキー名は、login.conf で設定したキーの、authentcator.param. という文字で始まるものが取得できるという特徴があります。

この特徴を利用して定義ガイドに記載のあるinputmatch 以外の設定値である、url の値も取得できるようにしています。
なお、設定値で ${field.password.value} といったプレースホルダについては、このメソッドが呼ばれた段階で実際の値に置き換わっていますので、そのまま後続の処理で利用可能です。

50行目で、AD(LDAP)認証を行って接続処理を実行しています。認証出来ない場合は例外が発生するので、これを受けて、54行目で Web Performer としての認証エラーの例外クラスに置きかえて投げるようにしています。

実行結果

ログイン後に、@USER を表示するだけの入出力を用意しておきました。
ログイン成功時と失敗時の画面を乗せておきます。実際にAD認証できたのかってのはちょっとこれだけではわからないですけどね・・・

最後に

今回は、Active Directory で管理されているアカウントとパスワードで認証する方法を紹介しました。

SSOの仕組みまでの導入までは至っていないが、せめてWebアプリケーションへのログインはドメインと共通のパスワードを利用したいなどのニーズ対応出来ます。

また、独自認証は標準の認証クラスを継承してさえいれば利用出来ますので、AD認証でチャレンジして失敗したら、データベース認証にしてみる、といった特殊な認証機能にも対応出来るようになりますので、要件に合わせて活用してみてください。