1
1
/*
2
- * Copyright 2002-2012 the original author or authors.
2
+ * Copyright 2002-2015 the original author or authors.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5
5
* the License. You may obtain a copy of the License at
47
47
* Specialized LDAP authentication provider which uses Active Directory configuration conventions.
48
48
* <p>
49
49
* It will authenticate using the Active Directory
50
- * <a href="http://msdn.microsoft.com/en-us/library/ms680857%28VS.85%29.aspx">{@code userPrincipalName}</a>
51
- * (in the form {@code username@domain}). If the username does not already end with the domain name, the
52
- * {@code userPrincipalName} will be built by appending the configured domain name to the username supplied in the
53
- * authentication request. If no domain name is configured, it is assumed that the username will always contain the
54
- * domain name.
50
+ * <a href="http://msdn.microsoft.com/en-us/library/ms680857%28VS.85%29.aspx">{@code userPrincipalName}</a> or
51
+ * <a href="http://msdn.microsoft.com/en-us/library/ms679635%28v=vs.85%29.aspx">{@code sAMAccountName}</a> (or a custom
52
+ * {@link #setSearchFilter(String) searchFilter}) in the form {@code username@domain}. If the username does not
53
+ * already end with the domain name, the {@code userPrincipalName} will be built by appending the configured domain
54
+ * name to the username supplied in the authentication request. If no domain name is configured, it is assumed that
55
+ * the username will always contain the domain name.
55
56
* <p>
56
57
* The user authorities are obtained from the data contained in the {@code memberOf} attribute.
57
58
*
@@ -96,39 +97,31 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLda
96
97
private final String rootDn ;
97
98
private final String url ;
98
99
private boolean convertSubErrorCodesToExceptions ;
100
+ private String searchFilter = "(&(objectClass=user)(|(sAMAccountName={0})(userPrincipalName={0})))" ;
99
101
100
102
// Only used to allow tests to substitute a mock LdapContext
101
103
ContextFactory contextFactory = new ContextFactory ();
102
104
103
- /**
104
- * @param domain the domain for which authentication should take place
105
- */
106
- // public ActiveDirectoryLdapAuthenticationProvider(String domain) {
107
- // this (domain, null);
108
- // }
109
-
110
105
/**
111
106
* @param domain the domain name (may be null or empty)
112
107
* @param url an LDAP url (or multiple URLs)
113
108
*/
114
109
public ActiveDirectoryLdapAuthenticationProvider (String domain , String url ) {
115
110
Assert .isTrue (StringUtils .hasText (url ), "Url cannot be empty" );
116
111
this .domain = StringUtils .hasText (domain ) ? domain .toLowerCase () : null ;
117
- //this.url = StringUtils.hasText(url) ? url : null;
118
112
this .url = url ;
119
113
rootDn = this .domain == null ? null : rootDnFromDomain (this .domain );
120
114
}
121
115
122
116
@ Override
123
117
protected DirContextOperations doAuthentication (UsernamePasswordAuthenticationToken auth ) {
124
118
String username = auth .getName ();
125
- String password = (String )auth .getCredentials ();
119
+ String password = (String ) auth .getCredentials ();
126
120
127
121
DirContext ctx = bindAsUser (username , password );
128
122
129
123
try {
130
124
return searchForUser (ctx , username );
131
-
132
125
} catch (NamingException e ) {
133
126
logger .error ("Failed to locate directory entry for authenticated user: " + username , e );
134
127
throw badCredentials (e );
@@ -168,7 +161,7 @@ private DirContext bindAsUser(String username, String password) {
168
161
// TODO. add DNS lookup based on domain
169
162
final String bindUrl = url ;
170
163
171
- Hashtable <String ,String > env = new Hashtable <String ,String >();
164
+ Hashtable <String , String > env = new Hashtable <String , String >();
172
165
env .put (Context .SECURITY_AUTHENTICATION , "simple" );
173
166
String bindPrincipal = createBindPrincipal (username );
174
167
env .put (Context .SECURITY_PRINCIPAL , bindPrincipal );
@@ -189,25 +182,26 @@ private DirContext bindAsUser(String username, String password) {
189
182
}
190
183
}
191
184
192
- void handleBindException (String bindPrincipal , NamingException exception ) {
185
+ private void handleBindException (String bindPrincipal , NamingException exception ) {
193
186
if (logger .isDebugEnabled ()) {
194
187
logger .debug ("Authentication for " + bindPrincipal + " failed:" + exception );
195
188
}
196
189
197
190
int subErrorCode = parseSubErrorCode (exception .getMessage ());
198
191
199
- if (subErrorCode > 0 ) {
200
- logger .info ("Active Directory authentication failed: " + subCodeToLogMessage (subErrorCode ));
201
-
202
- if (convertSubErrorCodesToExceptions ) {
203
- raiseExceptionForErrorCode (subErrorCode , exception );
204
- }
205
- } else {
192
+ if (subErrorCode <= 0 ) {
206
193
logger .debug ("Failed to locate AD-specific sub-error code in message" );
194
+ return ;
195
+ }
196
+
197
+ logger .info ("Active Directory authentication failed: " + subCodeToLogMessage (subErrorCode ));
198
+
199
+ if (convertSubErrorCodesToExceptions ) {
200
+ raiseExceptionForErrorCode (subErrorCode , exception );
207
201
}
208
202
}
209
203
210
- int parseSubErrorCode (String message ) {
204
+ private int parseSubErrorCode (String message ) {
211
205
Matcher m = SUB_ERROR_CODE .matcher (message );
212
206
213
207
if (m .matches ()) {
@@ -217,7 +211,7 @@ int parseSubErrorCode(String message) {
217
211
return -1 ;
218
212
}
219
213
220
- void raiseExceptionForErrorCode (int code , NamingException exception ) {
214
+ private void raiseExceptionForErrorCode (int code , NamingException exception ) {
221
215
String hexString = Integer .toHexString (code );
222
216
Throwable cause = new ActiveDirectoryAuthenticationException (hexString , exception .getMessage (), exception );
223
217
switch (code ) {
@@ -238,7 +232,7 @@ void raiseExceptionForErrorCode(int code, NamingException exception) {
238
232
}
239
233
}
240
234
241
- String subCodeToLogMessage (int code ) {
235
+ private String subCodeToLogMessage (int code ) {
242
236
switch (code ) {
243
237
case USERNAME_NOT_FOUND :
244
238
return "User was not found in directory" ;
@@ -270,28 +264,25 @@ private BadCredentialsException badCredentials(Throwable cause) {
270
264
return (BadCredentialsException ) badCredentials ().initCause (cause );
271
265
}
272
266
273
- @ SuppressWarnings ("deprecation" )
274
- private DirContextOperations searchForUser (DirContext ctx , String username ) throws NamingException {
275
- SearchControls searchCtls = new SearchControls ();
276
- searchCtls .setSearchScope (SearchControls .SUBTREE_SCOPE );
277
-
278
- String searchFilter = "(&(objectClass=user)(userPrincipalName={0}))" ;
279
-
280
- final String bindPrincipal = createBindPrincipal (username );
267
+ private DirContextOperations searchForUser (DirContext context , String username ) throws NamingException {
268
+ SearchControls searchControls = new SearchControls ();
269
+ searchControls .setSearchScope (SearchControls .SUBTREE_SCOPE );
281
270
271
+ String bindPrincipal = createBindPrincipal (username );
282
272
String searchRoot = rootDn != null ? rootDn : searchRootFromPrincipal (bindPrincipal );
283
273
284
274
try {
285
- return SpringSecurityLdapTemplate .searchForSingleEntryInternal (ctx , searchCtls , searchRoot , searchFilter ,
286
- new Object []{bindPrincipal });
275
+ return SpringSecurityLdapTemplate .searchForSingleEntryInternal (context , searchControls ,
276
+ searchRoot , searchFilter , new Object []{username });
287
277
} catch (IncorrectResultSizeDataAccessException incorrectResults ) {
288
- if (incorrectResults .getActualSize () == 0 ) {
289
- UsernameNotFoundException userNameNotFoundException = new UsernameNotFoundException ("User " + username + " not found in directory." );
290
- userNameNotFoundException .initCause (incorrectResults );
291
- throw badCredentials (userNameNotFoundException );
278
+ // Search should never return multiple results if properly configured - just rethrow
279
+ if (incorrectResults .getActualSize () != 0 ) {
280
+ throw incorrectResults ;
292
281
}
293
- // Search should never return multiple results if properly configured, so just rethrow
294
- throw incorrectResults ;
282
+ // If we found no results, then the username/password did not match
283
+ UsernameNotFoundException userNameNotFoundException = new UsernameNotFoundException ("User " + username
284
+ + " not found in directory." , incorrectResults );
285
+ throw badCredentials (userNameNotFoundException );
295
286
}
296
287
}
297
288
@@ -303,7 +294,7 @@ private String searchRootFromPrincipal(String bindPrincipal) {
303
294
throw badCredentials ();
304
295
}
305
296
306
- return rootDnFromDomain (bindPrincipal .substring (atChar + 1 , bindPrincipal .length ()));
297
+ return rootDnFromDomain (bindPrincipal .substring (atChar + 1 , bindPrincipal .length ()));
307
298
}
308
299
309
300
private String rootDnFromDomain (String domain ) {
@@ -342,6 +333,21 @@ public void setConvertSubErrorCodesToExceptions(boolean convertSubErrorCodesToEx
342
333
this .convertSubErrorCodesToExceptions = convertSubErrorCodesToExceptions ;
343
334
}
344
335
336
+ /**
337
+ * The LDAP filter string to search for the user being authenticated.
338
+ * Occurrences of {0} are replaced with the {@code username@domain}.
339
+ * <p>
340
+ * Defaults to: {@code (&(objectClass=user)(|(sAMAccountName={0})(userPrincipalName={0})))}
341
+ * </p>
342
+ *
343
+ * @param searchFilter the filter string
344
+ *
345
+ * @since 3.2
346
+ */
347
+ public void setSearchFilter (String searchFilter ) {
348
+ this .searchFilter = searchFilter ;
349
+ }
350
+
345
351
static class ContextFactory {
346
352
DirContext createContext (Hashtable <?,?> env ) throws NamingException {
347
353
return new InitialLdapContext (env , null );
0 commit comments