Sunday, January 5, 2014

もう少しだけ LDAP API の比較を

昨日の 記事 に続きまして, 今回はごくごく簡単な SEARCH をおこない, Apache Directory LDAP API と UnboundID LDAP SDK for Java とを比較してみました。

Apache Directory LDAP API の場合

手元の環境にある LDAP サーバーで Von Erich さんを探してみたいと思います。 たとえば, 次のようになりましょうか。

        LdapConnection conn;
        
        // ...
        
        String base = "dc=localdomain";
        String filter = "(sn=Von Erich)";
        SearchScope scope = SearchScope.SUBTREE;
        Cursor<Entry> cursor = conn.search(base, filter, scope);

Apache Directory LDAP API では, LDAP サーバーからのレスポンスを取り扱うには, カーソルを利用する (というか, カーソルのようなものがあるのだと仮定して, それを利用する) というコンセプトになるのですね。 LDAP 関連ではカーソルを使うという話題はあまりネット上で見かけた記憶もなかったので, 目新しい発想だなあと感じました。 その, カーソル的なものを使って, レスポンスに含まれる個々のエントリーの DN を標準出力に書き出すなら, たとえば次のようになります。

        try {
            while (cursor.next()) {
                Entry entry = cursor.get();
                System.out.println(entry.getDn().toString());
            }
        } catch (CursorException | LdapException e) {
            // ...
        } finally {
            cursor.close();
        }

Cursor インターフェースの next() や get() が CursorException や LdapException をスローする可能性があるほか, 使い終わったカーソルは閉じなければなりませんので, ここでは try-catch-finally 構文のお世話になっています。

実行すると, 次のような結果を得ることができました。

cn=Fritz Von Erich,ou=Iron Claw,dc=localdomain
cn=Kevin Von Erich,ou=Iron Claw,dc=localdomain
cn=David Von Erich,ou=Iron Claw,dc=localdomain
cn=Kerry Von Erich,ou=Iron Claw,dc=localdomain
cn=Mike Von Erich,ou=Iron Claw,dc=localdomain
cn=Chris Von Erich,ou=Iron Claw,dc=localdomain

ところで, LDAP インジェクション対策という観点からは, フィルターの文字列を自分で組み立てて用意するのはできれば避けたいところです。 そこで, API 側で用意されているプレースホルダー的なものに救いを求めることを考えてみます。 具体的には SearchRequest を使って, たとえば次のように書き改めることにします。 (なお, Cursor のメソッドがスローする可能性のある例外以外の例外については, 処理を省略しました。)

        LdapConnection conn;
        
        // ...
        
        Dn base = new Dn("dc=localdomain");
        ExprNode filter
                = new EqualityNode("sn", new StringValue("Von Erich"));
        SearchScope scope = SearchScope.SUBTREE;
        SearchRequest request = new SearchRequestImpl()
                .setBase(base)
                .setFilter(filter)
                .setScope(scope);
        Cursor<Response> cursor = conn.search(request);
        
        try {
            while (cursor.next()) {
                Response response = cursor.get();
                if (response instanceof SearchResultEntry) {
                    Entry entry = ((SearchResultEntry)response).getEntry();
                    System.out.println(entry.getDn().toString());
                }
            }
        } catch (CursorException | LdapException e) {
            // ...
        } finally {
            cursor.close();
        }

ExprNode のインスタンスがフィルターを表現することになりますが, search メソッドにはそれを直接引数として受け取るものがありません。 そのため, SearchRequest のインスタンス経由で渡すことを考えてみました。 それがよかったのかいけなかったのかわかりませんが, 先ほどとは若干様相が異なってしまいました。 たとえば, search メソッドが, Cursor<Entry> ではなく Cursor<Response> を返すように変化していますが, なぜそうなるのか, わたしにはまだよくわかっていません。 ただ, instanceof を使わないといけない箇所があったり, SearchResultEntry は Entry のサブインターフェースなのかと思いきや実はまったく親子関係がなかったり, なんというか, あまり見通しがよくなくなってしまったなあ..という感触を覚えます。

UnboundID LDAP SDK for Java の場合

同じことを UnboundID LDAP SDK for Java でやろうとしたら, 次のようになりました。 (なお, search がスローする可能性のある例外については, 処理を省略しました。)

        LDAPConnection conn;
        
        // ...
        
        String base = "dc=localdomain";
        SearchScope scope = SearchScope.SUB;
        Filter filter
                = Filter.createEqualityFilter("sn", "Von Erich");
        SearchResult result = conn.search(base, scope, filter);
        List entries = result.getSearchEntries();
        for (SearchResultEntry entry : entries) {
            System.out.println(entry.getDN());
        }

コンパクトに書くことができました。

細かいところで少し感心した点のは, スコープについてです。 というのも, BASE (0), ONE (1), SUB (2) のほか, SUBORDINATE_SUBTREE (3) を選択することもできるのです。 この最後の選択肢は, まだ LDAPv3 の標準仕様に正式に追加されていません。 だからでしょうか, JNDI も Apache Directory LDAP API も, 現状これをサポートしていません。

また, 上記コードでは, JNDI 的な Enumeration でもなく, Apache 的な Cursor でもなく, 検索結果を List として取得することができます。 なんというか, うれしい気がします。 ちなみに, 実体は編集不可のリスト (unmodifiable list) ですので add や remove などでいたずらをしようとすれば, UnsupportedOperationException が飛んできます。

感想

昨日は何とも判断がつきかねておりましたが, 今回は UnboundID LDAP SDK for Java のほうがだいぶ気に入りました。

No comments:

Post a Comment