作成日: 2008/12/31

メモ: C#.NET で LDAP を使ったディレクトリサービスの検索

概要

C#.NET で LDAP を使ってディレクトリサービスを検索するための覚え書きです。
なお、ディレクトリサービス関係には The Apache Directory Project の Apache Directory Server 1.5.4 (以下 ApacheDS) および Apache Directory Studio 1.3.0 (以下 Studio) を利用しました。
なお、Apache Directory Server 1.5.4 には別途 jre 5 以降が必要です。

検索対象となるデータの準備 (その1)

ApacheDS と Studio は、それぞれ「C:\Bin\Apache Directory Server」、「C:\Bin\Apache Directory Studio」 にインストールしました。また、ApacheDS のインストール時に作成されるインスタンスはデフォルトのまま 「C:\Bin\Apache Directory Server\instances\default」に作成しました。

インストール直後の ApacheDS のデフォルトインスタンスを Stduio を使って見てみると、以下のようになっています。

インストール直後の ApacheDS の状態

左のツリーには表示されていませんが、デフォルトでは「dc=example,dc=com」というベース DN を持つエントリが用意されています。
このサンプルでは、これは利用せず、別途「o=sample,c=jp」というベース DN を持つエントリを作成し、 そこにデータを追加します。

ApacheDS では、エントリはパーティションと呼ばれる領域に格納され、そこに個々のエントリのツリーデータが格納されます。

まず、「o=sample,c=jp」のエントリを作成します。

デフォルトインスタンスの定義ファイル「C:\Bin\Apache Directory Server\instances\default\conf\server.xml」を開き、 「o=sample,c=jp」のパーティション定義を追加します。ついでにデフォルトで作成されている「dc=example,dc=com」エントリは コメントアウトします。

変更後の設定ファイル

定義ファイルを保存したら、ApacheDS のサービスを再起動します。

ApacheDS のサービスを再起動

パーティションは「C:\Bin\Apache Directory Server\instances\default\partitions」配下に作成されるのですが、 「o=sample,c=jp」のパーティションとなる「sample」フォルダは再起動時に自動的に作成されます。
なお、設定ファイルでコメントアウトした「dc=example,dc=com」のパーティションである 「example」フォルダは削除しても構いません。

パーティション領域

ここでもう一度 ApacheDS のインスタンスを Stduio を使って見てみます。
追加した「o=sample,c=jp」のエントリが作成されています。

変更後の ApacheDS の状態

次に、以下の内容をテキストエディタで編集し、「sample.ldif」として保存します。

dn: o=sample,c=jp
o: sample
objectClass: top
objectClass: organization
description: sample entry

保存したファイルを Studio を使って ApacheDS のデフォルトインスタンスにインポートします。

「LDAP Browser」で「DIT」を右クリックして、メニューを表示させ、「LDIF Import」を選びます。

インポート作業1

「LDIF Import」画面で「Browse」をクリックし、さきほど作成した「sample.ldif」を選んでから、 [Finish] をクリックします。
※2 回以上同じファイルをインポートする場合、「インポートファイル名.log」(この例では「sample.ldif.log」) というファイル名でログファイルが同じフォルダに作成されるため、「Overwrite existing logfile」にチェックを入れないと 「Finish」ボタンが有効になりません。

コンテキストインポート作業2

その後、一旦、ApacheDS との接続を切り、再度接続します。

コンテキストインポート作業3 コンテキストインポート作業4

ツリー表示に「o=sample,c=jp」のエントリが表示されます。

コンテキストインポート作業5
検索対象となるデータの準備 (その2)

作成するサンプルでは単純なツリーを作成して検索するだけなのですが、ついでなので、独自の属性値「userRoomCode」を設定できるようにします。 独自の属性を設定できるようにするためには、スキーマを定義して ApacheDS にインポートする必要があります。

スキーマを定義する際には、OID と呼ばれる識別子を指定する必要があります。 本来なら OID はグローバルにユニークとなる値を取得する必要がありますが、ここではサンプル用なので勝手に定義しています。
もし、ネット上に公開する場合など、グローバルに通用するサービスでスキーマを定義する場合は、 必ずユニークな OID を取得して使うようにしてください。

