LDAPクライアントプログラミングは怖くない（Java編）

# はじめに

　[前回記事](https://qiita.com/hoshino_osstech/items/6dc97a2f9a96dd54c04f)では、.NET／Visual BasicでLDAPクライアントプログラミングをする場合の事前準備やコード例を記しました。
　今回はJava編を書いていきます。


# 準備するもの

　Javaの開発環境…は当たり前なので詳述しません。

　LDAPに直接関連して事前準備すべきもの…については、[前回記事](https://qiita.com/hoshino_osstech/items/6dc97a2f9a96dd54c04f)にて記載しましたのでそちらを参照してください。（今回も同じLDAP情報を事前入手した、という仮定とします。）
　ここで、LDAPS時の証明書が止むを得ず自己署名証明書だったりした場合は、Javaのトラストストアに証明書を格納して、Javaプログラム実行時にそのトラストストアを参照するよう設定してください。

# LDAPクライアントプログラミングしてみる

　[前回記事](https://qiita.com/hoshino_osstech/items/6dc97a2f9a96dd54c04f)で書いた .NET／Visual BasicでLDAPデータを読み書きするコードを、Javaに移植してみます。

### プログラム

　JavaでLDAPクライアントプログラミングするにあたり、標準ライブラリだけで見ても javax.naming.directory 系と javax.naming.ldap 系が存在します。どちらを使っても実現できますが、今回は前者を使うことにします。
　さて、ごりごりっとコード書きましょう。こんな感じ。

```java
// 本記事ではimport分は省略します

public class LDAPClientTest {	
    public static void main(String[] args) throws NamingException {
        //-------------
        // Connect
        //-------------
        Hashtable<String, String> env = new Hashtable<String, String>();
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
        env.put(Context.PROVIDER_URL, "ldaps://ldapserver.mydomain.test:636");
        env.put(Context.SECURITY_AUTHENTICATION, "simple"); // 基本認証で繋ぐ
            env.put(Context.SECURITY_PRINCIPAL, "cn=datamaster,dc=ldapserver,dc=mydomain,dc=test");
        env.put(Context.SECURITY_CREDENTIALS, "xahTh8phou6Ieyoh");
        DirContext dirContext = new InitialDirContext(env);

        //-------------
        // ADD
        //-------------
        Attributes attrsForAdd = new BasicAttributes();
        Attribute objectClassAttribute = new BasicAttribute("objectClass");
        objectClassAttribute.add("top");
        objectClassAttribute.add("person");
        objectClassAttribute.add("inetOrgPerson");
        objectClassAttribute.add("organizationalPerson");
        attrsForAdd.put(objectClassAttribute);
        attrsForAdd.put(new BasicAttribute("cn", "Hoshino Ko"));
        attrsForAdd.put(new BasicAttribute("sn", "Hoshino"));
        dirContext.createSubcontext("uid=user0000111,ou=staff,dc=ldapserver,dc=mydomain,dc=test");
        System.out.println(">> Add finished!");

        //-------------
        // MODIFY
        //-------------
        Attributes attrsForModify = new BasicAttributes();
        attrsForModify.put(new BasicAttribute("cn", "Hoshino \"LDAP\" Ko")); // ミドルネームを入れてみる
        dirContext.modifyAttributes("uid=user0000111,ou=staff,dc=ldapserver,dc=mydomain,dc=test", 
                DirContext.REPLACE_ATTRIBUTE, attrsForModify);
        System.out.println(">> Modify finished!");
        
        //-------------
        // SEARCH
        //-------------
        SearchControls searchControls = new SearchControls();
        searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
        searchControls.setReturningAttributes(new String[]{"uid", "cn", "sn"});
        NamingEnumeration<SearchResult> result =
                dirContext.search("ou=staff,dc=ldapserver,dc=mydomain,dc=test", "(cn=Hoshino*)", searchControls);
        System.out.println("----- Search Result -----");
        while (result.hasMore()) { // 検索で複数人ヒットした場合、全員分表示
            SearchResult searchResult = result.next();
            NamingEnumeration<? extends Attribute> attrEnum = searchResult.getAttributes().getAll();
            while (attrEnum.hasMore()) { // 取得した属性値を全部表示
                Attribute attribute = attrEnum.next();
                NamingEnumeration<?> valueEnum = attribute.getAll();
                while (valueEnum.hasMore()) { // 同じ属性名の属性値が複数存在していたら全部表示
                    System.out.println(attribute.getID() + ": " + valueEnum.next());
                }
            }
            System.out.println();
        }
        System.out.println("-------------------------");
        System.out.println(">> Search finished!");

        //-------------
        // DELETE
        //-------------
        dirContext.destroySubcontext("uid=user0000111,ou=staff,dc=ldapserver,dc=mydomain,dc=test");
        System.out.println(">> Delete finished!");
    }
}
```

### プログラム実行結果

実行してみると、以下のような出力を得ることができます。
属性値の出力順序が変わったこと以外は、[前回記事](https://qiita.com/hoshino_osstech/items/6dc97a2f9a96dd54c04f)と同じです。

```
>> Add finished!
>> Modify finished!
----- Search Result -----
uid: user0000111
sn: Hoshino
cn: Hoshino "LDAP" Ko

-------------------------
>> Search finished!
>> Delete finished!
```


# アプリケーション例を作ってみる

　ここまでは[前回記事](https://qiita.com/hoshino_osstech/items/6dc97a2f9a96dd54c04f)と同等で、LDAPの基本的なデータ操作（Add,Modify,Search,Delete）をするサンプルプログラムを見てきました。
　今回記事ではさらに、実用的なアプリの実装も可能そうだなーと感じていただくために、アプリケーション例を作ってみます。

### 文字データだけでなくバイナリデータも扱ってみる

　弊社のエンジニアは、[弊社のホームページ](https://www.osstech.co.jp/)や名刺に顔写真を載せていたりします。
LDAPではもちろんバイナリデータも扱えますので、ここでは、LDAPサーバから写真データを取り出すコードを書いてみます。


```java

    // 写真データは、他の属性値とは別に後から取得
    NamingEnumeration<SearchResult> photoResult = 
        getSearchResult("(uid=" + uid + ")", new String[]{"jpegPhoto"});
    Object jpegPhoto = getFirstAttributeValue(photoResult.next(), "jpegPhoto");

    // ファイルに保存
    if (jpegPhoto != null) {
        File photoFile = new File("＜ユーザ毎の写真ファイル名＞");
        try (FileOutputStream fos = new FileOutputStream(photoFile)) {
            fos.write((byte[]) jpegPhoto);
        }
    }
```

　文字データといったユーザ情報は先に検索しておくこととし、写真データは後から取得するよう書いてます。写真データは比較的データサイズが大きいので、サーバやネットワークの負荷を上げないよう、必要に応じて取得するためです。

### 取得したデータをWebページに表示してみる

　LDAPサーバから文字データも写真データも取得できるようになったので…、ServletからLDAPのデータを取得することで、Webブラウザから社員情報を見れるようにしてみます。
　あ、そういえば今インターンさんに社内用受付システムを作ってもらっているので…、ついでに勝手に機能追加してしまえー。と、勢いで作ってしまったものがこちらになります。

<b>（動画の途中で音声が出ます。音量調節やイヤホンなど、予め準備・調整してくださいませ。）</b>

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">OSSテクノロジ社 受付システム　勝手に改造版 <a href="https://t.co/DN59SHQyFs">pic.twitter.com/DN59SHQyFs</a></p>&mdash; hoshino@osstech (@hoshino_osstech) <a href="https://twitter.com/hoshino_osstech/status/1206832477991342080?ref_src=twsrc%5Etfw">2019年12月17日</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 


　画面の右側のブラウザは、居室の外にある受付テーブル上にタブレットで開いておくもの。画面の左側のブラウザは、音声出力のために居室内にて開いておくものです。
　機能としては動画の通りなのですが、「お客様がいらっしゃって受付タブレットで社員データを選択すると、居室内で音声呼び出しが入る」という機能です。
　LDAPから、表示用名前データ、音声呼び出し用の名前呼び方データ、そして写真データを取得することで実現しています。

LDAPのAPI以外にも、以下のような様々な要素を使っています。
　・swiper.js で写真スワイプ
　・WebSocket でユーザ操作内容を通信
　・Web Speech API／WebSpeech Synthesis で音声出力
今回の主題から外れているため、これらについては本記事では詳細述べません。興味ある方いらっしゃいましたらご連絡ください。


# おわりに

というわけで今回は、Javaを使って、さくっとLDAPクライアントプログラミングしてみました。

結論は[前回記事](https://qiita.com/hoshino_osstech/items/6dc97a2f9a96dd54c04f)と同じです。
JavaでもLDAPクライアントプログラミングは怖くないので、LDAPを使い倒しましょう！

以上です。