コンピュータクワガタ

かっぱのかっぱによるコンピュータ関連のサイトです

No. 5 Javaからの接続。

参考 http://codezine.jp/a/article/aid/200.aspx?p=2
上記サイトが詳しい。このサイトを見て、もう少し生々しい形のソースを書いた。
クラスの詳細な解説等は、上記サイトを見たほうがいい。
Javaのバージョンは5.0を前提としている。

バインドとアンバインド

staticメソッドで、接続のサンプル。

package net.kuwalab;

import java.util.Hashtable;

import javax.naming.Context;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;

public class LdapTest {
    public static void main(String[] args) {
        // LDAP接続
        DirContext ctx = connect("ldap://192.168.0.201", null,
                "dc=kuwalab,dc=net", "uid=namakuwa,ou=user", "test");
        if (ctx == null) {
            System.out.println("接続失敗");
            return;
        }
        System.out.println("接続成功");
        close(ctx);
    }

    private static DirContext connect(String url, String port, String dn,
            String id, String password) {
        DirContext ctx = null;
        try {
            Hashtable<String, String> env = new Hashtable<String, String>();
            env.put(Context.INITIAL_CONTEXT_FACTORY,
                    "com.sun.jndi.ldap.LdapCtxFactory");
            StringBuilder sb = new StringBuilder();
            sb.append(url);
            if (port != null) {
                sb.append(":").append(port);
            }
            env.put(Context.PROVIDER_URL, sb.toString());
            env.put(Context.SECURITY_AUTHENTICATION, "simple");
            env.put(Context.SECURITY_PRINCIPAL, id + "," + dn);
            env.put(Context.SECURITY_CREDENTIALS, password);

            ctx = new InitialDirContext(env);
        } catch (Exception e) {
            e.printStackTrace();
            if (ctx != null) {
                try {
                    ctx.close();
                } catch (Exception ex) {
                    // 無視
                }
                ctx = null;
            }
        }

        return ctx;
    }
    
    private static void close(DirContext ctx) {
        if (ctx != null) {
            try {
                ctx.close();
            } catch (Exception ex) {
                // 無視
            }
        }
    }
}

特に難しいことはない。urlは、ldapsのテストもしたいのでプロトコルからメソッドに渡している。
URLは一生懸命StringBuilderで組み立てているが、PRINCIPALは「+」で連結している。
これをベースに、検索をしてみる。

検索

最初の例は、staticメソッドだったが、やっぱりあまりにもなので作り変える。

package net.kuwalab;

import java.util.Enumeration;
import java.util.Hashtable;

import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;

public class LdapTest {
    private DirContext ctx;

    public static void main(String[] args) {
        // LDAP接続
        LdapTest lt = new LdapTest();
        boolean result = lt.connect("ldap://192.168.0.201", null,
                "dc=kuwalab,dc=net", "uid=namakuwa,ou=user", "test");
        if (!result) {
            System.out.println("接続失敗");
            return;
        }
        System.out.println("接続成功");
        lt.search("uid=namakuwa,ou=user,dc=kuwalab,dc=net", "uid=namakuwa");
        lt.close();

        result = lt.connect("ldap://192.168.0.201", null, "dc=kuwalab,dc=net",
                "cn=Manager", "test");
        if (!result) {
            System.out.println("接続失敗");
            return;
        }
        System.out.println("接続成功");
        lt.search("ou=user,dc=kuwalab,dc=net", "uid=*");
        lt.close();
    }

    public boolean connect(String url, String port, String dn, String id,
            String password) {
        try {
            Hashtable<String, String> env = new Hashtable<String, String>();
            env.put(Context.INITIAL_CONTEXT_FACTORY,
                    "com.sun.jndi.ldap.LdapCtxFactory");
            StringBuilder sb = new StringBuilder();
            sb.append(url);
            if (port != null) {
                sb.append(":").append(port);
            }
            env.put(Context.PROVIDER_URL, sb.toString());
            env.put(Context.SECURITY_AUTHENTICATION, "simple");
            env.put(Context.SECURITY_PRINCIPAL, id + "," + dn);
            env.put(Context.SECURITY_CREDENTIALS, password);

            ctx = new InitialDirContext(env);
        } catch (Exception e) {
            e.printStackTrace();
            if (ctx != null) {
                try {
                    ctx.close();
                } catch (Exception ex) {
                    // 無視
                }
                ctx = null;
            }
            return false;
        }
        return true;
    }

    public void close() {
        if (ctx != null) {
            try {
                ctx.close();
            } catch (Exception ex) {
                // 無視
            }
        }
    }