なお、このサンプルで使っている OID は、ApacheDS のカスタムスキーマ定義のドキュメントに サンプルとして載っている OID の後ろに、勝手に枝番を追加して利用しています。
この 「1.3.6.1.4.1.18060」から始まる OID は Apache Software Foundation のためのものなので、 お遊びでなく、ちゃんとした業務に利用する OID としては、たとえローカルで運用するサービスであっても不適切だと思われます。 が、ここでは、サンプルということでご容赦くださいませ。

以下の内容をテキストエディタで編集し、「sampleSchema.ldif」として保存します。

dn: cn=schema
changetype: modify
add: attributeTypes
attributeTypes: ( 1.3.6.1.4.1.18060.0.4.3.2.1.1.1
        NAME 'userRoomCode'
        DESC 'User Room Code'
        EQUALITY caseIgnoreMatch
        SUBSTR caseIgnoreSubstringsMatch
        SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
        SINGLE-VALUE
 )
-
add: objectClasses
objectClasses: ( 1.3.6.1.4.1.18060.0.4.3.2.1.2.1
        NAME 'mySample'
        DESC 'My Sample Object Class'
        SUP top
        AUXILIARY
        MAY ( userRoomCode )
 )
-

「LDAP Browser」で「DIT」を右クリックして、メニューを表示させ、「LDIF Import」を選びます。

スキーマインポート作業1

「LDIF Import」画面で「Browse」をクリックし、さきほど作成した「sampleSchema.ldif」を選んでから、 [Finish] をクリックします。

スキーマインポート作業2

その後、一旦、ApacheDS との接続を切り、再度接続します。
※切断・再接続は不要のようなのですが、私の環境では、切断・再接続をしないと、インポートしたスキーマ定義を Studio 側でうまく取得できませんでした。

スキーマ定義が反映されたことを確認してみます。
「Root DSE」をクリックしてから、メニューの 「LDAP」-「Open Schema Browser」を選びます。

スキーマインポート作業3

「Filter」に「mySample」と入力し、一覧部に表示される「mySample」をクリックすると、 追加したオブジェクトクラスが表示されます。

スキーマインポート作業4

属性も見てみます。「MAY Attributes」をクリックして表示される「userRoomCode」をクリックします。

スキーマインポート作業5

追加した属性が表示されます。

スキーマインポート作業6
検索対象となるデータの準備 (その3)

検索対象となるデータを追加します。

「LDAP Browser」で「o=sample,c=jp」を右クリックして、メニューを表示させ、「New Entry」を選びます。

データの追加作業1

「New Entry」画面で「Create entry from scratch」を選び、「Next」をクリックします。

データの追加作業2

「Available object classes」から「organizationUnit」を選び、「Add」をクリックします。

データの追加作業3

「Next」をクリックします。

データの追加作業4

「RDN」で、左側に「ou」、右側に「mymembers」と入力し、「Next」をクリックします。

データの追加作業5

「Finish」をクリックします。

データの追加作業6

以上で「ou=mymembers,o=sample,c=jp」が追加されました。

さらに、追加した「ou=mymembers,o=sample,c=jp」を右クリックして、メニューを表示させ、「New Entry」を選びます。

データの追加作業7

「New Entry」画面で「Create entry from scratch」を選び、「Next」をクリックします。

データの追加作業8

「Available object classes」から「inetOrgPerson」を選び、「Add」をクリックします。

データの追加作業9

「Available object classes」から「mySample」を選び、「Add」をクリックします。

データの追加作業10

「Next」をクリックします。

データの追加作業11

「RDN」で、左側に「uid」、右側に「00001」と入力し、「Next」をクリックします。

データの追加作業12

「cn」に「太郎」、「sn」に「山田」と入力します。

データの追加作業13

一覧上で右クリックし、メニューから「New Attribute」を選びます。

データの追加作業14

「Attribute type」に「userRoomCode」と入力し、「Finish」をクリックします。

データの追加作業15

追加した「userRoomCode」に「101」と入力し、「Finish」をクリックします。

データの追加作業16

以上で「uid=00001,ou=mymembers,o=sample,c=jp」が追加されました。

