Follow the advice in this chapter to write effective, maintainable, high performance directory client applications.
Unless your application performs only read operations, you should authenticate to the directory server. Some directory administrators require authentication even to read directory data.
Once you authenticate (bind), directory servers like OpenDJ make authorization decisions based on your identity. With servers like OpenDJ that support proxied authorization, once authenticated your application can also request an operation on behalf of another identity, for example the identity of the end user.
Your application therefore should have an account used to authenticate
such as cn=My Killer App,ou=Apps,dc=example,dc=com
. The
directory administrator can then authorize appropriate access for your
application, and also monitor your application's requests to help you
troubleshoot problems if they arise.
Your application can use simple, password-based authentication. When you opt for password-based authentication, also use Start TLS for example to avoid sending the password as clear text over the network. If you prefer to manage certificates rather than passwords, directory servers like OpenDJ can do client authentication as well.
LDAP is a stateful protocol. You authenticate (bind), you do stuff, you unbind. The server maintains a context that lets it make authorization decisions concerning your requests. You should therefore reuse connections when possible.
You can make multiple requests without having to set up a new connection and authenticate for every request. You can issue a request and get results asynchronously, while you issue another request. You can even share connections in a pool, avoiding the overhead of setting up and tearing down connections if you use them often.
In a network built for HTTP applications, your long-lived LDAP connections can get cut by network equipment configured to treat idle and even just old connections as stale resources to reclaim.
When you maintain a particularly long-lived connection such as a connection for a persistent search, periodically perform a health check to make sure nothing on the network quietly decided to drop your connection without notification. A health check might involve reading an attribute on a well-known entry in the directory.
OpenDJ LDAP SDK offers
Connections.newHeartBeatConnectionFactory()
methods to
ensure your ConnectionFactory
serves connections that
are periodically checked to detect whether they are still alive.
By the time your application makes it to production, you should know
what attributes you want, so request them explicitly and request all
the attributes you need in the same search. For example, if all you need
is mail
and cn
, then specify both
attributes in your SearchRequest
.
The difference between a general filter
(mail=*@example.com)
and a good, specific filter like
(mail=user@example.com)
can be huge numbers of entries
and enormous amounts of processing time, both for the directory server
that has to return search results, and also for your application that has
to sort through the results. Many use cases can be handled with short,
specific filters. As a rule, prefer equality filters over substring
filters.
Some directory servers like OpenDJ reject unindexed searches by default, because unindexed searches are generally far more resource intensive. If your application needs to use a filter that results in an unindexed search, then work with your directory administrator to find a solution, such as having the directory maintain the indexes required by your application.
Furthermore, always use &
with
!
to restrict the potential result set before returning
all entries that do not match part of the filter. For example, (&(location=Oslo)(!(mail=birthday.girl@example.com)))
.
When you modify attributes with multiple values, for example when you modify a list of group members, replace or delete specific values individually, rather than replacing the entire list of values. Making modifications specific helps directory servers replicate your changes more effectively.
Trust the LDAP result code that your application gets from the
directory server. For example, if you request a modify application and you
get ResultCode.SUCCESS
, then consider the operation a
success rather than issuing a search immediately to get the modified
entry.
The LDAP replication model is loosely convergent. In other words,
the directory server can, and probably does, send you
ResultCode.SUCCESS
before replicating your change to
every directory server instance across the network. If you issue a read
immediately after a write, and a load balancer sends your request to another
directory server instance, you could get a result that differs from what
you expect.
The loosely convergent model also means that the entry could have changed since you read it. If needed, you can use LDAP assertions to set conditions for your LDAP operations.
If you need to determine which groups an account belongs to, request
isMemberOf
for example with OpenDJ when you read the
account entry. Other directory servers use other names for this attribute
that identifies the groups to which an account belongs.
Directory servers expose their capabilities, suffixes they support, and so forth as attribute values on the root DSE. See the section on Reading Root DSEs.
This allows your application to discover a variety of information at run time, rather than storing configuration separately. Thus putting effort into querying the directory about its configuration and the features it supports can make your application easier to deploy and to maintain.
For example, rather than hard-coding
dc=example,dc=com
as a suffix DN in your configuration,
you can search the root DSE on OpenDJ for namingContexts
,
and then search under the naming context DNs to locate the entries you are
looking for in order to initialize your configuration.
Directory servers also expose their schema over LDAP. The root DSE
attribute subschemaSubentry
shows the DN of the entry
holding LDAP schema definitions. See the section, Getting Schema
Information. Note that LDAP object class and attribute
type names are case-insensitive, so isMemberOf
and
ismemberof
refer to the same attribute for example.
When you use large attribute values such as photos or audio messages, consider storing the objects themselves elsewhere and keeping only a reference to external content on directory entries. In order to serve results quickly with high availability, directory servers both cache content and also replicate it everywhere.
Textual entries with a bunch of attributes and perhaps a certificate are often no larger than a few KB. Your directory administrator might therefore be disappointed to learn that your popular application stores users' photo and .mp3 collections as attributes of their accounts.
A persistent search lets your application receive updates from the server as they happen by keeping the connection open and forcing the server to check whether to return additional results any time it performs a modification in the scope of your search. Directory administrators therefore might hesitate to grant persistent search access to your application. Directory servers like OpenDJ can let you discover updates with less overhead by searching the change log periodically. If you do have to use a persistent search instead, try to narrow the scope of your search.
Directory servers also support a resource-intensive operation called server-side sorting. When your application requests a server-side sort, the directory server retrieves all the entries matching your search, and then returns the whole set of entries in sorted order. For result sets of any size server-side sorting therefore ties up server resources that could be used elsewhere. Alternatives include both sorting the results after your application receives them, and also working with the directory administrator to have appropriate browsing (virtual list view) indexes maintained on the directory server for applications that must regularly page through long lists of search results.
Directory servers like OpenDJ come with schema definitions for a wide range of standard object classes and attribute types. This is because directories are designed to be shared by many applications. Directories use unique, typically IANA-registered object identifiers (OID) to avoid object class and attribute type name clashes. The overall goal is Internet-wide interoperability.
You therefore should reuse schema definitions that already exist whenever you reasonably can. Reuse them as is. Do not try to redefine existing schema definitions.
If you must add schema definitions for your application, extend existing object classes with AUXILIARY classes of your own. Take care to name your definitions such that they do not clash with other names.
When you have defined schema required for your application, work with the directory administrator to have your definitions added to the directory service. Directory servers like OpenDJ let directory administrators update schema definitions over LDAP, so there is not generally a need to interrupt the service to add your application. Directory administrators can however have other reasons why they hesitate to add your schema definitions. Coming to the discussion prepared with good schema definitions, explanations of why they should be added, and evident regard for interoperability makes it easier for the directory administrator to grant your request.
When a directory server returns a search result, the result is not necessarily an entry. If the result is a referral, then your application should follow up with an additional search based on the URIs provided in the result.
LDAP result codes are standard and clearly defined. When you receive
a Result
, check the ResultCode
value to
determine what action your application should take. When the result is not
what you expect, you can also read or at least log the message string from
ResultCode.getDiagnosticMessage()
.
If you can read the directory server access log, then you can check
what the server did with your application's request. For example, the
following OpenDJ access log excerpt shows a successful connection from
cn=My Killer App,ou=Apps,dc=example,dc=com
performing
a simple bind after Start TLS, and then a simple search before unbind.
The lines are wrapped for readability, whereas in the log each record starts
with the time stamp.
[20/Apr/2012:13:31:05 +0200] CONNECT conn=5 from=127.0.0.1:51561 to=127.0.0.1:1389 protocol=LDAP [20/Apr/2012:13:31:05 +0200] EXTENDED REQ conn=5 op=0 msgID=1 name="StartTLS" oid="1.3.6.1.4.1.1466.20037" [20/Apr/2012:13:31:05 +0200] EXTENDED RES conn=5 op=0 msgID=1 name="StartTLS" oid="1.3.6.1.4.1.1466.20037" result=0 etime=0 [20/Apr/2012:13:31:07 +0200] BIND REQ conn=5 op=1 msgID=2 version=3 type=SIMPLE dn="cn=My Killer App,ou=Apps,dc=example,dc=com" [20/Apr/2012:13:31:07 +0200] BIND RES conn=5 op=1 msgID=2 result=0 authDN="cn=My Killer App,ou=Apps,dc=example,dc=com" etime=1 [20/Apr/2012:13:31:07 +0200] SEARCH REQ conn=5 op=2 msgID=3 base="dc=example,dc=com" scope=wholeSubtree filter="(uid=kvaughan)" attrs="isMemberOf" [20/Apr/2012:13:31:07 +0200] SEARCH RES conn=5 op=2 msgID=3 result=0 nentries=1 etime=6 [20/Apr/2012:13:31:07 +0200] UNBIND REQ conn=5 op=3 msgID=4 [20/Apr/2012:13:31:07 +0200] DISCONNECT conn=5 reason="Client Unbind"
Notice that each operation type is shown in upper case, and that the
server tracks both the connection (conn=5
), operation
(op=[0-3]
), and message ID (msgID=[1-4]
)
numbers to make it easy to filter records. The etime
refers
to how long the server worked on the request in milliseconds. Result code
0 corresponds to ResultCode.SUCCESS
, as described in
RFC 4511.