    public void search(String baseDn, String filter) {
        SearchControls searchControls = new SearchControls();
        // 以下のURL参照。
        // http://java.sun.com/j2se/1.5.0/ja/docs/ja/api/javax/naming/directory/SearchControls.html
        searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
        try {
            // 検索結果のエントリを取得
            NamingEnumeration<SearchResult> result = ctx.search(baseDn, filter,
                    searchControls);
            
            // エントリを1件ずつ処理
            while (result.hasMore()) {
                SearchResult sr = result.next(); // エントリ
                NamingEnumeration attributes = sr.getAttributes().getAll();
                // エントリに含まれる属性を1件ずつ取得して処理
                while (attributes.hasMore()) {
                    Attribute attr = (Attribute) attributes.nextElement();
                    Enumeration values = attr.getAll();
                    while (values.hasMoreElements()) {
                        System.out.println(attr.getID() + "="
                                + values.nextElement());
                    }
                }
            }
        } catch (NamingException e) {
            e.printStackTrace();
        }
    }
}

connectメソッドで接続し、searchで検索、closeで閉じる。
テストするにはこれぐらいでいい。
searchメソッドの肝は、SearchControlsクラスと、値の取得。
SearchControlsクラスをOBJECT_SCOPEで作成すると、指定のbaseDnのみが検索される。例にあるようにSUBTREE_SCOPEとするとbaseDnとそのサブツリーが検索される。あとよく使いそうなのが、ONELEVEL_SCOPEでbaseDnの直下のエントリのみが検索される。
サンプルとしては、SearchControlsを指定できるようにしたほうがいいかもしれない。
検索語の結果の取得がまたややこしい。例のresultには、検索結果のエントリがすべて格納されるので、それぞれを取り出し(SearchResultクラス)て使用する。
取り出した、SearchResultの中にエントリの属性とその属性値が含まれるのでそれもwhile等でループさせ取得する。
サンプル等を読んでてもすぐにはピンとこなかった。
一応、上記の例の出力結果。

