Advanced Topics for LDAP users

«« Previous
Next »»

The lessons in the LDAP trail provide details on the mapping between the LDAP and the JNDI. They also give hints and tips for accessing the LDAP service through the JNDI.

LDAP


X.500, is a CCITT standard for directory services that is part of the OSI suite of services. X.500 standard defines a protocol (among others) for a client application to access the X.500 directory called the Directory Access Protocol (DAP). It is layered on top of the Open Systems Interconnection (OSI) protocol stack.

The Internet community, recognizing the need for an X.500-like service but faced with different underlying network infrastructure (TCP/IP instead of OSI), designed a new protocol based on the X.500 DAP protocol, called Lightweight DAP, or LDAP. RFC 2251 defines what is now called version 3 of the LDAP (or LDAP v3), an improved version of its predecessor LDAP v2 specified in RFC 1777.

The goal of the LDAP was a protocol that could be easily implemented, with special focus on being able to build small and simple clients. One way that it attempted to achieve simplification was to use a lot of strings and to minimize wherever possible the use of structures. DNs, for example, are represented in the protocol as strings, as are attribute type names and many attribute values.

The protocol consists of the client's sending requests to the server, to which the server responds, though not necessarily in the same order in which the requests were sent. Each request is tagged with an ID so that requests and responses can be matched. The protocol works over either TCP or UDP, although the TCP version is most commonly used.

Because of the focus on clients, the LDAP community also defined standards for the string representation of DNs ( RFC 2553), search filters ( RFC 1960), and attribute syntaxes ( RFC 1778), for a C language based API ( RFC 1823), and for the format of URLs for accessing LDAP services ( RFC 1959).

LDAP v3 supports internationalization, various authentication mechanisms, referrals, and a generic deployment mechanism. It allows new features to be added to the protocol without also requiring changes to the protocol by using extensions and controls.

LDAP v3


Internationalization

Internationalization is addressed via an international character set (ISO 10646) to represent protocol elements that are strings (such as DNs). Version 3 also differs from version 2 in that it uses UTF-8 to encode its strings.

Authentication

In addition to anonymous, simple (clear-text password) authentication, LDAP v3 uses the Simple Authentication and Security Layer (SASL) authentication framework ( RFC 2222) to allow different authentication mechanisms to be used with the LDAP. SASL specifies a challenge-response protocol in which data is exchanged between the client and the server for the purposes of authentication.

Several SASL mechanisms are currently defined: DIGEST-MD5, CRAM-MD5, Anonymous, External, S/Key, GSSAPI, and Kerberos v4. An LDAP v3 client can use any of these SASL mechanisms, provided that the LDAP v3 server supports them. Moreover, new (yet-to-be defined) SASL mechanisms can be used without changes to the LDAP having to be made.

Referrals

A referral is information that a server sends back to the client indicating that the requested information can be found at another location (possibly at another server). In the LDAP v2, servers are supposed to handle referrals and not return them to the client. This is because handling referrals can be very complicated and therefore would result in more-complicated clients. As servers were built and deployed, referrals were found to be useful, but not many servers supported server-side referral handling. So a way was found to retrofit the protocol to allow it to return referrals. This was done by placing the referral inside of the error message of a "partial result" error response.

The LDAP v3 has explicit support for referrals and allows servers to return the referrals directly to the client. Referrals won't be covered in this lesson but you can always refer to JNDI Tutorial for managing referrals in your application.

Deployment

A common protocol such as the LDAP is useful for ensuring that all of the directory clients and servers "speak the same language." When many different directory client applications and directory servers are deployed in a network, it also is very useful for all of these entities to talk about the same objects.

A directory schema specifies, among other things, the types of objects that a directory may have and the mandatory and optional attributes that each type of object may have. The LDAP v3 defines a schema ( RFC 2252 and RFC 2256) based on the X.500 standard for common objects found in the network, such as countries, localities, organizations, users/persons, groups, and devices. It also defines a way for a client application to access the server's schema so that it can find out the types of objects and attributes that a particular server supports.

The LDAP v3 further defines a set of syntaxes for representing attribute values ( RFC 2252).

Extensions

In addition to the repertoire of predefined operations, such as "search" and "modify," the LDAP v3 defines an "extended" operation. The "extended" operation takes a request as an argument and returns a response. The request contains an identifier that identifies the request and the arguments of the request. The response contains the results of performing the request. The pair of "extended" operation request/response is called an extension. For example, there can be an extension for Start TLS, which is a request for the client to the server to activate the Start TLS protocol.

These extensions can be standard (defined by the LDAP community) or proprietary (defined by a particular directory vendor). For writing applications that need to use Extensions refer to the JNDI Tutorial.

Controls

Another way to add new features is by using a control. The LDAP v3 allows the behavior of any operation to be modified through the use of controls. Any number of controls may be sent along with an operation, and any number of controls may be returned with its results. For example, you can send a Sort control along with a "search" operation that tells the server to sort the results of the search according to the "name" attribute.

Like extensions, such controls can be standard or proprietary. The standard controls are provided in the platform.

JNDI as an LDAP API


Both the JNDI and LDAP models define a hierarchical namespace in which you name objects. Each object in the namespace may have attributes that can be used to search for the object. At this high level, the two models are similar, so it is not surprising that the JNDI maps well to the LDAP.

Models

You can think of an LDAP entry as a JNDI DirContext. Each LDAP entry contains a name and a set of attributes, as well as an optional set of child entries. For example, the LDAP entry "o=JNDITutorial" may have as its attributes "objectclass" and "o", and it may have as its children "ou=Groups" and "ou=People".

In the JNDI, the LDAP entry "o=JNDITutorial" is represented as a context with the name "o=JNDITutorial" that has two subcontexts, named: "ou=Groups" and "ou=People". An LDAP entry's attributes are represented by the Attributes interface, whereas individual attributes are represented by the Attribute interface.


Names

