org.acegisecurity.providers.ldap.LdapAuthenticationProvider Maven / Gradle / Ivy
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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
*
* http://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.acegisecurity.providers.ldap;
import org.acegisecurity.AuthenticationException;
import org.acegisecurity.BadCredentialsException;
import org.acegisecurity.GrantedAuthority;
import org.acegisecurity.AuthenticationServiceException;
import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import org.acegisecurity.providers.dao.AbstractUserDetailsAuthenticationProvider;
import org.acegisecurity.userdetails.UserDetails;
import org.acegisecurity.userdetails.ldap.LdapUserDetails;
import org.acegisecurity.userdetails.ldap.LdapUserDetailsImpl;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.dao.DataAccessException;
/**
* An {@link org.acegisecurity.providers.AuthenticationProvider} implementation that provides integration with an
* LDAP server.
*
* There are many ways in which an LDAP directory can be configured so this class delegates most of
* its responsibilites to two separate strategy interfaces, {@link LdapAuthenticator}
* and {@link LdapAuthoritiesPopulator}.
*
* LdapAuthenticator
* This interface is responsible for performing the user authentication and retrieving
* the user's information from the directory. Example implementations are {@link
* org.acegisecurity.providers.ldap.authenticator.BindAuthenticator BindAuthenticator} which authenticates the user by
* "binding" as that user, and {@link org.acegisecurity.providers.ldap.authenticator.PasswordComparisonAuthenticator
* PasswordComparisonAuthenticator} which performs a comparison of the supplied password with the value stored in the
* directory, either by retrieving the password or performing an LDAP "compare" operation.
* The task of retrieving the user attributes is delegated to the authenticator because the permissions on the
* attributes may depend on the type of authentication being used; for example, if binding as the user, it may be
* necessary to read them with the user's own permissions (using the same context used for the bind operation).
*
* LdapAuthoritiesPopulator
* Once the user has been authenticated, this interface is called to obtain the set of granted authorities for the
* user.
* The
* {@link org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator DefaultLdapAuthoritiesPopulator}
* can be configured to obtain user role information from the user's attributes and/or to perform a search for
* "groups" that the user is a member of and map these to roles.
*
* A custom implementation could obtain the roles from a completely different source, for example from a database.
*
*
* Configuration
A simple configuration might be as follows:
*
* <bean id="initialDirContextFactory" class="org.acegisecurity.providers.ldap.DefaultInitialDirContextFactory">
* <constructor-arg value="ldap://monkeymachine:389/dc=acegisecurity,dc=org"/>
* <property name="managerDn"><value>cn=manager,dc=acegisecurity,dc=org</value></property>
* <property name="managerPassword"><value>password</value></property>
* </bean>
*
* <bean id="ldapAuthProvider" class="org.acegisecurity.providers.ldap.LdapAuthenticationProvider">
* <constructor-arg>
* <bean class="org.acegisecurity.providers.ldap.authenticator.BindAuthenticator">
* <constructor-arg><ref local="initialDirContextFactory"/></constructor-arg>
* <property name="userDnPatterns"><list><value>uid={0},ou=people</value></list></property>
* </bean>
* </constructor-arg>
* <constructor-arg>
* <bean class="org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator">
* <constructor-arg><ref local="initialDirContextFactory"/></constructor-arg>
* <constructor-arg><value>ou=groups</value></constructor-arg>
* <property name="groupRoleAttribute"><value>ou</value></property>
* </bean>
* </constructor-arg>
* </bean>
*
* This would set up the provider to access an LDAP server with URL
* ldap://monkeymachine:389/dc=acegisecurity,dc=org. Authentication will be performed by attempting to bind
* with the DN uid=<user-login-name>,ou=people,dc=acegisecurity,dc=org. After successful
* authentication, roles will be assigned to the user by searching under the DN
* ou=groups,dc=acegisecurity,dc=org with the default filter (member=<user's-DN>). The role
* name will be taken from the "ou" attribute of each match.
*
* The authenticate method will reject empty passwords outright. LDAP servers may allow an anonymous
* bind operation with an empty password, even if a DN is supplied. In practice this means that if
* the LDAP directory is configured to allow unauthenitcated access, it might be possible to
* authenticate as any user just by supplying an empty password.
* More information on the misuse of unauthenticated access can be found in
*
* draft-ietf-ldapbis-authmeth-19.txt.
*
*
* @author Luke Taylor
* @version $Id: LdapAuthenticationProvider.java 1585 2006-07-20 13:15:55Z carlossg $
*
* @see org.acegisecurity.providers.ldap.authenticator.BindAuthenticator
* @see org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator
*/
public class LdapAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
//~ Static fields/initializers =====================================================================================
private static final Log logger = LogFactory.getLog(LdapAuthenticationProvider.class);
//~ Instance fields ================================================================================================
private LdapAuthenticator authenticator;
private LdapAuthoritiesPopulator authoritiesPopulator;
//~ Constructors ===================================================================================================
/**
* Create an initialized instance to the values passed as arguments
*
* @param authenticator
* @param authoritiesPopulator
*/
public LdapAuthenticationProvider(LdapAuthenticator authenticator, LdapAuthoritiesPopulator authoritiesPopulator) {
this.setAuthenticator(authenticator);
this.setAuthoritiesPopulator(authoritiesPopulator);
}
//~ Methods ========================================================================================================
private void setAuthenticator(LdapAuthenticator authenticator) {
Assert.notNull(authenticator, "An LdapAuthenticator must be supplied");
this.authenticator = authenticator;
}
private LdapAuthenticator getAuthenticator() {
return authenticator;
}
private void setAuthoritiesPopulator(LdapAuthoritiesPopulator authoritiesPopulator) {
Assert.notNull(authoritiesPopulator, "An LdapAuthoritiesPopulator must be supplied");
this.authoritiesPopulator = authoritiesPopulator;
}
protected LdapAuthoritiesPopulator getAuthoritiesPopulator() {
return authoritiesPopulator;
}
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
if (!userDetails.getPassword().equals(authentication.getCredentials().toString())) {
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"), userDetails);
}
}
/**
* Creates the final UserDetails object that will be returned by the provider once the user has
* been authenticated.The LdapAuthoritiesPopulator will be used to create the granted
* authorites for the user.
* Can be overridden to customize the creation of the final UserDetails instance. The default will
* merge any additional authorities retrieved from the populator with the propertis of original ldapUser
* object and set the values of the username and password.
*
* @param ldapUser The intermediate LdapUserDetails instance returned by the authenticator.
* @param username the username submitted to the provider
* @param password the password submitted to the provider
*
* @return The UserDetails for the successfully authenticated user.
*/
protected UserDetails createUserDetails(LdapUserDetails ldapUser, String username, String password) {
LdapUserDetailsImpl.Essence user = new LdapUserDetailsImpl.Essence(ldapUser);
user.setUsername(username);
user.setPassword(password);
GrantedAuthority[] extraAuthorities = getAuthoritiesPopulator().getGrantedAuthorities(ldapUser);
for (int i = 0; i < extraAuthorities.length; i++) {
user.addAuthority(extraAuthorities[i]);
}
return user.createUserDetails();
}
protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
if (!StringUtils.hasLength(username)) {
throw new BadCredentialsException(messages.getMessage("LdapAuthenticationProvider.emptyUsername",
"Empty Username"));
}
if (logger.isDebugEnabled()) {
logger.debug("Retrieving user " + username);
}
String password = (String) authentication.getCredentials();
Assert.notNull(password, "Null password was supplied in authentication token");
if (password.length() == 0) {
logger.debug("Rejecting empty password for user " + username);
throw new BadCredentialsException(messages.getMessage("LdapAuthenticationProvider.emptyPassword",
"Empty Password"));
}
try {
LdapUserDetails ldapUser = getAuthenticator().authenticate(username, password);
return createUserDetails(ldapUser, username, password);
} catch (DataAccessException ldapAccessFailure) {
throw new AuthenticationServiceException(ldapAccessFailure.getMessage(), ldapAccessFailure);
}
}
}