[IM]LDAP認証の実装がだいたいできてきました

INTER-Mediatorには、自分自身でユーザーテーブルを持つ認証(ビルトイン認証)が可能です。そして、データベースエンジンのユーザーによる認証(ネイティブ認証)もすでにサポートしています。次に取り組むのはやはりLDAPです。ただ、MySQLにはLDAP接続のプラグインがあり、そういう手法を使えば、ネイティブ認証の一環としてLDAP認証を実現することができます。しかし、通常とは異なるデータベース運用が必要になりますし、MySQLとPostgreSQLで動作に違いがあるとまた大変です。SQLiteはそういう統合はできません。LDAPについては、単に認証の確認だけだと、要するに認証バインドをするだけのことであり、PHPではさほど難しくはないので、データベースエンジンと独立してLDAP認証の仕組みを搭載しました。

LDAPサーバに対する設定

まず、LDAPサーバ情報は、params.phpファイルに、変数で記載する方法にしました。LDAPでの認証時は、ユーザーはユーザー名だけを与えるのではなく、ユーザーレコードのDN(Distinguished Name)を指定する必要があります。たとえば、OS X Serverで、ホスト名がhomeserver.msyk.netの場合、

uid=msyk,cn=users,dc=homeserver,dc=msyk,dc=net

というのがユーザーを特定するDNとなります。ここで、dc…以降は「検索ベース」、cn=usersは「コンテナ」とします。さらに、OS X Serverはuidという属性名でユーザー名を指定しますが、これもActive Directoryでは違うこともあって、結果的に上記のユーザー名「msyk」以外の部分をすべて、変数で与えて、DNを構成するようにしています。サーバーのURL、ポート番号も合わせて、結局、params.phpファイルに以下のように記載をすることにします。

$ldapServer = "ldap://homeserver.msyk.net";
$ldapPort = 389;
$ldapBase = "dc=homeserver,dc=msyk,dc=net";
$ldapContainer = "cn=users";
$ldapAccountKey = "uid";

なお、$ldapServer変数に設定されている文字列の長さが0以上なら、LDAP認証を試みるように動作します。これらの変数部分を全部コメントにすると、LDAP認証に関しては何も行わず、以前の通りの動作になるようにしました。

PHPのLDAPライブラリの機能を使って認証するとしたら、その認証のためのバインドはサーバーからLDAPサーバーに送られます。ということは、クライアントからサーバーに対して、ユーザー名とパスワードを送り届けないといけません。これは、ネイティブ認証の仕組みを利用しますが、そのために、鍵ペアを生成して、params.phpファイルに記述します。以下の部分です。もちろん、最初から入っている鍵ではなく、コメントに書いている方法で自分が使用する鍵に置き換えてください。もちろん、クライアントに送られるのは、公開鍵です。

$generatedPrivateKey = <<<EOL
-----BEGIN RSA PRIVATE KEY-----
MIIBOwIBAAJBAKihibtt92M6A/z49CqNcWugBd3sPrW3HF8TtKANZd1EWQ/agZ65
 :
jU6zr1wG9awuXj8j5x37eFXnfD/p92GpteyHuIDpog==
-----END RSA PRIVATE KEY-----
EOL;

ネイティブ認証との混合動作

そして、LDAPアカウントでのログインは簡単にできました。ただし、1回の認証バインドに0.2〜0.3秒ほどかかります。同一のサブネットです。たとえば、フォームのサンプルだと、1ページの合成にデータベースアクセスを15回ほど行うので、5秒くらいのアクセス時間の増加になります。これは、ちょっと長いです。また、単に認証ができたとしても、グループ設定との連動等といった問題があります。

そこで、LDAPアカウントで認証が成功したら、ビルトイン認証で使うテーブル(authuserテーブル)に、そのLDAPのアカウントの情報をレコードとして追加することにしました。1回成功したら、ビルトイン認証テーブルにユーザーを追加し、パスワードもわかっているのでハッシュ化したパスワードを保存します。そのために、ビルトイン認証を行って失敗するとLDAP認証を行うという流れにしました。ビルトイン認証とLDAP認証は切り替えるのではなく、この順序で両方行います。
しかし、その追加したユーザーがいつまでも使えるのは問題で、タイムアウトさせないといけません。そこで、結果的にはビルトイン認証のテーブルにDateTime型のフィールド「limitdt」を追加する必要が発生しました。現在の実装では、そのフィールドの有無で落ちることはないようにしてありますが、LDAPを使う場合には追加したフィールドの定義がされていないといけません。

こうしてユーザーのレコードを作っておくと、グループへの登録ができます。ユーザー名で識別され、1度登録すると消されないので、主キー値は保持されるはずです。タイムアウトすると、LDAP認証を試みます。ただ、この部分、現在実装中で、うまくいったりいかなかったり、一進一退ですが、ある程度のところまで動くようになっています。ちなみに、MoodleもLDAP認証をサポートしますが、ログインを1度することで、ユーーザーレコードが作られる仕組みになっており、やはり同じような仕組みをINTER-Mediatorでも実装しています。

認証処理の流れ

初めて、LDAPアカウントでログインしようとするとき、ビルドイン認証のテーブルにはそのユーザーはありません。したがって、ビルトイン認証では失敗しますが、その後のLDAP認証で成功します。このとき、新たにビルトイン認証のテーブルに、そのユーザーのレコードを作りパスワードのハッシュと期限を記録します。クライアント側には認証のための情報が残されているので、この次の通信時からは、自動的にビルトイン認証が成功します。したがって、1画面作るのに20回の通信処理が必要でも、最初の1回だけがLDAP認証されます。

次の通信処理が、LDAP認証のタイムアウト以内の期間なら、ビルトイン認証が成功して終了します。そのとき、limitdtフィールドも現在の時刻に更新します。しばらくクライアントを利用しないなどでタイムアウトになると、ビルトイン認証は失敗しますが、引き続くLDAP認証が成功するように、クライアントでは、LDAP認証とネイティブ認証に必要な情報が残されています。そして、1度LDAP認証に成功すると、次回以降はまたネイティブ認証がタイムアウトまで成功します。

認証に必要な情報はクッキーに保存することもできるので、翌日にまた認証を継続させることもできます。

今現在、動作が怪しいのは、LDAP認証のパスワードを変えたときです。まず、この方法だと、LDAP認証のタイムアウトの時間が来るまでLDAPでの認証は行われないので、その期間は変更前のパスワードが通ります。これは、とりあえず、仕方ないことの1つとしておきます。そして、タイムアウトしてLDAP認証すると、当然ながら以前のパスワードでは認証が通らないので認証エラーとなり、ログインパネルが表示されます。ここで、新しくなったパスワードを入力すると、また同じように動作するというのが期待する動作です。このあたりの挙動が現状、今ひとつな感じです。

FileMakerはサポートせず

なお、LDAP認証は、PDOのみで、FileMaker Serverはサポートしません。どうしようかと思ったのですが、Admin Consoleでの設定で「外部サーバーアカウント」をクライアント認証で使えるようにしておけば、ネイティブ認証で事実上、Active Directory、Open Directoryは利用できます。LinuxのLDAPは使えないけど、FileMakerのクライアントで使えないで、Web側だけで使える仕組みまでのサポートは必要かどうか疑問ですので、FileMakerはINTER-Mediator組み込みのLDAP認証はサポートしないということにしたいと思います。