接続成功
loginShell=/bin/bash
gecos=testgecos aa
gidNumber=2000
uidNumber=2001
uid=namakuwa
objectClass=account
objectClass=posixAccount
objectClass=top
homeDirectory=/home/namakuwa
cn=namakuwa
接続成功
userPassword=[B@1c29ab2
loginShell=/bin/bash
uidNumber=2001
gidNumber=2000
objectClass=account
objectClass=posixAccount
objectClass=top
uid=namakuwa
gecos=testgecos aa
cn=namakuwa
homeDirectory=/home/namakuwa
loginShell=/bin/bash
gidNumber=2000
uidNumber=2002
userPassword=[B@13a328f
uid=kuwagata
objectClass=account
objectClass=posixAccount
objectClass=top
homeDirectory=/home/kuwagata
cn=kuwagata

追加・削除

追加と、削除は簡単。

package net.kuwalab;

import java.util.Enumeration;
import java.util.Hashtable;

import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;

public class LdapTest {
    private DirContext ctx;

    public static void main(String[] args) {
        // LDAP接続
        LdapTest lt = new LdapTest();
        boolean result = lt.connect("ldap://192.168.0.201", null, "dc=kuwalab,dc=net",
                "cn=Manager", "test");
        if (!result) {
            System.out.println("接続失敗");
            return;
        }
        System.out.println("接続成功");
        lt.search("ou=user,dc=kuwalab,dc=net", "uid=*");

        // エントリの追加
        Attributes attrs = new BasicAttributes();
        // この辺がJava5
        lt.add(attrs, "objectClass", "account", "posixAccount", "top");
        lt.add(attrs, "uid", "java");
        lt.add(attrs, "cn", "java");
        lt.add(attrs, "userPassword", "test");
        lt.add(attrs, "loginShell", "/bin/bash");
        lt.add(attrs, "uidNumber", "2003");
        lt.add(attrs, "gidNumber", "2000");
        lt.add(attrs, "homeDirectory", "/home/java");
        lt.addEntry("uid=java,ou=user,dc=kuwalab,dc=net", attrs);
        System.out.println("再検索");
        lt.search("ou=user,dc=kuwalab,dc=net", "uid=java");
        // 再実行が大変なので削除
        lt.deleteEntry("uid=java,ou=user,dc=kuwalab,dc=net");

        lt.close();
    }

    public boolean connect(String url, String port, String dn, String id,
// 省略
    }

    public void close() {
// 省略
    }

    public void search(String baseDn, String filter) {
// 省略
    }

    public void add(Attributes attrs, String name, String... values) {
        Attribute attr = new BasicAttribute(name);
        for (int i = 0; i < values.length; i++) {
            attr.add(i, values[i]);
        }
        attrs.put(attr);
    }

    public void addEntry(String dn, Attributes attrs) {
        try {
            // 指定のDNにattrsを追加
            ctx.createSubcontext(dn, attrs);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void deleteEntry(String dn) {
        try {
            // 指定のDNの削除
            ctx.destroySubcontext(dn);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

追加は、Attributesに追加するエントリの属性をAttributeとして追加していく。最後にそのAttributesをエントリとして登録する。
上記のaddメソッドは、どちらかというとユーティリティメソッドなので、staticのほうがいい。
削除は、削除するDNを指定するだけ。
結果も省略。追加されているのがわかる。

再検索
loginShell=/bin/bash
gidNumber=2000
userPassword=[B@1cd8669
uid=java
uidNumber=2003
objectClass=account
objectClass=posixAccount
objectClass=top
homeDirectory=/home/java
cn=java

更新

更新はまた独特な形。
上記のクラスに以下のメソッドを追加。

    public void modifyAddEntry(List<ModificationItem> modifyList, String name,
            String value) {
        Attribute attr = new BasicAttribute(name, value);
        ModificationItem mod = new ModificationItem(
                DirContext.ADD_ATTRIBUTE, attr);
        modifyList.add(mod);
    }
    
    public void modifyReplaceEntry(List<ModificationItem> modifyList, String name,
            String value) {
        Attribute attr = new BasicAttribute(name, value);
        ModificationItem mod = new ModificationItem(
                DirContext.REPLACE_ATTRIBUTE, attr);
        modifyList.add(mod);
    }
    
    public void modifyDeleteEntry(List<ModificationItem> modifyList, String name,
            String value) {
        Attribute attr = new BasicAttribute(name, value);
        ModificationItem mod = new ModificationItem(
                DirContext.REMOVE_ATTRIBUTE, attr);
        modifyList.add(mod);
    }
    
    public void modify(String dn, List<ModificationItem> modifyList) {
        try {
            // 指定のDNの削除
            ctx.modifyAttributes(dn, modifyList.toArray(new ModificationItem[modifyList.size()]));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

変更は、ModificationItemの配列が必要となる。今回はListに蓄えて、最後に配列にしている。
変更にも、属性の追加、削除、変更があるが、それは、DirContextのそれぞれADD_ATTRIBUTE、REMOVE_ATTRIBUTE、REPLACE_ATTRIBUTEとなる。
あとは、変更する属性名と、属性値を指定すればいい。
メインは以下。

    public static void main(String[] args) {
        // LDAP接続
        LdapTest lt = new LdapTest();
        boolean result = lt.connect("ldap://192.168.0.201", null,
                "dc=kuwalab,dc=net", "uid=namakuwa,ou=user", "test");
        if (!result) {
            System.out.println("接続失敗");
            return;
        }
        System.out.println("接続成功");
        lt.search("uid=namakuwa,ou=user,dc=kuwalab,dc=net", "uid=namakuwa");
        lt.close();

        result = lt.connect("ldap://192.168.0.201", null, "dc=kuwalab,dc=net",
                "cn=Manager", "test");
        if (!result) {
            System.out.println("接続失敗");
            return;
        }
        System.out.println("接続成功");
        lt.search("ou=user,dc=kuwalab,dc=net", "uid=*");

        // エントリの追加
        Attributes attrs = new BasicAttributes();
        // この辺がJava5
        lt.add(attrs, "objectClass", "account", "posixAccount", "top");
        lt.add(attrs, "uid", "java");
        lt.add(attrs, "cn", "java");
        lt.add(attrs, "userPassword", "test");
        lt.add(attrs, "loginShell", "/bin/bash");
        lt.add(attrs, "uidNumber", "2003");
        lt.add(attrs, "gidNumber", "2000");
        lt.add(attrs, "homeDirectory", "/home/java");
        lt.addEntry("uid=java,ou=user,dc=kuwalab,dc=net", attrs);
        
        System.out.println("再検索");
        lt.search("ou=user,dc=kuwalab,dc=net", "uid=java");

        // 属性の変更
        List<ModificationItem> modifyList = new ArrayList<ModificationItem>();
        lt.modifyAddEntry(modifyList, "gecos", "test gecos");
        lt.modifyAddEntry(modifyList, "description", "test description");
        lt.modifyDeleteEntry(modifyList, "gecos", "test gecos");
        lt.modifyReplaceEntry(modifyList, "description", "change test description");
        lt.modify("uid=java,ou=user,dc=kuwalab,dc=net", modifyList);
        
        System.out.println("再検索");
        lt.search("ou=user,dc=kuwalab,dc=net", "uid=java");

        // 再実行が大変なので削除
        lt.deleteEntry("uid=java,ou=user,dc=kuwalab,dc=net");

        lt.close();
    }

結果は一部のみ。

再検索
loginShell=/bin/bash
gidNumber=2000
userPassword=[B@1cd8669
uid=java
uidNumber=2003
objectClass=account
objectClass=posixAccount
objectClass=top
homeDirectory=/home/java
cn=java
再検索
userPassword=[B@ca2dce
loginShell=/bin/bash
uidNumber=2003
gidNumber=2000
objectClass=account
objectClass=posixAccount
objectClass=top
uid=java
cn=java
homeDirectory=/home/java
description=change test description

変更した値が確認できる。

今回で動きは理解できたので、次回はもうちょっとJavaのソースを洗練させてみる。