1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
|
<?xml version="1.0" encoding="UTF-8"?>
<chapter id="user-authentication">
<title>User Authentication using Spring LDAP</title>
<sect1>
<title>Basic Authentication</title>
<para>While the core functionality of the <literal>ContextSource</literal>
is to provide <literal>DirContext</literal> instances for use by
<literal>LdapTemplate</literal>, it may also be used for authenticating
users against an LDAP server. The <literal>getContext(principal,
credentials)</literal> method of <literal>ContextSource</literal> will do
exactly that; construct a <literal>DirContext</literal> instance according
to the <literal>ContextSource</literal> configuration, authenticating the
context using the supplied principal and credentials. A custom
authenticate method could look like this:</para>
<para><programlisting>public boolean authenticate(String userDn, String credentials) {
DirContext ctx = null;
try {
ctx = contextSource.getContext(userDn, credentials);
return true;
} catch (Exception e) {
// Context creation failed - authentication did not succeed
logger.error("Login failed", e);
return false;
} finally {
// It is imperative that the created DirContext instance is always closed
LdapUtils.closeContext(ctx);
}
}</programlisting>The userDn supplied to the <literal>authenticate</literal>
method needs to be the full DN of the user to authenticate (regardless of
the <literal>base</literal> setting on the
<literal>ContextSource</literal>). You will typically need to perform an
LDAP search based on e.g. the user name to get this DN:</para>
<para><programlisting>private String getDnForUser(String uid) {
Filter f = new EqualsFilter("uid", uid);
List result = ldapTemplate.search(DistinguishedName.EMPTY_PATH, f.toString(),
new AbstractContextMapper() {
protected Object doMapFromContext(DirContextOperations ctx) {
return ctx.getNameInNamespace();
}
});
if(result.size() != 1) {
throw new RuntimeException("User not found or not unique");
}
return (String)result.get(0);
}</programlisting>There are some drawbacks to this approach. The user is
forced to concern herself with the DN of the user, she can only search for
the user's uid, and the search always starts at the root of the tree (the
empty path). A more flexible method would let the user specify the search
base, the search filter, and the credentials. Spring LDAP 1.3.0 introduced
new authenticate methods in LdapTemplate that provide this
functionality:</para>
<itemizedlist>
<listitem>
<para><literal>boolean authenticate(Name base, String filter, String
password);</literal></para>
</listitem>
<listitem>
<para><literal>boolean authenticate(String base, String filter, String
password);</literal></para>
</listitem>
</itemizedlist>
<para>Using one of these methods, authentication becomes as simple as
this:</para>
<para><example>
<title>Authenticating a user using Spring LDAP.</title>
<programlisting>boolean authenticated = ldapTemplate.authenticate("", "(uid=john.doe)", "secret");</programlisting>
</example></para>
<tip>
<para>Don't write your own custom authenticate methods. Use the ones
provided in Spring LDAP 1.3.x.</para>
</tip>
</sect1>
<sect1>
<title>Performing Operations on the Authenticated Context</title>
<para>Some authentication schemes and LDAP servers require some operation
to be performed on the created <literal>DirContext</literal> instance for
the actual authentication to occur. You should test and make sure how your
server setup and authentication schemes behave; failure to do so might
result in that users will be admitted into your system regardless of the
DN/credentials supplied. This is a naïve implementation of an authenticate
method where a hard-coded <literal>lookup</literal> operation is performed
on the authenticated context:</para>
<para><programlisting>public boolean authenticate(String userDn, String credentials) {
DirContext ctx = null;
try {
ctx = contextSource.getContext(userDn, credentials);
// Take care here - if a base was specified on the ContextSource
// that needs to be removed from the user DN for the lookup to succeed.
<emphasis role="bold"> ctx.lookup(userDn);</emphasis>
return true;
} catch (Exception e) {
// Context creation failed - authentication did not succeed
logger.error("Login failed", e);
return false;
} finally {
// It is imperative that the created DirContext instance is always closed
LdapUtils.closeContext(ctx);
}
}</programlisting>It would be better if the operation could be provided as an
implementation of a callback interface, thus not limiting the operation to
always be a <literal>lookup</literal>. Spring LDAP 1.3.0 introduced the
callback interface
<literal>AuthenticatedLdapEntryContextCallback</literal> and a few
corresponding <literal>authenticate</literal> methods:</para>
<itemizedlist>
<listitem>
<para><literal>boolean authenticate(Name base, String filter, String
password, AuthenticatedLdapEntryContextCallback
callback);</literal></para>
</listitem>
<listitem>
<para><literal>boolean authenticate(String base, String filter, String
password, AuthenticatedLdapEntryContextCallback
callback);</literal></para>
</listitem>
</itemizedlist>
<para>This opens up for any operation to be performed on the authenticated
context:</para>
<example>
<title>Performing an LDAP operation on the authenticated context using
Spring LDAP.</title>
<programlisting>AuthenticatedLdapEntryContextCallback contextCallback = new AuthenticatedLdapEntryContextCallback() {
public void executeWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification) {
try {
ctx.lookup(ldapEntryIdentification.getRelativeDn());
}
catch (NamingException e) {
throw new RuntimeException("Failed to lookup " + ldapEntryIdentification.getRelativeDn(), e);
}
}
};
ldapTemplate.authenticate("", "(uid=john.doe)", "secret", contextCallback));</programlisting>
</example>
</sect1>
<sect1>
<title>Retrieving the Authentication Exception</title>
<para>So far, the methods have only been able to tell the user whether or
not the authentication succeeded. There has been no way of retrieving the
actual exception. Spring LDAP 1.3.1 introduced the
<literal>AuthenticationErrorCallback</literal> and a few more
<literal>authenticate</literal> methods:</para>
<itemizedlist>
<listitem>
<para><literal>boolean authenticate(Name base, String filter, String
password, AuthenticationErrorCallback errorCallback);</literal></para>
</listitem>
<listitem>
<para><literal>boolean authenticate(String base, String filter, String
password, AuthenticationErrorCallback errorCallback);</literal></para>
</listitem>
<listitem>
<para><literal>boolean authenticate(Name base, String filter, String
password, AuthenticatedLdapEntryContextCallback callback,
AuthenticationErrorCallback errorCallback);</literal></para>
</listitem>
<listitem>
<para><literal>boolean authenticate(String base, String filter, String
password, AuthenticatedLdapEntryContextCallback callback,
AuthenticationErrorCallback errorCallback);</literal></para>
</listitem>
</itemizedlist>
<para>A convenient collecting implementation of the error callback
interface is also provided:</para>
<para><programlisting>public final class CollectingAuthenticationErrorCallback implements AuthenticationErrorCallback {
private Exception error;
public void execute(Exception e) {
this.error = e;
}
public Exception getError() {
return error;
}
}</programlisting>The code needed for authenticating a user and retrieving the
authentication exception in case of an error boils down to this:</para>
<para><example>
<title>Authenticating a user and retrieving the authentication
exception.</title>
<programlisting>import org.springframework.ldap.core.support.CollectingAuthenticationErrorCallback;
...
CollectingAuthenticationErrorCallback errorCallback = new CollectingAuthenticationErrorCallback();
boolean result = ldapTemplate.authenticate("", filter.toString(), "invalidpassword", errorCallback);
if (!result) {
Exception error = errorCallback.getError();
// error is likely of type org.springframework.ldap.AuthenticationException
}</programlisting>
</example></para>
</sect1>
<sect1>
<title>Use Spring Security</title>
<para>While the approach above may be sufficient for simple authentication
scenarios, requirements in this area commonly expand rapidly. There is a
multitude of aspects that apply, including authentication, authorization,
web integration, user context management, etc. If you suspect that the
requirements might expand beyond just simple authentication, you should
definitely consider using <ulink type=""
url="http://static.springsource.org/spring-security/site/">Spring
Security</ulink> for your security purposes instead. It is a full-blown,
mature security framework addressing the above aspects as well as several
others.</para>
</sect1>
</chapter>
|