This should be included in the Spring Security documentation.
I culled various bits of information from google searches and came up with the following working solution using Spring 3.0.3, Spring LDAP 1.3.1 and Spring Security 3.0.5.
applicationContext.xml
Code:
<security:ldap-server id="adServer"
url="${security.authentication.ldap.server.url}"/>
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider ref="adAuthProvider"/>
<security:authentication-provider ref="anonymousAuthenticationProvider"/>
</security:authentication-manager>
<bean id="userContextMapper" class="com.blah.app.services.security.AdUserContextMapper"/>
<bean id="adAuthenticator" class="com.blah.app.services.security.AdAuthenticator">
<property name="contextFactory" ref="adServer" />
<property name="principalPrefix" value="" />
</bean>
<bean id="adAuthProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
<constructor-arg ref="adAuthenticator"/>
<property name="userDetailsContextMapper" ref="userContextMapper"/>
</bean>
AdAuthenticator.java
Code:
public class AdAuthenticator implements LdapAuthenticator
{
private DefaultSpringSecurityContextSource _contextFactory;
private String _principalPrefix = "";
public DirContextOperations authenticate(Authentication authentication)
{
// Grab the username and password out of the authentication object.
String principal = _principalPrefix + authentication.getName();
String password = "";
if(authentication.getCredentials() != null)
{
password = authentication.getCredentials().toString();
}
// If we have a valid username and password, try to authenticate.
if(!("".equals(principal.trim())) && !("".equals(password.trim())))
{
_contextFactory.setPassword(password);
_contextFactory.setUserDn(principal);
InitialLdapContext ldapContext = (InitialLdapContext) _contextFactory.getReadWriteContext();
// We need to pass the context back out, so that the auth provider
// can add it to the
// Authentication object.
DirContextOperations authAdapter = new DirContextAdapter();
authAdapter.addAttributeValue("ldapContext", ldapContext);
return authAdapter;
}
else
{
throw new BadCredentialsException("Blank username and/or password!");
}
}
public DefaultSpringSecurityContextSource getContextFactory()
{
return _contextFactory;
}
/**
* Set the context factory to use for generating a new LDAP context.
*
* @param contextFactory
*/
public void setContextFactory(DefaultSpringSecurityContextSource contextFactory)
{
_contextFactory = contextFactory;
}
public String getPrincipalPrefix()
{
return _principalPrefix;
}
/**
* Set the string to be prepended to all principal names prior to attempting authentication
* against the LDAP server. (For example, if the Active Directory wants the domain-name-plus
* backslash prepended, use this.)
*
* @param principalPrefix
*/
public void setPrincipalPrefix(String principalPrefix)
{
if(principalPrefix != null)
{
_principalPrefix = principalPrefix;
}
else
{
_principalPrefix = "";
}
}
}
AdUserContextMapper.java
Code:
public class AdUserContextMapper implements UserDetailsContextMapper
{
private static Log log = LogFactory.getLog(AdUserContextMapper.class);
@Override
public UserDetails mapUserFromContext(DirContextOperations p_dirContext, String p_userName, Collection<GrantedAuthority> p_authorities)
{
String userName;
String displayName = "";
String mail = "";
int index = 0;
if( (index = p_userName.indexOf("\\")) != -1 )
userName = p_userName.substring(index+1);
else if( (index = p_userName.indexOf("@") ) != -1 )
userName = p_userName.substring(0, index);
else
userName = p_userName;
InitialLdapContext ldapContext = (InitialLdapContext)p_dirContext.getObjectAttribute("ldapContext");
String returnedAtts[] ={ "displayName", "mail" };
SearchControls sc = new SearchControls();
sc.setReturningAttributes(returnedAtts);
sc.setSearchScope(SearchControls.SUBTREE_SCOPE);
try
{
NamingEnumeration<SearchResult> result = ldapContext.search("", "(&(objectclass=user)(sAMAccountName=" + userName + "))", sc);
if( result.hasMoreElements() )
{
SearchResult sr = result.nextElement();
Attributes attributes = sr.getAttributes();
Attribute displayNameAttr = attributes.get("displayName");
Attribute mailAttr = attributes.get("mail");
if( displayNameAttr != null )
displayName = (String)displayNameAttr.get();
if( mailAttr != null )
mail = (String)mailAttr.get();
}
}
catch(Throwable e)
{
throw new RuntimeException("Failed to retrieve user attributes from ldap server. See wrapped exception for details.", e);
}
finally
{
LdapUtils.closeContext(ldapContext);
}
Principal principal = new Principal();
principal.setUserName(p_userName);
principal.setUserFullName(displayName);
principal.setEmailAddress(mail);
// fetch your granted authorities and set them here if they are
// not based on AD group membership.
// principal.setGrantedAuthorities(new GrantedAuthority[]{"ROLE_USER"});
return principal;
}
@Override
public void mapUserToContext(UserDetails p_userDetails, DirContextAdapter p_dirContext)
{
}
}
I found AD would authenticate both <domain>\<username> and <username>@<dotted.domain.name.com>