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 * <bean id="initialDirContextFactory" class="org.acegisecurity.providers.ldap.DefaultInitialDirContextFactory">
74 * <constructor-arg value="ldap://monkeymachine:389/dc=acegisecurity,dc=org"/>
75 * <property name="managerDn"><value>cn=manager,dc=acegisecurity,dc=org</value></property>
76 * <property name="managerPassword"><value>password</value></property>
77 * </bean>
78 *
79 * <bean id="ldapAuthProvider" class="org.acegisecurity.providers.ldap.LdapAuthenticationProvider">
80 * <constructor-arg>
81 * <bean class="org.acegisecurity.providers.ldap.authenticator.BindAuthenticator">
82 * <constructor-arg><ref local="initialDirContextFactory"/></constructor-arg>
83 * <property name="userDnPatterns"><list><value>uid={0},ou=people</value></list></property>
84 * </bean>
85 * </constructor-arg>
86 * <constructor-arg>
87 * <bean class="org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator">
88 * <constructor-arg><ref local="initialDirContextFactory"/></constructor-arg>
89 * <constructor-arg><value>ou=groups</value></constructor-arg>
90 * <property name="groupRoleAttribute"><value>ou</value></property>
91 * </bean>
92 * </constructor-arg>
93 * </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=<user-login-name>,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=<user's-DN>)</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