導入
最小限の機能
現在このクラスは、
Zend_Auth_Adapter_Ldap
認証アダプタが必要とする機能のみを満たすように作られています。
ディレクトリ内のエントリを検索したり、
エントリの作成や修正、名前の変更を行ったりといった機能は現時点ではサポートしていません。
これらの機能は将来実装される予定です。
Zend_Ldap は LDAP の操作を行うクラスです。バインドだけが可能で、
LDAP ディレクトリ内のエントリの検索や変更には対応していません。
動作原理
このコンポーネントは、現在 Zend_Ldap と
Zend_Ldap_Exception のふたつのクラスで構成されています。
Zend_Ldap クラスは、単一の LDAP サーバへのバインドを表します。
バインド用のパラメータは、明示的に指定するか、あるいはオプションの配列形式で指定します。
Zend_Ldap クラスの使用法は LDAP サーバの形式によって異なり、
以下のいずれかのパターンとなります。
OpenLDAP を使用している場合は、以下の例のようになります (AD を使って
いない 場合は bindRequiresDn
オプションが重要となることに注意しましょう)。
's0.foo.net',
'username' => 'CN=user1,DC=foo,DC=net',
'password' => 'pass1',
'bindRequiresDn' => true,
'accountDomainName' => 'foo.net',
'baseDn' => 'OU=Sales,DC=foo,DC=net',
);
$ldap = new Zend_Ldap($options);
$acctname = $ldap->getCanonicalAccountName('abaker',
Zend_Ldap::ACCTNAME_FORM_DN);
echo "$acctname\n";
]]>
Microsoft AD を使う場合の例はこのようになります。
'dc1.w.net',
'useStartTls' => true,
'username' => 'user1@w.net',
'password' => 'pass1',
'accountDomainName' => 'w.net',
'accountDomainNameShort' => 'W',
'baseDn' => 'CN=Users,DC=w,DC=net',
);
$ldap = new Zend_Ldap($options);
$acctname = $ldap->getCanonicalAccountName('bcarter',
Zend_Ldap::ACCTNAME_FORM_DN);
echo "$acctname\n";
]]>
ここでは、getCanonicalAccountName() メソッドでアカウントの DN
を取得していることに注意しましょう。
これは、ただ単にこのクラスに現在存在するコードの例をできるだけ多く見せたいからというだけのことです。
バインド時の、ユーザ名の自動正規化
bindRequiresDN が TRUE
かつ DN 形式のユーザ名がオプションで設定されていない場合、
bind() を DN でないユーザ名でコールするとバインドに失敗します。
しかし、DN 形式のユーザ名がオプションで設定されていれば、Zend_Ldap
はまずそのユーザ名でバインドを行い、bind()
で指定したユーザ名に対応するアカウントの DN を取得した上で
改めてその DN でバインドしなおします。
この振る舞いは Zend_Auth_Adapter_Ldap にとっては重要です。
これは、ユーザが指定したユーザ名を直接 bind() に渡します。
次の例は、DN でないユーザ名 'abaker'
を bind() で使用する方法を示すものです。
's0.foo.net',
'username' => 'CN=user1,DC=foo,DC=net',
'password' => 'pass1',
'bindRequiresDn' => true,
'accountDomainName' => 'foo.net',
'baseDn' => 'OU=Sales,DC=foo,DC=net',
);
$ldap = new Zend_Ldap($options);
$ldap->bind('abaker', 'moonbike55');
$acctname = $ldap->getCanonicalAccountName('abaker',
Zend_Ldap::ACCTNAME_FORM_DN);
echo "$acctname\n";
]]>
この例において bind() をコールすると、
ユーザ名 'abaker' が DN 形式でないことと
bindRequiresDn が TRUE であることから、まず
'CN=user1,DC=foo,DC=net' と 'pass1'
を用いてバインドします。それから 'abaker' の DN を取得し、
いったんバインドを解除したうえであらためて
'CN=Alice Baker,OU=Sales,DC=foo,DC=net'
でバインドしなおします。
Zend_Ldap のオプション
Zend_Ldap コンポーネント用のオプションの配列は、
コンストラクタあるいは setOptions() メソッドで指定することができます。
次のようなオプションが使用可能です。
Zend_Ldap のオプション
名前
説明
host
LDAP サーバのデフォルトのホスト名。connect()
で指定しなかった場合に使用します (bind()
の際にユーザ名を正規化するときにも使用します)。
port
LDAP サーバのデフォルトのポート。connect()
で指定しなかった場合に使用します。
useStartTls
LDAP クライアント側に TLS (SSLv2) での暗号化トランスポートを要求するか否か。
実運用環境では TRUE を指定することを強く推奨します。
これにより、パスワードが平文で送られてしまうことを防ぎます。
デフォルト値は FALSE です。というのも、
この機能を使用するにはサーバ側に別途証明書のインストールが必要となることが多いからです。
useSsl オプションと useStartTls オプションは共存できません。
useStartTls オプションのほうが useSsl
より推奨されていますが、中にはこの新しい仕組みをサポートしていないサーバもあります。
useSsl
LDAP クライアント側に SSL での暗号化トランスポートを要求するか否か。
useSsl オプションと useStartTls オプションは共存できません。
username
デフォルトの認証ユーザ名。サーバによっては、DN 形式を要求するものもあります。
password
デフォルトの認証パスワード (上のユーザ名との組み合わせでのみ使用します)。
bindRequiresDn
TRUE を指定すると、ユーザ名が DN 形式でない場合に
Zend_Ldap はバインド時に使用してアカウントの DN を取得します。
デフォルト値は FALSE です。
baseDn
(アカウントなどの) 検索に使用するデフォルトのベース DN。
このオプションは、アカウントに関する大半の操作で必須となります。
そのアカウントが存在する DN を指す必要があります。
accountCanonicalForm
アカウント名の正規化方式を表す整数値。以下の
アカウント名の正規化 のセクションを参照ください。
accountDomainName
対象となる LDAP サーバの FQDN ドメイン
(例 example.com)。
accountDomainNameShort
対象となる LDAP サーバの '短い' ドメイン。
これは、Windows ネットワークの NetBIOS ドメイン名として用いられますが、
AD 以外のサーバで用いられることもあります。
accountFilterFormat
アカウントを検索する際に使用する LDAP 検索フィルタ。
この文字列は
printf()
形式のものとなり、ユーザ名を表す '%s'
をひとつ含む必要があります。デフォルト値は
'(&(objectClass=user)(sAMAccountName=%s))' です。
ただし、bindRequiresDn が TRUE
の場合のデフォルト値は
'(&(objectClass=posixAccount)(uid=%s))'
となります。独自のスキーマを使用している場合は、
それにあわせてこのオプションを変更しなければなりません。
allowEmptyPassword
LDAP サーバによっては、匿名バインドの際のパスワードに空文字を設定できるものもあります。
この挙動は、ほとんどの場合において好ましくないものです。
そのため、空のパスワードは明示的に無効にしています。
この値を TRUE にすると、
バインド時に空文字列のパスワードを使用できるようになります。
optReferrals
TRUE に設定すると、
参照先を追跡するよう LDAP クライアントに指示します。
デフォルト値は FALSE です。
アカウント名の正規化
オプション accountDomainName および accountDomainNameShort
は、次のふたつの目的で使用します。
(1) 複数ドメインによる認証 (どちらか一方が使えないときの代替機能) を実現する。
(2) ユーザ名を正規化する。
特に、名前の正規化の際にはオプション
accountCanonicalForm で指定した形式を使用します。
このオプションの値は、次のいずれかとなります。
accountCanonicalForm
名前
値
例
ACCTNAME_FORM_DN
1
CN=Alice Baker,CN=Users,DC=example,DC=com
ACCTNAME_FORM_USERNAME
2
abaker
ACCTNAME_FORM_BACKSLASH
3
EXAMPLE\abaker
ACCTNAME_FORM_PRINCIPAL
4
abaker@example.com
デフォルトの正規化は、アカウントのドメイン名のオプションが
どのように設定されているかによって変わります。
accountDomainNameShort が指定されている場合は、デフォルトの
accountCanonicalForm の値は
ACCTNAME_FORM_BACKSLASH となります。
それ以外の場合は、もし accountDomainName
が設定されていればデフォルトは
ACCTNAME_FORM_PRINCIPAL となります。
アカウント名の正規化をすることで、bind()
に何が渡されたのかにかかわらずアカウントの識別に用いる文字列が一貫性のあるものになります。
たとえば、ユーザがアカウント名として
abaker@example.com あるいは単に abaker
だけを指定したとしても、accountCanonicalForm
が 3 に設定されていれば正規化後の名前は
EXAMPLE\abaker となります。
複数ドメインの認証とフェイルオーバー
Zend_Ldap コンポーネント自身は、
複数サーバでの認証を試みません。
しかし、Zend_Ldap はこのような場合に対応するようにも設計されています。
サーバのオプションを指定した配列の配列を順にたどり、
個々のサーバへのバインドを試みるのです。上で説明したように、
bind() は自動的に名前を正規化します。したがって、ユーザが
abaker@foo.net を指定したのか、あるいは W\bcarter
や cdavis と指定したのかにはかかわらず、
bind() メソッドが成功するかどうかは
バインド時に認証情報が正しく指定されたかどうかによって決まります。
次の例は、複数ドメインでの認証と
フェイルオーバー機能を実装するために必要な技術を説明するものです。
array(
'host' => 's0.foo.net',
'username' => 'CN=user1,DC=foo,DC=net',
'password' => 'pass1',
'bindRequiresDn' => true,
'accountDomainName' => 'foo.net',
'accountDomainNameShort' => 'FOO',
'accountCanonicalForm' => 4, // ACCT_FORM_PRINCIPAL
'baseDn' => 'OU=Sales,DC=foo,DC=net',
),
'server2' => array(
'host' => 'dc1.w.net',
'useSsl' => true,
'username' => 'user1@w.net',
'password' => 'pass1',
'accountDomainName' => 'w.net',
'accountDomainNameShort' => 'W',
'accountCanonicalForm' => 4, // ACCT_FORM_PRINCIPAL
'baseDn' => 'CN=Users,DC=w,DC=net',
),
);
$ldap = new Zend_Ldap();
foreach ($multiOptions as $name => $options) {
echo "Trying to bind using server options for '$name'\n";
$ldap->setOptions($options);
try {
$ldap->bind($acctname, $password);
$acctname = $ldap->getCanonicalAccountName($acctname);
echo "SUCCESS: authenticated $acctname\n";
return;
} catch (Zend_Ldap_Exception $zle) {
echo ' ' . $zle->getMessage() . "\n";
if ($zle->getCode() === Zend_Ldap_Exception::LDAP_X_DOMAIN_MISMATCH) {
continue;
}
}
}
]]>
何らかの理由でバインドに失敗すると、その次のオプションでのバインドを試みます。
getCanonicalAccountName をコールすると、
正規化したアカウント名を取得することができます。
これを使用して、アプリケーションから関連データを取得できるようになります。
accountCanonicalForm = 4 をすべてのサーバのオプションに設定することで、
どのサーバを使用する場合にも一貫した正規化が行えるようになっています。
ドメイン部つきのアカウント名 (単なる abaker
ではなく abaker@foo.net や FOO\abaker など)
を指定した場合は、そのドメインが設定済みのオプションのどれとも一致しなければ
特別な例外 LDAP_X_DOMAIN_MISMATCH が発生します。
この例外は、そのアカウントがサーバに見つからないことを表します。
この場合はバインドは行われず、
サーバとの余計な通信は発生しません。
この例では continue という指示は無意味であることに注意しましょう。
しかし、実際には、エラー処理やデバッグなどのために
LDAP_NO_SUCH_OBJECT と LDAP_INVALID_CREDENTIALS
だけではなく LDAP_X_DOMAIN_MISMATCH もチェックすることになるでしょう。
上のコードは、
Zend_Auth_Adapter_Ldap
の中で使用するコードと非常によく似ています。実際のところ、
複数ドメインとファイルオーバー機能をもつ LDAP 基本印象を行うのなら、
この認証アダプタを使用する (あるいはコードをコピーする) ことをおすすめします。