SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

LDAPとJNDIでシングルサインオン可能なWebを作る

JavaからLDAPを操作する方法


  • X ポスト
  • このエントリーをはてなブックマークに追加

各クラスの説明

 以下の図に本稿のクラス構成を示します。

 図内のクラスの各役割をパッケージごとに以下の表にまとめます。

 「javax.naming.directory」パッケージ内のクラス・インターフェースは、次の通りです。

「javax.naming.directory」パッケージ内のクラス・インターフェース
クラス名(インターフェース名)役割
DirContextディレクトリサービスのインタフェースとなり、オブジェクトに関連付けられた属性のチェックおよび変更、ディレクトリ検索などのメソッドを持つ。
InitialDirContextディレクトリ操作を実行するための開始コンテキスト。
SearchResultDirContext.search()メソッドの結果として返されたNamingEnumeration内のひとつの要素(エントリ)を表す。
Attributesエントリの属性集合。
Attributeエントリの属性。
SearchControls検索範囲の条件および検索結果として返されたものをカプセル化する。
ModificationItemエントリの変更項目を表す。

 「to.msn.wings.ldapsample」パッケージ内のクラスは、本サンプルのアプリケーション仕様に特化した処理が記述されます。

「to.msn.wings.ldapsample」パッケージ内のクラス・インターフェース
クラス名(インターフェース名)役割
EmployeeCRUDServletコントローラの役割をはたす。画面からサブミットされたaction値を元に、該当するDAO(Data Access Object)のメソッドを呼び、ディレクトリサービスのデータを操作する。
Employee社員を表すPOJO(Plain Old Java Object)。
EmployeeDao社員をデータ操作するメソッドを定義したインターフェース。
EmployeeDaoLDAPImplEmployeeDaoの実装クラス。LDAPでデータを操作する処理を記述。
EmployeeDaoFactoryEmployeeDaoの実インスタンスを生成するファクトリクラス。

 「to.msn.wings.ldap」パッケージのクラスは、LDAPに依存している処理を記述します。

「to.msn.wings.ldap」パッケージ内のクラス
クラス名役割
LDAPServiceManagerLDAPのデータ操作をする処理を記述。

 「to.msn.wings.jndi」パッケージのクラスは、特定のディレクトリサービスに依存しない汎用的なJNDIを呼び出す処理を記述します。

「to.msn.wings.jndi」パッケージ内のクラス
クラス名役割
NamingServiceManagerJNDIのデータ操作メソッドを呼び出す。
EntryObjectディレクトリサービスから検索されたエントリの属性値を格納するクラス。

各クラス説明

 以下に、サンプル中でLDAPサーバーへのバインド、データ操作をしている箇所のソースを中心に説明します。

バインド

 LDAPサーバーに接続する処理はInitialDirContextクラスのコンストラクタを呼び出し、引数として、環境に依存したパラメータを設定したHashtableを渡します。

「LDAPServiceManager.java」抜粋
public static synchronized LDAPServiceManager getInstance(
  String hostname,String principal,String credentials) throws 
NamingException{
  if ( ldapServiceManager == null ){
    Hashtable env = new Hashtable();

    // コンテキスト初期化ファクトリ
    env.put(Context.INITIAL_CONTEXT_FACTORY,
            "com.sun.jndi.ldap.LdapCtxFactory");
    // プロバイダURL(例:ldap://localhost/dc=example,dc=com)
    env.put(Context.PROVIDER_URL , "ldap://" + hostname );

    // LDAPバージョン
    env.put("java.naming.ldap.version" , "3" );

    // rootdn(管理者DN) cn=Manager,dc=example,dc=com
    env.put(Context.SECURITY_PRINCIPAL , principal );

    // rootpw(管理者パスワード) secret
    env.put(Context.SECURITY_CREDENTIALS , credentials );
    ldapServiceManager = new LDAPServiceManager( env );
  }
  return ldapServiceManager;
}

  /** コンストラクタ */
  private LDAPServiceManager(Hashtable env) throws NamingException{
    super( env );
  }
「NamingServiceManager.java」抜粋
  /** コンストラクタ */
  protected NamingServiceManager(Hashtable env)
    throws NamingException{
    dirContext = new InitialDirContext( env );
  }

 InitialDirContextのコンストラクタの引数に設定するHashtableの設定キー・値を、以下の表にまとめます。

