連載
» 2000年11月15日 00時00分 UPDATE

ディレクトリサービスの仕組みと活用(6):LDAPプログラミングの基本

[梁瀬介次,@IT]

 前回お話ししたように、本記事ではNetscape Directory SDKを取り上げることにします。言語としては、マルチプラットフォームを目指すことにして、Javaを使ってみます。「Javaはよく分からない」という方も多いと思いますが、プログラミングの経験さえあれば最低限理屈は分かるように、できる限り解説を入れていきます。

 実際に試してみたい方はNetscapeのサイトから「Netscape Directory SDK 4.0 for Java」をダウンロードしてインストールしてみてください。また、JDK(Java Development Kit)1.1以降も必要です。セットアップ手順はリリースノートにも記載されていますが、「CLASSPATH」環境変数に、インストールディレクトリ直下の「packages」ディレクトリにある「〜.jar」ファイルが含まれるようにすれば、コンパイル、実行とも行えるようになります。

LDAPアクセスの基本

 LDAPアクセスの方法はRFC2251で詳しく規定されています。関連仕様も含めてLDAPの仕様はAPI仕様と言ってもいいぐらいきめ細かく汎用的に仕上がっています。とはいえ、これを読みこなすには(英文であるという点も含め)少々骨も折れることでしょう。以下で、Netscape Directory SDKを基にLDAP APIの構成要素を整理してみます。

 LDAP APIを用いれば、LDAPディレクトリに次のような操作を行うことができます。

  • Connect/Disconnect(接続/切断)
  • Search(検索)
  • Add(追加)
  • Modify(変更)
  • Delete(削除)

 など

 操作の基本はエントリに対するアクセスです。エントリの条件を指定して検索し、エントリのアトリビュートを取得したり、逆にアトリビュートを組み立ててエントリを登録したりできます。

 Netscape Directory SDK for Javaでも、これらの操作に対応したAPIクラスが用意されています。クラスとはAPIや変数のカテゴリのようなものと思ってください。用途別にいくつかに分類されます。主なものを紹介しましょう。

・LDAPConnection

 すべての操作の基本となるクラスです。LDAPサーバとの接続と切断(正式にはbind、unbindと呼ばれます)、その後のSearchやAdd、Modifyなどの操作もすべてこのクラスから行います。

・LDAPEntry

 「エントリ」を示すクラスです。検索時には、検索結果がこのLDAPEntryに格納されて、LDAPConnectionから渡されてきます。

・LDAPAttributeSet

・LDAPAttribute

 「アトリビュート」を示します。LDAPEntryにも含まれます。LDAPAttributeSetは複数のアトリビュートの集合です。配列またはリストになっています。その中にLDAPAttributeという実際のアトリビュートが複数格納されています。LDAPAttributeにはアトリビュート名とアトリビュート値が格納されています。アトリビュート値も配列化されている場合があります。

・LDAPSearchResults

 「検索結果」です。該当したLDAPEntryを複数含みます。

・LDAPModificationSet

 エントリのAdd、Modify時に使用します。LDAPAttributeSetに追加したり変更するデータをセットし、そのLDAPAttributeSetをLDAPModificationSetに格納し、Add、Modifyを行います。

検索プログラムを眺めてみよう

 では、簡単な検索のサンプルを眺めてみましょう。

/**
     LDAP Search Sample
             for Netscape Directory SDK 4.0

         Using: ldapSearch host username password filter

**/
import netscape.ldap.*;
import java.util.*;

