1. 序章

この記事では、純粋なJavaを使用してLDAPでユーザーを認証する方法について説明します。 さらに、ユーザーの識別名(DN)を検索する方法についても説明します。 LDAPはユーザーを認証するためにDNを必要とするため、これは重要です。

検索とユーザー認証を行うには、Javaネーミングおよびディレクトリインターフェイス( JNDI )のディレクトリサービスアクセス機能を使用します。

最初に、LDAPとJNDIとは何かについて簡単に説明します。 次に、JNDIAPIを介してLDAPで認証する方法について説明します。

2. LDAPとは何ですか?

ライトウェイトディレクトリアクセスプロトコル(LDAP)は、クライアントがディレクトリサービスから要求を送信および応答する方法を定義します。 このプロトコルを使用してディレクトリサービスをLDAPサーバーと呼びます。

LDAPサーバーによって提供されるデータは、X.500に基づく情報モデルに格納されます。 これは、電子ディレクトリサービスのコンピュータネットワーク標準のグループです。

3. JNDIとは何ですか?

JNDIは、アプリケーションがネーミングおよびディレクトリサービスを検出してアクセスするための標準APIを提供します。 その基本的な目的は、アプリケーションがコンポーネントやリソースにアクセスする方法を提供することです。 これは、ローカルとネットワークの両方で行われます。

階層名前空間内の名前によるサービス、データ、またはオブジェクトへのシングルポイントアクセスを提供するため、この機能の基礎となる名前付けサービス。 これらのローカルリソースまたはネットワークアクセス可能なリソースのそれぞれに付けられた名前は、ネーミングサービスをホストするサーバーで構成されます。

JNDI のネーミングサービスインターフェイスを使用して、LDAPなどのディレクトリサービスにアクセスできます。 これは、ディレクトリサービスが、名前付きエントリごとに属性のリストを関連付けることができる特殊なタイプのネーミングサービスにすぎないためです。

属性に加えて、各ディレクトリエントリには1つ以上の子が含まれる場合があります。 これにより、エントリを階層的にリンクできます。 JNDIでは、ディレクトリエントリの子は、親コンテキストのサブコンテキストとして表されます。

JNDI APIの主な利点は、LDAPなどの基盤となるサービスプロバイダーの実装から独立していることです。 したがって、JNDIを使用して、プロトコル固有のAPIを使用せずにLDAPディレクトリサービスにアクセスできます。

JNDIはJavaSEプラットフォームの一部であるため、JNDIを使用するために外部ライブラリは必要ありません。 さらに、Java EEのコアテクノロジーであるため、エンタープライズアプリケーションの実装に広く使用されています。

4. LDAPで認証するためのJNDIAPIの概念

サンプルコードについて説明する前に、LDAPベースの認証にJNDIAPIを使用するための基本事項について説明します。

LDAPサーバーに接続するには、最初にJNDI InitialDirContextオブジェクトを作成する必要があります。 その際、環境プロパティをHashtableとしてコンストラクターに渡して構成する必要があります。

特に、この Hashtable に、認証に使用するユーザー名とパスワードのプロパティを追加する必要があります。 そのためには、ユーザーのDNとパスワードをそれぞれContext.SECURITY_PRINCIPALプロパティとContext.SECURITY_CREDENTIALSプロパティに設定する必要があります。

InitialDirContext は、メインのJNDIディレクトリサービスインターフェイスであるDirContextを実装します。 このインターフェイスを介して、新しいコンテキストを使用して、LDAPサーバーでさまざまなディレクトリサービス操作を実行できます。 これには、名前と属性をオブジェクトにバインドしたり、ディレクトリエントリを検索したりすることが含まれます。

JNDIによって返されるオブジェクトは、基になるLDAPエントリと同じ名前と属性を持つことに注意してください。 したがって、エントリを検索するには、その名前と属性を基準として使用して検索できます。

JNDIを介してディレクトリエントリを取得したら、Attributesインターフェイスを使用してその属性を確認できます。 さらに、Attributeインターフェースを使用してそれぞれを検査できます。

5. ユーザーのDNがない場合はどうなりますか?

場合によっては、認証にすぐに使用できるユーザーのDNがないことがあります。 これを回避するには、最初に管理者の資格情報を使用してInitialDirContextを作成する必要があります。 その後、それを使用して、ディレクトリサーバーから関連するユーザーを検索し、そのDNを取得できます。

次に、ユーザーのDNを取得したら、新しい InitialDirContext を作成することでユーザーを認証できます。今回は、ユーザーの資格情報を使用します。 これを行うには、最初に環境プロパティでユーザーのDNとパスワードを設定する必要があります。 その後、作成時にこれらのプロパティをInitDirContextのコンストラクターに渡す必要があります。

JNDI APIを使用してLDAPを介してユーザーを認証する方法について説明したので、サンプルコードを見ていきましょう。

6. サンプルコード

この例では、ApacheDSディレクトリサーバーの埋め込みバージョンを使用します。 これは、Javaを使用して構築され、単体テスト内で組み込みモードで実行するように設計されたLDAPサーバーです。

設定方法を見てみましょう。

6.1. 組み込みApacheDSサーバーのセットアップ

組み込みのApacheDSサーバーを使用するには、Maven依存関係を定義する必要があります。

<dependency>
    <groupId>org.apache.directory.server</groupId>
    <artifactId>apacheds-test-framework</artifactId>
    <version>2.0.0.AM26</version>
    <scope>test</scope>
</dependency>

次に、JUnit4を使用して単体テストクラスを作成する必要があります。 このクラスで組み込みのApacheDSサーバーを使用するには、ApacheDSライブラリからAbstractLdapTestUnitを拡張することを宣言する必要があります。 このライブラリはまだJUnit5と互換性がないため、JUnit4を使用する必要があります。

さらに、サーバーを構成するには、単体テストクラス宣言の上にJavaアノテーションを含める必要があります。 完全なコード例から、どの値を与えるかを確認できます。これについては後で説明します。

最後に、users.ldifもクラスパスに追加する必要があります。 これは、コード例を実行するときに、ApacheDSサーバーがこのファイルからLDIF形式のディレクトリエントリをロードできるようにするためです。 その際、サーバーはユーザー JoeSimmsのエントリをロードします。

次に、ユーザーを認証するサンプルコードについて説明します。 LDAPサーバーに対して実行するには、単体テストクラスのメソッドにコードを追加する必要があります。 これにより、ファイルで定義されているように、DNとパスワードを使用してLDAPを介してジョーが認証されます。

6.2. ユーザーの認証

ユーザーJoeSimms を認証するには、新しいInitialDirContextオブジェクトを作成する必要があります。 これにより、ディレクトリサーバーへの接続が作成され、DNとパスワードを使用してLDAPを介してユーザーが認証されます。

これを行うには、最初にこれらの環境プロパティをHashtableに追加する必要があります。

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

environment.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
environment.put(Context.PROVIDER_URL, "ldap://localhost:10389");
environment.put(Context.SECURITY_AUTHENTICATION, "simple");
environment.put(Context.SECURITY_PRINCIPAL, "cn=Joe Simms,ou=Users,dc=baeldung,dc=com");
environment.put(Context.SECURITY_CREDENTIALS, "12345");

次に、 authenticateUser という新しいメソッド内で、環境プロパティをコンストラクターに渡してInitialDirContextオブジェクトを作成します。 次に、コンテキストを閉じてリソースを解放します。

DirContext context = new InitialDirContext(environment);
context.close();

最後に、ユーザーを認証します。

assertThatCode(() -> authenticateUser(environment)).doesNotThrowAnyException();

ユーザー認証が成功する場合について説明したので、失敗するタイミングを調べてみましょう。

6.3. ユーザー認証の失敗の処理

以前と同じ環境プロパティを適用して、間違ったパスワードを使用して認証を失敗させましょう。

environment.put(Context.SECURITY_CREDENTIALS, "wrongpassword");

次に、このパスワードを使用したユーザーの認証が期待どおりに失敗することを確認します。

assertThatExceptionOfType(AuthenticationException.class).isThrownBy(() -> authenticateUser(environment));

次に、DNがない場合にユーザーを認証する方法について説明します。

6.4. 管理者としてユーザーのDNを検索する

ユーザーを認証したい場合、すぐにユーザーのDNが手元にないことがあります。 この状況では、最初に管理者の資格情報を使用してディレクトリコンテキストを作成し、ユーザーのDNを検索してから、それを使用してユーザーを認証する必要があります。

前と同じように、最初にHashtableにいくつかの環境プロパティを追加する必要があります。 ただし、今回は、管理者のDNを Context.SECURITY_PRINCIPAL として使用し、デフォルトの管理者パスワードをContext.SECURITY_CREDENTIALSプロパティとして使用します。

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

environment.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
environment.put(Context.PROVIDER_URL, "ldap://localhost:10389");
environment.put(Context.SECURITY_AUTHENTICATION, "simple");
environment.put(Context.SECURITY_PRINCIPAL, "uid=admin,ou=system");
environment.put(Context.SECURITY_CREDENTIALS, "secret");

次に、これらのプロパティを使用してInitialDirContextオブジェクトを作成します。

DirContext adminContext = new InitialDirContext(environment);

これにより、管理者として認証されたサーバーへの接続を使用して、ディレクトリコンテキストが作成されます。 これにより、ユーザーのDNを検索するためのセキュリティ権限が付与されます。

次に、ユーザーのCN、つまりユーザーの一般名に基づいて検索用のフィルターを定義します。

String filter = "(&(objectClass=person)(cn=Joe Simms))";

次に、この filter を使用してユーザーを検索し、SearchControlsオブジェクトを作成します。

String[] attrIDs = { "cn" };
SearchControls searchControls = new SearchControls();
searchControls.setReturningAttributes(attrIDs);
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);

次に、filterSearchControlsを使用してユーザーを検索します。

NamingEnumeration<SearchResult> searchResults
  = adminContext.search("dc=baeldung,dc=com", filter, searchControls);
  
String commonName = null;
String distinguishedName = null;
if (searchResults.hasMore()) {
    
    SearchResult result = (SearchResult) searchResults.next();
    Attributes attrs = result.getAttributes();
    
    distinguishedName = result.getNameInNamespace();
    assertThat(distinguishedName, isEqualTo("cn=Joe Simms,ou=Users,dc=baeldung,dc=com")));

    commonName = attrs.get("cn").toString();
    assertThat(commonName, isEqualTo("cn: Joe Simms")));
}

DNができたので、ユーザーを認証しましょう。

6.5. ユーザーのルックアップDNを使用した認証

ユーザーのDNを認証できるようになったら、既存の環境プロパティの管理者のDNとパスワードをユーザーのDNとパスワードに置き換えます。

environment.put(Context.SECURITY_PRINCIPAL, distinguishedName);
environment.put(Context.SECURITY_CREDENTIALS, "12345");

次に、これらを配置したら、ユーザーを認証しましょう。

assertThatCode(() -> authenticateUser(environment)).doesNotThrowAnyException();

最後に、管理者のコンテキストを閉じてリソースを解放します。

adminContext.close();

7. 結論

この記事では、JNDIを使用して、ユーザーのDNとパスワードを使用してLDAPでユーザーを認証する方法について説明しました。

また、DNがない場合にDNを検索する方法を検討しました。

いつものように、例の完全なソースコードは、GitHubにあります。