InitialDirContextのコンストラクタに渡すHashtableのキー・値
キー説明
Context.INITIAL_CONTEXT_FACTORYcom.sun.jndi.ldap
.LdapCtxFactory
InitialContextのファクトリクラス
java.naming.ldap.version3LDAPのバージョン
Context.SECURITY_PRINCIPALcn=Manager,
dc=example,dc=com
slapd.confに設定したrootdn(管理者DN)
Context.SECURITY_CREDENTIALSsecretslapd.confに設定したrootpw(管理者パスワード)

新規登録

 エントリの新規登録は、javax.naming.directory.DirContextインターフェースのcreateSubcontextメソッドにより行います。

「EmployeeDaoLDAPImpl.java」抜粋
public void add(Employee emp) throws Exception {

  Attributes attrs = new BasicAttributes(); // 属性集合

  // cn属性(社員ID)
  Attribute attrEmpId = new BasicAttribute( "cn" );
  attrEmpId.add( 0 , emp.getId() );
  attrs.put( attrEmpId );

  // メールアドレス
  Attribute attrEmail = new BasicAttribute( "mail" );
  attrEmail.add(0 , emp.getMail() );
  attrs.put( attrEmail );

  // sn属性(名前)
  Attribute attrSn = new BasicAttribute( "sn" );
  attrSn.add( 0 , emp.getName() );
  attrs.put( attrSn );

  // objectClass(必須)
  Attribute attrObjClass = new BasicAttribute( "objectClass" );
  attrObjClass.add( 0 , "top"   );
  attrObjClass.add( 1 , "person");
  attrObjClass.add( 2 , "inetOrgPerson");
  attrs.put( attrObjClass );

  serviceManager.add(
      "cn=" + emp.getId() + ",ou=Users,dc=example,dc=com" , attrs );
}
「NamingServiceManager.java」抜粋
  public void add(String dn,Attributes attrs)
    throws NamingException{
    dirContext.createSubcontext( dn , attrs );
  }

 ContextインターフェースのcreateSubcontextメソッドの引数を、以下の表にまとめます。

ContextインターフェースのcreateSubcontextメソッドの引数
引数説明
nameString更新対象のエントリのDN(識別名)を指定
attrsAttributes新規登録エントリの属性集合

抽出

 エントリの抽出は、javax.naming.directory.DirContextインターフェースのsearchメソッドにより行います。

「EmployeeDaoLDAPImpl.java」抜粋
public List findByMail(String mail,int matchCond) throws Exception{

  String mailCond  = makeMatchString( mail , matchCond );
  List employeeList
    = serviceManager.search(
        "ou=Users,dc=example,dc=com" ,
        "mail=" + mailCond ,
        SearchControls.OBJECT_SCOPE );

  List empList = new ArrayList();// リターン用のリスト(型:Employee)
  for (Iterator iterEmployee
    = employeeList.iterator(); iterEmployee.hasNext();) {
    // キー=属性名,値=属性値集合
    EntryObject entry = (EntryObject) iterEmployee.next();
    Employee emp = createEmployee(entry);
    empList.add(emp);
  }
  return empList;
}

「NamingServiceManager.java」抜粋
public List search(String baseDn,String filter,int searchScope)
  throws NamingException {

  SearchControls searchControls = new SearchControls();
  searchControls.setSearchScope( searchScope );
  NamingEnumeration searchResultEnum
    = dirContext.search( baseDn , filter , searchControls );

  List entryList = new ArrayList();

  // 検索結果のループ
  while ( searchResultEnum.hasMore() ){
    SearchResult searchResult =
      (SearchResult)searchResultEnum.next();
    // 属性の集合を取得
    NamingEnumeration attrs = searchResult.getAttributes().getAll();

    EntryObject entry = new EntryObject();
    while ( attrs.hasMore() ) {
      Attribute attr = (Attribute) attrs.nextElement();
      Enumeration attrValEnum = attr.getAll(); // 属性値の集合
      List attrValueList = new ArrayList();
      while (attrValEnum.hasMoreElements()) {
        String elem = (String)attrValEnum.nextElement();
        attrValueList.add( elem );
      }
      entry.put( attr.getID() , attrValueList );
    }
    entryList.add(entry);
  }
  return entryList;
}

 LDAPのツリー上を検索する際には、

  • 検索をツリー上のどこから開始するか
  • 検索条件(フィルター)
  • ツリー上のどこまでを検索スコープとするか

 の3つを指定する必要があります。DirContextインターフェースのsearchメソッドに渡す3つの引数を、以下の表にまとめます。