As a result of federation, the names that you supply to the JNDI's context methods can span multiple namespaces. These are called composite names. When using the JNDI to access an LDAP service, you should be aware that the forward slash character ("/") in a string name has special meaning to the JNDI. If the LDAP entry's name contains this character, then you need to escape it (using the backslash character ("\")). For example, an LDAP entry with the name "cn=O/R" must be presented as the string "cn=O\\/R" to the JNDI context methods. For more information about Names check out the JNDI Tutorial. The LdapName and Rdn classes simplify creation and manipulation of LDAP names.

LDAP names as they are used in the protocol are always fully qualified names that identify entries that start from the root of the LDAP namespace (as defined by the server). Following are some examples of fully qualified LDAP names.

cn=Ted Geisel, ou=Marketing, o=Some Corporation, c=gb
cn=Vinnie Ryan, ou=People, o=JNDITutorial

In the JNDI, however, names are always relative; that is, you always name an object relative to a context. For example, you can name the entry "cn=Vinnie Ryan" relative to the context named "ou=People, o=JNDITutorial". Or you can name the entry "cn=Vinnie Ryan, ou=People" relative to the context named "o=JNDITutorial". Or, you can create an initial context that points at the root of the LDAP server's namespace and name the entry "cn=Vinnie Ryan, ou=People, o=JNDITutorial".

How LDAP Operations Map to JNDI APIs


The LDAP defines a set of operations or requests. In the JNDI, these map to operations on the DirContext and LdapContext interfaces (which are sub interfaces of Context). For example, when a caller invokes a DirContext method, the LDAP service provider implements the method by sending LDAP requests to the LDAP server.

The following table shows how operations in the LDAP correspond to JNDI methods.

LDAP Operation Corresponding JNDI Methods 
bind The corresponding way of creating an initial connection to the LDAP server in the JNDI is the creation of an InitialDirContext. When the application creates an initial context, it supplies client authentication information via environment properties. To change that authentication information for an existing context, use Context.addToEnvironment() and Context.removeFromEnvironment().
unbind   Context.close() is used to free resources used by a context. It differs from the LDAP "unbind" operation in that within a given service provider implementation, resources can be shared among contexts, so closing one context won't free all of the resources if those resources are being shared with another context. Make sure to close all contexts if your intent is to free all resources. 
search  The corresponding method in the JNDI is the overloading of DirContext.search() that accepts a search filter ( RFC 2254). 
modify The corresponding method in the JNDI is the overloading of DirContext.modifyAttributes() that accepts an array of DirContext.ModificationItems. 
add  The corresponding methods in the JNDI are DirContext.bind() and DirContext.createSubcontext(). You can use either to add a new LDAP entry. Using bind(), you can specify not only a set of attributes for the new entry but also a Java object to be added along with the attributes.  
delete  The corresponding methods in the JNDI are Context.unbind() and Context.destroySubcontext(). You can use either to remove an LDAP entry. 
modify DN/RDN  The corresponding method in the JNDI is Context.rename(). 
compare  The corresponding operation in the JNDI is a suitably constrained DirContext.search(). 
abandon  When you close a context, all of its outstanding requests are abandoned. Similarly, when you close a NamingEnumeration, the corresponding LDAP "search" request is abandoned. 
extended operation  The corresponding method in the JNDI is LdapContext.extendedOperation().

How LDAP Error Codes Map to JNDI Exceptions


The LDAP defines a set of status codes that are returned with LDAP responses sent by the LDAP server. In the JNDI, error conditions are indicated as checked exceptions that are subclasses of NamingException

The LDAP service provider translates the LDAP status code it receives from the LDAP server to the appropriate subclass of NamingException. The following table shows the mapping between LDAP status codes and JNDI exceptions.

LDAP Status Code Meaning  Exception or Action 
0 Success  Report success. 
Operations error NamingException
Protocol error  CommunicationException
Time limit exceeded.   TimeLimitExceededException 
Size limit exceeded.  SizeLimitExceededException 
Compared false.  Used by DirContext.search(). Does not generate an exception. 
Compared true.  Used by DirContext.search(). Does not generate an exception. 
Authentication method not supported.  AuthenticationNotSupportedException 
Strong authentication required.  AuthenticationNotSupportedException 
Partial results being returned.  If the environment property "java.naming.referral" is set to "ignore" or the contents of the error do not contain a referral, throw a PartialResultException. Otherwise, use contents to build a referral. 
10  Referral encountered.  If the environment property "java.naming.referral" is set to "ignore", then ignore. If the property is set to "throw", throw ReferralException. If the property is set to "follow", then the LDAP provider processes the referral. If the "java.naming.ldap.referral.limit" property has been exceeded, throw LimitExceededException. 
11  Administrative limit exceeded.  LimitExceededException 
12  Unavailable critical extension requested.  OperationNotSupportedException 
13  Confidentiality required.  AuthenticationNotSupportedException 
14  SASL bind in progress.  Used internally by the LDAP provider during authentication. 
16 No such attribute exists.  NoSuchAttributeException 
17 An undefined attribute type.  InvalidAttributeIdentifierException 
18 1Inappropriate matching   InvalidSearchFilterException 
19 A constraint violation.  InvalidAttributeValueException 
20 An attribute or value already in use.  AttributeInUseException 
21 An invalid attribute syntax.  InvalidAttributeValueException
32 No such object exists.  NameNotFoundException 
33 Alias problem  NamingException 
34 An invalid DN syntax.  InvalidNameException 
35  Is a leaf.  Used by the LDAP provider; usually doesn't generate an exception. 
36 Alias dereferencing problem  NamingException 
48 Inappropriate authentication  AuthenticationNotSupportedException 
49  Invalid credentials  AuthenticationException 
50  Insufficient access rights  NoPermissionException 
51  Busy  ServiceUnavailableException
52  Unavailable  ServiceUnavailableException 
53  Unwilling to perform  OperationNotSupportedException 
54  Loop detected.  NamingException
64  Naming violation  InvalidNameException
65  Object class violation  SchemaViolationException 
66  Not allowed on non-leaf.  ContextNotEmptyException 
67  Not allowed on RDN.  SchemaViolationException 
68  Entry already exists.  NameAlreadyBoundException 
69  Object class modifications prohibited.  SchemaViolationException 
71  Affects multiple DSAs.  NamingException 
80  Other  NamingException

Security


An LDAP service provides a generic directory service. It can be used to store information of all sorts. All LDAP servers have some system in place for controlling who can read and update the information in the directory.

To access the LDAP service, the LDAP client first must authenticate itself to the service. That is, it must tell the LDAP server who is going to be accessing the data so that the server can decide what the client is allowed to see and do. If the client authenticates successfully to the LDAP server, then when the server subsequently receives a request from the client, it will check whether the client is allowed to perform the request. This process is called access control.

The LDAP standard has proposed ways in which LDAP clients can authenticate to LDAP servers ( RFC 2251 and RFC 2829). These are discussed in general in the LDAP Authentication section and Authentication Mechanisms section. This lesson also contains descriptions of how to use the anonymous, simple and SASL authentication mechanisms.

Access control is supported in different ways by different LDAP server implementations. It is not discussed in this lesson.

Another security aspect of the LDAP service is support the use of secure channels to communicate with clients, for example to send and receive attributes that contain secrets, such as passwords and keys. LDAP servers use SSL for this purpose.

Modes of Authenticating to LDAP


In the LDAP, authentication information is supplied in the "bind" operation. In LDAP v2, a client initiates a connection with the LDAP server by sending the server a "bind" operation that contains the authentication information.

In the LDAP v3, this operation serves the same purpose, but it is optional. A client that sends an LDAP request without doing a "bind" is treated as an anonymous client (see the Anonymous section for details). In the LDAP v3, the "bind" operation may be sent at any time, possibly more than once, during the connection. A client can send a "bind" request in the middle of a connection to change its identity. If the request is successful, then all outstanding requests that use the old identity on the connection are discarded and the connection is associated with the new identity.

The authentication information supplied in the "bind" operation depends on the authentication mechanism that the client chooses.

Authenticating to the LDAP by Using the JNDI

In the JNDI, authentication information is specified in environment properties. When you create an initial context by using the InitialDirContext class (or its superclass or subclass), you supply a set of environment properties, some of which might contain authentication information. You can use the following environment properties to specify the authentication information.

◈ Context.SECURITY_AUTHENTICATION ("java.naming.security.authentication").
Specifies the authentication mechanism to use. For the LDAP service provider in the JDK, this can be one of the following strings: "none", "simple", sasl_mech, where sasl_mech is a space-separated list of SASL mechanism names. See the Authentication Mechanisms for a description of these strings.

◈ Context.SECURITY_PRINCIPAL ("java.naming.security.principal").
Specifies the name of the user/program doing the authentication and depends on the value of the Context.SECURITY_AUTHENTICATION property. See the next few sections in this lesson for details and examples.

◈ Context.SECURITY_CREDENTIALS ("java.naming.security.credentials").
Specifies the credentials of the user/program doing the authentication and depends on the value of the Context.SECURITY_AUTHENTICATION property. See the next few sections in this lesson for details and examples.

When the initial context is created, the underlying LDAP service provider extracts the authentication information from these environment properties and uses the LDAP "bind" operation to pass them to the server.

The following example shows how, by using a simple clear-text password, a client authenticates to an LDAP server.

// Set up the environment for creating the initial context
Hashtable<String, Object> env = new Hashtable<String, Object>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");

// Authenticate as S. User and password "mysecret"
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, 
        "cn=S. User, ou=NewHires, o=JNDITutorial");
env.put(Context.SECURITY_CREDENTIALS, "mysecret");

// Create the initial context
DirContext ctx = new InitialDirContext(env);

// ... do something useful with ctx

Using Different Authentication Information for a Context

If you want to use different authentication information for an existing context, then you can use Context.addToEnvironment() and Context.removeFromEnvironment() to update the environment properties that contain the authentication information. Subsequent invocations of methods on the context will use the new authentication information to communicate with the server.

The following example shows how the authentication information of a context is changed to "none" after the context has been created.

// Authenticate as S. User and the password "mysecret"
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, 
        "cn=S. User, ou=NewHires, o=JNDITutorial");
env.put(Context.SECURITY_CREDENTIALS, "mysecret");

// Create the initial context
DirContext ctx = new InitialDirContext(env);

