2.2 - Binding and unbinding
In LDAP, if one wants to access the data in the base, the common way to do it is to bind to the server. However, it’s important to understand that binding is a different from connecting.
Creating a connection to an LDAP server opens a socket between the client and the server. You must provide the address and the port in order to do this.
The bind operation, on the other hand, creates a Session which will hold user information for the duration of the session. This information is limited, but includes the user’s credentials.
But it’s important to know that it’s possible to bind anonymously, which doesn’t require a user or password, and still be able to send requests to the server (although the server can forbid anonymous binds).
Once the user has finished interacting with the server, they can unbind, destroying the session held on the server. This operation does not close the connection, because, again bind != connection!
Binding
There are two possible types of binds in LDAP:
- Simple
- SASL
The first one is based on a userid/password sent to the server, which verifies the credentials are valid. It’s also possible to proceed with an anonymous bind explicitly.
The second type is more complicated, and is used whenever authentication with a specific mechanism, like DIGEST-MD5, Kerberos or certificate based is required.
Simple Bind
One can issue three kinds of simple binds:
- anonymous bind
- name/password bind
- unauthenticated authentication bind
The first one is the easiest, but depending on the server’s configuration, will be accepted or rejected (not all servers allow anonymous binds)
Most of the time, the bind operation will not return anything. You either get bound, or will receive an LdapException if an error occurs.
Issuing an anonymous bind is simple, you neither provide a user nor a password:
@Test
public void testAnonymousBindRequest() throws Exception
{
connection.bind();
}
Issuing a user/password bind is slightly more complex, because those credentials must be included:
@Test
public void testSimpleBindRequest() throws Exception
{
connection.bind( "uid=admin,ou=system", "secret" );
}
Note It’s important to note that the user’s name is a Dn, not a simple name like ‘John Doe”
Last, not least, there is a quite unknown feature in LDAP bind that allows you to issue a Bind request without providing a password. It’s equivalent to an anonymous bind, except that the server can log the user’s name, thus being able to trace what the user does. Servers might forbid such bind, and this will be the case if the server disallow anonymous binds.
Note that this kind of bind will be supported only if the server allows anonymous binds. It’s not supported by ApacheDS.
@Test
public void testSimpleBindRequest() throws Exception
{
connection.bind( "uid=admin,ou=system" );
}
SASL Binding
There are various SASL mechanisms that can be used to bind to a LDAP server. The Apache LDAP API support the following 5 mechanisms:
PLAIN
To be completed…
CRAM-MD5
To be completed…
DIGEST-MD5
To be completed…
EXTERNAL
To be completed…
GSSAPI
First, non-trivial Kerberos authentication requires configuration. Creating a Kerberos configuration is not well documented elsewhere, so we include here sample code. One approach is to create a JAAS configuration file. Here’s what such a file might contain:
myapp {
com.sun.security.auth.module.Krb5LoginModule required debug=true useKeyTab=true principal="host/ilab2.myorg.org@MYORG.ORG" refreshKrb5Config=true keyTab="/etc/krb5.keytab";
};
See online documentation for Krb5LoginModule for possible options. This example uses the host’s principal, stored in /etc/krb5.keytab. What goes after @ is the Kerberos domain name.
Note that if you specify a credential cache, the cache must be in a file. Many operating systems now put the user’s credentials in KEYRING or KCM. That worn’t work with Java.
“myapp” is the name supplied as the login context name. See the code below.
When you run the program, you must tell java where the configuration file is. e.g. “java -Djava.security.auth.login.config=jaas.config”.
Here is code to connect to ldap with this configuration. We assume that ldapNetworkConnection has already been opened using connect.
saslGssApiRequest = new SaslGssApiRequest();
saslGssApiRequest.setLoginModuleConfiguration(Configuration.getConfiguration());
saslGssApiRequest.setLoginContextName( "myapp");
saslGssApiRequest.setMutualAuthentication( true );
try {
BindResponse br = ldapNetworkConnection.bind( saslGssApiRequest );
ldapNetworkConnection.startTls();
} catch ( LdapException e ) {
e.printStackTrace();
}
At this point you can do operations such as search. Note that the argument to setLoginContextName must match the name in the configuration file. This sample uses mutual authentication. It is possible that some LDAP servers might not support that. If so you can set it to false. You may not need startTls if the connection is already secure.
Sometimes it is more convenient to supply the configuration information programmatically. Here is an example that sets the same options as the config file
class KerberosConfiguration extends Configuration {
@Override
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
Map<String, String> options = new HashMap<String, String>();
options.put("useKeyTab", "true");
try {
options.put("principal", "host/" + InetAddress.getLocalHost().getCanonicalHostName() + “@MYORG.ORG”);
} catch (Exception e){
System.out.println("Can't find our hostname " + e);
}
options.put("refreshKrb5Config", "true");
options.put("keyTab", "/etc/krb5.keytab");
options.put("debug", "true");
return new AppConfigurationEntry[]{
new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule",
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
options),};
}
}
Here is how it is used:
saslGssApiRequest = new SaslGssApiRequest();
saslGssApiRequest.setLoginModuleConfiguration(new KerberosConfiguration());
saslGssApiRequest.setMutualAuthentication( true );
try {
BindResponse br = ldapNetworkConnection.bind( saslGssApiRequest );
ldapNetworkConnection.startTls();
} catch ( LdapException e ) {
e.printStackTrace();
}
Note that it is not necessary to set the login context name, since it is only needed to process the configuration file.’
Warnings:
-
Apache Kerby does not implement the option to prompt the user for a password, nor does it permit a password to be passed to GSSAPI. Thus authentication is limited to key tables and credential caches.
-
The ** Apache Kerby** code sets two system properties. In the default case, it clears **KRB5_CONF** and sets _javax.security.auth.useSubjectCredsOnly_ to true. If other code in your application uses **Kerberos**, be aware that there could be conflicts. Be particularly concerned about multi-threaded code that uses different values. Note that in Kirby you can explicitly set **KRB5_CONF**, with _setKrb5ConfFilePath_, or cause Kirby to set it with setRealmName, setKdcHost, and setKdcPort. If you use different settings for these options in different threads, there is a potential thread-safety issue.
-
The Java implementation of Kerberos has the ability to read standard Kerberos credential cache files, e.g. /tmp/krb5cc%{uid}. However it can’t read credentials from KEYRING or KCM. If your code asks for authentication to come from an existing credential cache, make sure that it is in a file. If you write an application that expects to use existing credentials for logged in users, without prompting for a password, you may need to set default_ccache_name in /etc/krb5.conf to a file, e.g. default_ccache_name = /tmp/krb5cc%{uid} This is not an issue if your authentication comes from key tables or prompts the user.
-
The encryption types supported depends upon the Java version. E,g. aes128-cts-hmac-sha256-128 and aes256-cts-hmac-sha384-192 are supported only as of Java 11.
Rebinding
It’s possible to issue a Bind on an already bound connection and the existing LDAP session will be terminated, and replaced by a new LDAP session. In any case, the connection is not dropped when doing so. Note that if the connection was encrypted, it remains encrypted.
@Test
public void testRebind() throws Exception
{
connection.bind( "uid=admin,ou=system", "secret" );
// Now, rebind
connection.bind( "cn=john doe,dc=example,dc=com", "secret" );
assertTrue( connection.isConnected() );
assertTrue( connection.isAuthenticated() );
}
If you issue a bind on the same connection with some different credentials, then the existing session will be terminated on the server, and a new one will be created. All the pending requests for the initial session will be lost.
Unbinding
This is a trivial operation : you just send an UnbindRequest to the server, which will invalidate your session.
It’s important to know that when you issue an Unbind, the connection is dropped. It’s done like this:
@Test
public void testUnbind() throws Exception
{
connection.bind( "uid=admin,ou=system", "secret" );
// Now, unbind
connection.unBind();
assertFalse( connection.isConnected() );
assertFalse( connection.isAuthenticated() );
// And Bind again.
connection.bind( "uid=admin,ou=system", "secret" );
}
Last, but not least, if you close the connection, the session also terminates.