View Javadoc

1   /* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
2    *
3    * Licensed under the Apache License, Version 2.0 (the "License");
4    * you may not use this file except in compliance with the License.
5    * You may obtain a copy of the License at
6    *
7    *     http://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  
16  package org.acegisecurity.providers.ldap.authenticator;
17  
18  import org.acegisecurity.BadCredentialsException;
19  
20  import org.acegisecurity.ldap.InitialDirContextFactory;
21  import org.acegisecurity.ldap.LdapTemplate;
22  import org.acegisecurity.ldap.LdapUtils;
23  
24  import org.acegisecurity.providers.encoding.PasswordEncoder;
25  
26  import org.acegisecurity.userdetails.UsernameNotFoundException;
27  import org.acegisecurity.userdetails.ldap.LdapUserDetails;
28  import org.acegisecurity.userdetails.ldap.LdapUserDetailsImpl;
29  
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  
33  import org.springframework.util.Assert;
34  
35  import java.util.Iterator;
36  
37  
38  /**
39   * An {@link org.acegisecurity.providers.ldap.LdapAuthenticator LdapAuthenticator} which compares the login
40   * password with the value stored in the directory.
41   *
42   * <p>
43   * This can be achieved either by retrieving the password attribute for the user and comparing it locally,
44   * or by peforming an LDAP "compare" operation. If the password attribute (default "userPassword") is found in the
45   * retrieved attributes it will be compared locally. If not, the remote comparison will be attempted.
46   * </p>
47   * <p>
48   * If passwords are stored in digest form in the repository, then a suitable {@link PasswordEncoder}
49   * implementation must be supplied. By default, passwords are encoded using the {@link LdapShaPasswordEncoder}.
50   * </p>
51   *
52   * @author Luke Taylor
53   * @version $Id: PasswordComparisonAuthenticator.java 1784 2007-02-24 21:00:24Z luke_t $
54   */
55  public final class PasswordComparisonAuthenticator extends AbstractLdapAuthenticator {
56      //~ Static fields/initializers =====================================================================================
57  
58      private static final Log logger = LogFactory.getLog(PasswordComparisonAuthenticator.class);
59  
60      //~ Instance fields ================================================================================================
61  
62      private PasswordEncoder passwordEncoder = new LdapShaPasswordEncoder();
63      private String passwordAttributeName = "userPassword";
64  
65      //~ Constructors ===================================================================================================
66  
67      public PasswordComparisonAuthenticator(InitialDirContextFactory initialDirContextFactory) {
68          super(initialDirContextFactory);
69      }
70  
71      //~ Methods ========================================================================================================
72  
73      public LdapUserDetails authenticate(final String username, final String password) {
74          // locate the user and check the password
75          LdapUserDetails user = null;
76  
77          Iterator dns = getUserDns(username).iterator();
78  
79          LdapTemplate ldapTemplate = new LdapTemplate(getInitialDirContextFactory());
80  
81          while (dns.hasNext() && (user == null)) {
82              final String userDn = (String) dns.next();
83  
84              if (ldapTemplate.nameExists(userDn)) {
85                  LdapUserDetailsImpl.Essence userEssence = (LdapUserDetailsImpl.Essence)
86                          ldapTemplate.retrieveEntry(userDn, getUserDetailsMapper(), getUserAttributes());
87                  userEssence.setUsername(username);
88                  user = userEssence.createUserDetails();
89              }
90          }
91  
92          if ((user == null) && (getUserSearch() != null)) {
93              user = getUserSearch().searchForUser(username);
94          }
95  
96          if (user == null) {
97              throw new UsernameNotFoundException(username);
98          }
99  
100         String retrievedPassword = user.getPassword();
101 
102         if (retrievedPassword != null) {
103             if (!verifyPassword(password, retrievedPassword)) {
104                 throw new BadCredentialsException(messages.getMessage(
105                         "PasswordComparisonAuthenticator.badCredentials", "Bad credentials"));
106             }
107 
108             return user;
109         }
110 
111         if (logger.isDebugEnabled()) {
112             logger.debug("Password attribute wasn't retrieved for user '" + username + "' using mapper "
113                 + getUserDetailsMapper() + ". Performing LDAP compare of password attribute '" + passwordAttributeName
114                 + "'");
115         }
116 
117         String encodedPassword = passwordEncoder.encodePassword(password, null);
118         byte[] passwordBytes = LdapUtils.getUtf8Bytes(encodedPassword);
119 
120         if (!ldapTemplate.compare(user.getDn(), passwordAttributeName, passwordBytes)) {
121             throw new BadCredentialsException(messages.getMessage("PasswordComparisonAuthenticator.badCredentials",
122                     "Bad credentials"));
123         }
124 
125         return user;
126     }
127 
128     public void setPasswordAttributeName(String passwordAttribute) {
129         Assert.hasLength(passwordAttribute, "passwordAttributeName must not be empty or null");
130         this.passwordAttributeName = passwordAttribute;
131     }
132 
133     public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
134         Assert.notNull(passwordEncoder, "passwordEncoder must not be null.");
135         this.passwordEncoder = passwordEncoder;
136     }
137 
138     /**
139      * Allows the use of both simple and hashed passwords in the directory.
140      *
141      * @param password the password supplied by the user
142      * @param ldapPassword the (possibly hashed) password (from the directory)
143      *
144      * @return true if they match
145      */
146     private boolean verifyPassword(String password, String ldapPassword) {
147         if (ldapPassword.equals(password)) {
148             return true;
149         }
150 
151         if (passwordEncoder.isPasswordValid(ldapPassword, password, null)) {
152             return true;
153         }
154 
155         return false;
156     }
157 }