// ... do something useful with ctx

// Change to using no authentication
ctx.addToEnvironment(Context.SECURITY_AUTHENTICATION, "none");

// ... do something useful with ctx

Authentication Failures

Authentication can fail for a number of reasons. For example, if you supply incorrect authentication information, such as an incorrect password or principal name, then an AuthenticationException is thrown.

Here is an example that is a variation of the previous example. This time, an incorrect password causes the authentication to fail.

// Authenticate as S. User and give an incorrect password
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, 
        "cn=S. User, ou=NewHires, o=JNDITutorial");
env.put(Context.SECURITY_CREDENTIALS, "notmysecret");

This produces the following output.

javax.naming.AuthenticationException: [LDAP: error code 49 - Invalid Credentials]
        ...

Because different servers support different authentication mechanisms, you might request an authentication mechanism that the server does not support. In this case, an AuthenticationNotSupportedException will be thrown.

Here is an example that is a variation of the previous example. This time, an unsupported authentication mechanism ("custom") causes the authentication to fail.

// Authenticate as S. User and the password "mysecret"
env.put(Context.SECURITY_AUTHENTICATION, "custom");
env.put(Context.SECURITY_PRINCIPAL, 
        "cn=S. User, ou=NewHires, o=JNDITutorial");
env.put(Context.SECURITY_CREDENTIALS, "mysecret");

This produces the following output.

javax.naming.AuthenticationNotSupportedException: custom
        ...

Authentication Mechanisms


Different versions of the LDAP support different types of authentication. The LDAP v2 defines three types of authentication: anonymous, simple (clear-text password), and Kerberos v4.

The LDAP v3 supports anonymous, simple, and SASL authentication. SASL is the Simple Authentication and Security Layer ( RFC 2222). It specifies a challenge-response protocol in which data is exchanged between the client and the server for the purposes of authentication and establishment of a security layer on which to carry out subsequent communication. By using SASL, the LDAP can support any type of authentication agreed upon by the LDAP client and server.

This lesson contains descriptions of how to authenticate by using Anonymous, Simple, and SASL authentication.

Specifying the Authentication Mechanism

The authentication mechanism is specified by using the Context.SECURITY_AUTHENTICATION environment property. The property may have one of the following values.

Property Name

sasl_mech: A space-separated list of SASL mechanism names.
none: Use no authentication (anonymous)
simple: Use weak authentication (clear-text password)

Property Value

sasl_mech: Use one of the SASL mechanisms listed (e.g., "CRAM-MD5" means to use the CRAM-MD5 SASL mechanism described in RFC 2195)

The Default Mechanism

If the client does not specify any authentication environment properties, then the default authentication mechanism is "none". The client will then be treated as an anonymous client.

If the client specifies authentication information without explicitly specifying the Context.SECURITY_AUTHENTICATION property, then the default authentication mechanism is "simple".

Anonymous


As just stated, the default authentication mechanism is "none" if no authentication environment properties have been set. If the client sets the Context.SECURITY_AUTHENTICATION environment property to "none", then the authentication mechanism is "none" and all other authentication environment properties are ignored. You would want to do this explicitly only to ensure that any other authentication properties that might have been set are ignored. In either case, the client will be treated as an anonymous client. This means that the server does not know or care who the client is and will allow the client to access (read and update) any data that has been configured to be accessible by any unauthenticated client.

Because none of the directory examples in the Naming and Directory Operations lesson set any of the authentication environment properties, all of them use anonymous authentication.

Here is an example that explicitly sets the Context.SECURITY_AUTHENTICATION property to "none" (even though doing this is not strictly necessary because that is the default).

// Set up the environment for creating the initial context
Hashtable<String, Object> env = new Hashtable<String, Object>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");

// Use anonymous authentication
env.put(Context.SECURITY_AUTHENTICATION, "none");

// Create the initial context
DirContext ctx = new InitialDirContext(env);

// ... do something useful with ctx

Simple


Simple authentication consists of sending the LDAP server the fully qualified DN of the client (user) and the client's clear-text password (see RFC 2251 and RFC 2829). This mechanism has security problems because the password can be read from the network. To avoid exposing the password in this way, you can use the simple authentication mechanism within an encrypted channel (such as SSL), provided that this is supported by the LDAP server.

Both the LDAP v2 and v3 support simple authentication.

To use the simple authentication mechanism, you must set the three authentication environment properties as follows.

Context.SECURITY_AUTHENTICATION.

Set to "simple".

Context.SECURITY_PRINCIPAL.

Set to the fully qualified DN of the entity that is being authenticated (e.g., "cn=S. User, ou=NewHires, o=JNDITutorial"). It is of type java.lang.String.

Context.SECURITY_CREDENTIALS.

Set to the password of the principal (e.g., "mysecret"). It is of type java.lang.String, char array (char[]), or byte array (byte[]). If the password is a java.lang.String or a char array, then it is encoded using UTF-8 for the LDAP v3 and using ISO-Latin-1 for the LDAP v2 for transmission to the server. If the password is a byte[], then it is transmitted as is to the server.

Note: If you supply an empty string, an empty byte/char array, or null to the Context.SECURITY_CREDENTIALS environment property, then the authentication mechanism will be "none". This is because the LDAP requires the password to be nonempty for simple authentication. The protocol automatically converts the authentication to "none" if a password is not supplied.

SASL


The LDAP v3 protocol uses the SASL to support pluggable authentication. This means that the LDAP client and server can be configured to negotiate and use possibly nonstandard and/or customized mechanisms for authentication, depending on the level of protection desired by the client and the server. The LDAP v2 protocol does not support the SASL.

Several SASL mechanisms are currently defined:

◈ Anonymous ( RFC 2245)
◈ CRAM-MD5 ( RFC 2195 )
◈ Digest-MD5 ( RFC 2831)
◈ External ( RFC 2222)
◈ Kerberos V4 ( RFC 2222)
◈ Kerberos V5 ( RFC 2222)
◈ SecurID ( RFC 2808)
◈ S/Key ( RFC 2222)

SASL Mechanisms Supported by LDAP Servers

Of the mechanisms on the previous list, popular LDAP servers (such as those from Oracle, OpenLDAP, and Microsoft) support External, Digest-MD5, and Kerberos V5. RFC 2829 proposes the use of Digest-MD5 as the mandatory default mechanism for LDAP v3 servers.

Here is a simple program for finding out the list of SASL mechanisms that an LDAP server supports.

// Create initial context
DirContext ctx = new InitialDirContext();

// Read supportedSASLMechanisms from root DSE
Attributes attrs = ctx.getAttributes(
    "ldap://localhost:389", new String[]{"supportedSASLMechanisms"});

Here is the output produced by running this program against a server that supports the External SASL mechanism.

{supportedsaslmechanisms=supportedSASLMechanisms: 
                         EXTERNAL, GSSAPI, DIGEST-MD5}

Specifying the Authentication Mechanism

To use a particular SASL mechanism, you specify its Internet Assigned Numbers Authority (IANA)-registered mechanism name in the Context.SECURITY_AUTHENTICATION environment property. You can also specify a list of mechanisms for the LDAP provider to try. This is done by specifying an ordered list of space-separated mechanism names. The LDAP provider will use the first mechanism for which it finds an implementation.

Here's an example that asks the LDAP provider to try to get the implementation for the DIGEST-MD5 mechanism and if that's not available, use the one for GSSAPI.

env.put(Context.SECURITY_AUTHENTICATION, "DIGEST-MD5 GSSAPI");

You might get this list of authentication mechanisms from the user of your application. Or you might get it by asking the LDAP server, via a call similar to that shown previously. The LDAP provider itself does not consult the server for this information. It simply attempts to locate and use the implementation of the specified mechanisms.

The LDAP provider in the platform has built-in support for the External, Digest-MD5, and GSSAPI (Kerberos v5) SASL mechanisms. You can add support for additional mechanisms.

Specifying Input for the Authentication Mechanism

Some mechanisms, such as External, require no additional input--the mechanism name alone is sufficient for the authentication to proceed. The External example shows how to use the External SASL mechanism.

