Skip to content

Commit 98f0a21

Browse files
Haaroleanjzheaux
authored andcommitted
Add ActiveDirectoryLdapAuthenticationProvider#setAuthoritiesPopulator
Closes gh-4490
1 parent aafa4dd commit 98f0a21

File tree

2 files changed

+96
-33
lines changed

2 files changed

+96
-33
lines changed

ldap/src/main/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProvider.java

Lines changed: 29 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,12 +17,9 @@
1717
package org.springframework.security.ldap.authentication.ad;
1818

1919
import java.io.Serializable;
20-
import java.util.ArrayList;
21-
import java.util.Arrays;
2220
import java.util.Collection;
2321
import java.util.HashMap;
2422
import java.util.Hashtable;
25-
import java.util.List;
2623
import java.util.Map;
2724
import java.util.regex.Matcher;
2825
import java.util.regex.Pattern;
@@ -39,7 +36,6 @@
3936
import org.springframework.dao.IncorrectResultSizeDataAccessException;
4037
import org.springframework.ldap.CommunicationException;
4138
import org.springframework.ldap.core.DirContextOperations;
42-
import org.springframework.ldap.core.DistinguishedName;
4339
import org.springframework.ldap.core.support.DefaultDirObjectFactory;
4440
import org.springframework.ldap.support.LdapUtils;
4541
import org.springframework.security.authentication.AccountExpiredException;
@@ -50,11 +46,10 @@
5046
import org.springframework.security.authentication.LockedException;
5147
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
5248
import org.springframework.security.core.GrantedAuthority;
53-
import org.springframework.security.core.authority.AuthorityUtils;
54-
import org.springframework.security.core.authority.SimpleGrantedAuthority;
5549
import org.springframework.security.core.userdetails.UsernameNotFoundException;
5650
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
5751
import org.springframework.security.ldap.authentication.AbstractLdapAuthenticationProvider;
52+
import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator;
5853
import org.springframework.util.Assert;
5954
import org.springframework.util.StringUtils;
6055

@@ -72,9 +67,9 @@
7267
* <p>
7368
* The user authorities are obtained from the data contained in the {@code memberOf}
7469
* attribute.
75-
*
70+
* <p>
7671
* <h3>Active Directory Sub-Error Codes</h3>
77-
*
72+
* <p>
7873
* When an authentication fails, resulting in a standard LDAP 49 error code, Active
7974
* Directory also supplies its own sub-error codes within the error message. These will be
8075
* used to provide additional log information on why an authentication has failed. Typical
@@ -90,13 +85,14 @@
9085
* <li>773 - user must reset password</li>
9186
* <li>775 - account locked</li>
9287
* </ul>
93-
*
88+
* <p>
9489
* If you set the {@link #setConvertSubErrorCodesToExceptions(boolean)
9590
* convertSubErrorCodesToExceptions} property to {@code true}, the codes will also be used
9691
* to control the exception raised.
9792
*
9893
* @author Luke Taylor
9994
* @author Rob Winch
95+
* @author Roman Zabaluev
10096
* @since 3.1
10197
*/
10298
public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLdapAuthenticationProvider {
@@ -135,27 +131,31 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLda
135131
// Only used to allow tests to substitute a mock LdapContext
136132
ContextFactory contextFactory = new ContextFactory();
137133

134+
private LdapAuthoritiesPopulator authoritiesPopulator = new DefaultActiveDirectoryAuthoritiesPopulator();
135+
138136
/**
139-
* @param domain the domain name (may be null or empty)
137+
* @param domain the domain name (can be null or empty)
140138
* @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)
142140
*/
143141
public ActiveDirectoryLdapAuthenticationProvider(String domain, String url, String rootDn) {
144142
Assert.isTrue(StringUtils.hasText(url), "Url cannot be empty");
145143
this.domain = StringUtils.hasText(domain) ? domain.toLowerCase() : null;
146144
this.url = url;
147145
this.rootDn = StringUtils.hasText(rootDn) ? rootDn.toLowerCase() : null;
146+
this.setAuthoritiesPopulator(this.authoritiesPopulator);
148147
}
149148

150149
/**
151-
* @param domain the domain name (may be null or empty)
150+
* @param domain the domain name (can be null or empty)
152151
* @param url an LDAP url (or multiple URLs)
153152
*/
154153
public ActiveDirectoryLdapAuthenticationProvider(String domain, String url) {
155154
Assert.isTrue(StringUtils.hasText(url), "Url cannot be empty");
156155
this.domain = StringUtils.hasText(domain) ? domain.toLowerCase() : null;
157156
this.url = url;
158157
this.rootDn = (this.domain != null) ? rootDnFromDomain(this.domain) : null;
158+
this.setAuthoritiesPopulator(this.authoritiesPopulator);
159159
}
160160

161161
@Override
@@ -179,26 +179,10 @@ protected DirContextOperations doAuthentication(UsernamePasswordAuthenticationTo
179179
}
180180
}
181181

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-
*/
186182
@Override
187183
protected Collection<? extends GrantedAuthority> loadUserAuthorities(DirContextOperations userData, String username,
188184
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);
202186
}
203187

