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

SEC-617: Make LDAPAuthenticationProvider a standalone class.

Luke Taylor 18 жил өмнө
parent
commit
22052115b6

+ 54 - 59
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.
- *
- * <p>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.
+ * <p>
+ * 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}.</p>
+ * 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.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.
- * <p>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.
+ * <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).</p>
+ * 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 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.
  *
- * <p>A custom implementation could obtain the roles from a completely different source, for example from a database.
- * </p>
+ * <p>
+ * A custom implementation could obtain the roles from a completely different source, for example from a database.
  *
  * <h3>Configuration</h3>
  *
@@ -94,21 +93,22 @@ import org.springframework.ldap.core.DirContextOperations;
  *      &lt;/constructor-arg>
  *    &lt;/bean></pre>
  *
- * <p>This would set up the provider to access an LDAP server with URL
+ * <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=&lt;user-login-name&gt;,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=&lt;user's-DN&gt;)</tt>. The role
- * name will be taken from the "ou" attribute of each match.</p>
+ * 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 unauthenitcated access, it might be possible to
+ * 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>.
- * </p>
+ *
  *
  * @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(userData, username);
 
-            GrantedAuthority[] extraAuthorities = getAuthoritiesPopulator().getGrantedAuthorities(user, username);
+            UserDetails user = userDetailsContextMapper.mapUserFromContext(userData, username, extraAuthorities);
 
-            return userDetailsContextMapper.mapUserFromContext(user, username, extraAuthorities);
+            return createSuccessfulAuthentication(userToken, user);
 
-        } catch (DataAccessException ldapAccessFailure) {
+        } 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 ==================================================================================================

+ 13 - 18
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());
     }