Most other mechanisms require some additional input. Depending on the mechanism, the type of input might vary. Following are some common inputs required by mechanisms.

◈ Authentication id. The identity of the entity performing the authentication.
◈ Authorization id. The identity of the entity for which access control checks should be made if the authentication succeeds.
◈ Authentication credentials. For example, a password or a key.

The authentication and authorization ids might differ if the program (such as a proxy server) is authenticating on behalf of another entity. The authentication id is specified by using the Context.SECURITY_PRINCIPAL environment property. It is of type java.lang.String.

The password/key of the authentication id is specified by using the Context.SECURITY_CREDENTIALS environment property. It is of type java.lang.String, char array (char[]), or byte array (byte[]). If the password is a byte array, then it is transformed into a char array by using an UTF-8 encoding.

If the "java.naming.security.sasl.authorizationId" property has been set, then its value is used as the authorization ID. Its value must be of type java.lang.String. By default, the empty string is used as the authorization ID, which directs the server to derive an authorization ID from the client's authentication credentials.

The Digest-MD5 example shows how to use the Context.SECURITY_PRINCIPAL and Context.SECURITY_CREDENTIALS properties for Digest-MD5 authentication.

If a mechanism requires input other than those already described, then you need to define a callback object for the mechanism to use, you can check out the callback example in the JNDI Tutorial. The next part of this lesson discusses how to use SASL Digest-MD5 authentication mechanism.

Digest-MD5


Digest-MD5 authentication is the required authentication mechanism for LDAP v3 servers ( RFC 2829). Because the use of SASL is part of the LDAP v3 ( RFC 2251), servers that support only the LDAP v2 do not support Digest-MD5.

The Digest-MD5 mechanism is described in RFC 2831. It is based on the HTTP Digest Authentication ( RFC 2251). In Digest-MD5, the LDAP server sends data that includes various authentication options that it is willing to support plus a special token to the LDAP client. The client responds by sending an encrypted response that indicates the authentication options that it has selected. The response is encrypted in such a way that proves that the client knows its password. The LDAP server then decrypts and verifies the client's response.

To use the Digest-MD5 authentication mechanism, you must set the authentication environment properties as follows.

Context.SECURITY_AUTHENTICATION.

Set to the string "DIGEST-MD5".

Context.SECURITY_PRINCIPAL.

Set to the principal name. This is a server-specific format. Some servers support a login user id format, such as that defined for UNIX or Windows login screens. Others accept a distinguished name. Yet others use the authorization id formats defined in RFC 2829. In that RFC, the name should be either the string "dn:", followed by the fully qualified DN of the entity being authenticated, or the string "u:", followed by the user id. Some servers accept multiple formats. Examples of some of these formats are "cuser", "dn: cn=C. User, ou=NewHires, o=JNDITutorial", and "u: cuser" The data type of this property must be java.lang.String.

Context.SECURITY_CREDENTIALS.

Set to the password of the principal (e.g., "mysecret"). It is of type java.lang.String, char array (char[]), or byte array (byte[]). If the password is a java.lang.String or char[], then it is encoded by using UTF-8 for transmission to the server. If the password is a byte[], then it is transmitted as is to the server.
The following example shows how a client performs authentication using Digest-MD5 to an LDAP server.

// Set up the environment for creating the initial context
Hashtable<String, Object> env = new Hashtable<String, Object>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");

// Authenticate as C. User and password "mysecret"
env.put(Context.SECURITY_AUTHENTICATION, "DIGEST-MD5");
env.put(Context.SECURITY_PRINCIPAL,
        "dn:cn=C. User, ou=NewHires, o=JNDITutorial");
env.put(Context.SECURITY_CREDENTIALS, "mysecret");

// Create the initial context
DirContext ctx = new InitialDirContext(env);

// ... do something useful with ctx

Note: The Oracle Directory Server, v5.2 supports the Digest-MD5 authentication mechanism for users that have clear-text passwords. You must set the password encryption mode before you create the user. If you have already created the user, delete it and recreate it. To set the password encryption mode using the Administration Console, select the Configuration tab and the Data node. In the Passwords pane, select the "No encryption (CLEAR)" option for "Password encryption." The server accepts simple user names (that is, the value of the "uid" attribute for entries that have one) and the "dn:" format of user names. See the server's documentation for detailed information.

Specifying the Realm

A realm defines the namespace from which the authentication entity (the value of the Context.SECURITY_PRINCIPAL property) is selected. A server might have multiple realms. For example, a server for a university might be configured to have two realms, one for its student users and another for faculty users. Realm configuration is done by the directory administrator. Some directories have a default single realm. For example, the Oracle Directory Server, v5.2, uses the fully qualified hostname of the machine as the default realm.

In Digest-MD5 authentication, you must authenticate to a specific realm. You may use the following authentication environment property to specify the realm. If you do not specify a realm, then any one of the realms offered by the server will be used.

java.naming.security.sasl.realm

Set to the realm of the principal. This is a deployment-specific and/or server-specific case-sensitive string. It identifies the realm or domain from which the principal name (Context.SECURITY_PRINCIPAL) should be chosen. If this realm does not match one of the realms offered by the server, then the authentication fails.

The following example shows how to set the environment properties for performing authentication using Digest-MD5 and a specified realm. To make this example work in your environment, you must change the source code so that the realm value reflects what has been configured on your directory server.

// Authenticate as C. User and password "mysecret" in realm "JNDITutorial"
env.put(Context.SECURITY_AUTHENTICATION, "DIGEST-MD5");
env.put(Context.SECURITY_PRINCIPAL,
        "dn:cn=C. User, ou=NewHires, o=JNDITutorial");
env.put(Context.SECURITY_CREDENTIALS, "mysecret");
env.put("java.naming.security.sasl.realm", "JNDITutorial");

SSL and Custom Sockets


In addition to SASL authentication, most LDAP servers allow their services to be accessed through SSL. SSL is especially useful for LDAP v2 servers because the v2 protocol does not support SASL authentication.

An SSL-enabled server often supports SSL in two ways. In the most basic way, the server supports SSL ports in addition to normal (unprotected) ports. The other way in which a server supports SSL is via the use of the Start TLS Extension ( RFC 2830). This option is available only to LDAP v3 servers and is described in detail in the section.

Using the SSL Socket Property

By default, the LDAP service provider in the JDK uses plain sockets when communicating with the LDAP server. To request that SSL sockets be use, set the Context.SECURITY_PROTOCOL property to "ssl".

In the following example, the LDAP server is offering SSL at port 636. To run this program, you must enable SSL on port 636 on your LDAP server. This procedure is typically carried out by the directory's administrator.

Server Requirements: The LDAP server must be set up with an X.509 SSL server certificate and have SSL enabled. Typically, you must first obtain a signed certificate for the server from a certificate authority (CA). Then, follow the instructions from your directory vendor on how to enable SSL. Different vendors have different tools for doing this.

For the Oracle Directory Server, v5.2 , use the Manage Certificates tool in the Administration Console to generate a Certificate Signing Request (CSR). Submit the CSR to a CA to obtain an X.509 SSL server certificate. Using the Administration Console, add the certificate to the server's list of certificates. Also install the CA's certificate if it is not already in the server's list of trusted CAs. Enable SSL by using the Configuration tab in the Administration Console. Select the server in the left pane. Select the Encryption tab in the right pane. Click the checkboxes for "Enable SSL for this server" and "Use this cipher family: RSA", ensuring that the server certificate you have added is in the list of certificates.

Client Requirements: You need to ensure that the client trusts the LDAP server that you'll be using. You must install the server's certificate (or its CA's certificate) in your JRE's database of trusted certificates. Here is an example.

# cd JAVA_HOME/lib/security
# keytool -import -file server_cert.cer -keystore jssecacerts

// Set up the environment for creating the initial context
Hashtable<String, Object> env = new Hashtable<String, Object>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:636/o=JNDITutorial");

// Specify SSL
env.put(Context.SECURITY_PROTOCOL, "ssl");

// Authenticate as S. User and password "mysecret"
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL,
        "cn=S. User, ou=NewHires,o=JNDITutorial");
