2
0
Эх сурвалжийг харах

SEC-1181: Basic AuthenticationProvider for Active Directory.

Luke Taylor 14 жил өмнө
parent
commit
530f686149

+ 48 - 40
ldap/src/main/java/org/springframework/security/ldap/SpringSecurityLdapTemplate.java

@@ -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();
     }
 
     /**

+ 134 - 0
ldap/src/main/java/org/springframework/security/ldap/authentication/AbstractLdapAuthenticationProvider.java

@@ -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;
+    }
+}

+ 7 - 113
ldap/src/main/java/org/springframework/security/ldap/authentication/LdapAuthenticationProvider.java

@@ -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));
-    }
 }
 

+ 175 - 0
ldap/src/main/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProvider.java

@@ -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;
+    }
+
+}

+ 25 - 0
ldap/src/test/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProviderTests.java

@@ -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")));
+    }
+}