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;
17  
18  import org.acegisecurity.AuthenticationException;
19  import org.acegisecurity.BadCredentialsException;
20  import org.acegisecurity.GrantedAuthority;
21  import org.acegisecurity.AuthenticationServiceException;
22  import org.acegisecurity.ldap.LdapDataAccessException;
23  
24  import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
25  import org.acegisecurity.providers.dao.AbstractUserDetailsAuthenticationProvider;
26  
27  import org.acegisecurity.userdetails.UserDetails;
28  import org.acegisecurity.userdetails.ldap.LdapUserDetails;
29  import org.acegisecurity.userdetails.ldap.LdapUserDetailsImpl;
30  
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  
34  import org.springframework.util.Assert;
35  import org.springframework.util.StringUtils;
36  import org.springframework.dao.DataAccessException;
37  
38  
39  /**
40   * An {@link org.acegisecurity.providers.AuthenticationProvider} implementation that provides integration with an
41   * LDAP server.
42   *
43   * <p>There are many ways in which an LDAP directory can be configured so this class delegates most of
44   * its responsibilites to two separate strategy interfaces, {@link LdapAuthenticator}
45   * and {@link LdapAuthoritiesPopulator}.</p>
46   *
47   * <h3>LdapAuthenticator</h3>
48   * This interface is responsible for performing the user authentication and retrieving
49   * the user's information from the directory. Example implementations are {@link
50   * org.acegisecurity.providers.ldap.authenticator.BindAuthenticator BindAuthenticator} which authenticates the user by
51   * "binding" as that user, and {@link org.acegisecurity.providers.ldap.authenticator.PasswordComparisonAuthenticator
52   * PasswordComparisonAuthenticator} which performs a comparison of the supplied password with the value stored in the
53   * directory, either by retrieving the password or performing an LDAP "compare" operation.
54   * <p>The task of retrieving the user attributes is delegated to the authenticator because the permissions on the
55   * attributes may depend on the type of authentication being used; for example, if binding as the user, it may be
56   * necessary to read them with the user's own permissions (using the same context used for the bind operation).</p>
57   *
58   * <h3>LdapAuthoritiesPopulator</h3>
59   * Once the user has been authenticated, this interface is called to obtain the set of granted authorities for the
60   * user.
61   * The
62   * {@link org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator DefaultLdapAuthoritiesPopulator}
63   * can be configured to obtain user role information from the user's attributes and/or to perform a search for
64   * "groups" that the user is a member of and map these to roles.
65   *
66   * <p>A custom implementation could obtain the roles from a completely different source, for example from a database.
67   * </p>
68   *
69   * <h3>Configuration</h3>
70   *
71   * A simple configuration might be as follows:
72   * <pre>
73   *    &lt;bean id="initialDirContextFactory" class="org.acegisecurity.providers.ldap.DefaultInitialDirContextFactory">
74   *      &lt;constructor-arg value="ldap://monkeymachine:389/dc=acegisecurity,dc=org"/>
75   *      &lt;property name="managerDn">&lt;value>cn=manager,dc=acegisecurity,dc=org&lt;/value>&lt;/property>
76   *      &lt;property name="managerPassword">&lt;value>password&lt;/value>&lt;/property>
77   *    &lt;/bean>
78   *
79   *    &lt;bean id="ldapAuthProvider" class="org.acegisecurity.providers.ldap.LdapAuthenticationProvider">
80   *      &lt;constructor-arg>
81   *        &lt;bean class="org.acegisecurity.providers.ldap.authenticator.BindAuthenticator">
82   *          &lt;constructor-arg>&lt;ref local="initialDirContextFactory"/>&lt;/constructor-arg>
83   *          &lt;property name="userDnPatterns">&lt;list>&lt;value>uid={0},ou=people&lt;/value>&lt;/list>&lt;/property>
84   *        &lt;/bean>
85   *      &lt;/constructor-arg>
86   *      &lt;constructor-arg>
87   *        &lt;bean class="org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator">
88   *          &lt;constructor-arg>&lt;ref local="initialDirContextFactory"/>&lt;/constructor-arg>
89   *          &lt;constructor-arg>&lt;value>ou=groups&lt;/value>&lt;/constructor-arg>
90   *          &lt;property name="groupRoleAttribute">&lt;value>ou&lt;/value>&lt;/property>
91   *        &lt;/bean>
92   *      &lt;/constructor-arg>
93   *    &lt;/bean></pre>
94   *
95   * <p>This would set up the provider to access an LDAP server with URL
96   * <tt>ldap://monkeymachine:389/dc=acegisecurity,dc=org</tt>. Authentication will be performed by attempting to bind
97   * with the DN <tt>uid=&lt;user-login-name&gt;,ou=people,dc=acegisecurity,dc=org</tt>. After successful
98   * authentication, roles will be assigned to the user by searching under the DN
99   * <tt>ou=groups,dc=acegisecurity,dc=org</tt> with the default filter <tt>(member=&lt;user's-DN&gt;)</tt>. The role
100  * name will be taken from the "ou" attribute of each match.</p>
101  * <p>
102  * The authenticate method will reject empty passwords outright. LDAP servers may allow an anonymous
103  * bind operation with an empty password, even if a DN is supplied. In practice this means that if
104  * the LDAP directory is configured to allow unauthenitcated access, it might be possible to
105  * authenticate as <i>any</i> user just by supplying an empty password.
106  * More information on the misuse of unauthenticated access can be found in
107  * <a href="http://www.ietf.org/internet-drafts/draft-ietf-ldapbis-authmeth-19.txt">
108  * draft-ietf-ldapbis-authmeth-19.txt</a>.
109  * </p>
110  *
111  * @author Luke Taylor
112  * @version $Id: LdapAuthenticationProvider.java 1995 2007-08-30 21:12:16Z luke_t $
113  *
114  * @see org.acegisecurity.providers.ldap.authenticator.BindAuthenticator
115  * @see org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator
116  */
117 public class LdapAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
118     //~ Static fields/initializers =====================================================================================
119 
120     private static final Log logger = LogFactory.getLog(LdapAuthenticationProvider.class);
121 
122     //~ Instance fields ================================================================================================
123 
124     private LdapAuthenticator authenticator;
125     private LdapAuthoritiesPopulator authoritiesPopulator;
126     private boolean includeDetailsObject = true;
127 
128     //~ Constructors ===================================================================================================
129 
130     /**
131      * Create an instance with the supplied authenticator and authorities populator implementations.
132      *
133      * @param authenticator the authentication strategy (bind, password comparison, etc)
134      *          to be used by this provider for authenticating users.
135      * @param authoritiesPopulator the strategy for obtaining the authorities for a given user after they've been
136      *          authenticated.
137      */
138     public LdapAuthenticationProvider(LdapAuthenticator authenticator, LdapAuthoritiesPopulator authoritiesPopulator) {
139         this.setAuthenticator(authenticator);
140         this.setAuthoritiesPopulator(authoritiesPopulator);
141     }
142 
143     /**
144      * Creates an instance with the supplied authenticator and a null authorities populator.
145      * In this case, the authorities must be mapped from the user context.
146      *
147      * @param authenticator the authenticator strategy.
148      */
149     public LdapAuthenticationProvider(LdapAuthenticator authenticator) {
150         this.setAuthenticator(authenticator);
151         this.setAuthoritiesPopulator(new NullAuthoritiesPopulator());
152     }    
153 
154     //~ Methods ========================================================================================================
155 
156     private void setAuthenticator(LdapAuthenticator authenticator) {
157         Assert.notNull(authenticator, "An LdapAuthenticator must be supplied");
158         this.authenticator = authenticator;
159     }
160 
161     private LdapAuthenticator getAuthenticator() {
162         return authenticator;
163     }
164 
165     private void setAuthoritiesPopulator(LdapAuthoritiesPopulator authoritiesPopulator) {
166         Assert.notNull(authoritiesPopulator, "An LdapAuthoritiesPopulator must be supplied");
167         this.authoritiesPopulator = authoritiesPopulator;
168     }
169 
170     protected LdapAuthoritiesPopulator getAuthoritiesPopulator() {
171         return authoritiesPopulator;
172     }
173 
174     protected void additionalAuthenticationChecks(UserDetails userDetails,
175                                                   UsernamePasswordAuthenticationToken authentication)
176         throws AuthenticationException {
177         if (!userDetails.getPassword().equals(authentication.getCredentials().toString())) {
178             throw new BadCredentialsException(messages.getMessage(
179                     "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"),
180                     includeDetailsObject ? userDetails : null);
181         }
182     }
183 
184     /**
185      * Creates the final <tt>UserDetails</tt> object that will be returned by the provider once the user has
186      * been authenticated.<p>The <tt>LdapAuthoritiesPopulator</tt> will be used to create the granted
187      * authorites for the user.</p>
188      *  <p>Can be overridden to customize the creation of the final UserDetails instance. The default will
189      * merge any additional authorities retrieved from the populator with the propertis of original <tt>ldapUser</tt>
190      * object and set the values of the username and password.</p>
191      *
192      * @param ldapUser The intermediate LdapUserDetails instance returned by the authenticator.
193      * @param username the username submitted to the provider
194      * @param password the password submitted to the provider
195      *
196      * @return The UserDetails for the successfully authenticated user.
197      */
198     protected UserDetails createUserDetails(LdapUserDetails ldapUser, String username, String password) {
199         LdapUserDetailsImpl.Essence user = new LdapUserDetailsImpl.Essence(ldapUser);
200         user.setUsername(username);
201         user.setPassword(password);
202 
203         GrantedAuthority[] extraAuthorities = getAuthoritiesPopulator().getGrantedAuthorities(ldapUser);
204 
205         for (int i = 0; i < extraAuthorities.length; i++) {
206             user.addAuthority(extraAuthorities[i]);
207         }
208 
209         return user.createUserDetails();
210     }
211 
212     protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
213         throws AuthenticationException {
214         if (!StringUtils.hasLength(username)) {
215             throw new BadCredentialsException(messages.getMessage("LdapAuthenticationProvider.emptyUsername",
216                     "Empty Username"));
217         }
218 
219         if (logger.isDebugEnabled()) {
220             logger.debug("Retrieving user " + username);
221         }
222 
223         String password = (String) authentication.getCredentials();
224         Assert.notNull(password, "Null password was supplied in authentication token");
225 
226         if (password.length() == 0) {
227             logger.debug("Rejecting empty password for user " + username);
228             throw new BadCredentialsException(messages.getMessage("LdapAuthenticationProvider.emptyPassword",
229                     "Empty Password"));
230         }
231 
232         try {
233             LdapUserDetails ldapUser = getAuthenticator().authenticate(username, password);
234 
235             return createUserDetails(ldapUser, username, password);
236 
237         } catch (DataAccessException ldapAccessFailure) {
238             throw new AuthenticationServiceException(ldapAccessFailure.getMessage(), ldapAccessFailure);
239         }
240     }
241 
242     public boolean isIncludeDetailsObject() {
243         return includeDetailsObject;
244     }
245 
246     public void setIncludeDetailsObject(boolean includeDetailsObject) {
247         this.includeDetailsObject = includeDetailsObject;
248     }
249 
250     //~ Inner Classes ==================================================================================================
251 
252     private static class NullAuthoritiesPopulator implements LdapAuthoritiesPopulator {
253         public GrantedAuthority[] getGrantedAuthorities(LdapUserDetails userDetails) throws LdapDataAccessException {
254             return new GrantedAuthority[0];
255         }
256     }
257 }
258