Spring LDAP を使用すると、Lightweight Directory Access Protocol を使用する Spring ベースのアプリケーションを簡単に構築できます。

このドキュメントのコピーは、印刷物または電子的に配布されるかどうかにかかわらず、そのようなコピーに料金を請求せず、さらに各コピーにこの著作権表示が含まれている場合に限り、自分で使用したり他の人に配布したりするために作成できます

1. 序文

Java Naming and Directory Interface (JNDI) は LDAP プログラミングにとって、Java Database Connectivity (JDBC) は SQL プログラミングにとって重要です。JDBC と JNDI/LDAP (Java LDAP) にはいくつかの類似点があります。長所と短所が異なる 2 つの完全に異なる API であるにもかかわらず、それらはいくつかのあまりお世辞の特徴を共有しています。

  • 最も単純なタスクを実行する場合でも、大規模な接続機能コードが必要です。

  • 何が起こっても、すべてのリソースを正しく閉じる必要があります。

  • 例外処理が難しい。

これらの点は、API の一般的なユースケースで大量のコード重複につながることがよくあります。誰もが知っているように、コードの重複は最悪の「コード臭」の 1 つです。つまり、Java での JDBC と LDAP のプログラミングは、どちらも信じられないほど退屈で反復的です。

Spring Framework のコアコンポーネントである Spring JDBC は、SQL プログラミングを簡素化するための優れたユーティリティを提供します。Java LDAP プログラミングにも同様のフレームワークが必要です。

2. 導入

このセクションでは、Spring LDAP について比較的簡単に導入します。次の内容が含まれます。

2.1. 概要

Spring LDAP は、Java での LDAP プログラミングを簡素化するように設計されています。ライブラリによって提供される機能の一部は次のとおりです。

  • JdbcTemplate (Javadoc) -LDAP プログラミングに対するスタイルテンプレートの簡素化。

  • JPA または Hibernate スタイルのアノテーションベースのオブジェクトとディレクトリのマッピング。

  • QueryDSL のサポートを含む、Spring Data リポジトリのサポート。

  • LDAP クエリと識別名の作成を簡素化するユーティリティ。

  • 適切な LDAP 接続プール。

  • クライアント側の LDAP 補正トランザクションのサポート。

2.2. 従来の Java LDAP と LdapTemplate の比較

すべての人のストレージを検索し、その名前をリストで返すメソッドを考えてみましょう。JDBC を使用して接続を作成し、ステートメントを使用してクエリを実行します。次に、結果セットをループして目的のを取得し、リストに追加します。

JNDI を使用して LDAP データベースに対して作業する場合、コンテキストを作成し、検索フィルターを使用して検索を実行します。次に、結果の名前の列挙をループし、必要な属性を取得してリストに追加します。

Java LDAP でこの人名検索メソッドを実装する従来の方法は、次の例のようになります。太字で示されているコードに注意してください。これは、メソッドのビジネス目的に関連するタスクを実際に実行するコードです。残りは接続機能です。

package com.example.repository;

public class TraditionalPersonRepoImpl implements PersonRepo {
   public List<String> getAllPersonNames() {
      Hashtable env = new Hashtable();
      env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
      env.put(Context.PROVIDER_URL, "ldap://localhost:389/dc=example,dc=com");

      DirContext ctx;
      try {
         ctx = new InitialDirContext(env);
      } catch (NamingException e) {
         throw new RuntimeException(e);
      }

      List<String> list = new LinkedList<String>();
      NamingEnumeration results = null;
      try {
         SearchControls controls = new SearchControls();
         controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
         results = ctx.search("", "(objectclass=person)", controls);

         while (results.hasMore()) {
            SearchResult searchResult = (SearchResult) results.next();
            Attributes attributes = searchResult.getAttributes();
            Attribute attr = attributes.get("cn");
            String cn = attr.get().toString();
            list.add(cn);
         }
      } catch (NameNotFoundException e) {
         // The base context was not found.
         // Just clean up and exit.
      } catch (NamingException e) {
         throw new RuntimeException(e);
      } finally {
         if (results != null) {
            try {
               results.close();
            } catch (Exception e) {
               // Never mind this.
            }
         }
         if (ctx != null) {
            try {
               ctx.close();
            } catch (Exception e) {
               // Never mind this.
            }
         }
      }
      return list;
   }
}

Spring LDAP AttributesMapper および LdapTemplate クラスを使用すると、次のコードでまったく同じ機能が得られます。

package com.example.repo;
import static org.springframework.ldap.query.LdapQueryBuilder.query;

public class PersonRepoImpl implements PersonRepo {
   private LdapTemplate ldapTemplate;

   public void setLdapTemplate(LdapTemplate ldapTemplate) {
      this.ldapTemplate = ldapTemplate;
   }

   public List<String> getAllPersonNames() {
      return ldapTemplate.search(
         query().where("objectclass").is("person"),
         new AttributesMapper<String>() {
            public String mapFromAttributes(Attributes attrs)
               throws NamingException {
               return attrs.get("cn").get().toString();
            }
         });
   }
}

定型コードの量は、従来の例よりも大幅に少なくなります。LdapTemplate 検索メソッドは、DirContext インスタンスが作成されていることを確認し、検索を実行し、指定された AttributesMapper を使用して属性を文字列にマップし、文字列を内部リストに収集し、最後にリストを返します。また、NamingEnumeration と DirContext が適切に閉じられていることを確認し、発生する可能性のある例外を処理します。

当然、これは Spring Framework サブプロジェクトであるため、次のように Spring を使用してアプリケーションを構成します。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:ldap="http://www.springframework.org/schema/ldap"
       xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/ldap https://www.springframework.org/schema/ldap/spring-ldap.xsd">

   <ldap:context-source
          url="ldap://localhost:389"
          base="dc=example,dc=com"
          username="cn=Manager"
          password="secret" />

   <ldap:ldap-template id="ldapTemplate" />

   <bean id="personRepo" class="com.example.repo.PersonRepoImpl">
      <property name="ldapTemplate" ref="ldapTemplate" />
   </bean>
</beans>
カスタム XML 名前空間を使用して Spring LDAP コンポーネントを構成するには、前の例のように、XML 宣言にこの名前空間への参照を含める必要があります。

2.3. 2.2 の新機能

2.2 の詳細については、2.2.0.RC1 [GitHub] (英語) の変更ログを参照してください。Spring LDAP 2.2 のハイライトは次のとおりです。

2.4. 2.1 の新機能

2.1 の詳細については、2.1.0.RC1 [GitHub] (英語) および 2.1.0 [GitHub] (英語) の変更ログを参照してください。Spring LDAP 2.1 のハイライトは次のとおりです。

2.5. 2.0 の新機能

バージョン 2.0 では、Spring LDAP API に対して大幅な最新化が行われましたが、下位互換性を可能な限り確保するために細心の注意が払われました。Spring LDAP 1.3.x で動作するコードは、変更を加えずに 2.0 ライブラリを使用する場合、いくつかの例外を除いてコンパイルおよび実行する必要があります。

例外は、いくつかの重要なリファクタリングを可能にするために新しいパッケージに移動された少数のクラスです。移動されたクラスは通常、意図したパブリック API の一部ではなく、移行手順はスムーズに行われるはずです。アップグレード後に Spring LDAP クラスが見つからない場合は常に、IDE でインポートを整理する必要があります。

ただし、いくつかの非推奨の警告が表示されることが予想されます。また、他にも多くの API の改善が行われています。2.0 バージョンを可能な限り活用するための推奨事項は、非推奨のクラスとメソッドから離れて、新しく改善された API ユーティリティに移行することです。

次のリストは、Spring LDAP 2.0 の最も重要な変更点を簡単に説明しています。

  • Java 6 は Spring LDAP で必要になりました。2.0 以降の Spring バージョンは引き続きサポートされます。

  • セントラル API は、ジェネリクスや可変引数などの Java 5+ 機能で更新されました。その結果、spring-ldap-tiger モジュール全体が非推奨になりました。コア Spring LDAP クラスを使用するように移行することをお勧めします。コアインターフェースのパラメーター化により、既存のコードで多くのコンパイル警告が発生します。これらの警告を取り除くために適切な措置を講じることをお勧めします。

  • ODM (Object-Directory Mapping) 機能がコアに移動され、ODM アノテーション付きクラスとの間でこの自動変換を使用する新しいメソッドが LdapOperations および LdapTemplate に追加されました。詳細については、オブジェクトディレクトリマッピング (ODM) を参照してください。

  • Spring LDAP の構成を簡素化するために、カスタム XML 名前空間が (最終的に) 提供されるようになりました。詳細については、構成を参照してください。

  • Spring LDAP は、Spring Data リポジトリと QueryDSL をサポートするようになりました。詳細については、Spring LDAP リポジトリを参照してください。

  • 属性値としての Name インスタンスが、DirContextAdapter および ODM での識別名の同等性に関して適切に処理されるようになりました。詳細については、属性値としての DirContextAdapter および識別名および属性値としての ODM および識別名を参照してください。

  • DistinguishedName および関連するクラスは、標準の Java LdapName を推奨して非推奨となりました。LdapName オブジェクトを操作するときにライブラリがどのように役立つかについては、識別名を動的に構築するを参照してください。

  • Fluent LDAP クエリ構築のサポートが追加されました。これにより、Spring LDAP で LDAP 検索を行う際のプログラミングがより快適になります。LDAP クエリビルダーのサポートの詳細については、LDAP クエリの構築および高度な LDAP クエリを参照してください。

  • LdapTemplate の古い authenticate メソッドは、LdapQuery オブジェクトで動作し、認証失敗時に例外をスローするいくつかの新しい authenticate メソッドを推奨して非推奨になりました。これにより、認証の試行が失敗した原因をユーザーが見つけやすくなります。

  • サンプル [GitHub] (英語) は、 2.0 の機能を利用するために洗練され、更新されています。LDAP ユーザー管理アプリケーション [GitHub] (英語) の有用な例を提供するためにかなりの労力が費やされました。

2.6. パッケージの概要

Spring LDAP を使用するには、少なくとも次のものが必要です。

  • spring-ldap-core: Spring LDAP ライブラリ

  • spring-core: フレームワークによって内部的に使用されるその他のユーティリティクラス

  • spring-beans: Java Bean を操作するためのインターフェースとクラス

  • slf4j: 内部で使用される単純なロギングファサード

必須の依存関係に加えて、特定の機能には次のオプションの依存関係が必要です。

  • spring-data-ldap: リポジトリサポートなどの基本インフラストラクチャ

  • spring-context: アプリケーションが Spring アプリケーションコンテキストを使用して接続されている場合に必要です。spring-context は、アプリケーションオブジェクトが一貫した API を使用してリソースを取得する機能を追加します。BaseLdapPathBeanPostProcessor を使用する予定がある場合は、必ず必要です。

  • spring-tx: クライアント側の補正トランザクションサポートを使用する場合に必要です。

  • spring-jdbc: クライアント側の補正トランザクションサポートを使用する場合に必要です。

  • commons-pool: プーリング機能を使用する場合に必要です。

  • spring-batch: LDIF 解析機能を Spring Batch と一緒に使用する場合に必要です。

spring-data-ldap は、spring-ldap.xsd が使用する spring-repository.xsd を推移的に追加します。このため、Spring Data の機能セットが使用されていない場合でも、Spring LDAP の XML 構成サポートには依存関係が必要です。

2.7. 入門

サンプル [GitHub] (英語) は、一般的な使用例で Spring LDAP を使用する方法のいくつかの有用な例を提供します。

2.8. サポート

質問がある場合は、spring-ldap タグを使用したスタックオーバーフロー (英語) で質問してください。プロジェクトの Web ページは https://spring.io/spring-ldap/ です。

2.9. 謝辞

Spring LDAP プロジェクトを開始する最初の取り組みは、ジェイウェイ (英語) によって後援されました。プロジェクトの現在のメンテナンスはピボタル によって資金提供されており、その後 VMware (英語) によって買収されました。

プロジェクト構造をチェックするのに便利なオープンソースライセンスを提供してくれたストラクチャー 101 (英語) に感謝します。

3. 基本的な使い方

このセクションでは、Spring LDAP の使用の基本について説明します。次の内容が含まれます。

3.1. AttributesMapper を使用した検索とルックアップ

次の例では、AttributesMapper (Javadoc) を使用して、すべての人物オブジェクトのすべての共通名のリストを作成します。

例 1: 単一の属性を返す AttributesMapper 
package com.example.repo;
import static org.springframework.ldap.query.LdapQueryBuilder.query;

public class PersonRepoImpl implements PersonRepo {
   private LdapTemplate ldapTemplate;

   public void setLdapTemplate(LdapTemplate ldapTemplate) {
      this.ldapTemplate = ldapTemplate;
   }

   public List<String> getAllPersonNames() {
      return ldapTemplate.search(
         query().where("objectclass").is("person"),
         new AttributesMapper<String>() {
            public String mapFromAttributes(Attributes attrs)
               throws NamingException {
               return (String) attrs.get("cn").get();
            }
         });
   }
}

AttributesMapper のインライン実装は、Attributes オブジェクトから必要な属性値を取得して返します。内部的に、LdapTemplate は見つかったすべてのエントリを繰り返し処理し、各エントリに対して指定された AttributesMapper を呼び出し、結果をリストに収集します。その後、リストは search メソッドによって返されます。

AttributesMapper 実装は、次のように完全な Person オブジェクトを返すように簡単に変更できることに注意してください。

例 2: Person オブジェクトを返す AttributesMapper
package com.example.repo;
import static org.springframework.ldap.query.LdapQueryBuilder.query;

public class PersonRepoImpl implements PersonRepo {
   private LdapTemplate ldapTemplate;
   ...
   private class PersonAttributesMapper implements AttributesMapper<Person> {
      public Person mapFromAttributes(Attributes attrs) throws NamingException {
         Person person = new Person();
         person.setFullName((String)attrs.get("cn").get());
         person.setLastName((String)attrs.get("sn").get());
         person.setDescription((String)attrs.get("description").get());
         return person;
      }
   }

   public List<Person> getAllPersons() {
      return ldapTemplate.search(query()
          .where("objectclass").is("person"), new PersonAttributesMapper());
   }
}

LDAP のエントリは、識別名 (DN) によって一意に識別されます。エントリの DN がある場合は、エントリを検索せずに直接取得できます。これは、Java LDAP では「ルックアップ」と呼ばれます。次の例は、Person オブジェクトのルックアップを示しています。

例 3: Person オブジェクトになるルックアップ
package com.example.repo;

