iDempiere（JPiere）のLDAP認証機能を検証してみる

前回は[iDempiereの画面から実装したプロセスを起動する方法](https://qiita.com/idempiere-fan/items/35a9a871b6f33f102d30)を紹介しました。
今回は、iDempiereの検証を進める中で気になっていた認証関連の調査結果をお伝えします。
認証サーバーとしてOpenLDAPを利用しましたので、その内容を共有させていただきます。

## Ⅰ.OpenLDAPを認証サーバとして動作させた結果
一部のソースコードを修正し、iDempiereの設定を行った結果、OpenLDAPとの認証ができました。
もちろん、OpenLDAP認証を実現するためには他にも適切な方法があるかもしれませんが、この記事は参考情報としてご覧いただければと思います。
課題については、当記事[「LDAP認証と運用時の課題」](https://qiita.com/idempiere-fan/items/6d779d2d3b382bc75848#%E2%85%B8ldap%E8%AA%8D%E8%A8%BC%E3%81%A8%E9%81%8B%E7%94%A8%E6%99%82%E3%81%AE%E8%AA%B2%E9%A1%8C)に記載しました。

LDAP認証を実際に導入する過程でさまざまな課題が浮かび上がりましたが、それらの詳細な解決策やベストプラクティスについては今後の課題となります。その一環として、LDAP認証の導入手順について以下で詳細に紹介いたします。


## Ⅱ.事前知識と準備
Javaソースコードの修正とiDempiereの画面を用いて設定を行います。
iDempiereの開発環境を構築し、JPiereのプラグインが動作できるようにしておきます。
iDempiereの[環境構築](https://qiita.com/idempiere-fan/items/ca8520f567c5939cc5cc)と[開発環境の準備](https://qiita.com/idempiere-fan/items/2d8fd80d2f80f2478e4f)はこちらをそれぞれ参照ください。
その他、必要となるスキル面は以下です。
* Eclipse
* iDempiere(JPiere)
* OpenLDAP (Ver3ベースです)
* php LDAP admin (OpenLDAPのアカウント登録時に使用します）

※ OpenLDAP 公式サイトはこちらから

https://www.openldap.org/

## Ⅲ.iDempiereとLDAP連携の参考記事
接続情報に関する点は、こちらを参考にさせていただきました。

https://www.adempierebr.com/Ldap

## Ⅳ.LDAP認証機能の検証のながれ
今回は以下のながれで動作検証を行いました。
以下の内容を次章以降から紹介します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623535/80a9c71b-41bc-36f8-72ee-088633f965f8.png)

## Ⅴ.OpenLDAPの環境構築
Dockerを使用した環境構築には、こちらを参考にさせていただきました。ありがとうございました。
以下の内容から、phpLDAPadminを用いた環境構築およびLDAPのユーザ登録方法を把握することができます。

https://qiita.com/yoshiyasu1111/items/c2d7d5f9a70afdaa8372

### 1) LDAP環境の起動
当検証において以下の内容でOpenLDAPサーバを利用します。
* LDAPサーバのDocker定義情報
以下の内容を```docker-compose.yml```ファイルに記述します。
```dockerfile
version: '3'

services:
  ldap-server:
    image: osixia/openldap:latest
    restart: always
    container_name: ldap-host
    environment:
      LDAP_ORGANISATION: "erp"
      LDAP_DOMAIN: "erp.work"
      LDAP_ADMIN_PASSWORD: "password"
    networks:
      - webnet

  ldap-admin:
    image: osixia/phpldapadmin:latest
    restart: always
    container_name: ldap-admin
    environment:
      PHPLDAPADMIN_LDAP_HOSTS: "ldap"
      PHPLDAPADMIN_HTTPS: "false"
    ports:
      - "8081:80"
    links:
      - "ldap-server:ldap"
    networks:
      - webnet

networks:
  webnet:
```
* LDAPサーバの起動
DOSプロンプトから```docker-compose up -d```コマンドを実装しLDAPサーバーを起動します。


**補足）登録結果**
次章以降の手順に沿って登録した結果、以下の状態になります。
※ phpLDAPadminにより確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623535/9c4f2c92-96c0-d616-2fe6-761fa4bc0410.png)

:::note
ユーザを登録する所属DNを```dc=erp,dc=work```とし、```ou=users```に属する```cn=ldapuser1```アカウントを登録した結果です。
またユーザ情報(ObjectClass)として、inetOrgPersonが適用されています。
今回検証したiDempiereの認証機能では、以下のRDN識別子を使用します。
**dc:ユーザの所属するtopのDomain Component名（erp.workの２階層で表現）**
**ou: ユーザの所属するOrganization Unit名（usersとして定義）**
**cn: ユーザーのcommonName名（iDempiereではこの名称がログインIDとなります）**
**userPassword: ユーザーのログインパスワード（iDempiereではこのパスワードがログインパスワードになります）**
:::

### 2) ログイン
iDempiereからログインするユーザをOpenLDAPに登録します。
ブラウザを起動し```localhost:8081```を入力しphp LDAP adminページにアクセスします。
LDAPサーバへログインします。
**Login DN:** cn=admin,dc=erp,dc=work
**Password:** password 
※ docker-compose.ymlの```LDAP_ADMIN_PASSWORD: "password"```で指定したパスワードを入力します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623535/f67c6b4e-ef83-08d3-6df5-c137dc53c9be.png)

### 3) 各RDN識別子の登録
**1\. 最初にouを追加します。**
左メニューの```dc=erp,dc=work```をクリックし、つづいて、```Create a child entry```をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623535/00decf9a-554a-5cfa-c0f9-29ebb0426a3d.png)