データの追加作業17

さらに、同じような手順により、「uid=00002,ou=mymembers,o=sample,c=jp」、「uid=00003,ou=mymembers,o=sample,c=jp」を追加し、 適当に「cn」、「sn」、「userRoomCode」を設定しました。

データの追加作業18
作成したデータの検索を行う

ソースだけです。すみません。。。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.DirectoryServices.Protocols;

namespace SampleSearch {

    class Program {

        /// <summary>
        /// ディレクトリサービスの検索を行う
        /// </summary>
        /// <param name="args">
        /// </param>
        static void Main(string[] args) {

            string ldapConnectionHostname = "localhost:10389";    // ApacheDS のデフォルトポートは 10389
            string ldapCredentialDN = "uid=admin,ou=system";      // ApacheDS のデフォルトインスタンスの管理者ユーザの DN
            string ldapCredentialPassword = "secret";             // 管理者ユーザのパスワード
            string ldapSearchDN = "ou=mymembers,o=sample,c=jp";   // 作成したデータ下を検索する
            string ldapSearchFilter = "(uid=*)";                  // 検索条件

            LdapConnection ldapConnection = null;

            try {

                // ディレクトリサービスに接続する
                ldapConnection = new LdapConnection(ldapConnectionHostname);
                ldapConnection.Credential = new NetworkCredential(ldapCredentialDN, ldapCredentialPassword);
                ldapConnection.AuthType = AuthType.Basic;
                ldapConnection.SessionOptions.ProtocolVersion = 3;
                ldapConnection.Bind();

                // 作成したデータの検索を行う
                SearchRequest schRequest = new SearchRequest();
                schRequest.DistinguishedName = ldapSearchDN;
                ////srequest.Scope = System.DirectoryServices.Protocols.SearchScope.Subtree;
                schRequest.Filter = ldapSearchFilter;

                SearchResponse schResponse = (SearchResponse) ldapConnection.SendRequest(schRequest);
                SearchResultEntryCollection resultList = schResponse.Entries;

                // 検索結果を表示する
                foreach (SearchResultEntry sre in resultList) {

                    string uid = GetAttributeValue(sre.Attributes, "uid");
                    string cn = GetAttributeValue(sre.Attributes, "cn");
                    string sn = GetAttributeValue(sre.Attributes, "sn");
                    string userRoomCode = GetAttributeValue(sre.Attributes, "userRoomCode");

                    uid = Null2Empty(uid);
                    cn = Null2Empty(cn);
                    sn = Null2Empty(sn);
                    userRoomCode = Null2Empty(userRoomCode);

                    Console.WriteLine(string.Format("uid: {0}, cn: {1}, sn: {2}, userRoomCode: {3}",
                                        uid, cn, sn, userRoomCode
                                    )
                                );
                }

            } catch (Exception ex) {

                Console.WriteLine(string.Format("失敗しました: {0}", ex.Message));

            } finally {

                if (ldapConnection != null) {
                    try {
                        ldapConnection.Dispose();
                    } catch (Exception ex) {
                        Console.WriteLine(ex.Message);
                    }
                }

            }

        }

        /// <summary>
        /// 属性から名前に対応する値を得る
        /// </summary>
        /// <returns>名前に対応する値(無い場合は null)</returns>
        private static string GetAttributeValue(SearchResultAttributeCollection attrs, string name) {

            string result = null;

            if (attrs.Contains(name)) {
                DirectoryAttribute attr = attrs[name];
                string[] valArray = (string[]) attr.GetValues(typeof(string));
                if (0 < valArray.Length) {
                    result = Null2Empty(valArray[0]).Trim();
                }
            }

            return result;
        }

        /// <summary>
        /// null なら空文字を返し、null でなければ元の値を返す
        /// </summary>
        /// <param name="v">null を含む値</param>
        /// <returns>null を含まない値</returns>
        private static string Null2Empty(string v) {

            if (v == null) {
                return "";
            }

            return v;
        }
    }
}
サンプルソースなど

ソース (Visual Studio 2008 で作成しました)

SampleSearch.zip (4.03 KB)

ApacheDS の設定に使用したファイル類

ApacheDS.zip (3.03 KB)