From 22052115b6a96a4f9a4cd4cedb6c8eb12b3568a0 Mon Sep 17 00:00:00 2001 From: Luke Taylor Date: Wed, 5 Dec 2007 14:39:46 +0000 Subject: [PATCH] SEC-617: Make LDAPAuthenticationProvider a standalone class. --- .../ldap/LdapAuthenticationProvider.java | 113 +++++++++--------- .../ldap/LdapAuthenticationProviderTests.java | 31 ++--- 2 files changed, 67 insertions(+), 77 deletions(-) diff --git a/core/src/main/java/org/springframework/security/providers/ldap/LdapAuthenticationProvider.java b/core/src/main/java/org/springframework/security/providers/ldap/LdapAuthenticationProvider.java index f3b935e429..d49c2af204 100644 --- a/core/src/main/java/org/springframework/security/providers/ldap/LdapAuthenticationProvider.java +++ b/core/src/main/java/org/springframework/security/providers/ldap/LdapAuthenticationProvider.java @@ -15,58 +15,57 @@ package org.springframework.security.providers.ldap; +import org.springframework.security.Authentication; import org.springframework.security.AuthenticationException; +import org.springframework.security.AuthenticationServiceException; import org.springframework.security.BadCredentialsException; import org.springframework.security.GrantedAuthority; -import org.springframework.security.AuthenticationServiceException; - +import org.springframework.security.SpringSecurityMessageSource; +import org.springframework.security.providers.AuthenticationProvider; import org.springframework.security.providers.UsernamePasswordAuthenticationToken; -import org.springframework.security.providers.ldap.authenticator.LdapShaPasswordEncoder; -import org.springframework.security.providers.encoding.PasswordEncoder; -import org.springframework.security.providers.dao.AbstractUserDetailsAuthenticationProvider; - import org.springframework.security.userdetails.UserDetails; -import org.springframework.security.userdetails.ldap.UserDetailsContextMapper; import org.springframework.security.userdetails.ldap.LdapUserDetailsMapper; +import org.springframework.security.userdetails.ldap.UserDetailsContextMapper; +import org.springframework.context.support.MessageSourceAccessor; +import org.springframework.ldap.NamingException; +import org.springframework.ldap.core.DirContextOperations; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; 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; -import org.springframework.ldap.core.DirContextOperations; - /** - * An {@link org.springframework.security.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 + * An {@link org.springframework.security.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}.

+ * 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.springframework.security.providers.ldap.authenticator.BindAuthenticator BindAuthenticator} which authenticates the user by - * "binding" as that user, and {@link org.springframework.security.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 + * org.springframework.security.providers.ldap.authenticator.BindAuthenticator BindAuthenticator} which authenticates + * the user by "binding" as that user, and + * {@link org.springframework.security.providers.ldap.authenticator.PasswordComparisonAuthenticator PasswordComparisonAuthenticator} + * which compares the supplied password with the value stored in the directory, using 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).

+ * 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.springframework.security.providers.ldap.populator.DefaultLdapAuthoritiesPopulator DefaultLdapAuthoritiesPopulator} + * The {@link org.springframework.security.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. - *

+ *

+ * A custom implementation could obtain the roles from a completely different source, for example from a database. * *

Configuration

* @@ -94,21 +93,22 @@ import org.springframework.ldap.core.DirContextOperations; * </constructor-arg> * </bean> * - *

This would set up the provider to access an LDAP server with URL + *

+ * This would set up the provider to access an LDAP server with URL * ldap://monkeymachine:389/dc=springframework,dc=org. Authentication will be performed by attempting to bind * with the DN uid=<user-login-name>,ou=people,dc=springframework,dc=org. After successful * authentication, roles will be assigned to the user by searching under the DN * ou=groups,dc=springframework,dc=org with the default filter (member=<user's-DN>). The role - * name will be taken from the "ou" attribute of each match.

+ * 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 + * the LDAP directory is configured to allow unauthenticated 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$ @@ -116,18 +116,18 @@ import org.springframework.ldap.core.DirContextOperations; * @see org.springframework.security.providers.ldap.authenticator.BindAuthenticator * @see org.springframework.security.providers.ldap.populator.DefaultLdapAuthoritiesPopulator */ -public class LdapAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider { +public class LdapAuthenticationProvider implements AuthenticationProvider { //~ Static fields/initializers ===================================================================================== private static final Log logger = LogFactory.getLog(LdapAuthenticationProvider.class); //~ Instance fields ================================================================================================ + protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); + private LdapAuthenticator authenticator; private LdapAuthoritiesPopulator authoritiesPopulator; private UserDetailsContextMapper userDetailsContextMapper = new LdapUserDetailsMapper(); - private PasswordEncoder passwordEncoder = new LdapShaPasswordEncoder(); - private boolean includeDetailsObject = true; //~ Constructors =================================================================================================== @@ -184,29 +184,20 @@ public class LdapAuthenticationProvider extends AbstractUserDetailsAuthenticatio return userDetailsContextMapper; } - protected void additionalAuthenticationChecks(UserDetails userDetails, - UsernamePasswordAuthenticationToken authentication) - throws AuthenticationException { - String presentedPassword = authentication.getCredentials() == null ? "" : - authentication.getCredentials().toString(); - if (!passwordEncoder.isPasswordValid(userDetails.getPassword(), presentedPassword, null)) { - throw new BadCredentialsException(messages.getMessage( - "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"), - includeDetailsObject ? userDetails : null); - } - } + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, + messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", + "Only UsernamePasswordAuthenticationToken is supported")); + + UsernamePasswordAuthenticationToken userToken = (UsernamePasswordAuthenticationToken)authentication; + + String username = userToken.getName(); - 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"); @@ -217,23 +208,27 @@ public class LdapAuthenticationProvider extends AbstractUserDetailsAuthenticatio } try { - DirContextOperations user = getAuthenticator().authenticate(authentication); + DirContextOperations userData = getAuthenticator().authenticate(authentication); - GrantedAuthority[] extraAuthorities = getAuthoritiesPopulator().getGrantedAuthorities(user, username); + GrantedAuthority[] extraAuthorities = getAuthoritiesPopulator().getGrantedAuthorities(userData, username); - return userDetailsContextMapper.mapUserFromContext(user, username, extraAuthorities); + UserDetails user = userDetailsContextMapper.mapUserFromContext(userData, username, extraAuthorities); - } catch (DataAccessException ldapAccessFailure) { + return createSuccessfulAuthentication(userToken, user); + + } catch (NamingException ldapAccessFailure) { throw new AuthenticationServiceException(ldapAccessFailure.getMessage(), ldapAccessFailure); } } - public boolean isIncludeDetailsObject() { - return includeDetailsObject; + protected Authentication createSuccessfulAuthentication(UsernamePasswordAuthenticationToken authentication, + UserDetails user) { + + return new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities()); } - public void setIncludeDetailsObject(boolean includeDetailsObject) { - this.includeDetailsObject = includeDetailsObject; + public boolean supports(Class authentication) { + return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication)); } //~ Inner Classes ================================================================================================== diff --git a/core/src/test/java/org/springframework/security/providers/ldap/LdapAuthenticationProviderTests.java b/core/src/test/java/org/springframework/security/providers/ldap/LdapAuthenticationProviderTests.java index f8227e19ef..814b206888 100644 --- a/core/src/test/java/org/springframework/security/providers/ldap/LdapAuthenticationProviderTests.java +++ b/core/src/test/java/org/springframework/security/providers/ldap/LdapAuthenticationProviderTests.java @@ -51,22 +51,19 @@ public class LdapAuthenticationProviderTests extends TestCase { //~ Methods ======================================================================================================== - public void testDifferentCacheValueCausesException() { + + public void testSupportsUsernamePasswordAuthenticationToken() { LdapAuthenticationProvider ldapProvider = new LdapAuthenticationProvider(new MockAuthenticator(), new MockAuthoritiesPopulator()); - UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken("ben", "benspassword"); - // User is authenticated here - UserDetails user = ldapProvider.retrieveUser("ben", authRequest); - // Assume the user details object is cached... + assertTrue(ldapProvider.supports(UsernamePasswordAuthenticationToken.class)); + } - // And a subsequent authentication request comes in on the cached data - authRequest = new UsernamePasswordAuthenticationToken("ben", "wrongpassword"); + public void testDefaultMapperIsSet() { + LdapAuthenticationProvider ldapProvider = new LdapAuthenticationProvider(new MockAuthenticator(), + new MockAuthoritiesPopulator()); - try { - ldapProvider.additionalAuthenticationChecks(user, authRequest); - fail("Expected BadCredentialsException should have failed with wrong password"); - } catch (BadCredentialsException expected) {} + assertTrue(ldapProvider.getUserDetailsContextMapper() instanceof LdapUserDetailsMapper); } public void testEmptyOrNullUserNameThrowsException() { @@ -74,12 +71,12 @@ public class LdapAuthenticationProviderTests extends TestCase { new MockAuthoritiesPopulator()); try { - ldapProvider.retrieveUser("", new UsernamePasswordAuthenticationToken("bob", "bobspassword")); + ldapProvider.authenticate(new UsernamePasswordAuthenticationToken(null, "password")); fail("Expected BadCredentialsException for empty username"); } catch (BadCredentialsException expected) {} try { - ldapProvider.retrieveUser(null, new UsernamePasswordAuthenticationToken("bob", "bobspassword")); + ldapProvider.authenticate(new UsernamePasswordAuthenticationToken("", "bobspassword")); fail("Expected BadCredentialsException for null username"); } catch (BadCredentialsException expected) {} } @@ -87,7 +84,7 @@ public class LdapAuthenticationProviderTests extends TestCase { public void testEmptyPasswordIsRejected() { LdapAuthenticationProvider ldapProvider = new LdapAuthenticationProvider(new MockAuthenticator()); try { - ldapProvider.retrieveUser("jen", new UsernamePasswordAuthenticationToken("jen", "")); + ldapProvider.authenticate(new UsernamePasswordAuthenticationToken("jen", "")); fail("Expected BadCredentialsException for empty password"); } catch (BadCredentialsException expected) {} } @@ -102,7 +99,7 @@ public class LdapAuthenticationProviderTests extends TestCase { assertNotNull(ldapProvider.getAuthoritiesPopulator()); UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken("ben", "benspassword"); - UserDetails user = ldapProvider.retrieveUser("ben", authRequest); + UserDetails user = (UserDetails) ldapProvider.authenticate(authRequest).getPrincipal(); assertEquals(2, user.getAuthorities().length); assertEquals("{SHA}nFCebWjxfaLbHHG1Qk5UU4trbvQ=", user.getPassword()); assertEquals("ben", user.getUsername()); @@ -113,8 +110,6 @@ public class LdapAuthenticationProviderTests extends TestCase { assertTrue(authorities.contains("ROLE_FROM_ENTRY")); assertTrue(authorities.contains("ROLE_FROM_POPULATOR")); - - ldapProvider.additionalAuthenticationChecks(user, authRequest); } public void testUseWithNullAuthoritiesPopulatorReturnsCorrectRole() { @@ -123,7 +118,7 @@ public class LdapAuthenticationProviderTests extends TestCase { userMapper.setRoleAttributes(new String[] {"ou"}); ldapProvider.setUserDetailsContextMapper(userMapper); UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken("ben", "benspassword"); - UserDetails user = ldapProvider.retrieveUser("ben", authRequest); + UserDetails user = (UserDetails) ldapProvider.authenticate(authRequest).getPrincipal(); assertEquals(1, user.getAuthorities().length); assertEquals("ROLE_FROM_ENTRY", user.getAuthorities()[0].getAuthority()); }