env.put(Context.SECURITY_CREDENTIALS, "mysecret");

// Create the initial context
DirContext ctx = new InitialDirContext(env);

// ... do something useful with ctx

Note: If you use SSL to connect to a server on a port that is not using SSL, then your program will hang. Similarly, if you use a plain socket to connect to a server's SSL socket, then your application will hang. This is a characteristic of the SSL protocol.

Using the LDAPS URL

Instead of requesting the use of SSL via the use of the Context.SECURITY_PROTOCOL property, you can also request the use of SSL via the use of LDAPS URLs. An LDAPS URL is similar to an LDAP URL except that the URL scheme is "ldaps" instead of "ldap". It specifies the use of SSL when communicating with the LDAP server.

In the following example, the LDAP server is offering SSL at port 636. To run this program, you must enable SSL on port 636 on your LDAP server.

// Set up the environment for creating the initial context
Hashtable<String, Object> env = new Hashtable<String, Object>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");

// Specify LDAPS URL
env.put(Context.PROVIDER_URL, "ldaps://localhost:636/o=JNDITutorial");

// Authenticate as S. User and password "mysecret"
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL,
        "cn=S. User, ou=NewHires, o=JNDITutorial");
env.put(Context.SECURITY_CREDENTIALS, "mysecret");

// Create the initial context
DirContext ctx = new InitialDirContext(env);

// ... do something useful with ctx
LDAPS URLs are accepted anywhere LDAP URLs are accepted.

Client Authentication: Using SSL with the External SASL Mechanism

SSL provides authentication and other security services at a lower layer than the LDAP. If authentication has already been done at the SSL, the LDAP layer can use that authentication information from SSL by using the External SASL mechanism.

The following example is like the previous SSL example, except that instead of using simple authentication, it uses the External SASL authentication. By using External, you do not need to supply any principal or password information, because they get picked up from the SSL.

Server Requirements: This example requires the LDAP server to allow certificate-based client authentication. In addition, the LDAP server must trust (the CAs of) the client certificates that it receives, and must be able to map the owner distinguished names in the client certificates to principals that it knows about. Follow the instructions from your directory vendor on how to perform these tasks.

Client Requirements: This example requires the client to have an X.509 SSL client certificate. Moreover, the certificate must be stored as the first key entry in a keystore file. If this entry is password-protected, it must have the same password as the keystore. For more information about JSSE keystores, see the Java Secure Socket Extension (JSSE) Reference Guide.

// Set up the environment for creating the initial context
Hashtable<String, Object> env = new Hashtable<String, Object>(11);
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:636/o=JNDITutorial");

// Principal and credentials will be obtained from the connection
env.put(Context.SECURITY_AUTHENTICATION, "EXTERNAL");

// Specify SSL
env.put(Context.SECURITY_PROTOCOL, "ssl");

// Create the initial context
DirContext ctx = new InitialDirContext(env);

...

To run this program so that the client's certificate is used for authentication, you must provide (as system properties) the location and password of the keystore containing the client's certificate. Here is an example of how to run the program.

java -Djavax.net.ssl.keyStore=MyKeystoreFile \
    -Djavax.net.ssl.keyStorePassword=mysecret \
    External

If you do not supply a keystore, the program will run using anonymous authentication because no client credential exists at the SSL.

This example shows the most basic way to accomplish certificate-based client authentication. More advanced ways can be accomplished by writing and using a custom socket factory that accesses the client certificate in a more flexible manner, perhaps by using an LDAP directory. The next section shows how to use a custom socket factory with your JNDI application.

Using Custom Sockets

When using SSL, the LDAP provider will, by default, use the socket factory, javax.net.ssl.SSLSocketFactory, for creating an SSL socket to communicate with the server, using the default JSSE configuration. The JSSE can be customized in a variety of ways, as detailed in the Java Secure Socket Extension (JSSE) Reference Guide. However, there are times when those customizations are not sufficient and you need to have more control over the SSL sockets, or sockets in general, used by the LDAP service provider. For example, you might need sockets that can bypass firewalls, or JSSE sockets that use nondefault caching/retrieval policies for its trust and key stores. To set the socket factory implementation used by the LDAP service provider, set the "java.naming.ldap.factory.socket" property to the fully qualified class name of the socket factory. This class must implement the javax.net.SocketFactory abstract class and provide an implementation of the getDefault() method that returns an instance of the socket factory.

Here is an example of a custom socket factory that produces plain sockets.

public class CustomSocketFactory extends SocketFactory {
    public static SocketFactory getDefault() {

        System.out.println("[acquiring the default socket factory]");
        return new CustomSocketFactory();
    }
        ...
}

Note that this example creates a new instance of CustomSocketFactory each time a new LDAP connection is created. This might be appropriate for some applications and socket factories. If you want to reuse the same socket factory, getDefault() should return a singleton.

To use this custom socket factory with a JNDI program, set the "java.naming.ldap.factory.socket" property, as shown in the following example.

// Set up the environment for creating the initial context
Hashtable<String, Object> env = new Hashtable<String, Object>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");

// Specify the socket factory
env.put("java.naming.ldap.factory.socket", "CustomSocketFactory");

// Create the initial context
DirContext ctx = new InitialDirContext(env);

// ... do something useful with ctx

The "java.naming.ldap.factory.socket" property is useful for setting the socket factory on a per context basis. Another way to control the sockets used by the LDAP service provider is to set the socket factory for all sockets used in the entire program, by using java.net.Socket.setSocketImplFactory(). Use of this method is less flexible because it affects all socket connections, not just LDAP connections and therefore, should be used with care.

More LDAP Operations


The rest of the LDAP lesson covers how the JNDI provides ability to perform certain interesting LDAP operations.

Renaming Objects

You use Context.rename() to rename an object in the directory. In the LDAP v2, this corresponds to the "modify RDN" operation that renames an entry within the same context (that is, renaming a sibling). In the LDAP v3, this corresponds to the "modify DN" operation, which is like "modify RDN," except that the old and new entries need not be in the same context. You can use Context.rename() to rename a leaf entry or an interior node. The example shown in the Naming and Directory Operations lesson renames a leaf entry. The following code renames an interior node from "ou=NewHires" to "ou=OldHires":

ctx.rename("ou=NewHires", "ou=OldHires");

Note: The Oracle Directory Server v5.2 does not support renaming interior nodes. If you run this example, then you will get a ContextNotEmptyException.

Renaming to a Different Part of the DIT

With the LDAP v3, you can rename an entry to a different part of the DIT. To do this by using Context.rename(), you must use a context that is the common ancestor for both the new and the old entries. For example, to rename "cn=C. User, ou=NewHires, o=JNDITutorial" to "cn=C. User, ou=People, o=JNDITutorial", you must use the context named by "o=JNDITutorial". Following is an example that demonstrates this. If you try to run this example against an LDAP v2 server, then you will get an InvalidNameException because version 2 does not support this feature.

ctx.rename("cn=C. User, ou=NewHires", "cn=C. User, ou=People");

Note: The Oracle Directory Server v5.2 does not support renaming with different parent nodes. If you run this example by using that server, then you will get a OperationNotSupportedException (indicating a "protocol error").

Keeping the Old Name Attributes

In the LDAP, when you rename an entry, you have the option of keeping the entry's old RDN as an attribute of the updated entry. For example, if you rename the entry "cn=C. User" to "cn=Claude User", you can specify whether you want the old RDN "cn=C. User" to be kept as an attribute.

To specify whether you want to keep the old name attribute when you use Context.rename(), use the "java.naming.ldap.deleteRDN" environment property. If this property's value is "true" (the default), the old RDN is removed. If its value is "false", then the old RDN is kept as an attribute of the updated entry. The complete example is here.

// Set the property to keep RDN
env.put("java.naming.ldap.deleteRDN", "false");

// Create the initial context
DirContext ctx = new InitialDirContext(env);

// Perform the rename
ctx.rename("cn=C. User, ou=NewHires", "cn=Claude User,ou=NewHires");

LDAP Compare


