SEC-1181: Basic AuthenticationProvider for Active Directory.
This commit is contained in:
parent
4dc5d7d16e
commit
530f686149
|
@ -195,47 +195,55 @@ public class SpringSecurityLdapTemplate extends LdapTemplate {
|
|||
|
||||
return (DirContextOperations) executeReadOnly(new ContextExecutor() {
|
||||
public Object executeWithContext(DirContext ctx) throws NamingException {
|
||||
final DistinguishedName ctxBaseDn = new DistinguishedName(ctx.getNameInNamespace());
|
||||
final DistinguishedName searchBaseDn = new DistinguishedName(base);
|
||||
final NamingEnumeration<SearchResult> resultsEnum = ctx.search(searchBaseDn, filter, params, searchControls);
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Searching for entry under DN '" + ctxBaseDn
|
||||
+ "', base = '" + searchBaseDn + "', filter = '" + filter + "'");
|
||||
}
|
||||
|
||||
Set<DirContextOperations> results = new HashSet<DirContextOperations>();
|
||||
try {
|
||||
while (resultsEnum.hasMore()) {
|
||||
SearchResult searchResult = resultsEnum.next();
|
||||
// Work out the DN of the matched entry
|
||||
DistinguishedName dn = new DistinguishedName(new CompositeName(searchResult.getName()));
|
||||
|
||||
if (base.length() > 0) {
|
||||
dn.prepend(searchBaseDn);
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Found DN: " + dn);
|
||||
}
|
||||
results.add(new DirContextAdapter(searchResult.getAttributes(), dn, ctxBaseDn));
|
||||
}
|
||||
} catch (PartialResultException e) {
|
||||
LdapUtils.closeEnumeration(resultsEnum);
|
||||
logger.info("Ignoring PartialResultException");
|
||||
}
|
||||
|
||||
if (results.size() == 0) {
|
||||
throw new IncorrectResultSizeDataAccessException(1, 0);
|
||||
}
|
||||
|
||||
if (results.size() > 1) {
|
||||
throw new IncorrectResultSizeDataAccessException(1, results.size());
|
||||
}
|
||||
|
||||
return results.toArray()[0];
|
||||
return searchForSingleEntryInternal(ctx, searchControls, base, filter, params);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method extracted to avoid code duplication in AD search.
|
||||
*/
|
||||
public static DirContextOperations searchForSingleEntryInternal(DirContext ctx, SearchControls searchControls,
|
||||
String base, String filter, Object[] params) throws NamingException {
|
||||
final DistinguishedName ctxBaseDn = new DistinguishedName(ctx.getNameInNamespace());
|
||||
final DistinguishedName searchBaseDn = new DistinguishedName(base);
|
||||
final NamingEnumeration<SearchResult> resultsEnum = ctx.search(searchBaseDn, filter, params, searchControls);
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Searching for entry under DN '" + ctxBaseDn
|
||||
+ "', base = '" + searchBaseDn + "', filter = '" + filter + "'");
|
||||
}
|
||||
|
||||
Set<DirContextOperations> results = new HashSet<DirContextOperations>();
|
||||
try {
|
||||
while (resultsEnum.hasMore()) {
|
||||
SearchResult searchResult = resultsEnum.next();
|
||||
// Work out the DN of the matched entry
|
||||
DistinguishedName dn = new DistinguishedName(new CompositeName(searchResult.getName()));
|
||||
|
||||
if (base.length() > 0) {
|
||||
dn.prepend(searchBaseDn);
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Found DN: " + dn);
|
||||
}
|
||||
results.add(new DirContextAdapter(searchResult.getAttributes(), dn, ctxBaseDn));
|
||||
}
|
||||
} catch (PartialResultException e) {
|
||||
LdapUtils.closeEnumeration(resultsEnum);
|
||||
logger.info("Ignoring PartialResultException");
|
||||
}
|
||||
|
||||
if (results.size() == 0) {
|
||||
throw new IncorrectResultSizeDataAccessException(1, 0);
|
||||
}
|
||||
|
||||
if (results.size() > 1) {
|
||||
throw new IncorrectResultSizeDataAccessException(1, results.size());
|
||||
}
|
||||
|
||||
return results.iterator().next();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
package org.springframework.security.ldap.authentication;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.MessageSourceAware;
|
||||
import org.springframework.context.support.MessageSourceAccessor;
|
||||
import org.springframework.ldap.core.DirContextOperations;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.SpringSecurityMessageSource;
|
||||
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
||||
import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.ldap.userdetails.LdapUserDetailsMapper;
|
||||
import org.springframework.security.ldap.userdetails.UserDetailsContextMapper;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Base class for the standard {@code LdapAuthenticationProvider} and the
|
||||
* {@code ActiveDirectoryLdapAuthenticationProvider}.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @since 3.1
|
||||
*/
|
||||
public abstract class AbstractLdapAuthenticationProvider implements AuthenticationProvider, MessageSourceAware {
|
||||
protected final Log logger = LogFactory.getLog(LdapAuthenticationProvider.class);
|
||||
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
|
||||
private boolean useAuthenticationRequestCredentials = true;
|
||||
private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();
|
||||
protected UserDetailsContextMapper userDetailsContextMapper = new LdapUserDetailsMapper();
|
||||
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
|
||||
messages.getMessage("LdapAuthenticationProvider.onlySupports",
|
||||
"Only UsernamePasswordAuthenticationToken is supported"));
|
||||
|
||||
final UsernamePasswordAuthenticationToken userToken = (UsernamePasswordAuthenticationToken)authentication;
|
||||
|
||||
String username = userToken.getName();
|
||||
String password = (String) authentication.getCredentials();
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Processing authentication request for user: " + username);
|
||||
}
|
||||
|
||||
if (!StringUtils.hasLength(username)) {
|
||||
throw new BadCredentialsException(messages.getMessage("LdapAuthenticationProvider.emptyUsername",
|
||||
"Empty Username"));
|
||||
}
|
||||
|
||||
Assert.notNull(password, "Null password was supplied in authentication token");
|
||||
|
||||
DirContextOperations userData = doAuthentication(userToken);
|
||||
|
||||
UserDetails user = userDetailsContextMapper.mapUserFromContext(userData, authentication.getName(),
|
||||
loadUserAuthorities(userData, authentication.getName(), (String)authentication.getCredentials()));
|
||||
|
||||
return createSuccessfulAuthentication(userToken, user);
|
||||
}
|
||||
|
||||
protected abstract DirContextOperations doAuthentication(UsernamePasswordAuthenticationToken auth);
|
||||
|
||||
protected abstract Collection<? extends GrantedAuthority> loadUserAuthorities(DirContextOperations userData, String username, String password);
|
||||
|
||||
/**
|
||||
* Creates the final {@code Authentication} object which will be returned from the {@code authenticate} method.
|
||||
*
|
||||
* @param authentication the original authentication request token
|
||||
* @param user the <tt>UserDetails</tt> instance returned by the configured <tt>UserDetailsContextMapper</tt>.
|
||||
* @return the Authentication object for the fully authenticated user.
|
||||
*/
|
||||
protected Authentication createSuccessfulAuthentication(UsernamePasswordAuthenticationToken authentication,
|
||||
UserDetails user) {
|
||||
Object password = useAuthenticationRequestCredentials ? authentication.getCredentials() : user.getPassword();
|
||||
|
||||
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(user, password,
|
||||
authoritiesMapper.mapAuthorities(user.getAuthorities()));
|
||||
result.setDetails(authentication.getDetails());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean supports(Class<?> authentication) {
|
||||
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the supplied password will be used as the credentials in the successful authentication
|
||||
* token. If set to false, then the password will be obtained from the UserDetails object
|
||||
* created by the configured {@code UserDetailsContextMapper}.
|
||||
* Often it will not be possible to read the password from the directory, so defaults to true.
|
||||
*
|
||||
* @param useAuthenticationRequestCredentials
|
||||
*/
|
||||
public void setUseAuthenticationRequestCredentials(boolean useAuthenticationRequestCredentials) {
|
||||
this.useAuthenticationRequestCredentials = useAuthenticationRequestCredentials;
|
||||
}
|
||||
|
||||
public void setMessageSource(MessageSource messageSource) {
|
||||
this.messages = new MessageSourceAccessor(messageSource);
|
||||
}
|
||||
|
||||
public void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) {
|
||||
this.authoritiesMapper = authoritiesMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows a custom strategy to be used for creating the <tt>UserDetails</tt> which will be stored as the principal
|
||||
* in the <tt>Authentication</tt> returned by the
|
||||
* {@link #createSuccessfulAuthentication(org.springframework.security.authentication.UsernamePasswordAuthenticationToken, org.springframework.security.core.userdetails.UserDetails)} method.
|
||||
*
|
||||
* @param userDetailsContextMapper the strategy instance. If not set, defaults to a simple
|
||||
* <tt>LdapUserDetailsMapper</tt>.
|
||||
*/
|
||||
public void setUserDetailsContextMapper(UserDetailsContextMapper userDetailsContextMapper) {
|
||||
Assert.notNull(userDetailsContextMapper, "UserDetailsContextMapper must not be null");
|
||||
this.userDetailsContextMapper = userDetailsContextMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides access to the injected {@code UserDetailsContextMapper} strategy for use by subclasses.
|
||||
*/
|
||||
protected UserDetailsContextMapper getUserDetailsContextMapper() {
|
||||
return userDetailsContextMapper;
|
||||
}
|
||||
}
|
|
@ -15,27 +15,13 @@
|
|||
|
||||
package org.springframework.security.ldap.authentication;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.MessageSourceAware;
|
||||
import org.springframework.context.support.MessageSourceAccessor;
|
||||
import org.springframework.ldap.NamingException;
|
||||
import org.springframework.ldap.core.DirContextOperations;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.LockedException;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.SpringSecurityMessageSource;
|
||||
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
||||
import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.ldap.ppolicy.PasswordPolicyException;
|
||||
import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator;
|
||||
|
@ -43,7 +29,8 @@ import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator;
|
|||
import org.springframework.security.ldap.userdetails.LdapUserDetailsMapper;
|
||||
import org.springframework.security.ldap.userdetails.UserDetailsContextMapper;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -128,21 +115,12 @@ import org.springframework.util.StringUtils;
|
|||
* @see BindAuthenticator
|
||||
* @see DefaultLdapAuthoritiesPopulator
|
||||
*/
|
||||
public class LdapAuthenticationProvider implements AuthenticationProvider, MessageSourceAware {
|
||||
//~ Static fields/initializers =====================================================================================
|
||||
|
||||
private static final Log logger = LogFactory.getLog(LdapAuthenticationProvider.class);
|
||||
|
||||
public class LdapAuthenticationProvider extends AbstractLdapAuthenticationProvider {
|
||||
//~ Instance fields ================================================================================================
|
||||
|
||||
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
|
||||
|
||||
private LdapAuthenticator authenticator;
|
||||
private LdapAuthoritiesPopulator authoritiesPopulator;
|
||||
private UserDetailsContextMapper userDetailsContextMapper = new LdapUserDetailsMapper();
|
||||
private boolean useAuthenticationRequestCredentials = true;
|
||||
private boolean hideUserNotFoundExceptions = true;
|
||||
private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();
|
||||
|
||||
//~ Constructors ===================================================================================================
|
||||
|
||||
|
@ -190,78 +168,14 @@ public class LdapAuthenticationProvider implements AuthenticationProvider, Messa
|
|||
return authoritiesPopulator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows a custom strategy to be used for creating the <tt>UserDetails</tt> which will be stored as the principal
|
||||
* in the <tt>Authentication</tt> returned by the
|
||||
* {@link #createSuccessfulAuthentication(UsernamePasswordAuthenticationToken, UserDetails)} method.
|
||||
*
|
||||
* @param userDetailsContextMapper the strategy instance. If not set, defaults to a simple
|
||||
* <tt>LdapUserDetailsMapper</tt>.
|
||||
*/
|
||||
public void setUserDetailsContextMapper(UserDetailsContextMapper userDetailsContextMapper) {
|
||||
Assert.notNull(userDetailsContextMapper, "UserDetailsContextMapper must not be null");
|
||||
this.userDetailsContextMapper = userDetailsContextMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides access to the injected {@code UserDetailsContextMapper} strategy for use by subclasses.
|
||||
*/
|
||||
protected UserDetailsContextMapper getUserDetailsContextMapper() {
|
||||
return userDetailsContextMapper;
|
||||
}
|
||||
|
||||
public void setHideUserNotFoundExceptions(boolean hideUserNotFoundExceptions) {
|
||||
this.hideUserNotFoundExceptions = hideUserNotFoundExceptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the supplied password will be used as the credentials in the successful authentication
|
||||
* token. If set to false, then the password will be obtained from the UserDetails object
|
||||
* created by the configured {@code UserDetailsContextMapper}.
|
||||
* Often it will not be possible to read the password from the directory, so defaults to true.
|
||||
*
|
||||
* @param useAuthenticationRequestCredentials
|
||||
*/
|
||||
public void setUseAuthenticationRequestCredentials(boolean useAuthenticationRequestCredentials) {
|
||||
this.useAuthenticationRequestCredentials = useAuthenticationRequestCredentials;
|
||||
}
|
||||
|
||||
public void setMessageSource(MessageSource messageSource) {
|
||||
this.messages = new MessageSourceAccessor(messageSource);
|
||||
}
|
||||
|
||||
public void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) {
|
||||
this.authoritiesMapper = authoritiesMapper;
|
||||
}
|
||||
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
|
||||
messages.getMessage("LdapAuthenticationProvider.onlySupports",
|
||||
"Only UsernamePasswordAuthenticationToken is supported"));
|
||||
|
||||
final UsernamePasswordAuthenticationToken userToken = (UsernamePasswordAuthenticationToken)authentication;
|
||||
|
||||
String username = userToken.getName();
|
||||
String password = (String) authentication.getCredentials();
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Processing authentication request for user: " + username);
|
||||
}
|
||||
|
||||
if (!StringUtils.hasLength(username)) {
|
||||
throw new BadCredentialsException(messages.getMessage("LdapAuthenticationProvider.emptyUsername",
|
||||
"Empty Username"));
|
||||
}
|
||||
|
||||
Assert.notNull(password, "Null password was supplied in authentication token");
|
||||
|
||||
@Override
|
||||
protected DirContextOperations doAuthentication(UsernamePasswordAuthenticationToken authentication) {
|
||||
try {
|
||||
DirContextOperations userData = getAuthenticator().authenticate(authentication);
|
||||
|
||||
UserDetails user = userDetailsContextMapper.mapUserFromContext(userData, username,
|
||||
loadUserAuthorities(userData, username, password));
|
||||
|
||||
return createSuccessfulAuthentication(userToken, user);
|
||||
return getAuthenticator().authenticate(authentication);
|
||||
} catch (PasswordPolicyException ppe) {
|
||||
// The only reason a ppolicy exception can occur during a bind is that the account is locked.
|
||||
throw new LockedException(messages.getMessage(ppe.getStatus().getErrorCode(),
|
||||
|
@ -278,30 +192,10 @@ public class LdapAuthenticationProvider implements AuthenticationProvider, Messa
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<? extends GrantedAuthority> loadUserAuthorities(DirContextOperations userData, String username, String password) {
|
||||
return getAuthoritiesPopulator().getGrantedAuthorities(userData, username);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the final {@code Authentication} object which will be returned from the {@code authenticate} method.
|
||||
*
|
||||
* @param authentication the original authentication request token
|
||||
* @param user the <tt>UserDetails</tt> instance returned by the configured <tt>UserDetailsContextMapper</tt>.
|
||||
* @return the Authentication object for the fully authenticated user.
|
||||
*/
|
||||
protected Authentication createSuccessfulAuthentication(UsernamePasswordAuthenticationToken authentication,
|
||||
UserDetails user) {
|
||||
Object password = useAuthenticationRequestCredentials ? authentication.getCredentials() : user.getPassword();
|
||||
|
||||
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(user, password,
|
||||
authoritiesMapper.mapAuthorities(user.getAuthorities()));
|
||||
result.setDetails(authentication.getDetails());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean supports(Class<?> authentication) {
|
||||
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,175 @@
|
|||
package org.springframework.security.ldap.authentication.ad;
|
||||
|
||||
import org.springframework.ldap.core.DirContextOperations;
|
||||
import org.springframework.ldap.core.DistinguishedName;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
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.LdapUtils;
|
||||
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
|
||||
import org.springframework.security.ldap.authentication.AbstractLdapAuthenticationProvider;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.naming.Context;
|
||||
import javax.naming.NamingException;
|
||||
import javax.naming.directory.SearchControls;
|
||||
import javax.naming.ldap.InitialLdapContext;
|
||||
import javax.naming.ldap.LdapContext;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Specialized LDAP authentication provider which uses Active Directory configuration conventions.
|
||||
* <p>
|
||||
* It will authenticate using the Active Directory {@code userPrincipalName} (in the form {@code username@domain}).
|
||||
* If the {@code usernameIncludesDomain} property is set to {@code true}, it is assumed that the user types in the
|
||||
* full value, including the domain. Otherwise (the default), the {@code userPrincipalName} will be built from the
|
||||
* username supplied in the authentication request.
|
||||
* <p>
|
||||
* The user authorities are obtained from the data contained in the {@code memberOf} attribute.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @since 3.1
|
||||
*/
|
||||
public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLdapAuthenticationProvider {
|
||||
private final String domain;
|
||||
private final String rootDn;
|
||||
private final String url;
|
||||
private boolean usernameIncludesDomain = false;
|
||||
|
||||
/**
|
||||
* @param domain the domain for which authentication should take place
|
||||
*/
|
||||
// public ActiveDirectoryLdapAuthenticationProvider(String domain) {
|
||||
// this (domain, null);
|
||||
// }
|
||||
|
||||
/**
|
||||
* @param domain the domain name
|
||||
* @param url an LDAP url (or multiple URLs)
|
||||
*/
|
||||
public ActiveDirectoryLdapAuthenticationProvider(String domain, String url) {
|
||||
Assert.isTrue(StringUtils.hasText(domain) || StringUtils.hasText(url), "Domain and url cannot both be empty");
|
||||
this.domain = StringUtils.hasText(domain) ? domain : null;
|
||||
this.url = StringUtils.hasText(url) ? url : null;
|
||||
rootDn = this.domain == null ? null : rootDnFromDomain(this.domain);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DirContextOperations doAuthentication(UsernamePasswordAuthenticationToken auth) {
|
||||
String username = auth.getName();
|
||||
String password = (String)auth.getCredentials();
|
||||
|
||||
LdapContext ctx = bindAsUser(username, password);
|
||||
|
||||
try {
|
||||
return searchForUser(ctx, username);
|
||||
|
||||
} catch (NamingException e) {
|
||||
logger.error("Failed to locate directory entry for authentication user: " + username, e);
|
||||
throw authenticationFailure();
|
||||
} finally {
|
||||
LdapUtils.closeContext(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the user authority list from the values of the {@code memberOf} attribute obtained from the user's
|
||||
* Active Directory entry.
|
||||
*/
|
||||
@Override
|
||||
protected Collection<? extends GrantedAuthority> loadUserAuthorities(DirContextOperations userData, String username, String password) {
|
||||
String[] groups = userData.getStringAttributes("memberOf");
|
||||
|
||||
if (groups == null) {
|
||||
logger.debug("No values for 'memberOf' attribute.");
|
||||
|
||||
return AuthorityUtils.NO_AUTHORITIES;
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("'memberOf' attribute values: " + Arrays.asList(groups));
|
||||
}
|
||||
|
||||
ArrayList<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(groups.length);
|
||||
|
||||
for (String group : groups) {
|
||||
authorities.add(new SimpleGrantedAuthority(new DistinguishedName(group).removeLast().getValue()));
|
||||
}
|
||||
|
||||
return authorities;
|
||||
}
|
||||
|
||||
private LdapContext bindAsUser(String username, String password) {
|
||||
// TODO. add DNS lookup based on domain
|
||||
final String bindUrl = url;
|
||||
|
||||
Hashtable<String,String> env = new Hashtable<String,String>();
|
||||
env.put(Context.SECURITY_AUTHENTICATION, "simple");
|
||||
env.put(Context.SECURITY_PRINCIPAL, createBindPrincipal(username));
|
||||
env.put(Context.PROVIDER_URL, bindUrl);
|
||||
env.put(Context.SECURITY_CREDENTIALS, password);
|
||||
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
|
||||
|
||||
try {
|
||||
return new InitialLdapContext(env, null);
|
||||
} catch (NamingException e) {
|
||||
logger.debug("Authentication failed", e);
|
||||
throw authenticationFailure();
|
||||
}
|
||||
}
|
||||
|
||||
private BadCredentialsException authenticationFailure() {
|
||||
return new BadCredentialsException(messages.getMessage(
|
||||
"LdapAuthenticationProvider.badCredentials", "Bad credentials"));
|
||||
}
|
||||
|
||||
private DirContextOperations searchForUser(LdapContext ctx, String username) throws NamingException {
|
||||
SearchControls searchCtls = new SearchControls();
|
||||
searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
|
||||
|
||||
String searchFilter = "(&(objectClass=user)(userPrincipalName={0}))";
|
||||
|
||||
final String bindPrincipal = createBindPrincipal(username);
|
||||
|
||||
String searchRoot = rootDn != null ? rootDn : searchRootFromPrincipal(bindPrincipal);
|
||||
|
||||
return SpringSecurityLdapTemplate.searchForSingleEntryInternal(ctx, searchCtls, searchRoot, searchFilter,
|
||||
new Object[]{bindPrincipal});
|
||||
}
|
||||
|
||||
private String searchRootFromPrincipal(String bindPrincipal) {
|
||||
return rootDnFromDomain(bindPrincipal.substring(bindPrincipal.lastIndexOf('@') + 1, bindPrincipal.length()));
|
||||
}
|
||||
|
||||
private String rootDnFromDomain(String domain) {
|
||||
String[] tokens = StringUtils.tokenizeToStringArray(domain, ".");
|
||||
StringBuilder root = new StringBuilder();
|
||||
|
||||
for (String token : tokens) {
|
||||
if (root.length() > 0) {
|
||||
root.append(',');
|
||||
}
|
||||
root.append("dc=").append(token);
|
||||
}
|
||||
|
||||
return root.toString();
|
||||
}
|
||||
|
||||
private String createBindPrincipal(String username) {
|
||||
if (usernameIncludesDomain || domain == null) {
|
||||
return username;
|
||||
}
|
||||
|
||||
return username + "@" + domain;
|
||||
}
|
||||
|
||||
public void setUsernameIncludesDomain(boolean usernameIncludesDomain) {
|
||||
Assert.isTrue(domain != null || usernameIncludesDomain,
|
||||
"If the domain name is not included in the username, a domain must be set in the constructor");
|
||||
this.usernameIncludesDomain = usernameIncludesDomain;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package org.springframework.security.ldap.authentication.ad;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.*;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
|
||||
/**
|
||||
* @author Luke Taylor
|
||||
*/
|
||||
public class ActiveDirectoryLdapAuthenticationProviderTests {
|
||||
|
||||
@Test
|
||||
public void simpleAuthenticationWithIsSucessful() throws Exception {
|
||||
ActiveDirectoryLdapAuthenticationProvider provider =
|
||||
new ActiveDirectoryLdapAuthenticationProvider(null, "ldap://192.168.1.200/");
|
||||
|
||||
Authentication result = provider.authenticate(new UsernamePasswordAuthenticationToken("luke@fenetres.monkeymachine.eu","p!ssw0rd"));
|
||||
|
||||
assertEquals(1, result.getAuthorities().size());
|
||||
assertTrue(result.getAuthorities().contains(new SimpleGrantedAuthority("blah")));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue