/*
* 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.springframework.security.ldap.authentication;
import java.util.Collection;
import org.springframework.ldap.NamingException;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.ldap.ppolicy.PasswordPolicyException;
import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator;
import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator;
import org.springframework.util.Assert;
/**
* An {@link org.springframework.security.authentication.AuthenticationProvider}
* implementation that authenticates against an LDAP server.
* <p>
* There are many ways in which an LDAP directory can be configured so this class
* delegates most of its responsibilities to two separate strategy interfaces,
* {@link LdapAuthenticator} and {@link LdapAuthoritiesPopulator}.
*
* <h3>LdapAuthenticator</h3> This interface is responsible for performing the user
* authentication and retrieving the user's information from the directory. Example
* implementations are
* {@link org.springframework.security.ldap.authentication.BindAuthenticator
* BindAuthenticator} which authenticates the user by "binding" as that user, and
* {@link org.springframework.security.ldap.authentication.PasswordComparisonAuthenticator
* PasswordComparisonAuthenticator} which compares the supplied password with the value
* stored in the directory, using an LDAP "compare" operation.
* <p>
* 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).
*
* <h3>LdapAuthoritiesPopulator</h3> Once the user has been authenticated, this interface
* is called to obtain the set of granted authorities for the user. The
* {@link 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.
*
* <p>
* A custom implementation could obtain the roles from a completely different source, for
* example from a database.
*
* <h3>Configuration</h3>
*
* A simple configuration might be as follows:
*
* <pre>
* <bean id="contextSource"
* class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
* <constructor-arg value="ldap://monkeymachine:389/dc=springframework,dc=org"/>
* <property name="userDn" value="cn=manager,dc=springframework,dc=org"/>
* <property name="password" value="password"/>
* </bean>
*
* <bean id="ldapAuthProvider"
* class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
* <constructor-arg>
* <bean class="org.springframework.security.ldap.authentication.BindAuthenticator">
* <constructor-arg ref="contextSource"/>
* <property name="userDnPatterns"><list><value>uid={0},ou=people</value></list></property>
* </bean>
* </constructor-arg>
* <constructor-arg>
* <bean class="org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator">
* <constructor-arg ref="contextSource"/>
* <constructor-arg value="ou=groups"/>
* <property name="groupRoleAttribute" value="ou"/>
* </bean>
* </constructor-arg>
* </bean>
* </pre>
*
* <p>
* This would set up the provider to access an LDAP server with URL
* <tt>ldap://monkeymachine:389/dc=springframework,dc=org</tt>. Authentication will be
* performed by attempting to bind with the DN
* <tt>uid=<user-login-name>,ou=people,dc=springframework,dc=org</tt>. After
* successful authentication, roles will be assigned to the user by searching under the DN
* <tt>ou=groups,dc=springframework,dc=org</tt> with the default filter
* <tt>(member=<user's-DN>)</tt>. The role name will be taken from the "ou"
* attribute of each match.
* <p>
* 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 unauthenticated access, it
* might be possible to authenticate as <i>any</i> user just by supplying an empty
* password. More information on the misuse of unauthenticated access can be found in
* <a href="http://www.ietf.org/internet-drafts/draft-ietf-ldapbis-authmeth-19.txt"> draft
* -ietf-ldapbis-authmeth-19.txt</a>.
*
*
* @author Luke Taylor
*
* @see BindAuthenticator
* @see DefaultLdapAuthoritiesPopulator
*/
public class LdapAuthenticationProvider extends AbstractLdapAuthenticationProvider {
// ~ Instance fields
// ================================================================================================
private LdapAuthenticator authenticator;
private LdapAuthoritiesPopulator authoritiesPopulator;
private boolean hideUserNotFoundExceptions = true;
// ~ Constructors
// ===================================================================================================
/**
* Create an instance with the supplied authenticator and authorities populator
* implementations.
*
* @param authenticator the authentication strategy (bind, password comparison, etc)
* to be used by this provider for authenticating users.
* @param authoritiesPopulator the strategy for obtaining the authorities for a given
* user after they've been authenticated.
*/
public LdapAuthenticationProvider(LdapAuthenticator authenticator,
LdapAuthoritiesPopulator authoritiesPopulator) {
this.setAuthenticator(authenticator);
this.setAuthoritiesPopulator(authoritiesPopulator);
}
/**
* Creates an instance with the supplied authenticator and a null authorities
* populator. In this case, the authorities must be mapped from the user context.
*
* @param authenticator the authenticator strategy.
*/
public LdapAuthenticationProvider(LdapAuthenticator authenticator) {
this.setAuthenticator(authenticator);
this.setAuthoritiesPopulator(new NullLdapAuthoritiesPopulator());
}
// ~ Methods
// ========================================================================================================
private void setAuthenticator(LdapAuthenticator authenticator) {
Assert.notNull(authenticator, "An LdapAuthenticator must be supplied");
this.authenticator = authenticator;
}
private LdapAuthenticator getAuthenticator() {
return this.authenticator;
}
private void setAuthoritiesPopulator(LdapAuthoritiesPopulator authoritiesPopulator) {
Assert.notNull(authoritiesPopulator,
"An LdapAuthoritiesPopulator must be supplied");
this.authoritiesPopulator = authoritiesPopulator;
}
protected LdapAuthoritiesPopulator getAuthoritiesPopulator() {
return this.authoritiesPopulator;
}
public void setHideUserNotFoundExceptions(boolean hideUserNotFoundExceptions) {
this.hideUserNotFoundExceptions = hideUserNotFoundExceptions;
}
@Override
protected DirContextOperations doAuthentication(
UsernamePasswordAuthenticationToken authentication) {
try {
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(this.messages.getMessage(
ppe.getStatus().getErrorCode(), ppe.getStatus().getDefaultMessage()));
}
catch (UsernameNotFoundException notFound) {
if (this.hideUserNotFoundExceptions) {
throw new BadCredentialsException(this.messages.getMessage(
"LdapAuthenticationProvider.badCredentials", "Bad credentials"));
}
else {
throw notFound;
}
}
catch (NamingException ldapAccessFailure) {
throw new InternalAuthenticationServiceException(
ldapAccessFailure.getMessage(), ldapAccessFailure);
}
}
@Override
protected Collection<? extends GrantedAuthority> loadUserAuthorities(
DirContextOperations userData, String username, String password) {
return getAuthoritiesPopulator().getGrantedAuthorities(userData, username);
}
}