1
1
/*
2
- * Copyright 2002-2016 the original author or authors.
2
+ * Copyright 2002-2024 the original author or authors.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
17
17
package org .springframework .security .ldap .authentication .ad ;
18
18
19
19
import java .io .Serializable ;
20
- import java .util .ArrayList ;
21
- import java .util .Arrays ;
22
20
import java .util .Collection ;
23
21
import java .util .HashMap ;
24
22
import java .util .Hashtable ;
25
- import java .util .List ;
26
23
import java .util .Map ;
27
24
import java .util .regex .Matcher ;
28
25
import java .util .regex .Pattern ;
39
36
import org .springframework .dao .IncorrectResultSizeDataAccessException ;
40
37
import org .springframework .ldap .CommunicationException ;
41
38
import org .springframework .ldap .core .DirContextOperations ;
42
- import org .springframework .ldap .core .DistinguishedName ;
43
39
import org .springframework .ldap .core .support .DefaultDirObjectFactory ;
44
40
import org .springframework .ldap .support .LdapUtils ;
45
41
import org .springframework .security .authentication .AccountExpiredException ;
50
46
import org .springframework .security .authentication .LockedException ;
51
47
import org .springframework .security .authentication .UsernamePasswordAuthenticationToken ;
52
48
import org .springframework .security .core .GrantedAuthority ;
53
- import org .springframework .security .core .authority .AuthorityUtils ;
54
- import org .springframework .security .core .authority .SimpleGrantedAuthority ;
55
49
import org .springframework .security .core .userdetails .UsernameNotFoundException ;
56
50
import org .springframework .security .ldap .SpringSecurityLdapTemplate ;
57
51
import org .springframework .security .ldap .authentication .AbstractLdapAuthenticationProvider ;
52
+ import org .springframework .security .ldap .userdetails .LdapAuthoritiesPopulator ;
58
53
import org .springframework .util .Assert ;
59
54
import org .springframework .util .StringUtils ;
60
55
72
67
* <p>
73
68
* The user authorities are obtained from the data contained in the {@code memberOf}
74
69
* attribute.
75
- *
70
+ * <p>
76
71
* <h3>Active Directory Sub-Error Codes</h3>
77
- *
72
+ * <p>
78
73
* When an authentication fails, resulting in a standard LDAP 49 error code, Active
79
74
* Directory also supplies its own sub-error codes within the error message. These will be
80
75
* used to provide additional log information on why an authentication has failed. Typical
90
85
* <li>773 - user must reset password</li>
91
86
* <li>775 - account locked</li>
92
87
* </ul>
93
- *
88
+ * <p>
94
89
* If you set the {@link #setConvertSubErrorCodesToExceptions(boolean)
95
90
* convertSubErrorCodesToExceptions} property to {@code true}, the codes will also be used
96
91
* to control the exception raised.
97
92
*
98
93
* @author Luke Taylor
99
94
* @author Rob Winch
95
+ * @author Roman Zabaluev
100
96
* @since 3.1
101
97
*/
102
98
public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLdapAuthenticationProvider {
@@ -135,27 +131,31 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLda
135
131
// Only used to allow tests to substitute a mock LdapContext
136
132
ContextFactory contextFactory = new ContextFactory ();
137
133
134
+ private LdapAuthoritiesPopulator authoritiesPopulator = new DefaultActiveDirectoryAuthoritiesPopulator ();
135
+
138
136
/**
139
- * @param domain the domain name (may be null or empty)
137
+ * @param domain the domain name (can be null or empty)
140
138
* @param url an LDAP url (or multiple URLs)
141
- * @param rootDn the root DN (may be null or empty)
139
+ * @param rootDn the root DN (can be null or empty)
142
140
*/
143
141
public ActiveDirectoryLdapAuthenticationProvider (String domain , String url , String rootDn ) {
144
142
Assert .isTrue (StringUtils .hasText (url ), "Url cannot be empty" );
145
143
this .domain = StringUtils .hasText (domain ) ? domain .toLowerCase () : null ;
146
144
this .url = url ;
147
145
this .rootDn = StringUtils .hasText (rootDn ) ? rootDn .toLowerCase () : null ;
146
+ this .setAuthoritiesPopulator (this .authoritiesPopulator );
148
147
}
149
148
150
149
/**
151
- * @param domain the domain name (may be null or empty)
150
+ * @param domain the domain name (can be null or empty)
152
151
* @param url an LDAP url (or multiple URLs)
153
152
*/
154
153
public ActiveDirectoryLdapAuthenticationProvider (String domain , String url ) {
155
154
Assert .isTrue (StringUtils .hasText (url ), "Url cannot be empty" );
156
155
this .domain = StringUtils .hasText (domain ) ? domain .toLowerCase () : null ;
157
156
this .url = url ;
158
157
this .rootDn = (this .domain != null ) ? rootDnFromDomain (this .domain ) : null ;
158
+ this .setAuthoritiesPopulator (this .authoritiesPopulator );
159
159
}
160
160
161
161
@ Override
@@ -179,26 +179,10 @@ protected DirContextOperations doAuthentication(UsernamePasswordAuthenticationTo
179
179
}
180
180
}
181
181
182
- /**
183
- * Creates the user authority list from the values of the {@code memberOf} attribute
184
- * obtained from the user's Active Directory entry.
185
- */
186
182
@ Override
187
183
protected Collection <? extends GrantedAuthority > loadUserAuthorities (DirContextOperations userData , String username ,
188
184
String password ) {
189
- String [] groups = userData .getStringAttributes ("memberOf" );
190
- if (groups == null ) {
191
- this .logger .debug ("No values for 'memberOf' attribute." );
192
- return AuthorityUtils .NO_AUTHORITIES ;
193
- }
194
- if (this .logger .isDebugEnabled ()) {
195
- this .logger .debug ("'memberOf' attribute values: " + Arrays .asList (groups ));
196
- }
197
- List <GrantedAuthority > authorities = new ArrayList <>(groups .length );
198
- for (String group : groups ) {
199
- authorities .add (new SimpleGrantedAuthority (new DistinguishedName (group ).removeLast ().getValue ()));
200
- }
201
- return authorities ;
185
+ return this .authoritiesPopulator .getGrantedAuthorities (userData , username );
202
186
}
203
187
204
188
private DirContext bindAsUser (String username , String password ) {
@@ -332,14 +316,14 @@ private String searchRootFromPrincipal(String bindPrincipal) {
332
316
+ "' does not contain the domain, and no domain has been configured" );
333
317
throw badCredentials ();
334
318
}
335
- return rootDnFromDomain (bindPrincipal .substring (atChar + 1 , bindPrincipal . length () ));
319
+ return rootDnFromDomain (bindPrincipal .substring (atChar + 1 ));
336
320
}
337
321
338
322
private String rootDnFromDomain (String domain ) {
339
323
String [] tokens = StringUtils .tokenizeToStringArray (domain , "." );
340
324
StringBuilder root = new StringBuilder ();
341
325
for (String token : tokens ) {
342
- if (root .length () > 0 ) {
326
+ if (! root .isEmpty () ) {
343
327
root .append (',' );
344
328
}
345
329
root .append ("dc=" ).append (token );
@@ -379,7 +363,6 @@ public void setConvertSubErrorCodesToExceptions(boolean convertSubErrorCodesToEx
379
363
* Defaults to: {@code (&(objectClass=user)(userPrincipalName={0}))}
380
364
* </p>
381
365
* @param searchFilter the filter string
382
- *
383
366
* @since 3.2.6
384
367
*/
385
368
public void setSearchFilter (String searchFilter ) {
@@ -397,6 +380,19 @@ public void setContextEnvironmentProperties(Map<String, Object> environment) {
397
380
this .contextEnvironmentProperties = new Hashtable <>(environment );
398
381
}
399
382
383
+ /**
384
+ * Set the strategy for obtaining the authorities for a given user after they've been
385
+ * authenticated. Consider adjusting this if you require a custom authorities mapping
386
+ * algorithm different from a default one. The default value is
387
+ * DefaultActiveDirectoryAuthoritiesPopulator.
388
+ * @param authoritiesPopulator authorities population strategy
389
+ * @since 6.3
390
+ */
391
+ public void setAuthoritiesPopulator (LdapAuthoritiesPopulator authoritiesPopulator ) {
392
+ Assert .notNull (authoritiesPopulator , "An LdapAuthoritiesPopulator must be supplied" );
393
+ this .authoritiesPopulator = authoritiesPopulator ;
394
+ }
395
+
400
396
static class ContextFactory {
401
397
402
398
DirContext createContext (Hashtable <?, ?> env ) throws NamingException {
0 commit comments