public class PersonRepoImpl implements PersonRepo {
   private LdapTemplate ldapTemplate;
   ...
   public Person findPerson(String dn) {
      return ldapTemplate.lookup(dn, new PersonAttributesMapper());
   }
}

前の例では、指定された DN を検索し、見つかった属性を提供された AttributesMapper に渡します。この場合、結果は Person オブジェクトになります。

3.2. LDAP クエリの構築

LDAP 検索には、次のような多くのパラメーターが含まれます。

  • ベース LDAP パス: 検索を開始する LDAP ツリー内の場所。

  • 検索の範囲: LDAP ツリーのどの深さまで検索するか。

  • 返す属性。

  • 検索フィルター: スコープ内の要素を選択するときに使用する条件。

Spring LDAP は、LDAP クエリを構築するための流れるような API を備えた LdapQueryBuilder (Javadoc) を提供します。

ベース DN dc=261consulting,dc=com から始まる検索を実行し、返される属性を cn および sn に制限し、(&(objectclass=person)(sn=?)) のフィルターを使用して、? を lastName パラメーターの値に置き換えたいとします。次の例は、LdapQueryBuilder を使用してそれを行う方法を示しています。

例 4: 検索フィルターを動的に構築する
package com.example.repo;
import static org.springframework.ldap.query.LdapQueryBuilder.query;

public class PersonRepoImpl implements PersonRepo {
   private LdapTemplate ldapTemplate;
   ...
   public List<String> getPersonNamesByLastName(String lastName) {

      LdapQuery query = query()
         .base("dc=261consulting,dc=com")
         .attributes("cn", "sn")
         .where("objectclass").is("person")
         .and("sn").is(lastName);

      return ldapTemplate.search(query,
         new AttributesMapper<String>() {
            public String mapFromAttributes(Attributes attrs)
               throws NamingException {

               return (String) attrs.get("cn").get();
            }
         });
   }
}
複雑な検索パラメーターの構築を簡素化するだけでなく、LdapQueryBuilder とその関連クラスは、検索フィルター内の安全でない文字を適切にエスケープする機能も提供します。これにより、ユーザーがそのような文字を使用して不要な操作を LDAP 操作に挿入する「LDAP インジェクション」を防止できます。
LdapTemplate には、LDAP 検索を実行するためのオーバーロードされたメソッドが多数含まれています。これは、可能な限り多くの異なるユースケースとプログラミングスタイルの設定に対応するためです。ほとんどのユースケースでは、入力として LdapQuery を使用するメソッドを使用することをお勧めします。
AttributesMapper は、検索およびルックアップデータを処理するときに使用できる利用可能なコールバックインターフェースの 1 つにすぎません。代替については、DirContextAdapter による属性アクセスと操作の簡素化を参照してください。

LdapQueryBuilder の詳細については、高度な LDAP クエリを参照してください。

3.3. 識別名を動的に構築する

識別名 ( LdapName (標準 Javadoc) (英語) ) の標準 Java 実装は、識別名の解析に関しては良好に機能します。ただし、実際に使用すると、この実装にはいくつかの欠点があります。

  • LdapName 実装は変更可能であり、ID を表すオブジェクトにはあまり適していません。

  • 変更可能な性質にもかかわらず、LdapName を使用して識別名を動的に構築または変更するための API は扱いにくいものです。インデックス付きまたは (特に) 名前付きコンポーネントの値を抽出するのも、少し厄介です。

  • LdapName の操作の多くは、チェック例外をスローします。通常、エラーが致命的であり、意味のある方法で修復できない状況では、try-catch ステートメントが必要です。

識別名の操作を簡素化するために、Spring LDAP は LdapNameBuilder (Javadoc) と、LdapName を操作するときに役立つ LdapUtils (Javadoc) の多数のユーティリティメソッドを提供します。

3.3.1. サンプル

このセクションでは、前のセクションで説明した内容の例をいくつか紹介します。最初の例では、LdapNameBuilder を使用して LdapName を動的に構築します。

例 5: LdapNameBuilder を使用して LdapName を動的に構築する
package com.example.repo;
import org.springframework.ldap.support.LdapNameBuilder;
import javax.naming.Name;

public class PersonRepoImpl implements PersonRepo {
  public static final String BASE_DN = "dc=example,dc=com";

  protected Name buildDn(Person p) {
    return LdapNameBuilder.newInstance(BASE_DN)
      .add("c", p.getCountry())
      .add("ou", p.getCompany())
      .add("cn", p.getFullname())
      .build();
  }
  ...
}

Person に次の属性があるとします。

属性名 属性値

country

スウェーデン

company

いくつかの会社

fullname

誰か

上記のコードは、次の識別名になります。

cn=Some Person, ou=Some Company, c=Sweden, dc=example, dc=com

次の例では、LdapUtils を使用して識別名から値を抽出します。

例 6: LdapUtils を使用した識別名からの値の抽出
package com.example.repo;
import org.springframework.ldap.support.LdapNameBuilder;
import javax.naming.Name;
public class PersonRepoImpl implements PersonRepo {
...
  protected Person buildPerson(Name dn, Attributes attrs) {
    Person person = new Person();
    person.setCountry(LdapUtils.getStringValue(dn, "c"));
    person.setCompany(LdapUtils.getStringValue(dn, "ou"));
    person.setFullname(LdapUtils.getStringValue(dn, "cn"));
    // Populate rest of person object using attributes.

    return person;
  }
}

1.4 を含む以前の Java バージョンではパブリック識別名の実装がまったく提供されなかったため、Spring LDAP 1.x は独自の実装である DistinguishedName を提供しました。この実装にはいくつかの欠点があり、バージョン 2.0 で非推奨となりました。ここで、LdapName を前述のユーティリティと共に使用する必要があります。

3.4. バインドとアンバインド

このセクションでは、データを追加および削除する方法について説明します。更新については、次のセクションで説明します。

3.4.1. データの追加

Java LDAP にデータを挿入することをバインディングと呼びます。LDAP 用語では、「バインド」はまったく別のものを意味するため、これはやや混乱します。JNDI バインドは LDAP 追加操作を実行し、指定された識別名を持つ新しいエントリを一連の属性に関連付けます。次の例では、LdapTemplate を使用してデータを追加します。

例 7: 属性を使用したデータの追加
package com.example.repo;

public class PersonRepoImpl implements PersonRepo {
   private LdapTemplate ldapTemplate;
   ...
   public void create(Person p) {
      Name dn = buildDn(p);
      ldapTemplate.bind(dn, null, buildAttributes(p));
   }

   private Attributes buildAttributes(Person p) {
      Attributes attrs = new BasicAttributes();
      BasicAttribute ocattr = new BasicAttribute("objectclass");
      ocattr.add("top");
      ocattr.add("person");
      attrs.put(ocattr);
      attrs.put("cn", "Some Person");
      attrs.put("sn", "Person");
      return attrs;
   }
}

手動属性構築は — 退屈で冗長ですが — 多くの目的に十分です。ただし、DirContextAdapter による属性アクセスと操作の簡素化に従って、バインド操作をさらに単純化できます。

3.4.2. データの削除

Java LDAP でのデータの削除は、バインド解除と呼ばれます。JNDI アンバインドは LDAP 削除操作を実行し、指定された識別名に関連付けられたエントリを LDAP ツリーから削除します。次の例では、LdapTemplate を使用してデータを削除します。

例 8: データの削除
package com.example.repo;

public class PersonRepoImpl implements PersonRepo {
   private LdapTemplate ldapTemplate;
   ...
   public void delete(Person p) {
      Name dn = buildDn(p);
      ldapTemplate.unbind(dn);
   }
}

3.5. 更新

Java LDAP では、rebind を使用する方法と modifyAttributes を使用する方法の 2 つの方法でデータを変更できます。

3.5.1. Rebind を使用した更新

rebind は、データを変更する粗雑な方法です。基本的には unbind の後に bind が続きます。次の例では、rebind を使用しています。

例 9: rebind を使用した変更
package com.example.repo;

public class PersonRepoImpl implements PersonRepo {
   private LdapTemplate ldapTemplate;
   ...
   public void update(Person p) {
      Name dn = buildDn(p);
      ldapTemplate.rebind(dn, null, buildAttributes(p));
   }
}

3.5.2. modifyAttributes を使用した更新

データを変更するより洗練された方法は、modifyAttributes を使用することです。この操作は、次のように、明示的な属性変更の配列を受け取り、特定のエントリに対して実行します。

例 10: modifyAttributes を使用した変更
package com.example.repo;

public class PersonRepoImpl implements PersonRepo {
   private LdapTemplate ldapTemplate;
   ...
   public void updateDescription(Person p) {
      Name dn = buildDn(p);
      Attribute attr = new BasicAttribute("description", p.getDescription())
      ModificationItem item = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, attr);
      ldapTemplate.modifyAttributes(dn, new ModificationItem[] {item});
   }
}

Attributes および ModificationItem 配列を構築するのは大変な作業です。ただし、DirContextAdapter による属性アクセスと操作の簡素化で説明しているように、Spring LDAP はこれらの操作を簡素化するためのより多くのヘルプを提供します。

4. DirContextAdapter による属性アクセスと操作の簡素化

あまり知られていない — そしておそらく過小評価されている — Java LDAP API の機能の 1 つは、DirObjectFactory を登録して、見つかった LDAP エントリからオブジェクトを自動的に作成する機能です。Spring LDAP は、この機能を利用して、特定の検索およびルックアップ操作で DirContextAdapter (Javadoc) インスタンスを返します。

DirContextAdapter は、特にデータを追加または変更する場合に、LDAP 属性を操作するための便利なツールです。

4.1. ContextMapper を使用した検索とルックアップ

LDAP ツリーでエントリが見つかると、その属性と識別名 (DN) が Spring LDAP によって使用され、DirContextAdapter が構築されます。これにより、次のように、AttributesMapper の代わりに ContextMapper (Javadoc) を使用して、見つかった値を変換できます。

例 11: ContextMapper を使用した検索
package com.example.repo;

public class PersonRepoImpl implements PersonRepo {
   ...
   private static class PersonContextMapper implements ContextMapper {
      public Object mapFromContext(Object ctx) {
         DirContextAdapter context = (DirContextAdapter)ctx;
         Person p = new Person();
         p.setFullName(context.getStringAttribute("cn"));
         p.setLastName(context.getStringAttribute("sn"));
         p.setDescription(context.getStringAttribute("description"));
         return p;
      }
   }

   public Person findByPrimaryKey(
      String name, String company, String country) {
      Name dn = buildDn(name, company, country);
      return ldapTemplate.lookup(dn, new PersonContextMapper());
   }
}

前の例で示したように、Attributes および Attribute クラスを経由せずに、属性値を名前で直接取得できます。これは、複数値の属性を操作する場合に特に便利です。複数値属性から値を抽出するには、通常、Attributes 実装から返された属性値の NamingEnumeration をループする必要があります。DirContextAdapter は、getStringAttributes() (Javadoc) または getObjectAttributes() (Javadoc) メソッドでこれを行います。次の例では、getStringAttributes メソッドを使用しています。

例 12: getStringAttributes() を使用した複数値属性値の取得
private static class PersonContextMapper implements ContextMapper {
   public Object mapFromContext(Object ctx) {
      DirContextAdapter context = (DirContextAdapter)ctx;
      Person p = new Person();
      p.setFullName(context.getStringAttribute("cn"));
      p.setLastName(context.getStringAttribute("sn"));
      p.setDescription(context.getStringAttribute("description"));
      // The roleNames property of Person is an String array
      p.setRoleNames(context.getStringAttributes("roleNames"));
      return p;
   }
}

4.1.1. AbstractContextMapper を使用する

Spring LDAP は、AbstractContextMapper (Javadoc) と呼ばれる ContextMapper の抽象基本実装を提供します。この実装は、指定された Object パラメーターの DirContexOperations へのキャストを自動的に処理します。AbstractContextMapper を使用すると、前に示した PersonContextMapper を次のように書き直すことができます。

例 13: AbstractContextMapper を使用する
private static class PersonContextMapper extends AbstractContextMapper {
  public Object doMapFromContext(DirContextOperations ctx) {
     Person p = new Person();
     p.setFullName(ctx.getStringAttribute("cn"));
     p.setLastName(ctx.getStringAttribute("sn"));
     p.setDescription(ctx.getStringAttribute("description"));
     return p;
  }
}

4.2. DirContextAdapter を使用したデータの追加と更新