public class ldapSearch {
     public static void main(String[] args) {

        LDAPConnection ld = null;
        LDAPEntry findEntry = null;
        LDAPSearchResults res;

        try {
            ld = new LDAPConnection(); // <--------------------- (2)

            String host = args[0];  //
            int port = 389;         // <------------------- (3)
            String buser = args[1]; //
            String bpass = args[2]; //

            ld.connect( host, port ,buser ,bpass); // <-------------- (1)

            String BaseDN = "o=ABC-Syouji, c=JP";           //
            int scope = LDAPConnection.SCOPE_SUB;           // <----- (5)
            String Filter = args[3];                    //
            String Attlib[] = {"DN", "cn;lang-ja", "mail"}; //
            boolean typesOnly = false;

            res = ld.search(BaseDN,     // (a)
                    scope,              // (b)
                    Filter,             // (c) <--------------------- (4)
                    Attlib,             // (d)
                    typesOnly );        // (e)

            while ( res.hasMoreElements() ) {       // <----------- (6)

                try {
                    findEntry = res.next();     // <---------------- (7)
                } catch ( LDAPException e ) {
                    System.out.println( ">>>Error: " + e.toString() );
                    continue;
                }

                System.out.println("DN: " + findEntry.getDN() );  // <---------- (8)

                LDAPAttributeSet findAttrs = findEntry.getAttributeSet();       //
                Enumeration enumAttrs = findAttrs.getAttributes();              // <- (9)

                while ( enumAttrs.hasMoreElements() ) {    // <--------------- (10)

/*            */    LDAPAttribute anAttr = (LDAPAttribute)enumAttrs.nextElement();
/*  (11) -->  */    String attrName = anAttr.getName();
/*            */    System.out.print("\t" + attrName + ": ");

/*            */    Enumeration enumVals = anAttr.getStringValues();
/*            */    if (enumVals != null) {
/*  (12) -->  */        while ( enumVals.hasMoreElements() ) {
/*            */            String aVal = (String)enumVals.nextElement();
/*            */            System.out.println(aVal);
/*            */       }
                    }
                }
            }

        } catch( LDAPException e ) {
            System.out.println( ">>>Error: " + e.toString() );
        }

        if ( (ld != null) && ld.isConnected() ) {
            try {
                ld.disconnect();                    // <------- (13)
            } catch ( LDAPException e ) {
                System.out.println( ">>>Error: " + e.toString() );
            }
        }

    }

}
リスト1 検索プログラムのサンプルコード(ここをクリックすると別のウィンドウでリストを表示します

 これはDirectory SDK 4.0に付属するサンプルをもう少し簡単にしたコード例です。順に説明します。ライブラリ宣言、エラーハンドリングなどJavaの文法にかかわるところは省いて説明します(今回使用したサンプルコードは、こちらからダウンロードできます)。

 このプログラムは、コマンドラインから実行して検索を行うプログラムです(Javaアプレットではありません)。

ldapSearch [サーバ名] [ログインユーザー] [パスワード] [検索条件(Filter)]


と引数を指定して実行します。

 まず(1)がLDAPサーバへの接続を示すことは理解してもらえるでしょう。ldというのは(2)で定義されているLDAPConnectionクラスのことです。

 接続のためのパラメータが(3)で定義されています。LDAPサーバ名、ポート番号、ログインユーザー名、パスワードです。ログインするユーザー名はDNで指定するのが一般的ですが、短縮名(uid)と呼ばれる省略した名前を使用することもできます。短縮名が使用できるかどうかはサーバーの設定にもよります。また、ここでは示していませんが、Anonymous(匿名)ユーザーとしてログインすることもできます。Anonymousユーザーでは検索はともかく変更などはできないように設定されていることが多いでしょう。

 そして接続後、(4)でSearchを実行しています。これもLDAPConnectionクラスを通じて行っているのが分かります。

 ここで注意したいのは、Searchの引数です。引数は検索条件を示しています。LDAPの大きな特徴でもありますので、少し詳しく説明してみましょう。

・BaseDN(検索ベース)

 これまでも登場してきました。検索の開始位置を示すDNです。ディレクトリツリー構造のうち、BaseDNで示されたエントリ以下が検索の対象となります。この例では「o=ABC-Syouji, c=JP」以下が対象です。

・Scope(スコープ)

 検索する階層レベルの範囲の指定です。次の3種類が定義されています。

・ BASE(SDKではSCOPE_BASE)

 指定したBaseDNのみ検索対象にします。例えばサンプルの例では、「o=ABC-Syouji, c=JP」のみが対象となります。検索したい対象DNが明らかな場合に指定します。

・ ONELEVEL(SCOPE_ONE)

 BaseDN直下の1レベルのみを検索の対象にします。

・ SUBTREE(SCOPE_SUB)

 BaseDN自身も含めた、BaseDN以下にぶら下がるすべてのレベル(サブツリー)を検索の対象にします。

 この例ではSCOPE_SUBを指定していますので、BaseDN以下すべてが対象になります。

・Filter(フィルタ)

 いわゆる検索条件と考えてよいでしょう。書式はRFC2254に定義されていますが、多少なじみにくく感じるかもしれません。

 例えば「姓(sn)が『Tanaka』でロケーション(l)が『Tokyo』」のエントリを検索するには

(&(sn=Tanaka)(l=Tokyo))

と指定します。( )が評価の順をかこっています。&(アンパサンド)はAND条件を示し、後ろに続く(sn=Tanaka)と(l=Tokyo)をともに満たさなくてはならないことを表しています。|(縦棒)はOR条件を、! (エクストラメーションマーク)はNOT条件を表します。

 もう1つ例を挙げましょう。

(&(l=Tokyo)(|(sn=Tanaka)(sn=Suzuki)))

 これは「ロケーションが『Tokyo』で、snが『Tanaka』か『Suzuki』」のエントリーを示します。もちろん単純に「sn=Tanaka」というだけのシンプルな条件でも構いません。

 また、*(アスタリスク)で前方置換、後方置換も行えます。

cn=Taro*

はcn(通常は氏名)がTaroで始まるエントリーを示します。

・Attribute

 取得するアトリビュート名を指定します。個々に指定するのではなく、指定せずに存在するすべてのアトリビュートを取得することも可能です。

・typesOnly

 アトリビュート値も取得するのか、それともアトリビュート名のみ取得するのかを指定します。真偽値(boolean。「True」か「False」かで指定する)で指定し、Trueであればアトリビュート名のみの取得となり、Falseであればアトリビュート値も取得します。

結果の取得と画面表示

 ここまでで実際の検索操作はほとんど終わりです。いかがでしょう、意外と簡単ではなかったでしょうか?

 では、次にその結果を取得して、画面に表示することになります。少しだけややこしいのですが、定型的な操作でもありますので、決まりきった「おまじない」のようなものだと考えてください。

(6) ここからしばらく、ループ操作に入ります。(4)の時点でエントリの検索結果はすでに「LDAPSearchResults」に配列として格納されています。「hasMoreElements( )」というのは、その配列から順番に取り出す前に、次の配列が存在しているかどうか(最後のエントリまで行き着いたかどうか)を確認しています。最後であれば、このループを抜けて、(13)を実行します。エントリが続く限りは、(7)(12)を繰り返します。

(7) LDAPSearchResultsの結果配列から、エントリ(LDAPEntry)を1つ取り出します。

(8) 画面にこのエントリのDNを表示します。「getDN( )」がエントリのDNを取り出すためのコマンドです。

(9) エントリからアトリビュートセット(LDAPAttributeSet)を取り出します。アトリビュートセットは複数のアトリビュートから成る配列ですので、EnumerationというJavaで使われる一種の配列定義へセットしています。このアトリビュートの集合は、(5)で指定したアトリビュートと一致しているはずです。

(10) ここからやはりループが始まります。(6)と同じなのですが、今度はアトリビュートセットのためのループです。アトリビュートセットからアトリビュートを取り出し終わると、このループを抜けられます。アトリビュートが続く限りは、(11)(12)を繰り返します。

(11) アトリビュートセットからアトリビュートを1つ取得します。そしてこのアトリビュート名を表示します。

(12) アトリビュートからアトリビュート値を取得します。ただし、アトリビュート値もやはり配列の可能性がありますので、ループを使って順番に取り出し、画面に値を表示しています。

 最後に(13)でサーバとの通信を切断して、終了です。

 正常に実行できると、画面には次のように表示されるはずです。

画面1 サンプルリストの実行例(画面をクリックすると拡大表示します) 画面1 サンプルリストの実行例(画面をクリックすると拡大表示します)

 サンプルでは、BaseDNやアトリビュートの指定はプログラム内で決め打ちでしたが、引数にしたり、Javaアプレット/サーブレット化してユーザーから指示させるなどの変更も比較的簡単にできるでしょう。

 また今回は紹介できませんでしたが、Add、Modify、DeleteなどのサンプルもDirectory SDKには付属しています。なかなか日本語の解説がないのが難点ですが、ここまでの説明も参考にしてみてください。

【コラム】 ちょっと変わったアトリビュート名

 アトリビュートにはomailcnなど各種あるという話は本文でもしてきました。しかしアトリビュートをよく見ていると、アトリビュート名の後ろに「;(セミコロン)」でかこって「lang-」や「binary」などと並べたアトリビュートがあります。これは一体何なのでしょう?

 これらはアトリビュートの追加属性を示すもので、RFC2252RFC2596などで定義されています。

 binaryは例えば次のように記述されます。

userCertificate;binary

名前の通り、このアトリビュートの値はバイナリ値であることを示すものです。この例のuserCertificateはユーザー証明書を示しています。ユーザー証明書はテキストデータではないバイナリ値ですので、BERと呼ばれるエンコードを施されてLDAPサーバから返されなければならないのです。アトリビュート名だけからそうしたデコードが必要なアトリビュートであることを知るためにbinaryが付加されている、というわけです。ユーザー証明書以外に、画像データなどもバイナリで示されるのが普通です。

 「lang-」はLanguageタグと呼ばれます。例えば

cn;lang-ja

などと定義されます。気付かれていると思いますが、これは値が日本語であることを示しています。jaの部分は国コードと呼ばれるもので、 RFC1766 ISO639で定義されています。

cn

cn;lang-ja

cn;lang-fr

と複数のcn(氏名)があれば、アメリカ人は「cn」を、日本人は「cn;lang-ja」を、フランス人は「cn;lang-fr」を取得して、それぞれの母国語で表示させることができます。ちなみに、追加属性を含んだ場合には、あくまで、それぞれ別のアトリビュート名として理解されます。プログラミング時などにあらかじめ配列化されていたりするわけではありませんので、注意して下さい。


連載の最後に

 駆け足でしたが、これで一通りはディレクトリサービスの一端がイメージしていただけたのではないかと思います。少なくとも取っ付きにくいというようなイメージを少しでも払拭していただけたなら、喜ばしく思います。

 ディレクトリ技術はWindows2000 ActiveDirectoryの登場もあって、今後PKIと並んで最もホットな基幹サービスとして位置付けられるようになるでしょう。決して乗り遅れないよう、皆さんの会社/組織での成功をお祈りしています。

Copyright© 2017 ITmedia, Inc. All Rights Reserved.

@IT Special

- PR -

TechTargetジャパン

この記事に関連するホワイトペーパー

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。