**2\. objectClassを指定する画面が表示されます。**
```Generic: Organisational Unit```をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623535/138cdee3-8a3d-87e9-c572-6df38f43562e.png)

**3\. Organisational Unit入力画面が表示されます。**
```users```を入力し```Create Object```ボタンをクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623535/5cc27df4-5273-8aae-7fc3-c19759489e57.png)

**4\. LDAP Entryの登録確認画面が表示されます。**
```Commit```ボタンをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623535/b78b4a5b-a3ba-5aa1-db7c-e1090d70ff86.png)
以上でouのRDN識別子登録は完了です。
```ou=users```にて当階層にアクセスすることができます。
続いて、ou=usersの下にユーザを追加します。

**5\. ユーザを登録します。**
左メニューのツリーを展開し、```ou=users```欄をクリックし、つづいて、```Create a child entry```をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623535/2cb8b4ef-689d-a4ce-9c40-e705dde9a2a7.png)

**6\. objectClassを指定する画面が表示されます。**
```Kolab: User Entry```をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623535/b32e1040-fcf5-462e-d221-a266a8c0b377.png)

:::note
LDAPにユーザを登録する場合、数多くのobjectClassを使用することができます。
ツールおよびご利用のユーザ管理方法に沿って適切なobjectClassを指定します。
今回の検証では、登録ユーザのcnおよびpasswordが必要になるため、Kolabを指定しました。
:::

**7\. ユーザ情報の登録画面が表示されます。**
各項目を入力します。
```Common Name```、```Email```は後続の手順でiDempiereに登録します。
```Common Name```は、姓名の間にスペースが付加されるため```ldapuser1```として入力しなおします。
また、```Password```を登録する際には```clear```を指定します。
なお、ここでのPasswordは、```user1password```とします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623535/0aa201a6-c837-e2cc-6315-7c4fa3632152.png)

:::note warn
Passwordを登録する際に**clear**を指定することによりパスワードがLDAP内に平文で保存されます。
iDempiereと連携してログインする場合、clear以外を指定すると正常にログインすることができませんでした。
また、iDempiereにおけるパスワード管理方法はデフォルトでは平文で保持（PostgreSQLのAD_Userテーブルに平文で保存されている）する設定になっています。
iDempiere側の設定に依存していると思われますが、まだ検証はできていません。
今回の検証では、iDempiereおよびLDAPに平文でパスワードを保持する設定を前提に進めます。
:::

入力後、画面をスクロールし```Create Object```ボタンをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623535/cf8320b5-3f21-5e0f-c904-4f0f9c431aca.png)

**8\. LDAP Entryの登録確認画面が表示されます。**
```Commit```ボタンをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623535/dbaa5eb0-d220-57f1-f425-984f2d374ad2.png)

※ 登録結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623535/9c4f2c92-96c0-d616-2fe6-761fa4bc0410.png)

以上でユーザ登録は完了です。

続いて、iDempiereの設定を行います。

## Ⅵ.iDempiere設定
iDempiere側で以下の点の設定、登録をします。
ログインユーザ、ロールを切り替えながら設定を進めます。
iDempiereサービスを起動していない場合は、[こちらを参考にサービスを起動します](https://qiita.com/idempiere-fan/items/2d8fd80d2f80f2478e4f)。
PostgreSQLサーバも起動しておきます。
* LDAPのDN定義の追加
* LDAP認証にOpenLDAP形式を使用する設定の追加
* LDAP認証するユーザの登録
* メールアドレスによる認証の無効化

### 1) LDAPのDN定義の追加

**1\. iDempiereにログインします。**
ブラウザのURLに```https://127.0.0.1:8443/webui```を入力しログインします。
ユーザ情報は以下を入力します。
* **EMail:** superuser@oss-erp.co.jp
* **Password:** System

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623535/6fea17ec-d975-7a0b-2ae9-fbacf7171e6b.png)

ロール情報は以下を入力します。
* **Tenant:** System
* **Role:** System Administrator

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623535/e1266ec1-b1dd-e5df-414b-6467b2ab4431.png)

**2\. System画面を開きます。**
検索欄に```Sys```を入力すると```System```が表示されます。
```System```をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623535/8d32d50f-eb18-44c4-fbad-808e842d3db5.png)

**3\. System画面が表示されます。**
LDAPのサーバー情報およびDN情報を入力します。
* **LDAP URL:** ldap://localhost:389
* **LDAP Domain:** ou=users,dc=erp,dc=work
入力したら保存ボタンをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623535/2e8a26a8-4a52-e992-a807-149e5cbd5911.png)

LDAPのDN定義の登録は以上です。

続いて、OpenLDAP形式を使用する定義を追加します。

### 2) LDAP認証にOpenLDAP形式を使用する設定の追加

**1\. System Configurator画面（新規登録）を開きます。**
検索欄に```Sys```を入力すると```System Configurator```が表示されます。
```System Configurator```行の```新規```ボタンをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623535/33cb176f-4dfd-60ff-8560-aa340457438a.png)

**2\. LDAP_TYPEを追加します。**
以下の欄を入力および選択します。
* **Name:** LDAP_TYPE
* **Configured Value:** openldap
* **Entity Type:** iDempiere
* **Configuration Level:** System
入力したら保存ボタンをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623535/aea499c5-0236-8c98-fbd1-2da4d4f053cf.png)

以上で、OpenLDAP形式を使用する設定は完了です。

つづいて、LDAP認証するユーザを登録します。

### 3) LDAP認証するユーザの登録
LDAP認証するユーザ情報を、iDempiereにも登録します。
株式会社OSS ERP Solutionsに属するユーザとして追加します。

**1\. 現在ログインしている```System:System Administrator```を```株式会社OSS ERP Solutions:Admin Role```に切り替えます。**
画面上部の```Change Role```をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623535/3df9c2e9-a4c4-3a7b-b8eb-f7ba3ccafb28.png)

**2\. テナントおよりロールの指定画面が表示されます。**
以下のロールを指定します。
* **Tenant:** 株式会社OSS ERP Solutions
* **Role:** Admin Role
入力したら```OK```ボタンをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623535/47af3caf-97b4-10c1-666e-af7bece10ad4.png)

**3\. User画面（新規登録）を開きます。**
検索欄に```user```を入力すると```User```が表示されます。
```User```行の```新規```ボタンをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623535/3369d970-4c54-e0de-9be2-8b7a6568c8b2.png)