The LDAP "compare" operation allows a client to ask the server whether the named entry has an attribute/value pair. This allows the server to keep certain attribute/value pairs secret (i.e., not exposed for general "search" access) while still allowing the client limited use of them. Some servers might use this feature for passwords, for example, although it is insecure for the client to pass clear-text passwords in the "compare" operation itself.

To accomplish this in the JNDI, use suitably constrained arguments for the following methods:

◈ search(Name name, String filter, SearchControls ctls)
◈ search(Name name, String filterExpr, Object[]filterArgs, SearchControls ctls)

1. The filter must be of the form "(name=value)". You cannot use wild cards.
2. The search scope must be SearchControls.OBJECT_SCOPE.
3. You must request that no attributes be returned. If these criteria are not met, then these methods will use an LDAP "search" operation instead of an LDAP "compare" operation.

Here's an example that causes an LDAP "compare" operation to be used.

// Value of the attribute
byte[] key = {(byte)0x61, (byte)0x62, (byte)0x63, (byte)0x64,
              (byte)0x65, (byte)0x66, (byte)0x67};

// Set up the search controls
SearchControls ctls = new SearchControls();
ctls.setReturningAttributes(new String[0]);       // Return no attrs
ctls.setSearchScope(SearchControls.OBJECT_SCOPE); // Search object only

// Invoke search method that will use the LDAP "compare" operation
NamingEnumeration answer = ctx.search("cn=S. User, ou=NewHires",
                                      "(mySpecialKey={0})",
                                       new Object[]{key}, ctls);

If the compare is successful, the resulting enumeration will contain a single item whose name is the empty name and which contains no attributes.

Search Results


When you use the search methods in the DirContext interface, you get back a NamingEnumeration. Each item in NamingEnumeration is a SearchResult, which contains the following information:

Name

Each SearchResult contains the name of the LDAP entry that satisfied the search filter. You obtain the name of the entry by using getName(). This method returns the composite name of the LDAP entry relative to the target context. The target context is the context to which the name parameter resolves. In LDAP parlance, the target context is the base object for the search. Here's an example.

NamingEnumeration answer = ctx.search("ou=NewHires",
    "(&(mySpecialKey={0}) (cn=*{1}))",  // Filter expression
    new Object[]{key, name},                // Filter arguments
    null);                                  // Default search controls

The target context in this example is that named by "ou=NewHires". The names in SearchResults in answer are relative to "ou=NewHires". For example, if getName() returns "cn=J. Duke", then its name relative to ctx will be "cn=J. Duke, ou=NewHires".

If you performed the search by using SearchControls.SUBTREE_SCOPE or SearchControls.OBJECT_SCOPE and the target context itself satisfied the search filter, then the name returned will be "" (the empty name) because that is the name relative to the target context.

This isn't the whole story. If the search involves referrals or dereferencing aliases, then the corresponding SearchResults will have names that are not relative to the target context. Instead, they will be URLs that refer directly to the entry. To determine whether the name returned by getName() is relative or absolute, use isRelative(). If this method returns true, then the name is relative to the target context; if it returns false, the name is a URL.

If the name is a URL and you need to use that URL, then you can pass it to the initial context, which understands URLs.

If you need to get the entry's full DN, you can use NameClassPair.getNameInNamespace().

Object

If the search was conducted requesting that the entry's object be returned (SearchControls.setReturningObjFlag() was invoked with true), then SearchResult will contain an object that represents the entry. To retrieve this object, you invoke getObject(). If a java.io.Serializable, Referenceable, or Reference object was previously bound to that LDAP name, then the attributes from the entry are used to reconstruct that object. Otherwise, the attributes from the entry are used to create a DirContext instance that represents the LDAP entry. In either case, the LDAP provider invokes DirectoryManager.getObjectInstance() on the object and returns the results.

Class Name

If the search was conducted requesting that the entry's object be returned, then the class name is derived from the returned object. If the search requested attributes that included the retrieval of the "javaClassName" attribute of the LDAP entry, then the class name is the value of that attribute. Otherwise, the class name is "javax.naming.directory.DirContext". The class name is obtained from getClassName().

Attributes

When you perform a search, you can select the return attributes either by supplying a parameter to one of the search() methods or by setting the search controls using SearchControls.setReturningAttributes() . If no attributes have been specified explicitly, then all of the LDAP entry's attributes are returned. To specify that no attributes be returned, you must pass an empty array (new String[0]).

To retrieve the LDAP entry's attributes, you invoke getAttributes() on the SearchResult.

LDAP Unsolicited Notifications


The LDAP v3 ( RFC 2251) defines an unsolicited notification, a message that is sent by an LDAP server to the client without any provocation from the client. An unsolicited notification is represented in the JNDI by the UnsolicitedNotification interface.

Because unsolicited notifications are sent asynchronously by the server, you can use the same event model used for receiving notifications about namespace changes and object content changes. You register interest in receiving unsolicited notifications by registering an UnsolicitedNotificationListener with an EventContext or EventDirContext.

Here is an example of an UnsolicitedNotificationListener.

public class UnsolListener implements UnsolicitedNotificationListener {
    public void notificationReceived(UnsolicitedNotificationEvent evt) {
        System.out.println("received: " + evt);
    }

    public void namingExceptionThrown(NamingExceptionEvent evt) {
        System.out.println(">>> UnsolListener got an exception");
            evt.getException().printStackTrace();
    }
}

Following is an example that registers an implementation of UnsolicitedNotificationListener with an event source. Note that only the listener argument to EventContext.addNamingListener() is relevant. The name and scope parameters are not relevant to unsolicited notifications.

// Get the event context for registering the listener
EventContext ctx = (EventContext)
    (new InitialContext(env).lookup("ou=People"));

// Create the listener
NamingListener listener = new UnsolListener();

// Register the listener with the context (all targets equivalent)
ctx.addNamingListener("", EventContext.ONELEVEL_SCOPE, listener);

When running this program, you need to point it at an LDAP server that can generate unsolicited notifications and prod the server to emit the notification. Otherwise, after one minute the program will exit silently.

A listener that implements UnsolicitedNotificationListener can also implement other NamingListener interfaces, such as NamespaceChangeListener and ObjectChangeListener.

Connection Management


JNDI provides a high-level interface for accessing naming and directory services. The mapping between a JNDI Context instance and an underlying network connection might not be one-to-one. The service provider is free to share and reuse connections as long as the interface semantics are preserved. The application developer usually does not need to know the details of how Context instances create and use connections. These details are useful when the developer needs to tune his program.

This lesson describes how the LDAP service provider uses connections. It describes when connections are created and how to specify special connection parameters, such as multiple servers and connection timeouts. This lesson also shows how to dynamically discover and use LDAP servers in network environments that support it.

A connection that is created must eventually be closed. This lesson contains a section that describes connection closures by the client and the server.

Finally, this lesson shows you how to use connection pooling to make applications that use many short-lived connections more efficient.

Note: Information presented in this lesson are specific to LDAP service provider in the JDK. LDAP service providers from other vendors might not use the same policies for managing connections.

Creation


There are several ways in which a connection is created. The most common way is from the creation of an initial context. When you create an InitialContext, InitialDirContext, or InitialLdapContext by using the LDAP service provider, a connection is set up immediately with the target LDAP server named in the Context.PROVIDER_URL property. Each time an initial context is created, a new LDAP connection is created. See the Pooling section for information on how to change this behavior.

If the property value contains more than one URL, then each URL is tried in turn until one is used to create a successful connection. The property value is then updated to be the successful URL. See the JNDI Tutorial for an example of how to create an initial context using a list of URLs.

There are three other direct ways in which a connection is created.

1. By passing a URL as the name argument to the initial context. When an LDAP or LDAPS URL is passed as a name parameter to the initial context, the information in the URL is used to create a new connection to the LDAP server, regardless of whether the initial context instance itself has a connection to an LDAP server. In fact, the initial context might not be connected to any server. See the JNDI Tutorial for more information on how URLs are used as names.

2. Another way that a connection is created is by use of a Reference. When a Reference containing an LDAP or LDAPS URL is passed to NamingManager.getObjectInstance() or DirectoryManager.getObjectInstance(), a new connection is created using information specified in the URL.

