Zend_Auth_Adapter_Ldap
supports web application authentication with LDAP services. Its
features include username and domain name canonicalization, multi-domain authentication, and failover
capabilities. It has been tested to work with
Microsoft
Active Directory and OpenLDAP, but it should also
work with other LDAP service providers.
This documentation includes a guide on using Zend_Auth_Adapter_Ldap
, an exploration of its
API, an outline of the various available options, diagnostic information for troubleshooting authentication
problems, and example options for both Active Directory and OpenLDAP servers.
To incorporate Zend_Auth_Adapter_Ldap
authentication into your application quickly, even if
you're not using Zend_Controller
, the meat of your code should look something like the
following:
<?php $username = $this->_request->getParam('username'); $password = $this->_request->getParam('password'); $auth = Zend_Auth::getInstance(); require_once 'Zend/Config/Ini.php'; $config = new Zend_Config_Ini('../application/config/config.ini', 'production'); $log_path = $config->ldap->log_path; $options = $config->ldap->toArray(); unset($options['log_path']); require_once 'Zend/Auth/Adapter/Ldap.php'; $adapter = new Zend_Auth_Adapter_Ldap($options, $username, $password); $result = $auth->authenticate($adapter); if ($log_path) { $messages = $result->getMessages(); require_once 'Zend/Log.php'; require_once 'Zend/Log/Writer/Stream.php'; require_once 'Zend/Log/Filter/Priority.php'; $logger = new Zend_Log(); $logger->addWriter(new Zend_Log_Writer_Stream($log_path)); $filter = new Zend_Log_Filter_Priority(Zend_Log::DEBUG); $logger->addFilter($filter); foreach ($messages as $i => $message) { if ($i-- > 1) { // $messages[2] and up are log messages $message = str_replace("\n", "\n ", $message); $logger->log("Ldap: $i: $message", Zend_Log::DEBUG); } } }
Of course the logging code is optional, but it is highly recommended that you use a logger.
Zend_Auth_Adapter_Ldap
will record just about every bit of information anyone could want in
$messages
(more below), which is a nice feature in itself for something that has a history of
being notoriously difficult to debug.
The Zend_Config_Ini
code is used above to load the adapter options. It is also optional. A
regular array would work equally well. The following is an example
application/config/config.ini
file that has options for two separate servers. With multiple
sets of server options the adapter will try each in order until the credentials are successfully
authenticated. The names of the servers (e.g., server1
and server2
) are largely
arbitrary. For details regarding the options array, see the Server Options section
below. Note that Zend_Config_Ini
requires that any values with equals characters
(=
) will need to be quoted (like the DNs shown below).
[production] ldap.log_path = /tmp/ldap.log ; Typical options for OpenLDAP ldap.server1.host = s0.foo.net ldap.server1.accountDomainName = foo.net ldap.server1.accountDomainNameShort = FOO ldap.server1.accountCanonicalForm = 3 ldap.server1.username = "CN=user1,DC=foo,DC=net" ldap.server1.password = pass1 ldap.server1.baseDn = "OU=Sales,DC=foo,DC=net" ldap.server1.bindRequiresDn = true ; Typical options for Active Directory ldap.server2.host = dc1.w.net ldap.server2.useSsl = true ldap.server2.accountDomainName = w.net ldap.server2.accountDomainNameShort = W ldap.server2.accountCanonicalForm = 3 ldap.server2.baseDn = "CN=Users,DC=w,DC=net"
The above configuration will instruct Zend_Auth_Adapter_Ldap
to attempt to authenticate users
with the OpenLDAP server s0.foo.net
first. If the authentication fails for any reason, the AD
server dc1.w.net
will be tried.
With servers in different domains, this configuration illustrates multi-domain authentication. You can also have multiple servers in the same domain to provide redundancy.
Note that in this case, even though OpenLDAP has no need for the short NetBIOS style domain name used by Windows we provide it here for name canonicalization purposes (described in the Username Canonicalization section below).
The Zend_Auth_Adapter_Ldap
constructor accepts three parameters.
The $options
parameter is required and must be an array containing one or more sets of
options. Note that it is an array of arrays of
Zend_Ldap options. Even if you will be using only one LDAP server, the
options must still be within another array.
Below is print_r()
output of an example options
parameter containing two sets of server options for LDAP servers s0.foo.net
and
dc1.w.net
(same options as the above INI representation):
Array ( [server2] => Array ( [host] => dc1.w.net [useSsl] => 1 [accountDomainName] => w.net [accountDomainNameShort] => W [accountCanonicalForm] => 3 [baseDn] => CN=Users,DC=w,DC=net ) [server1] => Array ( [host] => s0.foo.net [accountDomainName] => foo.net [accountDomainNameShort] => FOO [accountCanonicalForm] => 3 [username] => CN=user1,DC=foo,DC=net [password] => pass1 [baseDn] => OU=Sales,DC=foo,DC=net [bindRequiresDn] => 1 ) )
The information provided in each set of options above is different mainly because AD does not require a
username be in DN form when binding (see the bindRequiresDn
option in the
Server Options section below), which means we can omit the a number of options
associated with retrieving the DN for a username being authenticated.
What is a DN? | |
---|---|
A DN or "distinguished name" is a string that represents the path to an object within the LDAP directory. Each comma separated component is an attribute and value representing a node. The components are evaluated in reverse. For example, the user account CN=Bob Carter,CN=Users,DC=w,DC=net is located directly within the CN=Users,DC=w,DC=net container. This structure is best explored with an LDAP browser like the ADSI Edit MMC snap-in for Active Directory or phpLDAPadmin. |
The names of servers (e.g. 'server1
' and 'server2
' shown above) are largely
arbitrary, but for the sake of using Zend_Config
, the identifiers should be present (as
opposed to being numeric indexes) and should not contain any special characters used by the associated file
formats (e.g. the '.
' INI property separator, '&
' for XML entity references,
etc).
With multiple sets of server options, the adapter can authenticate users in multiple domains and provide failover so that if one server is not available, another will be queried.
The Gory Details - What exactly happens in the authenticate method? | |
---|---|
When the |
The username and password parameters of the Zend_Auth_Adapter_Ldap
constructor represent the
credentials being authenticated (i.e., the credentials supplied by the user through your HTML login form).
Alternatively, they may also be set with the setUsername()
and setPassword()
methods.
Each set of server options in the context of Zend_Auth_Adapter_Ldap consists of the
following options, which are passed, largely unmodifed, to Zend_Ldap::setOptions()
:
Таблица 3.2. Server Options
Name | Description |
---|---|
host | The hostname of LDAP server that these options represent. This option is required. |
port |
The port on which the LDAP server is listening. If useSsl is
true , the default port value is 636. If
useSsl is false , the default
port value is 389.
|
useSsl |
If true , this value indicates that the LDAP client should use SSL / TLS encrypted
transport. A value of true is strongly favored in production environments to
prevent passwords from be transmitted in clear text. The default value is false ,
as servers frequently require that a certificate be installed separately after installation.
This value also changes the default port value (see
port description above).
|
username |
The DN of the account used to perform account DN lookups. LDAP servers that require the
username to be in DN form when performing the "bind" require this option. Meaning, if
bindRequiresDn is true , this option is
required. This account does not need to be a privileged account - a account with read-only
access to objects under the baseDn is all that is necessary
(and preferred based on the Principle of Least Privilege).
|
password | The password of the account used to perform account DN lookups. If this option is not supplied, the LDAP client will attempt an "anonymous bind" when performing account DN lookups. |
bindRequiresDn |
Some LDAP servers require that the username used to bind be in DN form like
CN=Alice Baker,OU=Sales,DC=foo,DC=net (basically all servers
except AD). If this option is true , this instructs
Zend_Ldap to automatically retrieve the DN corresponding to the username being
authenticated, if it is not already in DN form, and then re-bind with the proper DN. The
default value is false . Currently only Microsoft Active Directory Server (ADS) is
known not to require usernames to be in DN form when binding, and
therefore this option may be false with AD (and it should be, as retrieving the DN
requires an extra round trip to the server). Otherwise, this option must be set to
true (e.g. for OpenLDAP). This option also controls the default
acountFilterFormat used when searching for accounts. See the
accountFilterFormat option.
|
baseDn | The DN under which all accounts being authenticated are located. This option is required. If you are uncertain about the correct baseDn value, it should be sufficient to derive it from the user's DNS domain using DC= components. For example, if the user's principal name is alice@foo.net, a baseDn of DC=foo,DC=net should work. A more precise location (e.g., OU=Sales,DC=foo,DC=net) will be more efficient, however. |
accountCanonicalForm |
A value of 2, 3 or 4 indicating the form to which account names should be canonicalized after
successful authentication. Values are as follows: 2 for traditional username style names (e.g.,
alice), 3 for backslash-style names (e.g., FOO\alice)
or 4 for principal style usernames (e.g., alice@foo.net). The default
value is 4 (e.g., alice@foo.net). For example, with a value of 3, the
identity returned by Zend_Auth_Result::getIdentity() (and
Zend_Auth::getIdentity() , if Zend_Auth was used) will always be
FOO\alice, regardless of what form Alice supplied, whether it be
alice, alice@foo.net, FOO\alice,
FoO\aLicE, foo.net\alice, etc. See the
Account Name Canonicalization section in the Zend_Ldap
documentation for details. Note that when using multiple sets of server options it is
recommended, but not required, that the same
accountCanonicalForm be used with all server options so that
the resulting usernames are always canonicalized to the same form (e.g., if you canonicalize to
EXAMPLE\username with an AD server but to
username@example.com with an OpenLDAP server, that may be awkward for the
application's high-level logic).
|
accountDomainName |
The FQDN domain name for which the target LDAP server is an authority (e.g.,
example.com ). This option is used to canonicalize names so that the username
supplied by the user can be converted as necessary for binding. It is also used to determine if
the server is an authority for the supplied username (e.g., if
accountDomainName is foo.net and the
user supplies bob@bar.net, the server will not be queried, and a failure
will result). This option is not required, but if it is not supplied, usernames in principal
name form (e.g., alice@foo.net) are not supported. It is strongly
recommended that you supply this option, as there are many use-cases that require generating
the principal name form.
|
accountDomainNameShort | The 'short' domain for which the target LDAP server is an authority (e.g., FOO). Note that there is a 1:1 mapping between the accountDomainName and accountDomainNameShort. This option should be used to specify the NetBIOS domain name for Windows networks but may also be used by non-AD servers (e.g., for consistency when multiple sets of server options with the backslash style accountCanonicalForm). This option is not required but if it is not supplied, usernames in backslash form (e.g., FOO\alice) are not supported. |
accountFilterFormat |
The LDAP search filter used to search for accounts. This string is a
printf() -style expression that must
contain one '%s ' to accomodate the username. The default value is
'(&(objectClass=user)(sAMAccountName=%s)) ', unless
bindRequiresDn is set to true , in which case
the default is '(&(objectClass=posixAccount)(uid=%s)) '. For example, if for
some reason you wanted to use bindRequiresDn = true with AD you would need to set
accountFilterFormat = '(&(objectClass=user)(sAMAccountName=%s)) '.
|
Замечание | |
---|---|
If you enable |
Zend_Auth_Adapter_Ldap
collects debugging information within its authenticate()
method. This information is stored in the Zend_Auth_Result
object as messages. The array
returned by Zend_Auth_Result::getMessages()
is described as follows:
Таблица 3.3. Debugging Messages
Messages Array Index | Description |
---|---|
Index 0 | A generic, user-friendly message that is suitable for displaying to users (e.g., "Invalid credentials"). If the authentication is successful, this string is empty. |
Index 1 | A more detailed error message that is not suitable to be displayed to users but should be logged for the benefit of server operators. If the authentication is successful, this string is empty. |
Indexes 2 and higher | All log messages in order starting at index 2. |
In practice index 0 should be displayed to the user (e.g., using the FlashMessenger helper), index 1 should
be logged and, if debugging information is being collected, indexes 2 and higher could be logged as well
(although the final message always includes the string from index 1).
For ADS, the following options are noteworthy:
Таблица 3.4. Options for Active Directory
Name | Additional Notes |
---|---|
host | As with all servers, this option is required. |
useSsl |
For the sake of security, this should be true if the server has the necessary
certificate installed.
|
baseDn | As with all servers, this option is required. By default AD places all user accounts under the Users container (e.g., CN=Users,DC=foo,DC=net), but the default is not common in larger organizations. Ask your AD administrator what the best DN for accounts for your application would be. |
accountCanonicalForm | You almost certainly want this to be 3 for backslash style names (e.g., FOO\alice), which are most familar to Windows users. You should not use the unqualified form 2 (e.g., alice), as this may grant access to your application to users with the same username in other trusted domains (e.g., BAR\alice and FOO\alice will be treated as the same user). (See also note below.) |
accountDomainName | This is required with AD unless accountCanonicalForm 2 is used, which, again, is discouraged. |
accountDomainNameShort | The NetBIOS name of the domain users are in and for which the AD server is an authority. This is required if the backslash style accountCanonicalForm is used. |
Замечание | |
---|---|
Technically there should be no danger of accidental cross-domain authentication with the current
|
For OpenLDAP or a generic LDAP server using a typical posixAccount style schema, the following options are noteworthy:
Таблица 3.5. Options for OpenLDAP
Name | Additional Notes |
---|---|
host | As with all servers, this option is required. |
useSsl |
For the sake of security, this should be true if the server has the necessary
certificate installed.
|
username | Required and must be a DN, as OpenLDAP requires that usernames be in DN form when performing a bind. Try to use an unprivileged account. |
password | The password corresponding to the username above, but this may be omitted if the LDAP server permits an anonymous binding to query user accounts. |
bindRequiresDn |
Required and must be true , as OpenLDAP requires that usernames be in DN form
when performing a bind.
|
baseDn | As with all servers, this option is required and indicates the DN under which all accounts being authenticated are located. |
accountCanonicalForm | Optional but the default value is 4 (principal style names like alice@foo.net), which may not be ideal if your users are used to backslash style names (e.g., FOO\alice). For backslash style names use value 3. |
accountDomainName | Required unless you're using accountCanonicalForm 2, which is not recommended. |
accountDomainNameShort | If AD is not also being used, this value is not required. Otherwise, if accountCanonicalForm 3 is used, this option is required and should be a short name that corresponds adequately to the accountDomainName (e.g., if your accountDomainName is foo.net, a good accountDomainNameShort value might be FOO). |