001: /* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
002: *
003: * Licensed under the Apache License, Version 2.0 (the "License");
004: * you may not use this file except in compliance with the License.
005: * You may obtain a copy of the License at
006: *
007: * http://www.apache.org/licenses/LICENSE-2.0
008: *
009: * Unless required by applicable law or agreed to in writing, software
010: * distributed under the License is distributed on an "AS IS" BASIS,
011: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: * See the License for the specific language governing permissions and
013: * limitations under the License.
014: */
015:
016: package org.acegisecurity.providers.ldap;
017:
018: import org.acegisecurity.AuthenticationException;
019: import org.acegisecurity.BadCredentialsException;
020: import org.acegisecurity.GrantedAuthority;
021: import org.acegisecurity.AuthenticationServiceException;
022: import org.acegisecurity.ldap.LdapDataAccessException;
023:
024: import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
025: import org.acegisecurity.providers.dao.AbstractUserDetailsAuthenticationProvider;
026:
027: import org.acegisecurity.userdetails.UserDetails;
028: import org.acegisecurity.userdetails.ldap.LdapUserDetails;
029: import org.acegisecurity.userdetails.ldap.LdapUserDetailsImpl;
030:
031: import org.apache.commons.logging.Log;
032: import org.apache.commons.logging.LogFactory;
033:
034: import org.springframework.util.Assert;
035: import org.springframework.util.StringUtils;
036: import org.springframework.dao.DataAccessException;
037:
038: /**
039: * An {@link org.acegisecurity.providers.AuthenticationProvider} implementation that provides integration with an
040: * LDAP server.
041: *
042: * <p>There are many ways in which an LDAP directory can be configured so this class delegates most of
043: * its responsibilites to two separate strategy interfaces, {@link LdapAuthenticator}
044: * and {@link LdapAuthoritiesPopulator}.</p>
045: *
046: * <h3>LdapAuthenticator</h3>
047: * This interface is responsible for performing the user authentication and retrieving
048: * the user's information from the directory. Example implementations are {@link
049: * org.acegisecurity.providers.ldap.authenticator.BindAuthenticator BindAuthenticator} which authenticates the user by
050: * "binding" as that user, and {@link org.acegisecurity.providers.ldap.authenticator.PasswordComparisonAuthenticator
051: * PasswordComparisonAuthenticator} which performs a comparison of the supplied password with the value stored in the
052: * directory, either by retrieving the password or performing an LDAP "compare" operation.
053: * <p>The task of retrieving the user attributes is delegated to the authenticator because the permissions on the
054: * attributes may depend on the type of authentication being used; for example, if binding as the user, it may be
055: * necessary to read them with the user's own permissions (using the same context used for the bind operation).</p>
056: *
057: * <h3>LdapAuthoritiesPopulator</h3>
058: * Once the user has been authenticated, this interface is called to obtain the set of granted authorities for the
059: * user.
060: * The
061: * {@link org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator DefaultLdapAuthoritiesPopulator}
062: * can be configured to obtain user role information from the user's attributes and/or to perform a search for
063: * "groups" that the user is a member of and map these to roles.
064: *
065: * <p>A custom implementation could obtain the roles from a completely different source, for example from a database.
066: * </p>
067: *
068: * <h3>Configuration</h3>
069: *
070: * A simple configuration might be as follows:
071: * <pre>
072: * <bean id="initialDirContextFactory" class="org.acegisecurity.providers.ldap.DefaultInitialDirContextFactory">
073: * <constructor-arg value="ldap://monkeymachine:389/dc=acegisecurity,dc=org"/>
074: * <property name="managerDn"><value>cn=manager,dc=acegisecurity,dc=org</value></property>
075: * <property name="managerPassword"><value>password</value></property>
076: * </bean>
077: *
078: * <bean id="ldapAuthProvider" class="org.acegisecurity.providers.ldap.LdapAuthenticationProvider">
079: * <constructor-arg>
080: * <bean class="org.acegisecurity.providers.ldap.authenticator.BindAuthenticator">
081: * <constructor-arg><ref local="initialDirContextFactory"/></constructor-arg>
082: * <property name="userDnPatterns"><list><value>uid={0},ou=people</value></list></property>
083: * </bean>
084: * </constructor-arg>
085: * <constructor-arg>
086: * <bean class="org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator">
087: * <constructor-arg><ref local="initialDirContextFactory"/></constructor-arg>
088: * <constructor-arg><value>ou=groups</value></constructor-arg>
089: * <property name="groupRoleAttribute"><value>ou</value></property>
090: * </bean>
091: * </constructor-arg>
092: * </bean></pre>
093: *
094: * <p>This would set up the provider to access an LDAP server with URL
095: * <tt>ldap://monkeymachine:389/dc=acegisecurity,dc=org</tt>. Authentication will be performed by attempting to bind
096: * with the DN <tt>uid=<user-login-name>,ou=people,dc=acegisecurity,dc=org</tt>. After successful
097: * authentication, roles will be assigned to the user by searching under the DN
098: * <tt>ou=groups,dc=acegisecurity,dc=org</tt> with the default filter <tt>(member=<user's-DN>)</tt>. The role
099: * name will be taken from the "ou" attribute of each match.</p>
100: * <p>
101: * The authenticate method will reject empty passwords outright. LDAP servers may allow an anonymous
102: * bind operation with an empty password, even if a DN is supplied. In practice this means that if
103: * the LDAP directory is configured to allow unauthenitcated access, it might be possible to
104: * authenticate as <i>any</i> user just by supplying an empty password.
105: * More information on the misuse of unauthenticated access can be found in
106: * <a href="http://www.ietf.org/internet-drafts/draft-ietf-ldapbis-authmeth-19.txt">
107: * draft-ietf-ldapbis-authmeth-19.txt</a>.
108: * </p>
109: *
110: * @author Luke Taylor
111: * @version $Id: LdapAuthenticationProvider.java 1995 2007-08-30 21:12:16Z luke_t $
112: *
113: * @see org.acegisecurity.providers.ldap.authenticator.BindAuthenticator
114: * @see org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator
115: */
116: public class LdapAuthenticationProvider extends
117: AbstractUserDetailsAuthenticationProvider {
118: //~ Static fields/initializers =====================================================================================
119:
120: private static final Log logger = LogFactory
121: .getLog(LdapAuthenticationProvider.class);
122:
123: //~ Instance fields ================================================================================================
124:
125: private LdapAuthenticator authenticator;
126: private LdapAuthoritiesPopulator authoritiesPopulator;
127: private boolean includeDetailsObject = true;
128:
129: //~ Constructors ===================================================================================================
130:
131: /**
132: * Create an instance with the supplied authenticator and authorities populator implementations.
133: *
134: * @param authenticator the authentication strategy (bind, password comparison, etc)
135: * to be used by this provider for authenticating users.
136: * @param authoritiesPopulator the strategy for obtaining the authorities for a given user after they've been
137: * authenticated.
138: */
139: public LdapAuthenticationProvider(LdapAuthenticator authenticator,
140: LdapAuthoritiesPopulator authoritiesPopulator) {
141: this .setAuthenticator(authenticator);
142: this .setAuthoritiesPopulator(authoritiesPopulator);
143: }
144:
145: /**
146: * Creates an instance with the supplied authenticator and a null authorities populator.
147: * In this case, the authorities must be mapped from the user context.
148: *
149: * @param authenticator the authenticator strategy.
150: */
151: public LdapAuthenticationProvider(LdapAuthenticator authenticator) {
152: this .setAuthenticator(authenticator);
153: this .setAuthoritiesPopulator(new NullAuthoritiesPopulator());
154: }
155:
156: //~ Methods ========================================================================================================
157:
158: private void setAuthenticator(LdapAuthenticator authenticator) {
159: Assert.notNull(authenticator,
160: "An LdapAuthenticator must be supplied");
161: this .authenticator = authenticator;
162: }
163:
164: private LdapAuthenticator getAuthenticator() {
165: return authenticator;
166: }
167:
168: private void setAuthoritiesPopulator(
169: LdapAuthoritiesPopulator authoritiesPopulator) {
170: Assert.notNull(authoritiesPopulator,
171: "An LdapAuthoritiesPopulator must be supplied");
172: this .authoritiesPopulator = authoritiesPopulator;
173: }
174:
175: protected LdapAuthoritiesPopulator getAuthoritiesPopulator() {
176: return authoritiesPopulator;
177: }
178:
179: protected void additionalAuthenticationChecks(
180: UserDetails userDetails,
181: UsernamePasswordAuthenticationToken authentication)
182: throws AuthenticationException {
183: if (!userDetails.getPassword().equals(
184: authentication.getCredentials().toString())) {
185: throw new BadCredentialsException(
186: messages
187: .getMessage(
188: "AbstractUserDetailsAuthenticationProvider.badCredentials",
189: "Bad credentials"),
190: includeDetailsObject ? userDetails : null);
191: }
192: }
193:
194: /**
195: * Creates the final <tt>UserDetails</tt> object that will be returned by the provider once the user has
196: * been authenticated.<p>The <tt>LdapAuthoritiesPopulator</tt> will be used to create the granted
197: * authorites for the user.</p>
198: * <p>Can be overridden to customize the creation of the final UserDetails instance. The default will
199: * merge any additional authorities retrieved from the populator with the propertis of original <tt>ldapUser</tt>
200: * object and set the values of the username and password.</p>
201: *
202: * @param ldapUser The intermediate LdapUserDetails instance returned by the authenticator.
203: * @param username the username submitted to the provider
204: * @param password the password submitted to the provider
205: *
206: * @return The UserDetails for the successfully authenticated user.
207: */
208: protected UserDetails createUserDetails(LdapUserDetails ldapUser,
209: String username, String password) {
210: LdapUserDetailsImpl.Essence user = new LdapUserDetailsImpl.Essence(
211: ldapUser);
212: user.setUsername(username);
213: user.setPassword(password);
214:
215: GrantedAuthority[] extraAuthorities = getAuthoritiesPopulator()
216: .getGrantedAuthorities(ldapUser);
217:
218: for (int i = 0; i < extraAuthorities.length; i++) {
219: user.addAuthority(extraAuthorities[i]);
220: }
221:
222: return user.createUserDetails();
223: }
224:
225: protected UserDetails retrieveUser(String username,
226: UsernamePasswordAuthenticationToken authentication)
227: throws AuthenticationException {
228: if (!StringUtils.hasLength(username)) {
229: throw new BadCredentialsException(messages.getMessage(
230: "LdapAuthenticationProvider.emptyUsername",
231: "Empty Username"));
232: }
233:
234: if (logger.isDebugEnabled()) {
235: logger.debug("Retrieving user " + username);
236: }
237:
238: String password = (String) authentication.getCredentials();
239: Assert.notNull(password,
240: "Null password was supplied in authentication token");
241:
242: if (password.length() == 0) {
243: logger.debug("Rejecting empty password for user "
244: + username);
245: throw new BadCredentialsException(messages.getMessage(
246: "LdapAuthenticationProvider.emptyPassword",
247: "Empty Password"));
248: }
249:
250: try {
251: LdapUserDetails ldapUser = getAuthenticator().authenticate(
252: username, password);
253:
254: return createUserDetails(ldapUser, username, password);
255:
256: } catch (DataAccessException ldapAccessFailure) {
257: throw new AuthenticationServiceException(ldapAccessFailure
258: .getMessage(), ldapAccessFailure);
259: }
260: }
261:
262: public boolean isIncludeDetailsObject() {
263: return includeDetailsObject;
264: }
265:
266: public void setIncludeDetailsObject(boolean includeDetailsObject) {
267: this .includeDetailsObject = includeDetailsObject;
268: }
269:
270: //~ Inner Classes ==================================================================================================
271:
272: private static class NullAuthoritiesPopulator implements
273: LdapAuthoritiesPopulator {
274: public GrantedAuthority[] getGrantedAuthorities(
275: LdapUserDetails userDetails)
276: throws LdapDataAccessException {
277: return new GrantedAuthority[0];
278: }
279: }
280: }
|