3. Finally, when a referral is followed manually or automatically, the information in the referral is used to create a new connection. See the JNDI Tutorial for information on referrals.

Shared Connections

Context instances and NamingEnumerations that are derived from one Context instance share the same connection until changes to one of the Context instances make sharing no longer possible. For example, if you invoke Context.lookup(), Context.listBindings() or DirContext.search() from the initial context and get back other Context instances, then all of those Context instances will share the same connection.

Here is an example.

// Create initial context
DirContext ctx = new InitialDirContext(env);

// Get a copy of the same context
Context ctx2 = (Context)ctx.lookup("");

// Get a child context
Context ctx3 = (Context) ctx.lookup("ou=NewHires");

In this example, ctx, ctx2, and ctx3 will share the same connection.

Sharing is done regardless of how the Context instance came into existence. For example, a Context instance obtained by following a referral will share the same connection as the referral.

When you change a Context instance's environment properties that are related to a connection, such as the principal name or credentials of the user, the Context instance on which you make these changes will get its own connection (if the connection is shared). Context instances that are derived from this Context instance in the future will share this new connection. Context instances that previously shared the old connection are not affected (that is, they continue to use the old connection).

Here is an example that uses two connections.

// Create initial context (first connection)
DirContext ctx = new InitialDirContext(env);

// Get a copy of the same context
DirContext ctx2 = (DirContext)ctx.lookup("");

// Change authentication properties in ctx2
ctx2.addToEnvironment(Context.SECURITY_PRINCIPAL,
    "cn=C. User, ou=NewHires, o=JNDITutorial");
ctx2.addToEnvironment(Context.SECURITY_CREDENTIALS, "mysecret");

// Method on ctx2 will use new connection
System.out.println(ctx2.getAttributes("ou=NewHires"));

ctx2 initially shares the same connection with ctx. But when its principal and password properties are changed, it can no longer use ctx's connection. The LDAP provider will automatically create a new connection for ctx2.

Similarly, if you use LdapContext.reconnect() to change the Context instance's connection controls, the Context instance will get its own connection if the connection was being shared.

If the Context instance's connection was not being shared (i.e., no Contexts have been derived from it), then changes to its environment or connection controls will not cause a new connection to be created. Instead, any changes relevant to the connection will be applied to the existing connection.

Creation Timeouts

Not all connection creations are successful. If the LDAP provider cannot establish a connection within a certain timeout period, it aborts the connection attempt. By default, this timeout period is the network (TCP) timeout value, which is in the order of a few minutes. To change the timeout period, you use the "com.sun.jndi.ldap.connect.timeout" environment property. The value of this property is the string representation of an integer representing the connection timeout in milliseconds.

Here is an example.

// Set up environment for creating initial context
Hashtable env = new Hashtable(11);
env.put(Context.INITIAL_CONTEXT_FACTORY,
    "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");

// Specify timeout to be 5 seconds
env.put("com.sun.jndi.ldap.connect.timeout", "5000");

// Create initial context
DirContext ctx = new InitialDirContext(env);

// do something useful with ctx

In this example, if a connection cannot be created within 5 seconds, an exception will be thrown.

If the Context.PROVIDER_URL property contains more than one URL, the provider will use the timeout for each URL. For example, if there are 3 URLs and the timeout has been specified to be 5 seconds, then the provider will wait for a maximum of 15 seconds in total.

Closing


Normal garbage collection takes care of removing Context instances when they are no longer in use. Connections used by Context instances being garbage collected will be closed automatically. Therefore, you do not need to explicitly close connections. Network connections, however, are limited resources and for certain programs, you might want to have control over their proliferation and usage. This section contains information on how to close connections and how to get notified if the server closes the connection.

Explicit Closures

You invoke Context.close() on a Context instance to indicate that you no longer need to use it. If the Context instance being closed is using a dedicated connection, the connection is also closed. If the Context instance is sharing a connection with other Context and unterminated NamingEnumeration instances, the connection will not be closed until close() has been invoked on all such Context and NamingEnumeration instances.

In the example from the Connection Creation example section, all three Context instances must be closed before the underlying connection is closed.

// Create initial context
DirContext ctx = new InitialDirContext(env);

// Get a copy of the same context
Context ctx2 = (Context)ctx.lookup("");

// Get a child context
Context ctx3 = (Context) ctx.lookup("ou=NewHires");

// do something useful with ctx, ctx2, ctx3

// Close the contexts when we're done
ctx.close();
ctx2.close();
ctx3.close();

Forced Implicit Closures

As mentioned previously, for those Context and NamingEnumeration instances that are no longer in scope, the Java runtime system will eventually garbage collect them, thus cleaning up the state that a close() would have done. To force the garbage collection, you can use the following code.

Runtime.getRuntime().gc();
Runtime.getRuntime().runFinalization();

Depending on the state of the program, performing this procedure can cause serious (temporary) performance degradation. If you need to ensure that connections are closed, track Context instances and close them explicitly.

Detecting Connection Closures

LDAP servers often have an idle timeout period after which they will close connections no longer being used. When you subsequently invoke a method on a Context instance that is using such a connection, the method will throw a CommunicationException. To detect when the server closes the connection that a Context instance is using, you register an UnsolicitedNotificationListener with the Context instance. AN example is shown in the LDAP Unsolicited Notifications section. Although the example is designed for receiving unsolicited notification from the server, it can also be used to detect connection closures by the server. After starting the program, stop the LDAP server and observe that the listener's namingExceptionThrown() method gets called.

Pooling


The Connection Creation section described when connections are created. It described how several Context instances can share the same connection.

Another type of connection sharing supported by the LDAP service provider is called connection pooling. In this type of sharing, the LDAP service provider maintains a pool of (possibly) previously used connections and assigns them to a Context instance as needed. When a Context instance is done with a connection (closed or garbage collected), the connection is returned to the pool for future use. Note that this form of sharing is sequential: a connection is retrieved from the pool, used, returned to the pool, and then, retrieved again from the pool for another Context instance.

The pool of connections is maintained per Java runtime system. For some situations, connection pooling can improve performance significantly. For example, only one connection is required for processing a search response that contains four referral references to the same LDAP server if connection pooling is used. Without connection pooling, such a scenario would require four separate connections.

The rest of this lesson describes in more detail how to use connection pooling.

How to Use Connection Pooling

You request connection pooling by adding the property, "com.sun.jndi.ldap.connect.pool" to the environment properties passed to the initial context constructor. Here is an example .

// Set up environment for creating initial context
Hashtable env = new Hashtable(11);
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");

// Enable connection pooling
env.put("com.sun.jndi.ldap.connect.pool", "true");

// Create one initial context (Get connection from pool)
DirContext ctx = new InitialDirContext(env);

// do something useful with ctx

// Close the context when we're done
ctx.close();   // Return connection to pool

// Create another initial context (Get connection from pool)
DirContext ctx2 = new InitialDirContext(env);

// do something useful with ctx2

// Close the context when we're done
ctx2.close();   // Return connection to pool

This example creates two initial contexts in succession. The second initial context will reuse the connection used by the first. To run this program and observe how the connections are retrieved and returned to the pool, use the following command line.

#java -Dcom.sun.jndi.ldap.connect.pool.debug=fine UsePool

This should produce output that looks as follows.

Create com.sun.jndi.ldap.LdapClient@5d173[localhost:389]
Use com.sun.jndi.ldap.LdapClient@5d173
{ou=ou: NewHires, objectclass=objectClass: top, organizationalUnit}
Release com.sun.jndi.ldap.LdapClient@5d173
Use com.sun.jndi.ldap.LdapClient@5d173
{ou=ou: People, objectclass=objectClass: top, organizationalunit}
Release com.sun.jndi.ldap.LdapClient@5d173

You can decide when and where to use pooling by including or omitting the "com.sun.jndi.ldap.connect.pool" property, and thus control pooling on a per-context basis. In the previous example, if you removed this property from the environment properties before creating the second initial context, the second initial context would not use a pooled connection.