` 属性値を抽出するときに便利ですが、DirContextAdapter はデータの追加と更新に関連する詳細を管理するためにさらに強力です。

4.2.1. DirContextAdapter を使用したデータの追加

次の例では、DirContextAdapter を使用して、データの追加で示されている create リポジトリメソッドの改善された実装を実装しています。

例 14: DirContextAdapter を使用したバインディング
package com.example.repo;

public class PersonRepoImpl implements PersonRepo {
   ...
   public void create(Person p) {
      Name dn = buildDn(p);
      DirContextAdapter context = new DirContextAdapter(dn);

      context.setAttributeValues("objectclass", new String[] {"top", "person"});
      context.setAttributeValue("cn", p.getFullname());
      context.setAttributeValue("sn", p.getLastname());
      context.setAttributeValue("description", p.getDescription());

      ldapTemplate.bind(context);
   }
}

バインドする 2 番目のパラメーターとして DirContextAdapter インスタンスを使用することに注意してください。これは Context である必要があります。属性を明示的に指定しないため、3 番目のパラメーターは null です。

また、objectclass 属性値を設定するときに setAttributeValues() メソッドを使用することにも注意してください。objectclass 属性は複数値です。多値属性データを抽出する際の問題と同様に、多値属性の構築は退屈で冗長な作業です。setAttributeValues() メソッドを使用することで、自分に合った DirContextAdapter ハンドルを使用できます。

4.2.2. DirContextAdapter を使用したデータの更新

modifyAttributes を使用した更新が推奨される方法であることは以前に説明しましたが、そのためには、属性の変更を計算し、それに応じて ModificationItem 配列を構築するタスクを実行する必要があります。DirContextAdapter は、次のように、これらすべてを実行できます。

例 15: DirContextAdapter を使用した更新
package com.example.repo;

public class PersonRepoImpl implements PersonRepo {
   ...
   public void update(Person p) {
      Name dn = buildDn(p);
      DirContextOperations context = ldapTemplate.lookupContext(dn);

      context.setAttributeValue("cn", p.getFullname());
      context.setAttributeValue("sn", p.getLastname());
      context.setAttributeValue("description", p.getDescription());

      ldapTemplate.modifyAttributes(context);
   }
}

マッパーが ldapTemplate.lookup() に渡されない場合、結果は DirContextAdapter インスタンスになります。lookup メソッドは Object を返しますが、lookupContext コンビニエンスメソッドメソッドは、戻り値を DirContextOperations (DirContextAdapter が実装するインターフェース) に自動的にキャストします。

create メソッドと update メソッドに重複したコードがあることに注意してください。このコードは、ドメインオブジェクトからコンテキストにマップされます。次のように、別のメソッドに抽出できます。

例 16: DirContextAdapter を使用した追加と変更
package com.example.repo;

public class PersonRepoImpl implements PersonRepo {
   private LdapTemplate ldapTemplate;

   ...
   public void create(Person p) {
      Name dn = buildDn(p);
      DirContextAdapter context = new DirContextAdapter(dn);

      context.setAttributeValues("objectclass", new String[] {"top", "person"});
      mapToContext(p, context);
      ldapTemplate.bind(context);
   }

   public void update(Person p) {
      Name dn = buildDn(p);
      DirContextOperations context = ldapTemplate.lookupContext(dn);
      mapToContext(person, context);
      ldapTemplate.modifyAttributes(context);
   }

   protected void mapToContext (Person p, DirContextOperations context) {
      context.setAttributeValue("cn", p.getFullName());
      context.setAttributeValue("sn", p.getLastName());
      context.setAttributeValue("description", p.getDescription());
   }
}

4.3. 属性値としての DirContextAdapter および識別名

LDAP でセキュリティグループを管理する場合、識別名を表す属性値を持つのが一般的です。識別名の等価性は文字列の等価性とは異なるため (たとえば、識別名の等価性では空白と大文字と小文字の違いは無視されます)、文字列の等価性を使用した属性変更の計算は期待どおりに機能しません。

たとえば、member 属性の値が cn=John Doe,ou=People で、ctx.addAttributeValue("member", "CN=John Doe, OU=People") を呼び出す場合、文字列が実際には同じ識別名を表している場合でも、属性は 2 つの値を持つと見なされます。

Spring LDAP 2.0 の時点で、属性変更メソッドに javax.naming.Name インスタンスを提供すると、DirContextAdapter は属性変更を計算するときに識別名の等価性を使用するようになります。前の例を ctx.addAttributeValue("member", LdapUtils.newLdapName("CN=John Doe, OU=People")) に変更すると、次の例に示すように、変更はレンダリングされません。

例 17: DirContextAdapter を使用したグループメンバーシップの変更
public class GroupRepo implements BaseLdapNameAware {
    private LdapTemplate ldapTemplate;
    private LdapName baseLdapPath;

    public void setLdapTemplate(LdapTemplate ldapTemplate) {
        this.ldapTemplate = ldapTemplate;
    }

    public void setBaseLdapPath(LdapName baseLdapPath) {
        this.setBaseLdapPath(baseLdapPath);
    }

    public void addMemberToGroup(String groupName, Person p) {
        Name groupDn = buildGroupDn(groupName);
        Name userDn = buildPersonDn(
            person.getFullname(),
            person.getCompany(),
            person.getCountry());

        DirContextOperation ctx = ldapTemplate.lookupContext(groupDn);
        ctx.addAttributeValue("member", userDn);

        ldapTemplate.update(ctx);
    }

    public void removeMemberFromGroup(String groupName, Person p) {
        Name groupDn = buildGroupDn(String groupName);
        Name userDn = buildPersonDn(
            person.getFullname(),
            person.getCompany(),
            person.getCountry());

        DirContextOperation ctx = ldapTemplate.lookupContext(groupDn);
        ctx.removeAttributeValue("member", userDn);

        ldapTemplate.update(ctx);
    }

    private Name buildGroupDn(String groupName) {
        return LdapNameBuilder.newInstance("ou=Groups")
            .add("cn", groupName).build();
    }

    private Name buildPersonDn(String fullname, String company, String country) {
        return LdapNameBuilder.newInstance(baseLdapPath)
            .add("c", country)
            .add("ou", company)
            .add("cn", fullname)
            .build();
   }
}

前の例では、ベース LDAP パスへの参照の取得に従って、ベース LDAP パスを取得するために BaseLdapNameAware を実装します。これが必要なのは、メンバー属性値としての識別名が常にディレクトリルートからの絶対値である必要があるためです。

4.4. 完全な PersonRepository クラス

Spring LDAP と DirContextAdapter の有用性を説明するために、次の例は LDAP の完全な Person リポジトリ実装を示しています。

package com.example.repo;
import java.util.List;

import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.ldap.LdapName;

import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.ContextMapper;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.filter.AndFilter;
import org.springframework.ldap.filter.EqualsFilter;
import org.springframework.ldap.filter.WhitespaceWildcardsFilter;

import static org.springframework.ldap.query.LdapQueryBuilder.query;

public class PersonRepoImpl implements PersonRepo {
   private LdapTemplate ldapTemplate;

   public void setLdapTemplate(LdapTemplate ldapTemplate) {
      this.ldapTemplate = ldapTemplate;
   }

   public void create(Person person) {
      DirContextAdapter context = new DirContextAdapter(buildDn(person));
      mapToContext(person, context);
      ldapTemplate.bind(context);
   }

   public void update(Person person) {
      Name dn = buildDn(person);
      DirContextOperations context = ldapTemplate.lookupContext(dn);
      mapToContext(person, context);
      ldapTemplate.modifyAttributes(context);
   }

   public void delete(Person person) {
      ldapTemplate.unbind(buildDn(person));
   }

   public Person findByPrimaryKey(String name, String company, String country) {
      Name dn = buildDn(name, company, country);
      return ldapTemplate.lookup(dn, getContextMapper());
   }

   public List findByName(String name) {
      LdapQuery query = query()
         .where("objectclass").is("person")
         .and("cn").whitespaceWildcardsLike("name");

      return ldapTemplate.search(query, getContextMapper());
   }

   public List findAll() {
      EqualsFilter filter = new EqualsFilter("objectclass", "person");
      return ldapTemplate.search(LdapUtils.emptyPath(), filter.encode(), getContextMapper());
   }

   protected ContextMapper getContextMapper() {
      return new PersonContextMapper();
   }

   protected Name buildDn(Person person) {
      return buildDn(person.getFullname(), person.getCompany(), person.getCountry());
   }

   protected Name buildDn(String fullname, String company, String country) {
      return LdapNameBuilder.newInstance()
        .add("c", country)
        .add("ou", company)
        .add("cn", fullname)
        .build();
   }

   protected void mapToContext(Person person, DirContextOperations context) {
      context.setAttributeValues("objectclass", new String[] {"top", "person"});
      context.setAttributeValue("cn", person.getFullName());
      context.setAttributeValue("sn", person.getLastName());
      context.setAttributeValue("description", person.getDescription());
   }

   private static class PersonContextMapper extends AbstractContextMapper<Person> {
      public Person doMapFromContext(DirContextOperations context) {
         Person person = new Person();
         person.setFullName(context.getStringAttribute("cn"));
         person.setLastName(context.getStringAttribute("sn"));
         person.setDescription(context.getStringAttribute("description"));
         return person;
      }
   }
}
場合によっては、オブジェクトの識別名 (DN) は、オブジェクトのプロパティを使用して構築されます。前の例では、Person の国、会社、フルネームが DN で使用されています。つまり、これらのプロパティを更新するには、実際には、Attribute 値の更新に加えて、rename() 操作を使用して LDAP ツリー内のエントリを移動する必要があります。これは非常に実装固有であるため、ユーザーがこれらのプロパティを変更できないようにするか、必要に応じて update() メソッドで rename() 操作を実行することにより、自分自身を追跡する必要があります。オブジェクトディレクトリマッピング (ODM) を使用すると、ドメインクラスに適切にアノテーションを付ければ、ライブラリが自動的にこれを処理できることに注意してください。

5. オブジェクトディレクトリマッピング (ODM)

オブジェクトリレーショナルマッピングフレームワーク (Hibernate や JPA など) は、アノテーションを使用してリレーショナルデータベース表を Java オブジェクトにマップする機能を開発者に提供します。Spring LDAP プロジェクトは、LdapOperations のいくつかのメソッドを通じて、LDAP ディレクトリに関して同様の機能を提供します。

  • <T> T findByDn(Name dn, Class<T> clazz)

  • <T> T findOne(LdapQuery query, Class<T> clazz)

  • <T> List<T> find(LdapQuery query, Class<T> clazz)

  • <T> List<T> findAll(Class<T> clazz)

  • <T> List<T> findAll(Name base, SearchControls searchControls, Class<T> clazz)

  • <T> List<T> findAll(Name base, Filter filter, SearchControls searchControls, Class<T> clazz)

  • void create(Object entry)

  • void update(Object entry)

  • void delete(Object entry)

5.1. アノテーション

オブジェクトマッピングメソッドで管理されるエンティティクラスは、org.springframework.ldap.odm.annotations パッケージからのアノテーションでアノテーションを付ける必要があります。利用可能なアノテーションは次のとおりです。

  • @Entry: エンティティがマップされる objectClass 定義を示すクラスレベルのアノテーション。(必要)

  • @Id: エンティティ DN を示します。この属性を宣言するフィールドは、javax.naming.Name クラスの派生物でなければなりません。(必須)

  • @Attribute: オブジェクトクラスフィールドへのディレクトリ属性のマッピングを示します。

  • @DnAttribute: オブジェクトクラスフィールドへの DN 属性のマッピングを示します。

  • @Transient: フィールドが永続的ではなく、OdmManager によって無視される必要があることを示します。

マネージクラスでは、@Entry および @Id アノテーションを宣言する必要があります。@Entry は、エンティティがマップされるオブジェクトクラスと、(オプションで) クラスによって表される LDAP エントリのディレクトリルートを指定するために使用されます。フィールドがマップされるすべてのオブジェクトクラスを宣言する必要があります。マネージクラスの新しいエントリを作成する場合、宣言されたオブジェクトクラスのみが使用されることに注意してください。

ディレクトリエントリが管理対象エンティティと一致すると見なされるためには、ディレクトリエントリによって宣言されたすべてのオブジェクトクラスが @Entry アノテーションによって宣言されている必要があります。例: LDAP ツリーに次のオブジェクトクラスを持つエントリがあるとします: inetOrgPerson,organizationalPerson,person,topperson オブジェクトクラスで定義された属性の変更のみに関心がある場合は、@Entry に @Entry(objectClasses = { "person", "top" }) のアノテーションを付けることができます。ただし、inetOrgPerson objectclass で定義された属性を管理する場合は、次を使用する必要があります: @Entry(objectClasses = { "inetOrgPerson", "organizationalPerson", "person", "top" })

@Id アノテーションは、エントリの識別名をフィールドにマップするために使用されます。フィールドは javax.naming.Name のインスタンスである必要があります。

@Attribute アノテーションは、オブジェクトクラスフィールドをエンティティフィールドにマップするために使用されます。@Attribute は、フィールドがマップされるオブジェクトクラスプロパティの名前を宣言する必要があり、オプションで LDAP 属性の構文 OID を宣言して、正確な一致を保証することができます。@Attribute は型宣言も提供します。これにより、属性が LDAP JNDI プロバイダーによってバイナリベースと見なされるか文字列ベースと見なされるかを示すことができます。

@DnAttribute アノテーションは、オブジェクトクラスフィールドをエントリの識別名のコンポーネントにマッピングするために使用されます。@DnAttribute アノテーションが付けられたフィールドには、エントリがディレクトリツリーから読み取られるときに、識別名の適切な値が自動的に入力されます。

次のアノテーションを持つクラスを考えてみましょう。

@DnAttribute(name="uid")
String uid;

次のような DN になります。

uid=carla,dc=springframework,dc=org

次に、Spring LDAP は、uid 属性を探す代わりに、uid=carla を使用して uid にデータを入力します。

Only fields of type `String` can be annotated with `@DnAttribute`. Other types are not supported.

次のようにインデックスを指定することもできます。

@DnAttribute(index=1)
String uid;

@DnAttribute(index=0)
String department;

これは、複数のコンポーネントを持つ DN に便利です。

uid=carla,department=engineering,dc=springframework,dc=org

index を使用すると、更新または削除するエンティティを作成または検索するときに、Spring LDAP が DN を計算することもできます。更新シナリオでは、識別名の一部である属性が変更された場合、ツリー内のエントリの移動も自動的に処理されます。

Note that while both attributes are present on `@DnAttribute`, if `index` is specified, then `name` is ignored.

@Transient アノテーションは、フィールドがオブジェクトディレクトリマッピングによって無視され、基礎となる LDAP プロパティにマップされないことを示します。@DnAttribute が Attribute にバインドされない場合は注意してください。つまり、これは識別名の一部に過ぎず、オブジェクト属性によって表されません。また、@Transient でアノテーションを付ける必要があります。

5.2. 実行

すべてのコンポーネントが適切に構成され、アノテーションが付けられたら、LdapTemplate のオブジェクトマッピングメソッドを次のように使用できます。

例 18: 実行
@Entry(objectClasses = { "person", "top" }, base="ou=someOu")
public class Person {
   @Id
   private Name dn;

   @Attribute(name="cn")
   @DnAttribute(value="cn", index=1)
   private String fullName;

   // No @Attribute annotation means this will be bound to the LDAP attribute
   // with the same value
   private String description;

   @DnAttribute(value="ou", index=0)
   @Transient
   private String company;

   @Transient
   private String someUnmappedField;
   // ...more attributes below
}


public class OdmPersonRepo {
   @Autowired
   private LdapTemplate ldapTemplate;

   public Person create(Person person) {
      ldapTemplate.create(person);
      return person;
   }

   public Person findByUid(String uid) {
      return ldapTemplate.findOne(query().where("uid").is(uid), Person.class);
   }

   public void update(Person person) {
      ldapTemplate.update(person);
   }

   public void delete(Person person) {
      ldapTemplate.delete(person);
   }

   public List<Person> findAll() {
      return ldapTemplate.findAll(Person.class);
   }

   public List<Person> findByLastName(String lastName) {
      return ldapTemplate.find(query().where("sn").is(lastName), Person.class);
   }
}

5.3. 属性値としての ODM および識別名

LDAP のセキュリティグループには通常、複数値の属性が含まれます。各値は、システム内のユーザーの識別名です。これらの種類の属性を処理する際の困難については、属性値としての DirContextAdapter および識別名で説明しています。

次の例に示すように、ODM は javax.naming.Name 属性値もサポートしているため、グループの変更が容易になります。

例 19: 例 グループ表現
@Entry(objectClasses = {"top", "groupOfUniqueNames"}, base = "cn=groups")
public class Group {

    @Id
    private Name dn;

    @Attribute(name="cn")
    @DnAttribute("cn")
    private String name;

    @Attribute(name="uniqueMember")
    private Set<Name> members;

    public Name getDn() {
        return dn;
    }

    public void setDn(Name dn) {
        this.dn = dn;
    }

    public Set<Name> getMembers() {
        return members;
    }

    public void setMembers(Set<Name> members) {
        this.members = members;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void addMember(Name member) {
        members.add(member);
    }

    public void removeMember(Name member) {
        members.remove(member);
    }
}

setMembersaddMemberremoveMember を使用してから ldapTemplate.update() を呼び出してグループメンバーを変更すると、属性の変更は識別名の等価性を使用して計算されます。つまり、識別名が等しいかどうかを判断するときに、識別名のテキスト形式は無視されます。

6. 高度な LDAP クエリ

このセクションでは、Spring LDAP で LDAP クエリを使用するさまざまな方法について説明します。

6.1. LDAP クエリビルダーのパラメーター

LdapQueryBuilder とそれに関連するクラスは、LDAP 検索に提供できるすべてのパラメーターをサポートすることを目的としています。次のパラメーターがサポートされています。

  • base: 検索を開始する LDAP ツリーのルート DN を指定します。

  • searchScope: 検索がトラバースする LDAP ツリーの深さを指定します。

  • attributes: 検索から返される属性を指定します。デフォルトはすべてです。

  • countLimit: 検索から返されるエントリの最大数を指定します。

  • timeLimit: 検索にかかる最大時間を指定します。

  • 検索フィルター: 探しているエントリが満たさなければならない条件。

LdapQueryBuilder は、LdapQueryBuilder の query メソッドへの呼び出しで作成されます。これは流れるようなビルダー API として意図されており、基本パラメーターが最初に定義され、その後にフィルター仕様呼び出しが続きます。LdapQueryBuilder の where メソッドの呼び出しでフィルター条件の定義が開始されると、後で (たとえば) base を呼び出そうとしても拒否されます。基本検索パラメーターはオプションですが、少なくとも 1 つのフィルター仕様呼び出しが必要です。次のクエリは、オブジェクトクラスが Person のすべてのエントリを検索します。

例 20: オブジェクトクラス Person を持つすべてのエントリを検索します
import static org.springframework.ldap.query.LdapQueryBuilder.query;
...

List<Person> persons = ldapTemplate.search(
      query().where("objectclass").is("person"),
      new PersonAttributesMapper());

次のクエリは、オブジェクトクラスが person で、cn (共通名) が John Doe のすべてのエントリを検索します。

例 21: オブジェクトクラス person および cn=John Doe を持つすべてのエントリを検索します
import static org.springframework.ldap.query.LdapQueryBuilder.query;
...

List<Person> persons = ldapTemplate.search(
      query().where("objectclass").is("person")
             .and("cn").is("John Doe"),
      new PersonAttributesMapper());

次のクエリは、person のオブジェクトクラスを持ち、dc=261consulting,dc=com の dc (ドメインコンポーネント) で始まるすべてのエントリを検索します。

例 22: dc=261consulting,dc=com で始まるオブジェクトクラス person を持つすべてのエントリを検索します
import static org.springframework.ldap.query.LdapQueryBuilder.query;
...

List<Person> persons = ldapTemplate.search(
      query().base("dc=261consulting,dc=com")
             .where("objectclass").is("person"),
      new PersonAttributesMapper());

次のクエリは、オブジェクトクラスが person で、dc (ドメインコンポーネント) の dc=261consulting,dc=com で始まるすべてのエントリの cn (共通名) 属性を返します。

例 23: dc=261consulting,dc=com で始まるクラス Person を持つすべてのエントリを検索し、cn 属性のみを返します
import static org.springframework.ldap.query.LdapQueryBuilder.query;
...

List<Person> persons = ldapTemplate.search(
      query().base("dc=261consulting,dc=com")
             .attributes("cn")
             .where("objectclass").is("person"),
      new PersonAttributesMapper());

次のクエリは、or を使用して、共通名 (cn) の複数のスペルを検索します。

例 24: or 条件で検索
import static org.springframework.ldap.query.LdapQueryBuilder.query;
...
List<Person> persons = ldapTemplate.search(
      query().where("objectclass").is("person"),
             .and(query().where("cn").is("Doe").or("cn").is("Doo"));
      new PersonAttributesMapper());

6.2. フィルター基準

前の例は、LDAP フィルターでの単純な等しい条件を示しています。LDAP クエリビルダーは、次の条件型をサポートしています。

  • is: 等号 (=) 条件を指定します。

  • gte: 以上 (>=) 条件を指定します。

  • lte: 以下 (⇐) 条件を指定します。

  • like: クエリにワイルドカードを含めることができる「類似」条件を指定します。たとえば、where("cn").like("J*hn Doe") は次のフィルターになります。: (cn=J*hn Doe).

  • whitespaceWildcardsLike: すべての空白がワイルドカードに置き換えられる条件を指定します。たとえば、where("cn").whitespaceWildcardsLike("John Doe") は次のフィルターになります。: (cn=John*Doe).

  • isPresent: 属性の存在をチェックする条件を指定します。たとえば、where("cn").isPresent() は次のフィルターになります。: (cn=*).

  • not: 現在の条件を否定する必要があることを指定します。たとえば、where("sn").not().is("Doe) は次のフィルターになります。: (!(sn=Doe))

6.3. ハードコードされたフィルター

ハードコードされたフィルターを LdapQuery への入力として指定したい場合があります。LdapQueryBuilder には、この目的のために 2 つの方法があります。

  • filter(String hardcodedFilter): 指定された文字列をフィルターとして使用します。指定された入力文字列はまったく変更されないことに注意してください。つまり、ユーザー入力からフィルターを構築する場合、この方法は特に適していません。

  • filter(String filterFormat, String…​ params): 指定された文字列を MessageFormat への入力として使用し、パラメーターを適切にエンコードして、フィルター文字列の指定された場所に挿入します。

  • filter(Filter filter): 指定されたフィルターを使用します。

ハードコードされたフィルターメソッドを前述の where アプローチと混在させることはできません。それはどちらかです。filter() を使用してフィルターを指定すると、後で where を呼び出そうとすると例外が発生します。

7. 構成

Spring LDAP を構成する推奨される方法は、カスタム XML 構成名前空間を使用することです。これを利用できるようにするには、次のように、Bean ファイルに Spring LDAP 名前空間宣言を含める必要があります。

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:ldap="http://www.springframework.org/schema/ldap"
       xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/ldap https://www.springframework.org/schema/ldap/spring-ldap.xsd">

7.1. ContextSource 設定

ContextSource は、<ldap:context-source> タグを使用して定義されます。最も単純な context-source 宣言では、次のようにサーバー URL、ユーザー名、パスワードを指定する必要があります。

例 25: 最も単純なコンテキストソース宣言
<ldap:context-source
    username="cn=Administrator"
    password="secret"
    url="ldap://localhost:389" />

前の例では、デフォルト値 (この段落の後の表を参照) と指定された URL および認証資格情報を使用して LdapContextSource を作成します。context-source で構成可能な属性は次のとおりです (* でマークされた必須属性)。

表 1: ContextSource 構成属性
属性 デフォルト 説明

id

contextSource

作成された Bean の ID。

username

LDAP サーバーで認証するときに使用するユーザー名 (プリンシパル)。これは通常、管理ユーザーの識別名 (たとえば、cn=Administrator) ですが、サーバーと認証方法によって異なる場合があります。authentication-source-ref が明示的に構成されていない場合は必須です。

password

LDAP サーバーで認証するときに使用するパスワード (資格情報)。authentication-source-ref が明示的に構成されていない場合は必須です。

url *

The URL of the LDAP server to use. The URL should be in the following format: ldap://myserver.example.com:389. For SSL access, use the ldaps protocol and the appropriate port — for example, ldaps://myserver.example.com:636. If you want fail-over functionality, you can specify more than one URL, separated by commas (,).

base

LdapUtils.emptyLdapName()

ベース DN。この属性が構成されている場合、LDAP 操作に提供され、LDAP 操作から受信されるすべての識別名は、指定された LDAP パスに関連しています。これにより、LDAP ツリーに対する作業が大幅に簡素化されます。ただし、基本パスにアクセスする必要がある場合がいくつかあります。詳細については、ベース LDAP パスへの参照の取得を参照してください。

anonymous-read-only

false

匿名 (認証されていない) コンテキストを使用して読み取り専用操作を実行するかどうかを定義します。このパラメーターをカバレッジトランザクションサポートと一緒に true に設定することはサポートされておらず、拒否されることに注意してください。

referral

null

こちら [Oracle] (英語) で説明されているように、参照を処理するための戦略を定義します。有効な値は次のとおりです。

  • ignore

  • follow

  • throw

native-pooling

false

ネイティブ Java LDAP 接続プールを使用するかどうかを指定します。代わりに Spring LDAP 接続プールの使用を検討してください。詳細については、プーリングのサポートを参照してください。

authentication-source-ref

SimpleAuthenticationSource インスタンス。

使用する AuthenticationSource インスタンスの ID ( カスタムプリンシパルと資格情報の管理を参照)。

authentication-strategy-ref

SimpleDirContextAuthenticationStrategy インスタンス。

使用する DirContextAuthenticationStrategy インスタンスの ID ( カスタム DirContext 認証処理を参照)。

base-env-props-ref

構築時に DirContext に送信される環境と共に提供されるカスタム環境プロパティの Map への参照。

7.1.1. DirContext 認証

LDAP サーバーで操作を実行するために使用する DirContext インスタンスを作成する場合、多くの場合、これらのコンテキストを認証する必要があります。Spring LDAP は、これを構成するためのさまざまなオプションを提供します。

このセクションでは、LdapTemplate で使用する DirContext インスタンスを構築するために、ContextSource のコア機能でコンテキストを認証する方法について説明します。LDAP は通常、ユーザー認証のみを目的として使用され、ContextSource もその目的で使用される場合があります。そのプロセスは Spring LDAP を使用したユーザー認証で説明されています。

デフォルトでは、読み取り専用操作と読み書き操作の両方に対して認証済みコンテキストが作成されます。context-source 要素で認証に使用する LDAP ユーザーの username および password を指定する必要があります。

username が LDAP ユーザーの識別名 (DN) である場合、context-source 要素で base LDAP パスが指定されているかどうかに関係なく、LDAP ツリーのルートからのユーザーの完全な DN である必要があります。

一部の LDAP サーバー設定では、匿名の読み取り専用アクセスが許可されています。読み取り専用操作に匿名コンテキストを使用する場合は、anonymous-read-only 属性を true に設定します。

カスタム DirContext 認証処理

Spring LDAP で使用されるデフォルトの認証メカニズムは SIMPLE 認証です。これは、プリンシパル ( username 属性で指定) とクレデンシャル ( password で指定) が、DirContext 実装コンストラクターに送信される Hashtable に設定されることを意味します。

この処理が十分ではない場合が多くあります。たとえば、LDAP サーバーは一般に、安全な TLS チャネルでのみ通信を受け入れるように設定されています。特定の LDAP プロキシ認証メカニズムまたはその他の問題を使用する必要がある場合があります。

context-source 要素への DirContextAuthenticationStrategy 実装参照を提供することにより、代替の認証メカニズムを指定できます。これを行うには、authentication-strategy-ref 属性を設定します。

TLS

Spring LDAP は、TLS セキュアチャネル通信を必要とする LDAP サーバーに対して DefaultTlsDirContextAuthenticationStrategy と ExternalTlsDirContextAuthenticationStrategy の 2 つの異なる構成オプションを提供します。どちらの実装も、ターゲット接続で TLS チャネルをネゴシエートしますが、実際の認証メカニズムが異なります。DefaultTlsDirContextAuthenticationStrategy が (指定された username および password を使用して) セキュアチャネルで SIMPLE 認証を適用する場合、ExternalTlsDirContextAuthenticationStrategy は EXTERNAL SASL 認証を使用し、認証用のシステムプロパティを使用して構成されたクライアント証明書を適用します。

LDAP サーバーの実装が異なれば、TLS チャネルの明示的なシャットダウンに対する応答も異なるため (一部のサーバーは接続を適切にシャットダウンする必要があり、他のサーバーはそれをサポートしていません)、TLS DirContextAuthenticationStrategy 実装は、shutdownTlsGracefully パラメーターを使用してシャットダウン動作を指定することをサポートします。このプロパティが false (デフォルト) に設定されている場合、明示的な TLS シャットダウンは行われません。true の場合、Spring LDAP は、ターゲットコンテキストを閉じる前に、TLS チャネルを適切にシャットダウンしようとします。

TLS 接続を使用する場合、ネイティブの LDAP プーリング機能 (native-pooling 属性を使用して指定) がオフになっていることを確認する必要があります。shutdownTlsGracefully が false に設定されている場合、これは特に重要です。ただし、TLS チャネルのネゴシエーションプロセスは非常にコストがかかるため、プーリングのサポートで説明されている Spring LDAP プーリングサポートを使用すると、パフォーマンスが大幅に向上します。
カスタムプリンシパルと資格情報の管理

認証された Context の作成に使用されるユーザー名 (つまり、ユーザー DN) とパスワードは、デフォルトで静的に定義されます (context-source 要素構成で定義されたものは、ContextSource の存続期間全体で使用されます)。望ましい動作ではありません。一般的なシナリオは、現在のユーザーに対して LDAP 操作を実行するときに、そのユーザーのプリンシパルと資格情報を使用する必要があるというものです。username および password を明示的に指定する代わりに、authentication-source-ref 要素を使用して、AuthenticationSource 実装への参照を context-source 要素に提供することにより、既定の動作を変更できます。認証された Context が作成されるたびに、AuthenticationSource は ContextSource によってプリンシパルと資格情報を照会されます。

Spring Security を使用する場合、Spring Security に同梱されている SpringSecurityAuthenticationSource のインスタンスを使用して ContextSource を構成することにより、現在ログインしているユーザーのプリンシパルと資格情報が常に使用されるようにすることができます。次の例は、その方法を示しています。

例 26: SpringSecurityAuthenticationSource の使用
<beans>
...
    <ldap:context-source
        url="ldap://localhost:389"
        authentication-source-ref="springSecurityAuthenticationSource"/>

    <bean id="springSecurityAuthenticationSource"
        class="org.springframework.security.ldap.authentication.SpringSecurityAuthenticationSource" />
...
</beans>
username または password の指定はありません。AuthenticationSource を使用する場合は、当社の context-source を使用します。これらのプロパティは、既定の動作を使用する場合にのみ必要です。
SpringSecurityAuthenticationSource を使用する場合、Spring Security の LdapAuthenticationProvider を使用して LDAP に対してユーザーを認証する必要があります。

7.1.2. ネイティブ Java LDAP プーリング

内部 Java LDAP プロバイダーは、いくつかの非常に基本的なプーリング機能を提供します。AbstractContextSource で pooled フラグを使用して、この LDAP 接続プールをオンまたはオフにすることができます。デフォルト値は false (リリース 1.3 以降) です。つまり、ネイティブ Java LDAP プーリングはオフになっています。LDAP 接続プールの構成は System プロパティを使用して管理されるため、Spring コンテキスト構成の外部で手動で処理する必要があります。ネイティブプーリング構成の詳細については、こちら (英語) を参照してください。

組み込みの LDAP 接続プールには重大な欠陥がいくつかあります。そのため、Spring LDAP は LDAP 接続プールに対してより洗練されたアプローチを提供します ( プーリングのサポートを参照)。プーリング機能が必要な場合は、これが推奨されるアプローチです。
プーリング構成に関係なく、ContextSource#getContext(String principal, String credentials) メソッドは常に、ネイティブ Java LDAP プーリングを明示的に使用しません。これは、パスワードのリセットができるだけ早く有効になるようにするためです。

7.1.3. 高度な ContextSource 構成

このセクションでは、ContextSource を構成するためのより高度な方法について説明します。

カスタム DirContext 環境プロパティ

場合によっては、context-source で直接構成できるものに加えて、追加の環境セットアッププロパティを指定する必要がある場合があります。このようなプロパティは Map で設定し、base-env-props-ref 属性で参照する必要があります。

7.2. LdapTemplate 設定

LdapTemplate は、<ldap:ldap-template> 要素を使用して定義されます。最も単純な ldap-template 宣言は、要素自体です。

例 27: 最も単純な ldap-template 宣言
<ldap:ldap-template />

この要素自体が、デフォルト ID を持つ LdapTemplate インスタンスを作成し、デフォルトの ContextSource を参照します。これは、contextSource (context-source 要素のデフォルト) の ID を持つことが期待されます。

次の表では、ldap-template で構成可能な属性について説明します。

表 2: LdapTemplate 構成属性
属性 デフォルト 説明

id

ldapTemplate

作成された Bean の ID。

context-source-ref

contextSource

使用する ContextSource インスタンスの ID。

count-limit

0

検索のデフォルトのカウント制限。0 は無制限を意味します。

time-limit

0

検索のデフォルトの制限時間 (ミリ秒)。0 は無制限を意味します。

search-scope

SUBTREE

検索のデフォルトの検索範囲。有効な値は次のとおりです。

  • OBJECT

  • ONELEVEL

  • SUBTREE

ignore-name-not-found

false

検索で NameNotFoundException を無視するかどうかを指定します。この属性を true に設定すると、無効な検索ベースによって引き起こされたエラーが静かに飲み込まれます。

ignore-partial-result

false

検索で PartialResultException を無視するかどうかを指定します。一部の LDAP サーバーでは、リフェラルに問題があります。通常、これらは自動的に実行されます。ただし、これが機能しない場合は、PartialResultException で明らかになります。この属性を true に設定すると、この問題を回避できます。

odm-ref

使用する ObjectDirectoryMapper インスタンスの ID。デフォルトは、デフォルト構成の DefaultObjectDirectoryMapper です。

7.3. ベース LDAP パスへの参照の取得

前述のように、すべての操作が相対的な LDAP ツリーのルートを指定して、ベース LDAP パスを ContextSource に指定できます。これは、システム全体で相対識別名のみを使用していることを意味します。これは通常、かなり便利です。ただし、LDAP ツリーの実際のルートに関連する完全な DN を構築できるようにするために、ベースパスへのアクセスが必要になる場合があります。一例として、LDAP グループ (groupOfNames オブジェクトクラスなど) を操作する場合があります。その場合、各グループメンバーの属性値は、参照されるメンバーの完全な DN である必要があります。

そのため、Spring LDAP には、起動時に Spring 制御の Bean に基本パスを提供できるメカニズムがあります。Bean に基本パスが通知されるようにするには、2 つのことを行う必要があります。まず、ベースパス参照を必要とする Bean は、BaseLdapNameAware インターフェースを実装する必要があります。次に、アプリケーションコンテキストで BaseLdapPathBeanPostProcessor を定義する必要があります。次の例は、BaseLdapNameAware を実装する方法を示しています。

例 28: BaseLdapNameAware の実装
package com.example.service;
public class PersonService implements PersonService, BaseLdapNameAware {
   ...
   private LdapName basePath;

   public void setBaseLdapPath(LdapName basePath) {
      this.basePath = basePath;
   }
   ...
   private LdapName getFullPersonDn(Person person) {
      return LdapNameBuilder.newInstance(basePath)
          .add(person.getDn())
          .build();
   }
   ...
}

次の例は、BaseLdapPathBeanPostProcessor を定義する方法を示しています。

例 29: ApplicationContext で BaseLdapPathBeanPostProcessor を指定する
<beans>
   ...
   <ldap:context-source
          username="cn=Administrator"
          password="secret"
          url="ldap://localhost:389"
          base="dc=261consulting,dc=com" />
   ...
   <bean class="org.springframework.ldap.core.support.BaseLdapPathBeanPostProcessor" />
</beans>

BaseLdapPathBeanPostProcessor のデフォルトの動作は、ApplicationContext で定義された単一の BaseLdapPathSource (AbstractContextSource) のベースパスを使用することです。複数の BaseLdapPathSource が定義されている場合は、baseLdapPathSourceName プロパティを設定して、使用するものを指定する必要があります。

8. Spring LDAP リポジトリ

Spring LDAP には、Spring Data リポジトリのサポートが組み込まれています。ここでは、基本的な機能と構成について説明します。Spring LDAP リポジトリを使用する場合は、次の点に注意してください。

  • XML 構成で <ldap:repositories> 要素を使用するか、構成クラスで @EnableLdapRepositories アノテーションを使用して、Spring LDAP リポジトリを有効にすることができます。

  • 自動生成されたリポジトリに LdapQuery パラメーターのサポートを含めるには、インターフェースで CrudRepository ではなく LdapRepository を継承します。

  • オブジェクトディレクトリマッピング (ODM) に従って、すべての Spring LDAP リポジトリは、ODM アノテーションでアノテーションが付けられたエンティティと連携する必要があります。

  • すべての ODM 管理クラスは ID として識別名を持っている必要があるため、すべての Spring LDAP リポジトリは ID 型・パラメーターを javax.naming.Name に設定する必要があります。組み込みの LdapRepository は、1 つの型 パラメーターのみを受け取ります。それは、ID をデフォルトで javax.naming.Name に設定するマネージドエンティティクラスです。

  • LDAP プロトコルの仕様により、ページングとソートは Spring LDAP リポジトリではサポートされていません。

8.1. QueryDSL のサポート

基本的な QueryDSL サポートは Spring LDAP に含まれています。このサポートには以下が含まれます。

  • Spring LDAP ODM アノテーションに基づいて QueryDSL クラスを生成するための、LdapAnnotationProcessor と呼ばれるアノテーションプロセッサー。ODM アノテーションの詳細については、オブジェクトディレクトリマッピング (ODM) を参照してください。

  • コードで QueryDSL クエリを作成および実行するための QueryDslLdapQuery と呼ばれるクエリ実装。

  • QueryDSL 述語の Spring Data リポジトリサポート。QueryDslPredicateExecutor には、適切なパラメーターを持つ追加のメソッドが多数含まれています。このインターフェースを LdapRepository と共に拡張して、このサポートをリポジトリに組み込むことができます。

9. プーリングのサポート

LDAP 接続をプールすると、LDAP の対話ごとに新しい LDAP 接続を作成するオーバーヘッドを軽減できます。Java LDAP プーリングのサポート (英語) は存在しますが、接続の検証やプールのメンテナンスなど、構成オプションと機能が制限されています。Spring LDAP は、ContextSource ごとに詳細なプール構成をサポートします。

プーリングのサポートは、アプリケーションコンテキスト構成で <ldap:context-source /> 要素に <ldap:pooling /> 子要素を提供することによって提供されます。読み取り専用および読み取り / 書き込み DirContext オブジェクトは別々にプールされます (anonymous-read-only が指定されている場合)。Jakarta コモンズプール [Apache] (英語) は、基礎となるプールの実装を提供するために使用されます。

9.1. DirContext 検証

JDK が提供する LDAP プーリング機能ではなく、カスタムプーリングライブラリを使用する主な動機は、プールされた接続の検証です。検証により、プールされた DirContext 接続をチェックして、プールからチェックアウトするとき、プールにチェックインするとき、プール内でアイドル状態になっているときに、それらが適切に接続および構成されていることを確認できます。

接続検証が構成されている場合、プールされた接続は DefaultDirContextValidator を使用して検証されます。DefaultDirContextValidator は、空の名前、"objectclass=*" のフィルター、および objectclass 属性のみと 500 ミリ秒のタイムアウトで単一の結果を制限するように設定された SearchControls を使用して DirContext.search(String, String, SearchControls) を実行します。返された NamingEnumeration に結果がある場合、DirContext は検証に合格します。結果が返されないか、例外がスローされた場合、DirContext は検証に失敗します。デフォルト設定は、ほとんどの LDAP サーバーで構成を変更しなくても機能し、DirContext を検証する最速の方法を提供します。カスタマイズが必要な場合は、プール構成で説明されている検証構成属性を使用して実行できます。

非一時的と見なされる例外をスローすると、接続は自動的に無効になります。例: DirContext インスタンスが javax.naming.CommunicationException をスローすると、一時的でないエラーとして解釈され、追加の testOnReturn 操作のオーバーヘッドなしでインスタンスが自動的に無効になります。非一時的であると解釈される例外は、PoolingContextSource の nonTransientExceptions プロパティを使用して構成されます。

9.2. プール構成

DirContext プールの構成のために、<ldap:pooling /> エレメントで以下の属性を使用できます。

表 3: プーリング構成属性
属性 デフォルト 説明

max-active

8

このプールから同時に割り当てることができる各型 (読み取り専用または読み取り / 書き込み) のアクティブな接続の最大数。正でない数値を無制限に使用できます。

max-total

-1

このプールから同時に割り当てることができる (すべての型の) アクティブな接続の全体的な最大数。正でない数値を無制限に使用できます。

max-idle

8

追加の接続が解放されることなくプール内でアイドル状態を維持できる、各型 (読み取り専用または読み取り / 書き込み) のアクティブな接続の最大数。正でない数値を無制限に使用できます。

min-idle

0

追加の接続を作成せずにプール内でアイドル状態を維持できる、各型 (読み取り専用または読み取り / 書き込み) のアクティブな接続の最小数。ゼロ (デフォルト) を使用して、何も作成しないようにすることができます。

max-wait

-1

例外をスローする前に接続が返されるのをプールが (接続が使用できない場合に) 待機する最大ミリ秒数。正でない数値を使用して、無期限に待機できます。

when-exhausted

BLOCK

プールが使い果たされたときの動作を指定します。

  • FAIL オプションは、プールが使い果たされると NoSuchElementException をスローします。

  • BLOCK オプションは、新しいオブジェクトが使用可能になるまで待機します。max-wait が正で、max-wait 時間の経過後に新しいオブジェクトが使用できない場合、NoSuchElementException がスローされます。

  • GROW オプションは、新しいオブジェクトを作成して返します (基本的に max-active は無意味になります)。

test-on-borrow

false

プールから借用する前にオブジェクトを検証するかどうか。オブジェクトの検証に失敗した場合、そのオブジェクトはプールから削除され、別のオブジェクトを借用しようとします。

test-on-return

false

オブジェクトがプールに返される前に検証されるかどうか。

test-while-idle

false

オブジェクトがアイドルオブジェクト evictor (存在する場合) によって検証されるかどうか。検証に失敗したオブジェクトは、プールから削除されます。

eviction-run-interval-millis

-1

アイドル状態のオブジェクト evictor スレッドの実行の間にスリープするミリ秒数。正でない場合、アイドルオブジェクトの Evictor スレッドは実行されません。

tests-per-eviction-run

3

アイドル状態のオブジェクト Evictor スレッド (存在する場合) の各実行中に検査するオブジェクトの数。

min-evictable-time-millis

1000 * 60 * 30 (30 minutes)

The minimum amount of time an object may sit idle in the pool before it is eligible for eviction by the idle object evictor (if any).

validation-query-base

LdapUtils.emptyName()

接続を検証するときに使用する検索ベース。test-on-borrowtest-on-return、または test-while-idle が指定されている場合にのみ使用されます。

validation-query-filter

objectclass=*

接続を検証するときに使用する検索フィルター。test-on-borrowtest-on-return、または test-while-idle が指定されている場合にのみ使用されます。

validation-query-search-controls-ref

null; default search control settings are described above.

The ID of a SearchControls instance to be used when validating connections. Only used if test-on-borrow, test-on-return, or test-while-idle is specified.

non-transient-exceptions

javax.naming.CommunicationException

Exception クラスのカンマ区切りリスト。リストされた例外は、積極的な無効化に関して非一時的であると見なされます。リストされた例外 (またはそれらのサブクラス) のいずれかが、プールされた DirContext インスタンスへの呼び出しによってスローされた場合、そのオブジェクトは追加の testOnReturn 操作なしで自動的に無効になります。

9.3. プール 2 の構成

DirContext プールを構成するために、<ldap:pooling2 /> 要素で次の属性を使用できます。

表 4: プーリング構成属性
属性 デフォルト 説明

max-total

-1

このプールから同時に割り当てることができる (すべての型の) アクティブな接続の全体的な最大数。正でない数値を無制限に使用できます。

max-total-per-key

8

キーごとに、プール (チェックアウトまたはアイドル) によって割り当てられるオブジェクトインスタンスの数の制限。制限に達すると、サブプールが使い果たされます。負の値は制限がないことを示します。

max-idle-per-key

8

余分な接続が解放されることなく、プール内でアイドル状態を維持できる各型 (読み取り専用または読み取り / 書き込み) のアクティブな接続の最大数。負の値は制限がないことを示します。

min-idle-per-key

0

追加の接続を作成せずにプール内でアイドル状態を維持できる、各型 (読み取り専用または読み取り / 書き込み) のアクティブな接続の最小数。ゼロ (デフォルト) を使用して、何も作成しないようにすることができます。

max-wait

-1

例外をスローする前に接続が返されるのをプールが (使用可能な接続がない場合に) 待機する最大ミリ秒数。正でない数値を使用して、無期限に待機できます。

block-when-exhausted

true

新しいオブジェクトが利用可能になるまで待機するかどうか。max-wait が正の場合、maxWait 時間の期限が切れた後に新しいオブジェクトが利用できない場合、NoSuchElementException がスローされます。

test-on-create

false

オブジェクトを借用する前に検証するかどうか。オブジェクトの検証に失敗すると、借用は失敗します。

test-on-borrow

false

オブジェクトがプールから借用される前に検証されるかどうかの指標。オブジェクトの検証に失敗した場合、そのオブジェクトはプールから削除され、別のオブジェクトを借用しようとします。

test-on-return

false

オブジェクトがプールに返される前に検証されるかどうかの指標。

test-while-idle

false

オブジェクトがアイドルオブジェクト evictor (存在する場合) によって検証されるかどうかのインジケーター。検証に失敗したオブジェクトは、プールから削除されます。

eviction-run-interval-millis

-1

アイドル状態のオブジェクト evictor スレッドの実行の間にスリープするミリ秒数。正でない場合、アイドルオブジェクトの Evictor スレッドは実行されません。

tests-per-eviction-run

3

アイドル状態のオブジェクト Evictor スレッド (存在する場合) の各実行中に検査するオブジェクトの数。

min-evictable-time-millis

1000 * 60 * 30 (30 minutes)

The minimum amount of time an object may sit idle in the pool before it is eligible for eviction by the idle object evictor (if any).

soft-min-evictable-time-millis

-1

アイドル状態のオブジェクト Evictor によるエビクションの対象となる前に、オブジェクトがプール内でアイドル状態を保つことができる最小時間。追加の条件として、少なくともキーごとの最小数のオブジェクトインスタンスがプール内に残っている必要があります。この設定は、正の値に設定されている場合、min-evictable-time-millis によってオーバーライドされます。

eviction-policy-class

org.apache.commons.pool2.impl.DefaultEvictionPolicy

このプールで使用されるエビクションポリシーの実装。プールは、スレッドコンテキストクラスローダーを使用してクラスを読み込もうとします。それが失敗した場合、プールは、このクラスをロードしたクラスローダーを使用してクラスをロードしようとします。

fairness

false

プールは、接続を公平に借用するのを待っているスレッドにサービスを提供します。true は、待機スレッドが FIFO キューで待機しているかのように処理されることを意味します。

jmx-enable

true

JMX は、プールのプラットフォーム MBean サーバーで有効になります。

jmx-name-base

null

JMX 対応プールに割り当てられる名前の一部として使用される JMX ネームベース。

jmx-name-prefix

pool

JMX 対応プールに割り当てられる名前の一部として使用される JMX 名のプレフィックス。

lifo

true

プールが、アイドル状態のオブジェクトに対して LIFO (後入れ先出し) 動作を行うか、FIFO (先入れ先出し) キューとして動作するかを示す指標。LIFO は常にプールから最後に使用されたオブジェクトを返しますが、FIFO は常にアイドルオブジェクトプール内の最も古いオブジェクトを返します。

validation-query-base

LdapUtils.emptyPath()

検証検索に使用するベース DN。

validation-query-filter

objectclass=*

検証クエリに使用するフィルター。

validation-query-search-controls-ref

null; default search control settings are described above.

The ID of a SearchControls instance to be used when validating connections. Used only if test-on-borrow, test-on-return, or test-while-idle is specified

non-transient-exceptions

javax.naming.CommunicationException

Exception クラスのカンマ区切りリスト。リストされた例外は、積極的な無効化に関して非一時的であると見なされます。リストされた例外 (またはそれらのサブクラス) のいずれかが、プールされた DirContext インスタンスへの呼び出しによってスローされた場合、そのオブジェクトは追加の testOnReturn 操作なしで自動的に無効になります。

9.4. 構成

プーリングを構成するには、次のように、<ldap:context-source> 要素にネストされた <ldap:pooling> 要素を追加する必要があります。

<beans>
   ...
    <ldap:context-source
        password="secret" url="ldap://localhost:389" username="cn=Manager">
        <ldap:pooling />
    </ldap:context-source>
   ...
</beans>

実際の状況では、おそらくプールオプションを構成し、接続の検証を有効にします。前の例は、一般的な考え方を示しています。

9.4.1. 検証構成

次の例では、クライアントアプリケーションに渡される前に各 DirContext をテストし、プール内でアイドル状態になっている DirContext オブジェクトをテストします。

<beans>
   ...
    <ldap:context-source
        username="cn=Manager" password="secret" url="ldap://localhost:389" >
        <ldap:pooling
            test-on-borrow="true"
            test-while-idle="true" />
    </ldap:context-source>
   ...
</beans>

9.5. 既知の問題

このセクションでは、Spring LDAP を使用するときに発生することがある課題について説明します。現在、以下の課題をカバーしています。

9.5.1. カスタム認証

PoolingContextSource は、ContextSource.getReadOnlyContext() から取得されたすべての DirContext オブジェクトが同じ環境を持ち、同様に、ContextSource.getReadWriteContext() から取得されたすべての DirContext オブジェクトが同じ環境を持っていると想定します。これは、AuthenticationSource で構成された LdapContextSource を PoolingContextSource でラップしても、期待どおりに機能しないことを意味します。プールは最初のユーザーの資格情報を使用して設定され、新しい接続が必要でない限り、リクエスト元スレッドの AuthenticationSource によって指定されたユーザーの後続のコンテキストリクエストは満たされません。

10. 不足しているオーバーロードされた API メソッドの追加

このセクションでは、オーバーロードされた独自の API メソッドを追加して新しい機能を実装する方法について説明します。

10.1. カスタム検索方法の実装

LdapTemplate には、DirContext で最も一般的な操作のオーバーロードされたバージョンがいくつか含まれています。ただし、主にメソッドシグネチャーが非常に多いため、すべてのメソッドシグネチャーの代替手段を提供していません。ただし、必要な DirContext メソッドを呼び出す手段を提供し、それでも LdapTemplate が提供する利点を得ることができます。

次の DirContext メソッドを呼び出すとします。

NamingEnumeration search(Name name, String filterExpr, Object[] filterArgs, SearchControls ctls)

LdapTemplate には対応するオーバーロードされたメソッドはありません。これを解決する方法は、次のようにカスタム SearchExecutor 実装を使用することです。

public interface SearchExecutor {
   public NamingEnumeration executeSearch(DirContext ctx) throws NamingException;
}

カスタム executor では、必要なメソッドを呼び出すために使用できる DirContext オブジェクトにアクセスできます。次に、属性のマッピングと結果の収集を担当するハンドラーを提供できます。たとえば、マッピングされた結果を内部リストに収集する CollectingNameClassPairCallbackHandler の使用可能な実装の 1 つを使用できます。実際に検索を実行するには、エグゼキューターとハンドラーを引数として取る LdapTemplate の search メソッドを呼び出す必要があります。最後に、ハンドラーが収集したものをすべて返す必要があります。次の例は、そのすべてを行う方法を示しています。

例 30: SearchExecutor と AttributesMapper を使用したカスタム検索方法
package com.example.repo;

public class PersonRepoImpl implements PersonRepo {
   ...
   public List search(final Name base, final String filter, final String[] params,
         final SearchControls ctls) {
      SearchExecutor executor = new SearchExecutor() {
         public NamingEnumeration executeSearch(DirContext ctx) {
            return ctx.search(base, filter, params, ctls);
         }
      };

      CollectingNameClassPairCallbackHandler handler =
         new AttributesMapperCallbackHandler(new PersonAttributesMapper());

      ldapTemplate.search(executor, handler);
      return handler.getList();
   }
}

AttributesMapper よりも ContextMapper を好む場合、次の例はそれがどのようになるかを示しています。

例 31: SearchExecutor と ContextMapper を使用したカスタム検索方法
package com.example.repo;

public class PersonRepoImpl implements PersonRepo {
   ...
   public List search(final Name base, final String filter, final String[] params,
         final SearchControls ctls) {
      SearchExecutor executor = new SearchExecutor() {
         public NamingEnumeration executeSearch(DirContext ctx) {
            return ctx.search(base, filter, params, ctls);
         }
      };

      CollectingNameClassPairCallbackHandler handler =
         new ContextMapperCallbackHandler(new PersonContextMapper());

      ldapTemplate.search(executor, handler);
      return handler.getList();
   }
}
ContextMapperCallbackHandler を使用する場合、SearchControls インスタンスで setReturningObjFlag(true) を呼び出したことを確認する必要があります。

10.2. その他のカスタムコンテキストメソッドの実装

カスタム search メソッドと同じ方法で、次のように ContextExecutor を使用して DirContext の任意のメソッドを実際に呼び出すことができます。

public interface ContextExecutor {
   public Object executeWithContext(DirContext ctx) throws NamingException;
}

カスタム ContextExecutor を実装する場合、executeReadOnly() または executeReadWrite() メソッドのどちらを使用するかを選択できます。次のメソッドを呼び出すとします。

Object lookupLink(Name name)

メソッドは DirContext で使用できますが、LdapTemplate には一致するメソッドがありません。これはルックアップメソッドなので、読み取り専用にする必要があります。次のように実装できます。

例 32: ContextExecutor を使用したカスタム DirContext メソッド
package com.example.repo;

public class PersonRepoImpl implements PersonRepo {
   ...
   public Object lookupLink(final Name name) {
      ContextExecutor executor = new ContextExecutor() {
         public Object executeWithContext(DirContext ctx) {
            return ctx.lookupLink(name);
         }
      };

      return ldapTemplate.executeReadOnly(executor);
   }
}

同様に、executeReadWrite() メソッドを使用して読み取り / 書き込み操作を実行できます。

11. DirContext の処理

このセクションでは、前処理と後処理を含む DirContext の処理方法について説明します。

11.1. カスタム DirContext 前処理および後処理

状況によっては、検索操作の前後に DirContext で操作を実行したい場合があります。これに使用されるインターフェースは DirContextProcessor と呼ばれます。次のリストは、DirContextProcessor インターフェースを示しています。

public interface DirContextProcessor {
   public void preProcess(DirContext ctx) throws NamingException;
   public void postProcess(DirContext ctx) throws NamingException;
}

LdapTemplate クラスには、次のように DirContextProcessor を取る検索メソッドがあります。

public void search(SearchExecutor se, NameClassPairCallbackHandler handler,
   DirContextProcessor processor) throws DataAccessException;

検索操作の前に、指定された DirContextProcessor インスタンスで preProcess メソッドが呼び出されます。検索が実行され、結果の NamingEnumeration が処理された後、postProcess メソッドが呼び出されます。これにより、検索に使用する DirContext を操作したり、検索を実行したときに DirContext を確認したりできます。これは非常に便利です (たとえば、リクエストとレスポンスの制御を処理する場合)。

カスタム SearchExecutor が必要ない場合は、次の便利なメソッドを使用することもできます。

public void search(Name base, String filter,
   SearchControls controls, NameClassPairCallbackHandler handler, DirContextProcessor processor)

public void search(String base, String filter,
   SearchControls controls, NameClassPairCallbackHandler handler, DirContextProcessor processor)

public void search(Name base, String filter,
   SearchControls controls, AttributesMapper mapper, DirContextProcessor processor)

public void search(String base, String filter,
   SearchControls controls, AttributesMapper mapper, DirContextProcessor processor)

public void search(Name base, String filter,
   SearchControls controls, ContextMapper mapper, DirContextProcessor processor)

public void search(String base, String filter,
   SearchControls controls, ContextMapper mapper, DirContextProcessor processor)

11.2. リクエスト制御 DirContextProcessor の実装

LDAPv3 プロトコルは、「コントロール」を使用して追加データを送受信し、事前定義された操作の動作に影響を与えます。リクエスト制御 DirContextProcessor の実装を簡素化するために、Spring LDAP は AbstractRequestControlDirContextProcessor 基本クラスを提供します。このクラスは、LdapContext からの現在のリクエストコントロールの取得を処理し、リクエストコントロールを作成するためのテンプレートメソッドを呼び出し、それを LdapContext に追加します。サブクラスで行う必要があるのは、createRequestControl と呼ばれるテンプレートメソッドと、検索後に必要なことを実行するための postProcess メソッドを実装することだけです。次のリストは、関連する署名を示しています。

public abstract class AbstractRequestControlDirContextProcessor implements
      DirContextProcessor {

   public void preProcess(DirContext ctx) throws NamingException {
      ...
   }

   public abstract Control createRequestControl();
}

典型的な DirContextProcessor は、次の例のようになります。

例 33: リクエスト制御 DirContextProcessor 実装
package com.example.control;

public class MyCoolRequestControl extends AbstractRequestControlDirContextProcessor {
   private static final boolean CRITICAL_CONTROL = true;
   private MyCoolCookie cookie;
   ...
   public MyCoolCookie getCookie() {
      return cookie;
   }

   public Control createRequestControl() {
      return new SomeCoolControl(cookie.getCookie(), CRITICAL_CONTROL);
   }

   public void postProcess(DirContext ctx) throws NamingException {
      LdapContext ldapContext = (LdapContext) ctx;
      Control[] responseControls = ldapContext.getResponseControls();

      for (int i = 0; i < responseControls.length; i++) {
         if (responseControls[i] instanceof SomeCoolResponseControl) {
            SomeCoolResponseControl control = (SomeCoolResponseControl) responseControls[i];
            this.cookie = new MyCoolCookie(control.getCookie());
         }
      }
   }
}
コントロールを使用する場合は、必ず LdapContextSource を使用してください。Control (標準 Javadoc) (英語) インターフェースは LDAPv3 に固有であり、DirContext の代わりに LdapContext を使用する必要があります。AbstractRequestControlDirContextProcessor サブクラスが LdapContext ではない引数で呼び出されると、IllegalArgumentException がスローされます。

11.3. ページ検索結果

一部の検索では、多数の結果が返される場合があります。少量の結果を除外する簡単な方法がない場合は、サーバーが呼び出されるたびに特定の数の結果のみを返すようにすると便利です。これは「ページ検索結果」として知られています。結果の各「ページ」は、次のページと前のページへのリンクとともに表示できます。この機能がない場合、クライアントは手動で検索結果をページに制限するか、結果全体を取得してから適切なサイズのページに分割する必要があります。前者はかなり複雑になり、後者は不必要な量のメモリを消費します。

一部の LDAP サーバーは PagedResultsControl をサポートしています。これは、検索操作の結果が指定されたサイズのページで LDAP サーバーによって返されることをリクエストします。ユーザーは、検索が呼び出される速度を制御することによって、ページが返される速度を制御します。ただし、呼び出し間で Cookie を追跡する必要があります。サーバーは、この Cookie を使用して、ページ化された結果リクエストで前回呼び出されたときに中断した場所を追跡します。

Spring LDAP は、前のセクションで説明したように、LdapContext の前処理と後処理の概念を使用して、ページ化された結果をサポートします。これは、PagedResultsDirContextProcessor クラスを使用して行います。PagedResultsDirContextProcessor クラスは、リクエストされたページサイズで PagedResultsControl を作成し、それを LdapContext に追加します。検索後、PagedResultsResponseControl を取得し、ページ結果 Cookie を取得します。これは、連続するページ結果リクエスト間のコンテキストを保持するために必要です。

次の例は、ページ検索結果機能を使用する方法を示しています。

例 34: PagedResultsDirContextProcessor を使用してページングされた結果
public List<String> getAllPersonNames() {
  final SearchControls searchControls = new SearchControls();
  searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);

  final PagedResultsDirContextProcessor processor =
        new PagedResultsDirContextProcessor(PAGE_SIZE);

  return SingleContextSource.doWithSingleContext(
        contextSource, new LdapOperationsCallback<List<String>>() {

      @Override
      public List<String> doWithLdapOperations(LdapOperations operations) {
        List<String> result = new LinkedList<String>();

        do {
          List<String> oneResult = operations.search(
            "ou=People",
            "(&(objectclass=person))",
            searchControls,
            CN_ATTRIBUTES_MAPPER,
            processor);
          result.addAll(oneResult);
        } while(processor.hasMore());

        return result;
      }
  });
}
ページ化された結果の Cookie が引き続き有効であるためには、ページ化された結果の呼び出しごとに同じ基になる接続を使用する必要があります。前の例で示したように、SingleContextSource を使用してこれを行うことができます。

12. トランザクションサポート

LDAP の世界に登場するリレーショナルデータベースの操作に慣れているプログラマーは、トランザクションの概念がないという事実にしばしば驚きを表明します。これはプロトコルで指定されておらず、どの LDAP サーバーもサポートしていません。これが大きな問題になる可能性があることを認識して、Spring LDAP はクライアント側のサポートを提供し、LDAP リソースのトランザクションを補正します。

LDAP トランザクションサポートは、LDAP 操作の Spring トランザクションサポートを管理する PlatformTransactionManager 実装である ContextSourceTransactionManager によって提供されます。コラボレーターとともに、トランザクションで実行された LDAP 操作を追跡し、各操作の前に状態を記録し、トランザクションをロールバックする必要がある場合に初期状態を復元するための手順を実行します。

実際のトランザクション管理に加えて、Spring LDAP トランザクションサポートにより、同じトランザクション全体で同じ DirContext インスタンスが使用されるようになります。つまり、トランザクションが完了するまで DirContext は実際には閉じられないため、より効率的なリソースの使用が可能になります。

トランザクションサポートを提供するために Spring LDAP で使用されるアプローチは多くの場合に十分ですが、従来の意味での「実際の」トランザクションでは決してありません。サーバーはトランザクションをまったく認識しないため、(たとえば) 接続が切断された場合、トランザクションをロールバックする方法はありません。これは慎重に検討する必要がありますが、別の方法として、トランザクションサポートをまったく使用せずに動作することにも注意してください。Spring LDAP のトランザクションサポートは、最高に優れています。
クライアント側のトランザクションサポートにより、元の操作で必要な作業に加えて、いくらかのオーバーヘッドが追加されます。ほとんどの場合、このオーバーヘッドは心配する必要はありませんが、アプリケーションが同じトランザクション内で複数の LDAP 操作を実行しない場合 (たとえば、modifyAttributes に続いて rebind)、または JDBC データソースとのトランザクション同期が不要な場合 ( JDBC トランザクションの統合を参照してください)、LDAP トランザクションサポートを使用しても得られるものはほとんどありません。

12.1. 構成

Spring トランザクションの構成に慣れている場合、Spring LDAP トランザクションの構成は非常になじみがあるはずです。トランザクションクラスに @Transactional のアノテーションを付け、TransactionManager インスタンスを作成し、Bean 構成に <tx:annotation-driven> 要素を含めることができます。次の例は、その方法を示しています。

<ldap:context-source
       url="ldap://localhost:389"
       base="dc=example,dc=com"
       username="cn=Manager"
       password="secret" />

<ldap:ldap-template id="ldapTemplate" />
<ldap:transaction-manager>
    <!--
    Note this default configuration will not work for more complex scenarios;
    see below for more information on RenamingStrategies.
    -->
   <ldap:default-renaming-strategy />
</ldap:transaction-manager>

<!--
   The MyDataAccessObject class is annotated with @Transactional.
-->
<bean id="myDataAccessObject" class="com.example.MyRepository">
  <property name="ldapTemplate" ref="ldapTemplate" />
</bean>

<tx:annotation-driven />
...
このセットアップはほとんどの単純なユースケースでうまく機能しますが、一部のより複雑なシナリオでは追加の構成が必要になります。特に、トランザクション内でサブツリーを作成または削除する必要がある場合は、名前の変更戦略に従って、代わりの TempEntryRenamingStrategy を使用する必要があります。

実際の状況では、リポジトリレベルではなく、サービスオブジェクトレベルでトランザクションを適用することになるでしょう。前の例は、一般的な考え方を示しています。

12.2. JDBC トランザクションの統合

LDAP に対して作業する場合の一般的な使用例は、データの一部が LDAP ツリーに保存され、他のデータがリレーショナルデータベースに保存されるというものです。この場合、さまざまなリソースの更新を同期する必要があるため、トランザクションサポートがさらに重要になります。

実際の XA トランザクションはサポートされていませんが、<ldap:transaction-manager> 要素に data-source-ref 属性を指定することにより、JDBC および LDAP アクセスを同じトランザクション内に概念的にラップするサポートが提供されています。これにより ContextSourceAndDataSourceTransactionManager が作成され、2 つのトランザクションが仮想的に 1 つのトランザクションであるかのように管理されます。コミットを実行すると、操作の LDAP 部分が常に最初に実行され、LDAP コミットが失敗した場合に両方のトランザクションがロールバックされます。トランザクションの JDBC 部分は、ネストされたトランザクションがサポートされていないことを除いて、DataSourceTransactionManager とまったく同じように管理されます。次の例は、data-source-ref 属性を持つ ldap:transaction-manager 要素を示しています。

<ldap:transaction-manager data-source-ref="dataSource" >
  <ldap:default-renaming-strategy />
<ldap:transaction-manager />
提供されるサポートはすべてクライアント側です。ラップされたトランザクションは XA トランザクションではありません。LDAP サーバーはその結果に投票できないため、2 フェーズコミットは実行されません。

次のように、<ldap:transaction-manager> 要素に session-factory-ref 属性を指定することで、Hibernate 統合でも同じことを実現できます。

<ldap:transaction-manager session-factory-ref="dataSource" >
  <ldap:default-renaming-strategy />
<ldap:transaction-manager />

12.3. LDAP 補正トランザクションの説明

Spring LDAP は、各変更操作 (bindunbindrebindmodifyAttributesrename) の前に LDAP ツリーの状態を記録することにより、補正トランザクションを管理します。これにより、トランザクションをロールバックする必要がある場合に、システムは補正操作を実行できます。

多くの場合、補正操作は非常に簡単です。例: bind 操作の補正ロールバック操作は、エントリのバインドを解除することです。ただし、その他の操作では、LDAP データベースの特定の特性により、別のより複雑なアプローチが必要になります。具体的には、エントリのすべての Attributes の値を常に取得できるとは限らないため、前述の戦略は (たとえば) unbind 操作には不十分です。

これが、Spring LDAP 管理トランザクション内で実行される各変更操作が内部的に 4 つの異なる操作 (記録操作、準備操作、コミット操作、ロールバック操作) に分割される理由です。次の表では、各 LDAP 操作について説明します。

LDAP 操作 記録 準備 コミット ロールバック

bind

バインドするエントリの DN を記録します。

エントリをバインドします。

操作なし。

記録された DN を使用して、エントリのバインドを解除します。

rename

元の DN とターゲット DN を記録します。

エントリの名前を変更します。

操作なし。

エントリの名前を元の DN に戻します。

unbind

元の DN を記録し、一時的な DN を計算します。

エントリの名前を一時的な場所に変更します。

一時エントリのバインドを解除します。

エントリの名前を一時的な場所から元の DN に戻します。

rebind

元の DN と新しい Attributes を記録し、一時的な DN を計算します。

エントリの名前を一時的な場所に変更します。

新しい Attributes を元の DN でバインドし、元のエントリを一時的な場所からバインド解除します。

エントリの名前を一時的な場所から元の DN に戻します。

modifyAttributes

変更するエントリの DN を記録し、変更を行うための補正 ModificationItem インスタンスを計算します。

modifyAttributes 操作を実行します。

操作なし。

計算された補正 ModificationItem インスタンスを使用して、modifyAttributes 操作を実行します。

Spring LDAP トランザクションサポートの内部動作の詳細な説明は、Javadoc にあります。

12.3.1. 名前の変更戦略

前のセクションの表で説明したように、一部の操作のトランザクション管理では、コミットで実際の変更を行う前に、操作の影響を受ける元のエントリの名前を一時的に変更する必要があります。エントリの一時 DN が計算される方法は、構成の <ldap:transaction-manager > 宣言の子要素で指定された TempEntryRenamingStrategy によって管理されます。Spring LDAP には、次の 2 つの実装が含まれています。

  • DefaultTempEntryRenamingStrategy (デフォルト): <ldap:default-renaming-strategy /> 要素を使用して指定します。エントリ DN の最下位部分にサフィックスを追加します。例: cn=john doe, ou=users の DN の場合、この戦略は cn=john doe_temp, ou=users の一時的な DN を返します。temp-suffix 属性を設定することにより、サフィックスを構成できます。

  • DifferentSubtreeTempEntryRenamingStrategy<ldap:different-subtree-renaming-strategy /> 要素を使用して指定します。サブツリー DN を DN の最下位部分に追加します。これにより、すべての一時エントリが LDAP ツリーの特定の場所に配置されます。一時サブツリー DN は、subtree-node 属性を設定することによって構成されます。例: subtree-node が ou=tempEntries で、エントリの元の DN が cn=john doe, ou=users の場合、一時的な DN は cn=john doe, ou=tempEntries です。構成されたサブツリーノードが LDAP ツリーに存在する必要があることに注意してください。

DefaultTempEntryRenamingStrategy は、状況によっては機能しません。例: 再帰的な削除を行う場合は、DifferentSubtreeTempEntryRenamingStrategy を使用する必要があります。これは、再帰的な削除操作が、実際にはサブツリー内の各ノードの深さ優先の削除で構成されているためです。子を持つエントリの名前を変更することはできず、DefaultTempEntryRenamingStrategy は各ノードを実際に削除するのではなく、同じサブツリーに (別の名前で) 残すため、この操作は失敗します。疑わしい場合は、DifferentSubtreeTempEntryRenamingStrategy を使用してください。

13. Spring LDAP を使用したユーザー認証

このセクションでは、Spring LDAP を使用したユーザー認証について説明します。次のトピックが含まれています。

13.1. 基本認証

ContextSource のコア機能は LdapTemplate で使用する DirContext インスタンスを提供することですが、LDAP サーバーに対してユーザーを認証するためにも使用できます。ContextSource の getContext(principal, credentials) メソッドはまさにそれを行います。ContextSource 構成に従って DirContext インスタンスを構築し、提供されたプリンシパルと資格情報を使用してコンテキストを認証します。カスタム認証方法は、次の例のようになります。

public boolean authenticate(String userDn, String credentials) {
  DirContext ctx = null;
  try {
    ctx = contextSource.getContext(userDn, credentials);
    return true;
  } catch (Exception e) {
    // Context creation failed - authentication did not succeed
    logger.error("Login failed", e);
    return false;
  } finally {
    // It is imperative that the created DirContext instance is always closed
    LdapUtils.closeContext(ctx);
  }
}

authenticate メソッドに提供される userDn は、認証するユーザーの完全な DN である必要があります ( ContextSource の base 設定に関係なく)。通常、この DN を取得するには、(たとえば) ユーザー名に基づいて LDAP 検索を実行する必要があります。次の例は、その方法を示しています。

private String getDnForUser(String uid) {
  List<String> result = ldapTemplate.search(
      query().where("uid").is(uid),
      new AbstractContextMapper() {
         protected String doMapFromContext(DirContextOperations ctx) {
            return ctx.getNameInNamespace();
         }
      });

  if(result.size() != 1) {
    throw new RuntimeException("User not found or not unique");
  }

  return result.get(0);
}

このアプローチにはいくつかの欠点があります。ユーザーの DN を気にする必要があり、検索できるのはユーザーの uid だけであり、検索は常にツリーのルート (空のパス) から開始されます。より柔軟な方法では、検索ベース、検索フィルター、資格情報を指定できます。Spring LDAP には、この機能を提供する LdapTemplate の認証メソッドが含まれています: boolean authenticate(LdapQuery query, String password);

この方法を使用すると、認証は次のように簡単になります。

例 35: Spring LDAP を使用したユーザーの認証
ldapTemplate.authenticate(query().where("uid").is("john.doe"), "secret");
次のセクションで説明するように、セットアップによっては、実際の認証を行うために追加の操作を実行する必要がある場合があります。詳細については、認証されたコンテキストでの操作の実行を参照してください。
独自のカスタム認証メソッドを作成しないでください。Spring LDAP で提供されているものを使用してください。

13.2. 認証されたコンテキストでの操作の実行

一部の認証スキームおよび LDAP サーバーでは、実際の認証を行うために、作成された DirContext インスタンスに対して何らかの操作を実行する必要があります。サーバーのセットアップと認証スキームがどのように動作するかをテストして確認する必要があります。そうしないと、提供された DN と資格情報に関係なく、ユーザーがシステムに入ることが許可される可能性があります。次の例は、認証されたコンテキストでハードコードされた lookup 操作が実行される、認証メソッドの単純な実装を示しています。

public boolean myAuthenticate(String userDn, String credentials) {
  DirContext ctx = null;
  try {
    ctx = contextSource.getContext(userDn, credentials);
    // Take care here - if a base was specified on the ContextSource
    // that needs to be removed from the user DN for the lookup to succeed.
    ctx.lookup(userDn);
    return true;
  } catch (Exception e) {
    // Context creation failed - authentication did not succeed
    logger.error("Login failed", e);
    return false;
  } finally {
    // It is imperative that the created DirContext instance is always closed
    LdapUtils.closeContext(ctx);
  }
}

操作を常に lookup に制限するのではなく、コールバックインターフェースの実装として操作を提供できる方がよいでしょう。Spring LDAP には、AuthenticatedLdapEntryContextMapper コールバックインターフェースと、対応する authenticate メソッド <T> T authenticate(LdapQuery query, String password, AuthenticatedLdapEntryContextMapper<T> mapper); が含まれています。

このメソッドを使用すると、次のように、認証されたコンテキストで任意の操作を実行できます。

例 36: Spring LDAP を使用した認証済みコンテキストでの LDAP 操作の実行
AuthenticatedLdapEntryContextMapper<DirContextOperations> mapper = new AuthenticatedLdapEntryContextMapper<DirContextOperations>() {
  public DirContextOperations mapWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification) {
    try {
      return (DirContextOperations) ctx.lookup(ldapEntryIdentification.getRelativeName());
    }
    catch (NamingException e) {
      throw new RuntimeException("Failed to lookup " + ldapEntryIdentification.getRelativeName(), e);
    }
  }
};

ldapTemplate.authenticate(query().where("uid").is("john.doe"), "secret", mapper);

13.3. 廃止された認証方法

前のセクションで説明した authenticate メソッドに加えて、非推奨の認証メソッドを多数使用できます。これらは正常に機能しますが、代わりに LdapQuery メソッドを使用することをお勧めします。

13.4. Spring Security の使用

前のセクションで説明したアプローチは単純な認証シナリオには十分かもしれませんが、この分野の要件は一般に急速に拡大します。認証、認可、Web 統合、ユーザーコンテキスト管理など、さまざまな側面が適用されます。要件が単純な認証を超えて拡大する可能性があると思われる場合は、代わりにセキュリティ目的で Spring Security を使用することを検討する必要があります。これは、前述の側面だけでなく他のいくつかの側面にも対処する、完全な機能を備えた成熟したセキュリティフレームワークです。

14. LDIF 解析

LDAP Directory Interchange Format (LDIF) ファイルは、ディレクトリデータをフラットファイル形式で記述するための標準的な媒体です。この形式の最も一般的な用途には、情報の転送とアーカイブが含まれます。ただし、標準では、格納されたデータへの変更をフラットファイル形式で記述する方法も定義されています。この後者の型の LDIF は、通常、changetype または modify LDIF と呼ばれます。

org.springframework.ldap.ldif パッケージは、LDIF ファイルを解析して具体的なオブジェクトに逆直列化するために必要なクラスを提供します。LdifParser は org.springframework.ldap.ldif パッケージのメインクラスであり、RFC 2849 に準拠するファイルを解析できます。このクラスは、リソースから行を読み取り、LdapAttributes オブジェクトにアセンブルします。

LdifParser は現在、changetype LDIF エントリを無視します。これは、アプリケーションのコンテキストでの有用性がまだ決定されていないためです。

14.1. オブジェクト表現

org.springframework.ldap.core パッケージの 2 つのクラスは、LDIF をコードで表す手段を提供します。

  • LdapAttribute: RFC2849 で定義されている LDIF オプションのサポートを追加して、javax.naming.directory.BasicAttribute を拡張します。

  • LdapAttributes: DN の特殊なサポートを追加して javax.naming.directory.BasicAttributes を拡張します。

LdapAttribute オブジェクトは、オプションを Set<String> として表します。LdapAttributes オブジェクトに追加された DN サポートは、javax.naming.ldap.LdapName クラスを採用しています。

14.2. パーサー

Parser インターフェースは操作の基盤を提供し、次の 3 つのサポートポリシー定義を採用しています。

  • SeparatorPolicy: ラインをアトリビュートにアセンブルするメカニズムを確立します。

  • AttributeValidationPolicy: 解析する前に、属性が正しく構造化されていることを確認します。

  • Specification: アセンブリ後にオブジェクト構造を検証できるメカニズムを提供します。

これらのインターフェースのデフォルトの実装は次のとおりです。

  • org.springframework.ldap.ldif.parser.LdifParser

  • org.springframework.ldap.ldif.support.SeparatorPolicy

  • org.springframework.ldap.ldif.support.DefaultAttributeValidationPolicy

  • org.springframework.ldap.schema.DefaultSchemaSpecification

これら 4 つのクラスが一緒になって、リソースを 1 行ずつ解析し、データを LdapAttributes オブジェクトに変換します。

SeparatorPolicy は、ソースファイルから読み取られた個々の行がどのように解釈されるかを決定します。これは、LDIF 仕様によって属性が複数の行にまたがることが許可されているためです。デフォルトのポリシーは、考慮されている行の性質を判断するために、読み取られた順序のコンテキストで行を評価します。control 属性と changetype レコードは無視されます。

DefaultAttributeValidationPolicy は REGEX 式を使用して、解析された各属性が (RFC 2849 に従って) 有効な属性形式に準拠していることを確認します。属性が検証に失敗すると、InvalidAttributeFormatException がログに記録され、レコードはスキップされます (パーサーは null を返します)。

14.3. スキーマ検証

解析されたオブジェクトをスキーマに対して検証するメカニズムは、org.springframework.ldap.schema パッケージの Specification インターフェースを通じて利用できます。DefaultSchemaSpecification は検証を行わず、レコードが有効であることがわかっていてチェックする必要がない場合に使用できます。このオプションは、検証が課すパフォーマンスの低下を防ぎます。BasicSchemaSpecification は、DN およびオブジェクトクラス宣言が提供されていることを確認するなど、基本的なチェックを適用します。現在、実際のスキーマに対する検証には、Specification インターフェースの実装が必要です。

14.4. Spring Batch Integration

LdifParser は、LDIF ファイルの解析を必要とするアプリケーションで使用できますが、Spring は、CSV などの区切りファイルを解析するための多くのファイル処理ユーティリティを提供するバッチ処理フレームワークを提供します。org.springframework.ldap.ldif.batch パッケージは、Spring Batch フレームワークで有効な構成オプションとして LdifParser を使用するために必要なクラスを提供します。このパッケージには 5 つのクラスがあります。これらを組み合わせることで、次の 3 つの基本的な使用例が提供されます。

  • ファイルから LDIF レコードを読み取り、LdapAttributes オブジェクトを返します。

  • ファイルから LDIF レコードを読み取り、レコードを Java オブジェクト (POJO) にマッピングします。

  • ファイルへの LDIF レコードの書き込み。

最初の使用例は LdifReader で達成されます。このクラスは Spring Batch の AbstractItemCountingItemStreamItemReader を継承し、その ResourceAwareItemReaderItemStream を実装します。これはフレームワークに自然に適合し、ファイルから LdapAttributes オブジェクトを読み取るために使用できます。

MappingLdifReader を使用して、LDIF オブジェクトを任意の POJO に直接マップできます。このクラスでは、RecordMapper インターフェースの実装を提供する必要があります。この実装は、オブジェクトを POJO にマッピングするためのロジックを実装する必要があります。

RecordCallbackHandler を実装し、その実装をいずれかのリーダーに提供できます。このハンドラーを使用して、スキップされたレコードを操作できます。詳細については、Spring Batch API ドキュメントを参照してください。

このパッケージの最後のメンバーである LdifAggregator を使用して、LDIF レコードをファイルに書き込むことができます。このクラスは、LdapAttributes オブジェクトの toString() メソッドを呼び出します。

15. ユーティリティ

このセクションでは、Spring LDAP で使用できる追加のユーティリティについて説明します。

15.1. 多値属性の増分取得

特定の属性に非常に多数の属性値 (>1500) がある場合、Active Directory は通常、これらすべての値を一度に返すことを拒否します。代わりに、多値プロパティの増分検索 [IETF] (英語) メソッドに従って属性値が返されます。これを行うには、呼び出し側で特定のマーカーの返された属性をインスペクションし、必要に応じて、すべての値が見つかるまで追加の検索リクエストを行う必要があります。

Spring LDAP の org.springframework.ldap.core.support.DefaultIncrementalAttributesMapper は、次のように、この種の属性を操作するときに役立ちます。

Object[] attrNames =  new Object[]{"oneAttribute", "anotherAttribute"};
Attributes attrs = DefaultIncrementalAttributeMapper.lookupAttributes(ldapTemplate, theDn, attrNames);

前の例では、返された属性範囲マーカーを解析し、リクエストされたすべての属性のすべての値が取得されるまで、必要に応じてリクエストを繰り返します。

16. テスト

このセクションでは、Spring LDAP を使用したテストについて説明します。次のトピックが含まれています。

16.1. 組み込みサーバーの使用

spring-ldap-test は、ApacheDS (英語) または UnboundID (英語) に基づく組み込み LDAP サーバーを提供します。

spring-ldap-test は ApacheDS 1.5.5 と互換性があります。新しいバージョンの ApacheDS はサポートされていません。

開始するには、spring-ldap-test 依存関係を含める必要があります。

次のリストは、Maven に spring-ldap-test を組み込む方法を示しています。

<dependency>
    <groupId>org.springframework.ldap</groupId>
    <artifactId>spring-ldap-test</artifactId>
    <version>{project-version}</version>
    <scope>test</scope>
</dependency>

次のリストは、Gradle に spring-ldap-test を組み込む方法を示しています。

testCompile "org.springframework.ldap:spring-ldap-test:{project-version}"

16.2. ApacheDS

ApacheDS を使用するには、いくつかの ApacheDS 依存関係を含める必要があります。

次の例は、Maven の ApacheDS 依存関係を含める方法を示しています。

<dependency>
    <groupId>org.apache.directory.server</groupId>
    <artifactId>apacheds-core</artifactId>
    <version>1.5.5</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.apache.directory.server</groupId>
    <artifactId>apacheds-core-entry</artifactId>
    <version>1.5.5</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.apache.directory.server</groupId>
    <artifactId>apacheds-protocol-shared</artifactId>
    <version>1.5.5</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.apache.directory.server</groupId>
    <artifactId>apacheds-protocol-ldap</artifactId>
    <version>1.5.5</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.apache.directory.server</groupId>
    <artifactId>apacheds-server-jndi</artifactId>
    <version>1.5.5</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.apache.directory.shared</groupId>
    <artifactId>shared-ldap</artifactId>
    <version>0.9.15</version>
    <scope>test</scope>
</dependency>

次の例は、Gradle の ApacheDS 依存関係を含める方法を示しています。

testCompile "org.apache.directory.server:apacheds-core:1.5.5",
            "org.apache.directory.server:apacheds-core-entry:1.5.5",
            "org.apache.directory.server:apacheds-protocol-shared:1.5.5",
            "org.apache.directory.server:apacheds-protocol-ldap:1.5.5",
            "org.apache.directory.server:apacheds-server-jndi:1.5.5",
            "org.apache.directory.shared:shared-ldap:0.9.15"

次の Bean 定義は、組み込み LDAP サーバーを作成します。

<bean id="embeddedLdapServer" class="org.springframework.ldap.test.EmbeddedLdapServerFactoryBean">
    <property name="partitionName" value="example"/>
    <property name="partitionSuffix" value="dc=261consulting,dc=com" />
    <property name="port" value="9321" />
</bean>

spring-ldap-test は、org.springframework.ldap.test.LdifPopulator を使用して LDAP サーバーに入力するメカニズムを提供します。これを使用するには、次のような Bean を作成します。

<bean class="org.springframework.ldap.test.LdifPopulator" depends-on="embeddedLdapServer">
    <property name="contextSource" ref="contextSource" />
    <property name="resource" value="classpath:/setup_data.ldif" />
    <property name="base" value="dc=jayway,dc=se" />
    <property name="clean" value="true" />
    <property name="defaultBase" value="dc=jayway,dc=se" />
</bean>

組み込み LDAP サーバーに対して動作する別の方法は、次のように org.springframework.ldap.test.TestContextSourceFactoryBean を使用することです。

<bean id="contextSource" class="org.springframework.ldap.test.TestContextSourceFactoryBean">
    <property name="defaultPartitionSuffix" value="dc=jayway,dc=se" />
    <property name="defaultPartitionName" value="jayway" />
    <property name="principal" value="uid=admin,ou=system" />
    <property name="password" value="secret" />
    <property name="ldifFile" value="classpath:/setup_data.ldif" />
    <property name="port" value="1888" />
</bean>

また、org.springframework.ldap.test.LdapTestUtils は組み込み LDAP サーバーをプログラムで操作する方法を提供します。

16.3. UnboundID

UnboundID を使用するには、UnboundID 依存関係を含める必要があります。

次の例は、Maven の UnboundID 依存関係を含める方法を示しています。

<dependency>
    <groupId>com.unboundid</groupId>
    <artifactId>unboundid-ldapsdk</artifactId>
    <version>3.1.1</version>
    <scope>test</scope>
</dependency>

次の例は、Gradle の UnboundID 依存関係を含める方法を示しています。

testCompile "com.unboundid:unboundid-ldapsdk:3.1.1"

次の Bean 定義は、組み込み LDAP サーバーを作成します。

<bean id="embeddedLdapServer" class="org.springframework.ldap.test.unboundid.EmbeddedLdapServerFactoryBean">
    <property name="partitionName" value="example"/>
    <property name="partitionSuffix" value="dc=261consulting,dc=com" />
    <property name="port" value="9321" />
</bean>

spring-ldap-test は、org.springframework.ldap.test.unboundid.LdifPopulator を使用して LDAP サーバーを設定する方法を提供します。これを使用するには、次のような Bean を作成します。

<bean class="org.springframework.ldap.test.unboundid.LdifPopulator" depends-on="embeddedLdapServer">
    <property name="contextSource" ref="contextSource" />
    <property name="resource" value="classpath:/setup_data.ldif" />
    <property name="base" value="dc=jayway,dc=se" />
    <property name="clean" value="true" />
    <property name="defaultBase" value="dc=jayway,dc=se" />
</bean>

組み込み LDAP サーバーに対して機能する別の方法は、org.springframework.ldap.test.unboundid.TestContextSourceFactoryBean を使用することです。これを使用するには、次のような Bean を作成します。

<bean id="contextSource" class="org.springframework.ldap.test.unboundid.TestContextSourceFactoryBean">
    <property name="defaultPartitionSuffix" value="dc=jayway,dc=se" />
    <property name="defaultPartitionName" value="jayway" />
    <property name="principal" value="uid=admin,ou=system" />
    <property name="password" value="secret" />
    <property name="ldifFile" value="classpath:/setup_data.ldif" />
    <property name="port" value="1888" />
</bean>

また、org.springframework.ldap.test.unboundid.LdapTestUtils は組み込み LDAP サーバーをプログラムで操作する方法を提供します。