mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-06-25 05:22:16 +00:00
Add ActiveDirectoryLdapAuthenticationProvider#setAuthoritiesPopulator
Closes gh-4490
This commit is contained in:
parent
aafa4dd614
commit
98f0a2120e
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2016 the original author or authors.
|
* Copyright 2002-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -17,12 +17,9 @@
|
|||||||
package org.springframework.security.ldap.authentication.ad;
|
package org.springframework.security.ldap.authentication.ad;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Hashtable;
|
import java.util.Hashtable;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
@ -39,7 +36,6 @@ import org.springframework.core.log.LogMessage;
|
|||||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||||
import org.springframework.ldap.CommunicationException;
|
import org.springframework.ldap.CommunicationException;
|
||||||
import org.springframework.ldap.core.DirContextOperations;
|
import org.springframework.ldap.core.DirContextOperations;
|
||||||
import org.springframework.ldap.core.DistinguishedName;
|
|
||||||
import org.springframework.ldap.core.support.DefaultDirObjectFactory;
|
import org.springframework.ldap.core.support.DefaultDirObjectFactory;
|
||||||
import org.springframework.ldap.support.LdapUtils;
|
import org.springframework.ldap.support.LdapUtils;
|
||||||
import org.springframework.security.authentication.AccountExpiredException;
|
import org.springframework.security.authentication.AccountExpiredException;
|
||||||
@ -50,11 +46,10 @@ import org.springframework.security.authentication.InternalAuthenticationService
|
|||||||
import org.springframework.security.authentication.LockedException;
|
import org.springframework.security.authentication.LockedException;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.core.authority.AuthorityUtils;
|
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
|
||||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
|
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
|
||||||
import org.springframework.security.ldap.authentication.AbstractLdapAuthenticationProvider;
|
import org.springframework.security.ldap.authentication.AbstractLdapAuthenticationProvider;
|
||||||
|
import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
@ -72,9 +67,9 @@ import org.springframework.util.StringUtils;
|
|||||||
* <p>
|
* <p>
|
||||||
* The user authorities are obtained from the data contained in the {@code memberOf}
|
* The user authorities are obtained from the data contained in the {@code memberOf}
|
||||||
* attribute.
|
* attribute.
|
||||||
*
|
* <p>
|
||||||
* <h3>Active Directory Sub-Error Codes</h3>
|
* <h3>Active Directory Sub-Error Codes</h3>
|
||||||
*
|
* <p>
|
||||||
* When an authentication fails, resulting in a standard LDAP 49 error code, Active
|
* When an authentication fails, resulting in a standard LDAP 49 error code, Active
|
||||||
* Directory also supplies its own sub-error codes within the error message. These will be
|
* Directory also supplies its own sub-error codes within the error message. These will be
|
||||||
* used to provide additional log information on why an authentication has failed. Typical
|
* used to provide additional log information on why an authentication has failed. Typical
|
||||||
@ -90,13 +85,14 @@ import org.springframework.util.StringUtils;
|
|||||||
* <li>773 - user must reset password</li>
|
* <li>773 - user must reset password</li>
|
||||||
* <li>775 - account locked</li>
|
* <li>775 - account locked</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
* <p>
|
||||||
* If you set the {@link #setConvertSubErrorCodesToExceptions(boolean)
|
* If you set the {@link #setConvertSubErrorCodesToExceptions(boolean)
|
||||||
* convertSubErrorCodesToExceptions} property to {@code true}, the codes will also be used
|
* convertSubErrorCodesToExceptions} property to {@code true}, the codes will also be used
|
||||||
* to control the exception raised.
|
* to control the exception raised.
|
||||||
*
|
*
|
||||||
* @author Luke Taylor
|
* @author Luke Taylor
|
||||||
* @author Rob Winch
|
* @author Rob Winch
|
||||||
|
* @author Roman Zabaluev
|
||||||
* @since 3.1
|
* @since 3.1
|
||||||
*/
|
*/
|
||||||
public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLdapAuthenticationProvider {
|
public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLdapAuthenticationProvider {
|
||||||
@ -135,20 +131,23 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLda
|
|||||||
// Only used to allow tests to substitute a mock LdapContext
|
// Only used to allow tests to substitute a mock LdapContext
|
||||||
ContextFactory contextFactory = new ContextFactory();
|
ContextFactory contextFactory = new ContextFactory();
|
||||||
|
|
||||||
|
private LdapAuthoritiesPopulator authoritiesPopulator = new DefaultActiveDirectoryAuthoritiesPopulator();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param domain the domain name (may be null or empty)
|
* @param domain the domain name (can be null or empty)
|
||||||
* @param url an LDAP url (or multiple URLs)
|
* @param url an LDAP url (or multiple URLs)
|
||||||
* @param rootDn the root DN (may be null or empty)
|
* @param rootDn the root DN (can be null or empty)
|
||||||
*/
|
*/
|
||||||
public ActiveDirectoryLdapAuthenticationProvider(String domain, String url, String rootDn) {
|
public ActiveDirectoryLdapAuthenticationProvider(String domain, String url, String rootDn) {
|
||||||
Assert.isTrue(StringUtils.hasText(url), "Url cannot be empty");
|
Assert.isTrue(StringUtils.hasText(url), "Url cannot be empty");
|
||||||
this.domain = StringUtils.hasText(domain) ? domain.toLowerCase() : null;
|
this.domain = StringUtils.hasText(domain) ? domain.toLowerCase() : null;
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.rootDn = StringUtils.hasText(rootDn) ? rootDn.toLowerCase() : null;
|
this.rootDn = StringUtils.hasText(rootDn) ? rootDn.toLowerCase() : null;
|
||||||
|
this.setAuthoritiesPopulator(this.authoritiesPopulator);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param domain the domain name (may be null or empty)
|
* @param domain the domain name (can be null or empty)
|
||||||
* @param url an LDAP url (or multiple URLs)
|
* @param url an LDAP url (or multiple URLs)
|
||||||
*/
|
*/
|
||||||
public ActiveDirectoryLdapAuthenticationProvider(String domain, String url) {
|
public ActiveDirectoryLdapAuthenticationProvider(String domain, String url) {
|
||||||
@ -156,6 +155,7 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLda
|
|||||||
this.domain = StringUtils.hasText(domain) ? domain.toLowerCase() : null;
|
this.domain = StringUtils.hasText(domain) ? domain.toLowerCase() : null;
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.rootDn = (this.domain != null) ? rootDnFromDomain(this.domain) : null;
|
this.rootDn = (this.domain != null) ? rootDnFromDomain(this.domain) : null;
|
||||||
|
this.setAuthoritiesPopulator(this.authoritiesPopulator);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -179,26 +179,10 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLda
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the user authority list from the values of the {@code memberOf} attribute
|
|
||||||
* obtained from the user's Active Directory entry.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
protected Collection<? extends GrantedAuthority> loadUserAuthorities(DirContextOperations userData, String username,
|
protected Collection<? extends GrantedAuthority> loadUserAuthorities(DirContextOperations userData, String username,
|
||||||
String password) {
|
String password) {
|
||||||
String[] groups = userData.getStringAttributes("memberOf");
|
return this.authoritiesPopulator.getGrantedAuthorities(userData, username);
|
||||||
if (groups == null) {
|
|
||||||
this.logger.debug("No values for 'memberOf' attribute.");
|
|
||||||
return AuthorityUtils.NO_AUTHORITIES;
|
|
||||||
}
|
|
||||||
if (this.logger.isDebugEnabled()) {
|
|
||||||
this.logger.debug("'memberOf' attribute values: " + Arrays.asList(groups));
|
|
||||||
}
|
|
||||||
List<GrantedAuthority> authorities = new ArrayList<>(groups.length);
|
|
||||||
for (String group : groups) {
|
|
||||||
authorities.add(new SimpleGrantedAuthority(new DistinguishedName(group).removeLast().getValue()));
|
|
||||||
}
|
|
||||||
return authorities;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private DirContext bindAsUser(String username, String password) {
|
private DirContext bindAsUser(String username, String password) {
|
||||||
@ -332,14 +316,14 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLda
|
|||||||
+ "' does not contain the domain, and no domain has been configured");
|
+ "' does not contain the domain, and no domain has been configured");
|
||||||
throw badCredentials();
|
throw badCredentials();
|
||||||
}
|
}
|
||||||
return rootDnFromDomain(bindPrincipal.substring(atChar + 1, bindPrincipal.length()));
|
return rootDnFromDomain(bindPrincipal.substring(atChar + 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
private String rootDnFromDomain(String domain) {
|
private String rootDnFromDomain(String domain) {
|
||||||
String[] tokens = StringUtils.tokenizeToStringArray(domain, ".");
|
String[] tokens = StringUtils.tokenizeToStringArray(domain, ".");
|
||||||
StringBuilder root = new StringBuilder();
|
StringBuilder root = new StringBuilder();
|
||||||
for (String token : tokens) {
|
for (String token : tokens) {
|
||||||
if (root.length() > 0) {
|
if (!root.isEmpty()) {
|
||||||
root.append(',');
|
root.append(',');
|
||||||
}
|
}
|
||||||
root.append("dc=").append(token);
|
root.append("dc=").append(token);
|
||||||
@ -379,7 +363,6 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLda
|
|||||||
* Defaults to: {@code (&(objectClass=user)(userPrincipalName={0}))}
|
* Defaults to: {@code (&(objectClass=user)(userPrincipalName={0}))}
|
||||||
* </p>
|
* </p>
|
||||||
* @param searchFilter the filter string
|
* @param searchFilter the filter string
|
||||||
*
|
|
||||||
* @since 3.2.6
|
* @since 3.2.6
|
||||||
*/
|
*/
|
||||||
public void setSearchFilter(String searchFilter) {
|
public void setSearchFilter(String searchFilter) {
|
||||||
@ -397,6 +380,19 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLda
|
|||||||
this.contextEnvironmentProperties = new Hashtable<>(environment);
|
this.contextEnvironmentProperties = new Hashtable<>(environment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the strategy for obtaining the authorities for a given user after they've been
|
||||||
|
* authenticated. Consider adjusting this if you require a custom authorities mapping
|
||||||
|
* algorithm different from a default one. The default value is
|
||||||
|
* DefaultActiveDirectoryAuthoritiesPopulator.
|
||||||
|
* @param authoritiesPopulator authorities population strategy
|
||||||
|
* @since 6.3
|
||||||
|
*/
|
||||||
|
public void setAuthoritiesPopulator(LdapAuthoritiesPopulator authoritiesPopulator) {
|
||||||
|
Assert.notNull(authoritiesPopulator, "An LdapAuthoritiesPopulator must be supplied");
|
||||||
|
this.authoritiesPopulator = authoritiesPopulator;
|
||||||
|
}
|
||||||
|
|
||||||
static class ContextFactory {
|
static class ContextFactory {
|
||||||
|
|
||||||
DirContext createContext(Hashtable<?, ?> env) throws NamingException {
|
DirContext createContext(Hashtable<?, ?> env) throws NamingException {
|
||||||
|
@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2002-2024 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.security.ldap.authentication.ad;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import org.springframework.ldap.core.DirContextOperations;
|
||||||
|
import org.springframework.ldap.core.DistinguishedName;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.authority.AuthorityUtils;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default strategy for obtaining user role information from the active directory.
|
||||||
|
* Creates the user authority list from the values of the {@code memberOf} attribute
|
||||||
|
* obtained from the user's Active Directory entry.
|
||||||
|
*
|
||||||
|
* @author Luke Taylor
|
||||||
|
* @author Roman Zabaluev
|
||||||
|
*/
|
||||||
|
public final class DefaultActiveDirectoryAuthoritiesPopulator implements LdapAuthoritiesPopulator {
|
||||||
|
|
||||||
|
private final Log logger = LogFactory.getLog(getClass());
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<? extends GrantedAuthority> getGrantedAuthorities(DirContextOperations userData,
|
||||||
|
String username) {
|
||||||
|
String[] groups = userData.getStringAttributes("memberOf");
|
||||||
|
if (groups == null) {
|
||||||
|
this.logger.debug("No values for 'memberOf' attribute.");
|
||||||
|
return AuthorityUtils.NO_AUTHORITIES;
|
||||||
|
}
|
||||||
|
if (this.logger.isDebugEnabled()) {
|
||||||
|
this.logger.debug("'memberOf' attribute values: " + Arrays.asList(groups));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<GrantedAuthority> authorities = new ArrayList<>(groups.length);
|
||||||
|
|
||||||
|
for (String group : groups) {
|
||||||
|
authorities.add(new SimpleGrantedAuthority(new DistinguishedName(group).removeLast().getValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return authorities;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user