**4\. ユーザ情報の新規登録画面が表示されます。**
それぞれの項目を入力します。
最初に```User Contact```情報を入力します。
* **Name:** LDAP User1
* **EMail Address:** ldapuser1@yourdomain.com.com
* **Password:** dummy
* **LDAP User Name:** ldapuser1
※ LDAP User Nameには、上記のLDAP登録した```CN```名と一致させます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623535/3d44d482-f358-a516-965f-58452ad4fec6.png)

:::note warn
LDAP認証を使用する際も、```EMail Address```および```Password```を入力する必要があります。
:::

ここまで入力したら**一旦保存します**。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623535/6550004b-ecee-e601-0b22-363f33cc0f96.png)

**5\. つづいてに権限情報を入力します。**
画面下の```Detail Record```エリアを開きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623535/aece25bd-bda2-5ec4-5a6f-ab91509c9c13.png)

```新規```ボタンをクリックし、```虫眼鏡```ボタンをクリック後、所属するロールを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623535/d7a3831b-f74e-f556-172b-614971312a52.png)

```虫眼鏡```ボタンをクリック後、ログインユーザのロールが表示されます。
```Name```欄のテキストを削除し、再表示ボタンをクリックすると登録されているロールが全て表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623535/692c1c03-73df-dc72-8169-77d33c580bca.png)

※ 再検索した結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623535/202d11e5-ea02-4b97-0e51-0c5bb62c801f.png)

今回の検証では、複数のロールに所属させました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623535/9ec1dbed-0001-9b5e-731b-4e9a8b0c2694.png)

ロールの設定と同様に、つづいて、```Org Access```タブをクリックしOrgを登録します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623535/22f00dd6-83c7-a21f-ec24-d6306a6aea63.png)

:::note
同エリアに```LDAP Access```というタブがありますが、登録は特に行いません。
:::

以上でユーザ登録の作業は完了です。

つづいて、最後の設定です。
メールアドレスによる認証を無効にし、ユーザIDでログインできるようにします。

### 4) メールアドレスによるログインの無効化
ログイン方法を変更するにあたり、再度ロールを切り替えて設定します。

**1\. ロールを切り替えます。**
上記の手順同様に、画面上部```Chainge Role```をクリックし、以下のロールに切り替えます。
ロール情報は以下を入力します。
* **Tenant:** System
* **Role:** System Administrator

**2\. メールアドレスログインを無効化します。**
検索欄に```sys```を入力すると```System Configurator```が表示されます。
```System Configurator```行をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623535/d5473ccf-1871-bc05-78f5-460831696b01.png)

**3\. 検索画面が表示されます。**
```Name```欄に```USE_EMAIL_FOR_LOGIN```を入力し、検索します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623535/ceb3d7b9-b67e-dedb-c2e8-9738bd969b5b.png)

**4\. USE_EMAIL_FOR_LOGINの設定内容が表示されます。**
```Configured Value```を```N```にします。
変更したら```保存```ボタンをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623535/18996d66-46c9-ece8-9015-5ce4fa85ae43.png)

以上でメールアドレスによるログインの無効化の設定は完了です。

iDempiere画面からの設定は全て完了しました。
つづいて、Eclipseを開きソースコードを一部修正します。

## Ⅶ.iDempiereのソースコード改修点
修正点は、RDN識別子名称の変更です。
デフォルトのuidがログイン関数による検証でどうしてもエラーになります。
RDN識別子でcnを使用するようにします。

### 1) RDN識別子の変更
* **対象のパッケージ：** org.compiere.db
* **修正クラス:** LDAP
* **修正ソースファイル:** org.adempiere.base/src/org/compiere/db/LDAP.java