204188
private DirContext bindAsUser(String username, String password) {
@@ -332,14 +316,14 @@ private String searchRootFromPrincipal(String bindPrincipal) {
332316
+ "' does not contain the domain, and no domain has been configured");
333317
throw badCredentials();
334318
}
335-
return rootDnFromDomain(bindPrincipal.substring(atChar + 1, bindPrincipal.length()));
319+
return rootDnFromDomain(bindPrincipal.substring(atChar + 1));
336320
}
337321

338322
private String rootDnFromDomain(String domain) {
339323
String[] tokens = StringUtils.tokenizeToStringArray(domain, ".");
340324
StringBuilder root = new StringBuilder();
341325
for (String token : tokens) {
342-
if (root.length() > 0) {
326+
if (!root.isEmpty()) {
343327
root.append(',');
344328
}
345329
root.append("dc=").append(token);
@@ -379,7 +363,6 @@ public void setConvertSubErrorCodesToExceptions(boolean convertSubErrorCodesToEx
379363
* Defaults to: {@code (&(objectClass=user)(userPrincipalName={0}))}
380364
* </p>
381365
* @param searchFilter the filter string
382-
*
383366
* @since 3.2.6
384367
*/
385368
public void setSearchFilter(String searchFilter) {
@@ -397,6 +380,19 @@ public void setContextEnvironmentProperties(Map<String, Object> environment) {
397380
this.contextEnvironmentProperties = new Hashtable<>(environment);
398381
}
399382

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+
400396
static class ContextFactory {
401397

402398
DirContext createContext(Hashtable<?, ?> env) throws NamingException {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.ldap.authentication.ad;
18+
19+
import java.util.ArrayList;
20+
import java.util.Arrays;
21+
import java.util.Collection;
22+
import java.util.List;
23+
24+
import org.apache.commons.logging.Log;
25+
import org.apache.commons.logging.LogFactory;
26+
27+
import org.springframework.ldap.core.DirContextOperations;
28+
import org.springframework.ldap.core.DistinguishedName;
29+
import org.springframework.security.core.GrantedAuthority;
30+
import org.springframework.security.core.authority.AuthorityUtils;
31+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
32+
import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator;
33+
34+
/**
35+
* The default strategy for obtaining user role information from the active directory.
36+
* Creates the user authority list from the values of the {@code memberOf} attribute
37+
* obtained from the user's Active Directory entry.
38+
*
39+
* @author Luke Taylor
40+
* @author Roman Zabaluev
41+
*/
42+
public final class DefaultActiveDirectoryAuthoritiesPopulator implements LdapAuthoritiesPopulator {
43+
44+
private final Log logger = LogFactory.getLog(getClass());
45+
46+
@Override
47+
public Collection<? extends GrantedAuthority> getGrantedAuthorities(DirContextOperations userData,
48+
String username) {
49+
String[] groups = userData.getStringAttributes("memberOf");
50+
if (groups == null) {
51+
this.logger.debug("No values for 'memberOf' attribute.");
52+
return AuthorityUtils.NO_AUTHORITIES;
53+
}
54+
if (this.logger.isDebugEnabled()) {
55+
this.logger.debug("'memberOf' attribute values: " + Arrays.asList(groups));
56+
}
57+
58+
List<GrantedAuthority> authorities = new ArrayList<>(groups.length);
59+
60+
for (String group : groups) {
61+
authorities.add(new SimpleGrantedAuthority(new DistinguishedName(group).removeLast().getValue()));
62+
}
63+
64+
return authorities;
65+
}
66+
67+
}

0 commit comments

Comments
 (0)