The LDAP provider keeps track of whether a connection is being used through indications from the application. It assumes that an application that is maintaining an open context handle is using the connection. Therefore, in order for the LDAP provider to properly manage the pooled connections, you must be diligent about invoking Context.close() on contexts that you no longer need.

Bad connections are automatically detected and removed from the pool by the LDAP provider. The probability of a context ending up with a bad connection is the same regardless of whether connection pooling is used.

Creation Timeout

The pool of connections maintained by the LDAP service provider may be limited in size; this is described in detail in the Connection Pooling Configuration section. When connection pooling has been enabled and no pooled connection is available, the client application will block, waiting for an available connection. You can use the "com.sun.jndi.ldap.connect.timeout" environment property to specify how long to wait for a pooled connection. If you omit this property, the application will wait indefinitely.

This same property is also used to specify a timeout period for establishment of the LDAP connection, as described in the Connection Creation section.

When Not to Use Pooling!!

Pooled connections are intended to be reused. Therefore, if you plan to perform operations on a Context instance that might alter the underlying connection's state, then you should not use connection pooling for that Context instance. For example, if you plan to invoke the Start TLS extended operation on a Context instance, or plan to change security-related properties (such as "java.naming.security.principal" or "java.naming.security.protocol") after the initial context has been created, you should not use connection pooling for that Context instance because the LDAP provider does not track any such state changes. If you use connection pooling in such situations, you might be compromising the security of your application.

Configuration


Connection pooling is configured and maintained per Java runtime. Connections are not shared across different runtimes. To use connection pooling, no configuration is required. Configuration is necessary only if you want to customize how pooling is done, such as to control the size of the pools and which types of connections are pooled.

You configure connection pooling by using a number of system properties at program startup time. Note that these are system properties, not environment properties and that they affect all connection pooling requests.

Here is an example of a command line that sets the maximum pool size to 20, the preferred pool size to 10, and the idle timeout to a minute for pooled connections.

# java -Dcom.sun.jndi.ldap.connect.pool.maxsize=20 \
       -Dcom.sun.jndi.ldap.connect.pool.prefsize=10 \
       -Dcom.sun.jndi.ldap.connect.pool.timeout=60000 \
    UsePool

The following table lists the system properties for configuring connection pooling. They are described in more detail in the rest of this section.

System Property Name Description  Default 
com.sun.jndi.ldap.connect.pool.authentication A list of space-separated authentication types of connections that may be pooled. Valid types are "none", "simple", and "DIGEST-MD5". "none simple"
com.sun.jndi.ldap.connect.pool.debug  A string that indicates the level of debug output to produce. Valid values are "fine" (trace connection creation and removal) and "all" (all debugging information).
com.sun.jndi.ldap.connect.pool.initsize  The string representation of an integer that represents the number of connections per connection identity to create when initially creating a connection for the identity. 
com.sun.jndi.ldap.connect.pool.maxsize  The string representation of an integer that represents the maximum number of connections per connection identity that can be maintained concurrently.  no maximum size
com.sun.jndi.ldap.connect.pool.prefsize  The string representation of an integer that represents the preferred number of connections per connection identity that should be maintained concurrently.  no preferred size
com.sun.jndi.ldap.connect.pool.protocol  A list of space-separated protocol types of connections that may be pooled. Valid types are "plain" and "ssl". "plain"
com.sun.jndi.ldap.connect.pool.timeout   The string representation of an integer that represents the number of milliseconds that an idle connection may remain in the pool without being closed and removed from the pool.  no timeout

What Gets Pooled

When you request that a Context instance use connection pooling by using the "com.sun.jndi.ldap.connect.pool" environment property, the connection that is used might or might not be pooled. The default rule is that plain (non-SSL) connections that use simple or no authentication are allowed to be pooled. You can change this default to include SSL connections and the DIGEST-MD5 authentication type by using system properties. To allow both plain and SSL connections to be pooled, set the "com.sun.jndi.ldap.connect.pool.protocol" system property to the string "plain ssl". To allow connections of anonymous (none), simple and DIGEST-MD5 authentication types to be pooled, set the com.sun.jndi.ldap.connect.pool.authentication system property to the string "none simple DIGEST-MD5".

There are a couple of environment properties that automatically disqualify a Context instance from using a pooled connection. A Context instance cannot use a pooled connection if it has its "java.naming.ldap.factory.socket" property set to a custom socket factory class, or its "java.naming.security.sasl.callback" property set to a custom callback handler class, or its "com.sun.jndi.ldap.trace.ber" property set to enable protocol tracing.

How Connections are Pooled

When a Context instance requests to use a pooled connection, the LDAP provider needs to determine whether the request can be satisfied by an existing pooled connection. It does this by assigning a connection identity to each pooled connection and checking whether the incoming request has the same connection identity as that of one of its pooled connections.

A connection identity is the set of the parameters required to create a possibly authenticated LDAP connection. Its composition depends on the authentication type of the request, as shown in the following table.

Authentication Type Connection Identity Contents 
none ◈ connection controls
◈ host name, port number as specified in the "java.naming.provider.url" property, referral, or URL supplied to the initial context
◈ the contents of the following properties:

java.naming.security.protocol
java.naming.ldap.version
simple ◈ all of the information listed for none
◈ the contents of following properties:

java.naming.security.principal
java.naming.security.credentials
DIGEST-MD5 ◈ all of the information listed for simple
◈ the contents of following properties:

java.naming.security.sasl.authorizationId
java.naming.security.sasl.realm
javax.security.sasl.qop
javax.security.sasl.strength
javax.security.sasl.server.authentication
javax.security.sasl.maxbuffer
javax.security.sasl.policy.noplaintext
javax.security.sasl.policy.noactive
javax.security.sasl.policy.nodictionary
javax.security.sasl.policy.noanonymous
javax.security.sasl.policy.forward
javax.security.sasl.policy.credentials

Pool Sizes

The LDAP provider maintains pools of connections; each pool holds connections (either in-use or idle) that have the same connection identity. There are three sizes that affect the management of each pool. These sizes are global and affect all pools.

The initial pool size is the number of connections per connection identity that the LDAP service provider creates when first creating the pool (which corresponds to when the application first requests a pooled connection for that connection identity). Authentication of each connection in the pool is performed on demand, as the connection gets used. By default, the initial pool size is 1 and can be changed by using the system property "com.sun.jndi.ldap.connect.pool.initsize". It is typically used at application start-up time to prime the pool with a certain number of connections to a server.

The maximum pool size is the maximum number of connections per connection identity that can be maintained concurrently by the LDAP service provider. Both in-use and idle connections contribute to this number. When the pool size reaches this number, no new connection for the corresponding connection identity may be created until a connection in the pool has been removed (i.e., the physical connection is closed). When the pool size reaches the maximum and all of the connections in the pool are in use, the application's request for a connection from that pool is blocked until a connection in the pool either becomes idle or is removed. A maximum pool size of 0 means that there is no maximum size: A request for a pooled connection will use an existing pooled idle connection or a newly created pooled connection.

The preferred pool size is the preferred number of connections per connection identity that the LDAP service provider should maintain. Both in-use and idle connections contribute to this number. When an application requests the use of a pooled connection and the pool size is less than the preferred size, the LDAP provider will create and use a new pooled connection regardless of whether an idle connection is available. When an application is finished with a pooled connection (by invoking Context.close() on all contexts that share the connection) and the pool size is greater than the preferred size, the LDAP provider will close and remove the pooled connection from the pool. A preferred pool size of 0 means that there is no preferred size: A request for a pooled connection will result in a newly created connection only if no idle ones are available.

Note that the maximum pool size overrides both the initial and preferred pool sizes. For example, setting the preferred pool size greater than the maximum pool size is effectively setting it to the maximum pool size.

Idle Connections

When the application is finished with a pooled connection (by invoking Context.close() on all contexts that share the connection), the underlying pooled connection is marked as idle, waiting to be reused. By default, idle connections remain in the pool indefinitely until they are garbage-collected. If the "com.sun.jndi.ldap.connect.pool.timeout" system property has been set, the LDAP provider will automatically close and remove pooled connections that have been idle for more than the specified period.

«« Previous
Next »»