validateメソッド内の以下の点を修正します。
```diff_java
	public static boolean validate (String ldapURL, String domain, String userName, String password)
	{
		Hashtable<String,String> env = new Hashtable<String,String>();
		env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
		//	ldap://dc.compiere.org
		env.put(Context.PROVIDER_URL, ldapURL);
		env.put(Context.SECURITY_AUTHENTICATION, "simple");
		StringBuilder principal;
		if ("openldap".equals(MSysConfig.getValue(MSysConfig.LDAP_TYPE))) {
-			principal = new StringBuilder("uid=").append(userName).append(",").append(domain);
+            principal = new StringBuilder("CN=").append(userName).append(",").append(domain);

		} else {
			principal = new StringBuilder(userName).append("@").append(domain);
		}
		env.put(Context.SECURITY_PRINCIPAL, principal.toString());
		env.put(Context.SECURITY_CREDENTIALS, password);
		//
		try
		{
			// Create the initial context
			InitialLdapContext ctx = new InitialLdapContext(env, null);
		//	DirContext ctx = new InitialDirContext(env);
			
			//	Test - Get the attributes
			ctx.getAttributes("");

		    // Print the answer
		    //if (false)
		    // dump (answer);
		}
		catch (AuthenticationException e)
		{
			if (log.isLoggable(Level.INFO)) log.info("Error: " + principal + " - " + e.getLocalizedMessage());
			return false;
		}
		catch (Exception e) 
		{
			log.log (Level.SEVERE, ldapURL + " - " + principal, e);
		    return false;
		}
		if (log.isLoggable(Level.INFO)) log.info("OK: " + principal);
		return true;
	}	//	validate

```
Javaコードの修正は以上です。

以上で、LDAP認証の準備は全て整いました。
実際に動作をします。

ソースコードを修正しているので、iDempiereサービスを再起動します。

## Ⅷ.動作確認
LDAP認証によるログインの確認です。
電子メールによる認証からユーザIDによる認証に変更しましたので、先ほどまで入力していた欄が```EMail```から```User```に変わっています。
画面のログイン画面にLDAPに登録したユーザ```ldapuser1```を入力し、パスワードに```user1password```を入力します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623535/3a9edb40-2307-5dbf-b0ae-62506f683ad0.png)

テナント・Role画面が表示されれば、LDAP認証処理は成功です。

ldapuser1ユーザに定義したロール情報が表示されますので、選択して```OK```ボタンによりログインします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623535/ff9f55d2-cfa6-0234-fd92-d0dadee002a1.png)

以下のトップ画面が表示されればログイン完了です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623535/81858c46-5f8e-d8de-ac5e-1bc28151bd41.png)

## Ⅸ.LDAP認証と運用時の課題
LDAP認証とその運用における課題についてです。今回はOpenLDAPを用いた認証機能を検証しましたが、いくつかの課題が浮かび上がりました。もしかすると設定等で回避することができるかもしれませんが、以下にそれらの課題をいくつか紹介いたします。

**1\. LDAPサーバのDN名が固定（ouの指定を含む）**
iDempiereのLDAPサーバ参照時のDN（LDAP Domain）の定義が1つしかなく、ユーザ認証のために使用するou=users,dc=erp,dc=workの定義を固定で設定する必要がありました。

**2\. ログインユーザのロールに応じたLDAP内のouの定義がiDempiereの仕様上難しい（階層状の定義など）**
ログインするユーザのロール階層に基づいてLDAPの階層を一致させることができませんでした。実際の運用では、LDAP上の定義として課部署など階層状に登録することが想定されますが、ログイン時の認証認可の制御については、認証(課部署情報を含まない)にはLDAPを使用し、認可についてはiDempiereの設定に依存する形になりました。

**3\. ソースコードの改修が必要**
OpenLDAPで認証する際、標準の実装ではRDN識別子としてuidが記述されており、InitialLdapContextメソッドによる認証の検証処理が正常に行えませんでした。OpenLDAPにユーザを登録する際に適用するObjectClassによっては、uidが存在しているものの、認証処理でエラーが発生しました。posixAccountの適用や個別にuidを追加したユーザを登録しても、認証エラーによりログインができませんでした。


## Ⅹ.次回は
今回は、認証機能の第1弾として**OpenLDAP認証**について動作確認を行いました。
次回も他の認証機能を用いたユーザ認証処理について調査を進めたいと思います。