Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
8
Help us understand the problem. What is going on with this article?

More than 1 year has passed since last update.

@vicugna-pacos

【Java】ActiveDirectoryの参照/更新

はじめに

ADのユーザーをメンテナンスする機能を提供するため、ユーザー一覧を取得したり、更新できるようにしたい。
JavaではJNDIを使ってADへアクセスできる。

ADの参照

単一ユーザーの取得

AD認証などで、ユーザーIDを指定して検索する場合は、下記URL参照。

ユーザー一覧の取得

特定のOUに所属するユーザーの一覧など、結果が複数件あることが予想される検索の場合、LDAPのページサイズを意識する必要がある。
WindowsのActive Directoryの場合、おそらく1,000件が結果の最大サイズ。
それを超える件数を取得したい場合は、ページングをしながら検索を繰り返す。

ページングしながらユーザー一覧を取得するサンプル
Hashtable<String, String> env = new Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://server"); // ADサーバーのアドレス
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, "userid@domain"); // ADサーバーにログオンするためのユーザーID@ドメイン
env.put(Context.SECURITY_CREDENTIALS, "password"); // パスワード

final int PAGE_SIZE = 1000;

InitialLdapContext context = null;
try {
    context = new InitialLdapContext(env, null);

    // 検索条件の指定
    String name = "OU=aaa, DC=bbb, DC=ccc";
    String filter = "(objectCategory=user)";

    SearchControls control = new SearchControls();
    control.setSearchScope(SearchControls.SUBTREE_SCOPE);

    byte[] cookie = null;

    do {
        context.setRequestControls(new Control[] { new PagedResultsControl(PAGE_SIZE, cookie,
                Control.CRITICAL) });

        // 検索実行
        NamingEnumeration<SearchResult> results = context.search(name, filter, control);

        // 結果取得
        while (results.hasMore()) {
            SearchResult result = results.next();
            AdUserBean user = new AdUserBean(result);

            System.out.println(user);
        }

        // 次のページがあるかチェック
        cookie = null;

        if (context.getResponseControls() != null) {
            Optional<Control> found = Arrays.stream(context.getResponseControls())
                    .filter(c -> c instanceof PagedResultsResponseControl).findFirst();

            if (found.isPresent()) {
                PagedResultsResponseControl prrc = (PagedResultsResponseControl) found.get();
                cookie = prrc.getCookie();
            }
        }

    } while (cookie != null);

} finally {
    try {
        if (context != null) {
            context.close();
        }
    } catch (NamingException e) {
    }
}
AdUserBean
public class AdUserBean {

    private String cn = null;
    private String name = null;
    /** 名前 */
    private String displayName = null;
    /** ユーザーID+ドメイン */
    private String userPrincipalName = null;
    /** LDAP識別名 */
    private String distinguishedName = null;
    /** メールアドレス */
    private String mail = null;
    /** ポケットベル */
    private String pager = null;

    /**
     * コンストラクタ
     */
    public AdUserBean() {

    }


    /**
     * コンストラクタ
     *
     * Active Directory検索結果から内容を取得する
     *
     * @param result
     * @throws NamingException
     */
    public AdUserBean(SearchResult result) throws NamingException {
        NamingEnumeration<? extends Attribute> attributes = result.getAttributes().getAll();

        while (attributes.hasMoreElements()) {
            Attribute attribute = attributes.next();
            Object value = attribute.get();

            if ("name".equals(attribute.getID())) {
                if (value instanceof String) {
                    name = (String) value;
                }

            } else if ("displayName".equals(attribute.getID())) {
                if (value instanceof String) {
                    displayName = (String) value;
                }

            } else if ("cn".equals(attribute.getID())) {
                if (value instanceof String) {
                    cn = (String) value;
                }

            } else if ("userPrincipalName".equals(attribute.getID())) {
                if (value instanceof String) {
                    userPrincipalName = (String) value;
                }

            } else if ("pager".equals(attribute.getID())) {
                if (value instanceof String) {
                    pager = (String) value;
                }

            } else if ("mail".equals(attribute.getID())) {
                if (value instanceof String) {
                    mail = (String) value;
                }

            } else if ("distinguishedName".equals(attribute.getID())) {
                if (value instanceof String) {
                    distinguishedName = (String) value;
                }

            }
        }

    }

// 以下略
}

