SEC-284: added allowEmptyPasswords property with default value "true"

This commit is contained in:
Luke Taylor 2006-05-31 15:00:59 +00:00
parent 7957d54d67
commit 02e7bbb982
3 changed files with 91 additions and 29 deletions

View File

@ -35,39 +35,59 @@ import org.springframework.util.StringUtils;
/** /**
* An {@link org.acegisecurity.providers.AuthenticationProvider} implementation that provides integration with an * An {@link org.acegisecurity.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 * LDAP server.
* its responsibilites to two separate strategy interfaces, {@link LdapAuthenticator} and {@link *
* LdapAuthoritiesPopulator}.</p> * <p>There are many ways in which an LDAP directory can be configured so this class delegates most of
* <h3>LdapAuthenticator</h3>This interface is responsible for performing the user authentication and retrieving * its responsibilites to two separate strategy interfaces, {@link LdapAuthenticator}
* and {@link LdapAuthoritiesPopulator}.</p>
*
* <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 * the user's information from the directory. Example implementations are {@link
* org.acegisecurity.providers.ldap.authenticator.BindAuthenticator BindAuthenticator} which authenticates the user by * org.acegisecurity.providers.ldap.authenticator.BindAuthenticator BindAuthenticator} which authenticates the user by
* "binding" as that user, and {@link org.acegisecurity.providers.ldap.authenticator.PasswordComparisonAuthenticator * "binding" as that user, and {@link org.acegisecurity.providers.ldap.authenticator.PasswordComparisonAuthenticator
* PasswordComparisonAuthenticator} which performs a comparison of the supplied password with the value stored in the * 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 * directory, either by retrieving the password or performing an LDAP "compare" operation.
* the user attributes is delegated to the authenticator because the permissions on the attributes may depend on the * <p>The task of retrieving the user attributes is delegated to the authenticator because the permissions on the
* type of authentication being used; for example, if binding as the user, it may be necessary to read them with the * attributes may depend on the type of authentication being used; for example, if binding as the user, it may be
* 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).</p>
* <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 * <h3>LdapAuthoritiesPopulator</h3>
* org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator DefaultLdapAuthoritiesPopulator} can be * Once the user has been authenticated, this interface is called to obtain the set of granted authorities for the
* configured to obtain user role information from the user's attributes and/or to perform a search for "groups" that * user.
* the user is a member of and map these to roles.<p>A custom implementation could obtain the roles from a * The
* completely different source, for example from a database.</p> * {@link org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator DefaultLdapAuthoritiesPopulator}
* <h3>Configuration</h3>A simple configuration might be as follows:<pre> * 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>
*
* <h3>Configuration</h3>A simple configuration might be as follows:
* <pre>
* &lt;bean id="initialDirContextFactory" class="org.acegisecurity.providers.ldap.DefaultInitialDirContextFactory"> * &lt;bean id="initialDirContextFactory" class="org.acegisecurity.providers.ldap.DefaultInitialDirContextFactory">
* &lt;constructor-arg value="ldap://monkeymachine:389/dc=acegisecurity,dc=org"/> * &lt;constructor-arg value="ldap://monkeymachine:389/dc=acegisecurity,dc=org"/>
* &lt;property name="managerDn">&lt;value>cn=manager,dc=acegisecurity,dc=org&lt;/value>&lt;/property> * &lt;property name="managerDn">&lt;value>cn=manager,dc=acegisecurity,dc=org&lt;/value>&lt;/property>
* &lt;property name="managerPassword">&lt;value>password&lt;/value>&lt;/property> &lt;/bean> * &lt;property name="managerPassword">&lt;value>password&lt;/value>&lt;/property>
* &lt;/bean>
*
* &lt;bean id="ldapAuthProvider" class="org.acegisecurity.providers.ldap.LdapAuthenticationProvider"> * &lt;bean id="ldapAuthProvider" class="org.acegisecurity.providers.ldap.LdapAuthenticationProvider">
* &lt;constructor-arg> &lt;bean class="org.acegisecurity.providers.ldap.authenticator.BindAuthenticator"> * &lt;constructor-arg>
* &lt;constructor-arg>&lt;ref local="initialDirContextFactory"/>&lt;/constructor-arg> * &lt;bean class="org.acegisecurity.providers.ldap.authenticator.BindAuthenticator">
* &lt;property name="userDnPatterns">&lt;list>&lt;value>uid={0},ou=people&lt;/value>&lt;/list>&lt;/property> * &lt;constructor-arg>&lt;ref local="initialDirContextFactory"/>&lt;/constructor-arg>
* &lt;/bean> &lt;/constructor-arg> &lt;constructor-arg> * &lt;property name="userDnPatterns">&lt;list>&lt;value>uid={0},ou=people&lt;/value>&lt;/list>&lt;/property>
* &lt;bean class="org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator"> * &lt;/bean>
* &lt;constructor-arg>&lt;ref local="initialDirContextFactory"/>&lt;/constructor-arg> * &lt;/constructor-arg>
* &lt;constructor-arg>&lt;value>ou=groups&lt;/value>&lt;/constructor-arg> * &lt;constructor-arg>
* &lt;property name="groupRoleAttribute">&lt;value>ou&lt;/value>&lt;/property> &lt;/bean> * &lt;bean class="org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator">
* &lt;/constructor-arg> &lt;/bean></pre><p>This would set up the provider to access an LDAP server with URL * &lt;constructor-arg>&lt;ref local="initialDirContextFactory"/>&lt;/constructor-arg>
* &lt;constructor-arg>&lt;value>ou=groups&lt;/value>&lt;/constructor-arg>
* &lt;property name="groupRoleAttribute">&lt;value>ou&lt;/value>&lt;/property>
* &lt;/bean>
* &lt;/constructor-arg>
* &lt;/bean></pre>
*
* <p>This would set up the provider to access an LDAP server with URL
* <tt>ldap://monkeymachine:389/dc=acegisecurity,dc=org</tt>. Authentication will be performed by attempting to bind * <tt>ldap://monkeymachine:389/dc=acegisecurity,dc=org</tt>. Authentication will be performed by attempting to bind
* with the DN <tt>uid=&lt;user-login-name&gt;,ou=people,dc=acegisecurity,dc=org</tt>. After successful * with the DN <tt>uid=&lt;user-login-name&gt;,ou=people,dc=acegisecurity,dc=org</tt>. After successful
* authentication, roles will be assigned to the user by searching under the DN * authentication, roles will be assigned to the user by searching under the DN
@ -90,6 +110,9 @@ public class LdapAuthenticationProvider extends AbstractUserDetailsAuthenticatio
private LdapAuthenticator authenticator; private LdapAuthenticator authenticator;
private LdapAuthoritiesPopulator authoritiesPopulator; private LdapAuthoritiesPopulator authoritiesPopulator;
/** The provider will reject an authentication request with an empty password if this is set to "true" */
private boolean allowEmptyPasswords = true;
//~ Constructors =================================================================================================== //~ Constructors ===================================================================================================
public LdapAuthenticationProvider(LdapAuthenticator authenticator, LdapAuthoritiesPopulator authoritiesPopulator) { public LdapAuthenticationProvider(LdapAuthenticator authenticator, LdapAuthoritiesPopulator authoritiesPopulator) {
@ -111,6 +134,15 @@ public class LdapAuthenticationProvider extends AbstractUserDetailsAuthenticatio
} }
} }
/**
* Determines whether the provider will reject empty passwords by default.
* This may be useful when using LDAP servers which interpret an empty password as
* anonymous access, even if a (possibly non-existent) principal is supplied.
*/
public void setAllowEmptyPasswords(boolean allowEmptyPasswords) {
this.allowEmptyPasswords = allowEmptyPasswords;
}
/** /**
* Creates the final <tt>UserDetails</tt> object that will be returned by the provider once the user has * Creates the final <tt>UserDetails</tt> object that will be returned by the provider once the user has
* been authenticated.<p>The <tt>LdapAuthoritiesPopulator</tt> will be used to create the granted * been authenticated.<p>The <tt>LdapAuthoritiesPopulator</tt> will be used to create the granted
@ -157,6 +189,12 @@ public class LdapAuthenticationProvider extends AbstractUserDetailsAuthenticatio
String password = (String) authentication.getCredentials(); String password = (String) authentication.getCredentials();
Assert.notNull(password, "Null password was supplied in authentication token"); Assert.notNull(password, "Null password was supplied in authentication token");
if(!allowEmptyPasswords && password.length() == 0) {
logger.debug("Rejecting empty password for user " + username);
throw new BadCredentialsException(messages.getMessage("LdapAuthenticationProvider.emptyPassword",
"Empty Password"));
}
LdapUserDetails ldapUser = authenticator.authenticate(username, password); LdapUserDetails ldapUser = authenticator.authenticate(username, password);
return createUserDetails(ldapUser, username, password); return createUserDetails(ldapUser, username, password);

View File

@ -38,6 +38,7 @@ SwitchUserProcessingFilter.expired=User account has expired
SwitchUserProcessingFilter.credentialsExpired=User credentials have expired SwitchUserProcessingFilter.credentialsExpired=User credentials have expired
AbstractAccessDecisionManager.accessDenied=Access is denied AbstractAccessDecisionManager.accessDenied=Access is denied
LdapAuthenticationProvider.emptyUsername=Empty username not allowed LdapAuthenticationProvider.emptyUsername=Empty username not allowed
LdapAuthenticationProvider.emptyPassword=Bad credentials
DefaultIntitalDirContextFactory.communicationFailure=Unable to connect to LDAP server DefaultIntitalDirContextFactory.communicationFailure=Unable to connect to LDAP server
DefaultIntitalDirContextFactory.badCredentials=Bad credentials DefaultIntitalDirContextFactory.badCredentials=Bad credentials
DefaultIntitalDirContextFactory.unexpectedException=Failed to obtain InitialDirContext due to unexpected exception DefaultIntitalDirContextFactory.unexpectedException=Failed to obtain InitialDirContext due to unexpected exception

View File

@ -86,6 +86,23 @@ public class LdapAuthenticationProviderTests extends TestCase {
} catch (BadCredentialsException expected) {} } catch (BadCredentialsException expected) {}
} }
public void testEmptyPasswordIsAcceptedByDefault() {
LdapAuthenticationProvider ldapProvider = new LdapAuthenticationProvider(new MockAuthenticator(),
new MockAuthoritiesPopulator());
ldapProvider.retrieveUser("jen", new UsernamePasswordAuthenticationToken("jen", ""));
}
public void testEmptyPasswordIsRejectedWhenFlagIsSet() {
LdapAuthenticationProvider ldapProvider = new LdapAuthenticationProvider(new MockAuthenticator(),
new MockAuthoritiesPopulator());
ldapProvider.setAllowEmptyPasswords(false);
try {
ldapProvider.retrieveUser("jen", new UsernamePasswordAuthenticationToken("jen", ""));
fail("Expected BadCredentialsException for empty password");
} catch (BadCredentialsException expected) {}
}
public void testNormalUsage() { public void testNormalUsage() {
LdapAuthenticationProvider ldapProvider = new LdapAuthenticationProvider(new MockAuthenticator(), LdapAuthenticationProvider ldapProvider = new LdapAuthenticationProvider(new MockAuthenticator(),
new MockAuthoritiesPopulator()); new MockAuthoritiesPopulator());
@ -114,17 +131,23 @@ public class LdapAuthenticationProviderTests extends TestCase {
Attributes userAttributes = new BasicAttributes("cn", "bob"); Attributes userAttributes = new BasicAttributes("cn", "bob");
public LdapUserDetails authenticate(String username, String password) { public LdapUserDetails authenticate(String username, String password) {
LdapUserDetailsImpl.Essence userEssence = new LdapUserDetailsImpl.Essence();
userEssence.setPassword("{SHA}anencodedpassword");
userEssence.setAttributes(userAttributes);
if (username.equals("bob") && password.equals("bobspassword")) { if (username.equals("bob") && password.equals("bobspassword")) {
LdapUserDetailsImpl.Essence userEssence = new LdapUserDetailsImpl.Essence();
userEssence.setDn("cn=bob,ou=people,dc=acegisecurity,dc=org"); userEssence.setDn("cn=bob,ou=people,dc=acegisecurity,dc=org");
userEssence.setPassword("{SHA}anencodedpassword"); userEssence.addAuthority(new GrantedAuthorityImpl("ROLE_FROM_ENTRY"));
userEssence.setAttributes(userAttributes);
return userEssence.createUserDetails();
} else if (username.equals("jen") && password.equals("")) {
userEssence.setDn("cn=jen,ou=people,dc=acegisecurity,dc=org");
userEssence.addAuthority(new GrantedAuthorityImpl("ROLE_FROM_ENTRY")); userEssence.addAuthority(new GrantedAuthorityImpl("ROLE_FROM_ENTRY"));
return userEssence.createUserDetails(); return userEssence.createUserDetails();
} }
throw new BadCredentialsException("Authentication of Bob failed."); throw new BadCredentialsException("Authentication failed.");
} }
} }