DirContextインターフェースのsearchメソッドの引数
引数説明
baseDNString抽出のツリー上の起点となるDN。
filterString抽出条件。
searchControlsSearchControls検索制御設定を格納したオブジェクト。searchScope属性に値を設定する。

 以下の表に、LDAPで用意されている検索スコープ3種類についてまとめます。JNDIでは、この3種類のスコープに対応した定数が、SearchControlsクラスに定義されています。

LDAPの検索スコープ
スコープSearchControlsの定数説明
ベースOBJECT_SCOPEベースエントリだけを検索の対象とする。
1レベルONELEVEL_SCOPEベースエントリの直下のエントリだけを検索の対象とする。
サブツリーSUBTREE_SCOPEベースエントリとその配下のツリー全体を検索の対象とする。

更新

 エントリの属性更新は、javax.naming.directory.DirContextインターフェースのmodifyAttributesメソッドにより行います。

「EmployeeDaoLDAPImpl.java」抜粋
public void update(Employee emp) throws Exception {
  ModificationItem[] mods = new ModificationItem[2];

  Attribute attrSn = new BasicAttribute( "sn" , emp.getName() );
  mods[0] =
    new ModificationItem( DirContext.REPLACE_ATTRIBUTE , attrSn );

  Attribute attrMail = new BasicAttribute( "mail" , emp.getMail() );
  mods[1] =
    new ModificationItem( DirContext.REPLACE_ATTRIBUTE , attrMail );

  String dn = "cn=" + emp.getId() + ",ou=Users,dc=example,dc=com";
  serviceManager.modify( dn , mods );
}
「NamingServiceManager.java」抜粋
public void modify(String dn , ModificationItem[] modificationItems)
  throws NamingException{
  dirContext.modifyAttributes( dn , modificationItems );
}

 DirContextインターフェースのmodifyAttributesメソッド引数を、以下の表にまとめます。

ContextインターフェースのdestroySubcontextメソッドの引数
引数説明
nameString更新対象のエントリのDN(識別名)を指定。
modsModificationItem[] 更新後データが格納された配列を指定。

 属性を変更する場合は、以下のように変更対象属性のAttributeインスタンスを生成し、ModificationItemのコンストラクタに渡します。

Attribute attrMail = new BasicAttribute( "mail" , emp.getMail() );
mods[1] =
    new ModificationItem( DirContext.REPLACE_ATTRIBUTE , attrMail );

削除

 エントリの削除は、javax.naming.ContextインターフェースのdestroySubcontextメソッドにより行います。

「EmployeeDaoLDAPImpl.java」抜粋
public void delete(Employee emp) throws Exception {
  serviceManager.delete(
    "cn=" + emp.getId() + ",ou=Users,dc=example,dc=com" );
}
「NamingServiceManager.java」抜粋
public void delete(String dn) throws NamingException{
  dirContext.destroySubcontext( dn );
}

 ContextインターフェースのdestroySubcontextメソッド引数を、以下の表にまとめます。

ContextインターフェースのdestroySubcontextメソッドの引数
引数説明
nameString削除対象のエントリのDN(識別名)を指定。

まとめ

 データベースやファイルのデータ操作は慣れていても、LDAPサーバーのデータを操作するともなると、戸惑ってしまう方も多いのではないでしょうか。本稿が、そのようなJavaエンジニアがJavaからLDAPを操作する最初のステップとして参考になれば幸いです。

参考資料

  1. @IT 『連載第1回:ディレクトリ統合 なぜ「シングル・サインオン」が必要なのか?』 山本秀宣・高橋悟史 著、2002年12月
  2. FreeBSD 5.1 + OpenLDAP(LDAP Browser/Editor)
  3. OpenLDAP入門』 稲地稔 著、技術評論社、2003年7月
  4. LDAP/OpenLDAPによるユーザ管理/認証ガイド』 LDAP研究会 著、秀和システム、2004年9月
  5. LDAPハンドブック』 河津正人・桑田雅彦・恒田正哉・山形昌也・島村英 著、ソフト・リサーチ・センター、2002年3月

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
特集記事連載記事一覧

もっと読む

この記事の著者

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるReact実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

WINGSプロジェクト 佐藤 治夫 (株式会社ビープラウド)(サトウ ハルオ)

WINGSプロジェクトについて> 有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 RSS Twitter: @yyamada(公式)、@yyamada/wings(メンバーリスト) Facebook

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/200 2006/10/11 19:36

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング