OpenLDAPとWebSphere Libertyを使ってWindows環境でLDAP認証を試す

# はじめに

LDAP認証を行うサーバー・サイドのJavaプログラムを開発することになったので、勉強のためにローカル環境(Windows 10環境)でサンプル・プログラムを動かしてみました。LDAPサーバーはOpenLDAPのDockerイメージを利用、データベースも付属のサンプルをそのまま利用して、まずはサーバー・サイドのサンプルJavaプログラムでLDAP認証してみることをゴールとしました。ladapsでの接続にちょっと手こずったので、注意点も含めてメモを残しておきます。

OpenLDAPのDockerイメージは以下のイメージを利用しました。
[osixia/docker-openldap](https://github.com/osixia/docker-openldap)

サーバーはWebSphere Libertyを利用。以下からダウンロードした**WebSphere Liberty 21.0.0.10 + Java SE 8 for Windows (wlp-1.0.57.cl211020210920-1900)**を使用しました。
[WebSphere Liberty for developers](https://www.ibm.com/support/pages/websphere-liberty-developers "21.0.0.10")
これから導入する方は、参考記事[5]をご一読することをお薦めします。

# OpenLDAPをDocker上で起動
Windowsコマンド・プロンプトから以下を入力してOpenLDAPを起動します。
Window環境なのでコマンドの継続記号はバックスラッシュ('\')ではなくハット('^')になります。

```
docker run --hostname localhost -p 389:389 -p 636:636 ^
    --name openldap ^
    --volume C:\openldap\myvol:/myvol ^
    --env LDAP_TLS_VERIFY_CLIENT=try ^
    --detach osixia/openldap:1.5.0

```
後でOpenLDAPの証明書を取り出したりするのに使いたいので`--volume C:\openldap\myvol:/myvol`を指定しておきます。

:::note warn
ldapsでアクセスしたいので--env LDAP_TLS_VERIFY_CLIENT=tryを指定しています。このTLSオプションはデフォルトがdemandに設定されており、デフォルトのままでldapsでアクセスするとエラーになります。
:::

OpenLDAPのコンテナが起動したら、`--volume`オプションで指定したディレクトリーにOpenLDAPの証明書ファイルをコピーします（以下参照）。コピーしたldap.crtは後で、WebSphere Libertyにインポートします。

dockerコンテナに入る。

```
docker exec -it openldap /bin/bash
```
OpenLDAPの証明書をコピーする。

```
cp /container/service/slapd/assets/certs/ldap.crt /myvol/
```

# WebSphere Libertyの鍵ストアにパスワードを設定
以下、テスト用のWebSphere LibertyサーバーとしてMyServerというサーバーを新規作成した前提で説明します。
まず、server.xmlを編集して鍵ストア(defaultKeyStore)のパスワードを設定します。

``` server.xml
<server description="new server">

    <!-- Enable features -->
    <featureManager>
        <feature>webProfile-8.0</feature>
        <feature>localConnector-1.0</feature>
    </featureManager>

    <!-- To access this server from a remote client add a host attribute to the following element, e.g. host="*" -->
    <httpEndpoint httpPort="9080" httpsPort="9443" id="defaultHttpEndpoint"/>
                  
    <!-- Automatically expand WAR files and EAR files -->
    <applicationManager autoExpand="true"/>


    <applicationMonitor updateTrigger="mbean"/>

    <webApplication id="LDAPTest" location="LDAPTest.war" name="LDAPTest"/>
    <keyStore id="defaultKeyStore" password="password" />
</server>
```

テスト用に作成したサーバー(MyServer)の`resources\security`ディレクトリー（下記参照）に既にkey.p12が存在する場合は削除します。サーバー起動時に前記ディレクトリーに鍵ストア・ファイル(`key.p12`)が存在しないと自動的に作成されるのですが、その時設定されたパスワードが上記server.xmlで指定したパスワードと一致しないとエラーになってしまうため、既存の鍵ストア・ファイル(`key.p12`)を削除します。こうすることで、次回サーバー起動時に上記server.xmlで指定したパスワードが設定された鍵ストア・ファイル(`key.p12`)が新規作成されます。

``` :鍵ストア・ファイルの配置場所
wlp
|               
+---usr
    |       
    +---servers
        |
        +---MyServer
            |           
            +---resources
                |
                +---security
                        key.p12
                        ltpa.keys
```

WebSphere Libertyを起動して、鍵ストアファイル(`key.p12`)が作成されたことを確認します。

# WebSphere Libertyの鍵ストア・ファイルにOpenLDAPの証明書をインポート
新しく作成されたWebSphere Libertyの鍵ストア(`key.p12`)にOpenLDAPの証明書(`ldap.crt`)をインポートします。
適当な作業ディレクトリーに以下の2つのファイルをコピーします。

* 新しく作成されたWebSphere Libertyの鍵ストア(`key.p12`)
* OpenLDAPの証明書(`ldap.crt`)

以下のkeytoolコマンドでインポートします。

```
keytool -importcert -file ldap.crt -alias localhost -keystore key.p12 -storepass password -storetype PKCS12
```

以下のkeytoolコマンドでOpenLDAPの証明書(`ldap.crt`)がインポートされたことを確認します。

```
keytool -list -v -keystore key.p12 -storetype PKCS12 -storepass password
```

OpenLDAPの証明書(`ldap.crt`)をインポートした鍵ストア・ファイル(`key.p12`)をサーバー(MyServer)の`resources\security`ディレクトリーに上書きコピーします。
サーバー(MyServer)を再起動してエラーがなければ、WebSphere LibertyとOpenLDAPの接続作業は完了です。
サンプル・プログラムで疎通確認をしてみます。

# OpenLDAPにテスト・ユーザーを登録
サンプル・プログラムでLDAP認証するために、前記Dockerイメージに付属のサンプル・データベースにテスト・ユーザーを登録します。まず、ldap.crtを搬出するのに使ったディレクトリー(`C:\openldap\myvol`)にテスト・ユーザーを登録するためのLDIFファイル(`u0001.ldif`)を作成します。

``` u0001.ldif
dn: uid=u0001,dc=example,dc=org
uid: u0001
cn: u0001
sn: 3
objectClass: top
objectClass: posixAccount
objectClass: inetOrgPerson
loginShell: /bin/bash
homeDirectory: /home/u0001
uidNumber: 10001
gidNumber: 10001
userPassword: u0001password
mail: u0001@example.org
gecos: TestUser 0001
```

以下のコマンドでテスト・ユーザーを登録します。

dockerコンテナに入る。

```
docker exec -it openldap /bin/bash
```
ldapaddコマンドでテスト・ユーザーを登録する。

```
ldapadd -x -D "cn=admin,dc=example,dc=org" -w admin -f /myvol/u0001.ldif -H ldap://localhost

```


# サンプル・プログラムで疎通確認

以下のようなサーブレットを作成します。WebSphere LibertyとOpenLDAPの疎通確認を目的としたものなので、ユーザーIDとパスワードはハードコーディングしています。

```LDAPAuthServlet.java
package com.example;

import java.io.IOException;
import java.util.Hashtable;

import javax.naming.AuthenticationException;
import javax.naming.Context;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/LDAPAuthServlet")
public class LDAPAuthServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

    public LDAPAuthServlet() {
    }

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String msg = null;
        Hashtable<String, String> env = new Hashtable<String, String>();
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
        env.put(Context.PROVIDER_URL, "ldaps://localhost:636");//ldapsで接続
        env.put(Context.SECURITY_AUTHENTICATION, "simple");

        //LDAP認証するユーザーIDとパスワード(OpenLDAPに登録したもの)を設定
        String userid = "u0001";
        String password = "u0001password";
        env.put(Context.SECURITY_PRINCIPAL, "uid="+userid+",dc=example,dc=org");
        env.put(Context.SECURITY_CREDENTIALS, password);

        try {
            //LDAP認証を実行
            DirContext ctx = new InitialDirContext(env);
            msg = "LDAP authentication successful.";
            ctx.close();
        } catch(AuthenticationException e) {
        	e.printStackTrace();
        	msg = e.getMessage();
        } catch(Exception e) {
        	e.printStackTrace();
        	msg = e.getMessage();
        }
        
		response.getWriter().append(msg);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}

}

```

上記サーブレットを含むwarをビルド、デプロイして、ブラウザーからアクセスしてみます。ブラウザーに`"LDAP authentication successful."`のメッセージが表示されれば成功です。OpenLDAPのログには以下のようなメッセージが出力されます。

```
618a340d conn=1162 fd=12 ACCEPT from IP=172.17.0.1:58158 (IP=0.0.0.0:636)
618a340d conn=1162 fd=12 TLS established tls_ssf=256 ssf=256
618a340d conn=1162 op=0 BIND dn="uid=u0001,dc=example,dc=org" method=128
618a340d conn=1162 op=0 BIND dn="uid=u0001,dc=example,dc=org" mech=SIMPLE ssf=0
618a340d conn=1162 op=0 RESULT tag=97 err=0 text=
618a340d conn=1162 op=1 UNBIND
618a340d conn=1162 fd=12 closed
```

以上です。

# WebSphere Application Server traditionalの場合
WebSphere Application Server traditional V9(以下tWASと表記)の場合は以下の手順でOpenLDAPの証明書をインポートします。

1. 管理コンソールにログインします。
2. 画面左側にあるメニューの「セキュリティー」を展開して、「SSL 証明書および鍵管理」をクリックします。
3. 「構成設定」セクションの「エンドポイント・セキュリティー構成の管理」をクリックします。 
4. アウトバウンドのnodesフォルダーの下にあるノードのリンクをクリックします。 
5. 「関連項目」セクションの「鍵ストアおよび証明書」をクリックします。
6. 一覧に表示されている「NodeDefaultTrustStore」をクリックします。 
7. 「追加プロパティー」セクションの「署名者証明書」をクリックします。
8. 「追加」ボタンをクリックします。
9. 「別名」フィールドに`localhost_cert`を入力します。
10. 「ファイル名」フィールドにOpenLDAPの証明書のパス(`C:\openldap\myvol\ldap.crt`)を入力します。
11. 「適用」ボタンをクリックします。
12. 「保存」をクリックします。

:::note
補足）上記8～10の手順は、以下8'～12'の手順で代替することも可能です。
:::

8'. 「ポートから取得」ボタンをクリックします。
9'. 「ホスト名」フィールドに`localhost`を入力します。
10'. 「ポート」フィールドに`636`を入力します。
11'. 「別名」フィールドにフィールドに`localhost_cert`を入力します。
12'. 「署名者情報の取得」をクリックします。



# 参考記事
以下の記事を参考にさせていただきました。
[1] [dockerでLDAP Serverを起動。ついでにphpLDAPadminも](https://qiita.com/kokardy/items/1179bb2f96f790acc6c5)
[2] [OpenLDAPでユーザー認証までやってみる](https://qiita.com/toshihirock/items/fc6ba5b6be8040e69ac9)
[3] [Webアプリケーションの認証動作テスト用に Docker で openldap を立てみる](https://qiita.com/tatsurou313/items/3d598ec4b616578ef521)
[4] [LDAPクライアントプログラミングは怖くない（Java編）](https://qiita.com/hoshino_osstech/items/8dd63d61df4915104396)
[5] [Libertyの始め方](https://qiita.com/TTakakiyo/items/bb122f513a1bdf618adc)
[6] [keytoolでWebSphere Liberty用の鍵ストアとトラストストアを作成する](https://qiita.com/sotoiwa/items/19493f4bc700df086f30)