hasMorehasMoreElementsの違い

search()メソッドの戻り値であるNamingEnumerationクラスには、普通のEnumerationクラスにあるhasMoreElementsメソッドと、独自のhasMoreメソッドの2種類がある。

while (results.hasMore()) {
    SearchResult result = results.next();
    // 略
}

hasMoreメソッドを使うと、例えばページサイズを超えた件数を取得しようとしたときにNamingExceptionがスローされる。
https://docs.oracle.com/javase/jp/11/docs/api/java.naming/javax/naming/NamingEnumeration.html

ただし、先のサンプルのようにページングしながらADを検索する場合は、コード側で指定したページサイズ>サーバー側のページサイズ となっていてもエラーにならず、サーバー側のページサイズで検索結果が返される。

属性値の取り方

Active Directoryの属性には、単一の値を持つ項目と複数の値を持つ項目がある。
それぞれ取得方法が異なるので注意。

属性値の取得部分を抜粋
/** ポケットベル */
private String pager = null;
/** ポケットベル(その他) */
private List<String> otherPager = null;

public AdUserBean(SearchResult result) throws NamingException {
    NamingEnumeration<? extends Attribute> attributes = result.getAttributes().getAll();

    while (attributes.hasMoreElements()) {
        Attribute attribute = attributes.next();
        Object value = attribute.get();

        if ("pager".equals(attribute.getID())) {
            // ポケットベル(単一の項目)
            if (value instanceof String) {
                pager = (String) value;
            }

        } else if ("otherPager".equals(attribute.getID())) {
            // ポケットベル(その他) (複数の項目)
            NamingEnumeration<?> values = attribute.getAll();

            while (values.hasMore()) {
                if (value instanceof String) {
                    if (otherPager == null) {
                        otherPager = new ArrayList<String>();
                    }

                    value = values.next();

                    otherPager.add((String) value);
                }
            }

        }
    }
}

ユーザー属性の変更

ユーザー属性の変更
private static void update() throws NamingException {

    Hashtable<String, String> env = new Hashtable<String, String>();
    env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
    env.put(Context.PROVIDER_URL, "ldap://server"); // ADサーバーのアドレス
    env.put(Context.SECURITY_AUTHENTICATION, "simple");
    env.put(Context.SECURITY_PRINCIPAL, "user@domain"); // ADサーバーにログオンするためのユーザーID@ドメイン
    env.put(Context.SECURITY_CREDENTIALS, "password"); // パスワード

    DirContext context = null;
    try {
        context = new InitialDirContext(env);

        Attributes attrs = new BasicAttributes();
        attrs.put(new BasicAttribute("pager", "123456"));
        attrs.put(new BasicAttribute("telephoneNumber", "1234567"));

        String name = "CN=username, OU=xxx, DC=yyy, DC=zzz";

        context.modifyAttributes(name, DirContext.REPLACE_ATTRIBUTE, attrs);

    } finally {
        try {
            if (context != null) {
                context.close();
            }
        } catch (NamingException e) {
        }
    }
}

modifyAttributesの引数に指定するDNは、そのユーザー(オブジェクト)が属するOUなどを正確に指定しないといけない。OUの階層を省略したりすると、「オブジェクトが見つからない」とエラーになる。

属性や検索フィルターの参考

メールアドレスは単体項目だが、電話番号は複数指定可能な項目。

Active Directory のユーザーの属性についての説明(他にも属性の種類ごとに説明ページが分かれている)
https://docs.microsoft.com/en-us/windows/win32/ad/naming-properties

Active Directory の属性一覧
https://docs.microsoft.com/en-us/windows/win32/adschema/attributes-all

LDAP の検索フィルター構文
http://software.fujitsu.com/jp/manual/manualfiles/M050000/B1WN4911/01/idmgr07/idmgr447.htm
https://www.ibm.com/support/knowledgecenter/ja/SSYJ99_8.5.0/admin-system/rbug_ldapfltrxprns.html

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
8
Help us understand the problem. What is going on with this article?