diff --git a/core/src/main/java/org/acegisecurity/providers/dao/DaoAuthenticationProvider.java b/core/src/main/java/org/acegisecurity/providers/dao/DaoAuthenticationProvider.java index 3f428834b6..b4a1e910e0 100644 --- a/core/src/main/java/org/acegisecurity/providers/dao/DaoAuthenticationProvider.java +++ b/core/src/main/java/org/acegisecurity/providers/dao/DaoAuthenticationProvider.java @@ -22,6 +22,7 @@ import net.sf.acegisecurity.BadCredentialsException; import net.sf.acegisecurity.DisabledException; import net.sf.acegisecurity.providers.AuthenticationProvider; import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken; +import net.sf.acegisecurity.providers.encoding.*; import org.springframework.beans.factory.InitializingBean; @@ -95,8 +96,9 @@ public class DaoAuthenticationProvider implements AuthenticationProvider, } if (!passwordEncoder.isPasswordValid(user.getPassword(), - authentication.getCredentials().toString(), user)) + authentication.getCredentials().toString(), user)) { throw new BadCredentialsException("Bad credentials presented"); + } if (!user.isEnabled()) { throw new DisabledException("User is disabled"); diff --git a/core/src/main/java/org/acegisecurity/providers/encoding/BaseDigestPasswordEncoder.java b/core/src/main/java/org/acegisecurity/providers/encoding/BaseDigestPasswordEncoder.java new file mode 100644 index 0000000000..e2c9f20984 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/providers/encoding/BaseDigestPasswordEncoder.java @@ -0,0 +1,50 @@ +/* Copyright 2004 Acegi Technology Pty Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sf.acegisecurity.providers.encoding; + +import net.sf.acegisecurity.providers.encoding.*; + + +/** + *
+ * Convenience base for Digest password encoders + *
+ * + * @author colin sampaleanu + * @version $Id$ + */ +public abstract class BaseDigestPasswordEncoder implements PasswordEncoder { + //~ Instance fields ======================================================== + + private boolean encodeHashAsBase64 = false; + + //~ Methods ================================================================ + + /** + * The encoded password is normally returned as Hex (32 char) version of + * the hash bytes. Setting this property to true will cause the encoded + * pass to be returned as Base64 text, which will consume 24 characters. + * + * @param encodeHashAsBase64 DOCUMENT ME! + */ + public void setEncodeHashAsBase64(boolean encodeHashAsBase64) { + this.encodeHashAsBase64 = encodeHashAsBase64; + } + + public boolean getEncodeHashAsBase64() { + return encodeHashAsBase64; + } +} diff --git a/core/src/main/java/org/acegisecurity/providers/encoding/Md5PasswordEncoder.java b/core/src/main/java/org/acegisecurity/providers/encoding/Md5PasswordEncoder.java new file mode 100644 index 0000000000..2f3b949950 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/providers/encoding/Md5PasswordEncoder.java @@ -0,0 +1,64 @@ +/* Copyright 2004 Acegi Technology Pty Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sf.acegisecurity.providers.encoding; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.codec.digest.DigestUtils; + + +/** + *+ * MD5 implementation of PasswordEncoder. + *
+ * + *+ * A null password is encoded to the same value as an empty ("") password. + *
+ * + * @author colin sampaleanu + * @version $Id$ + */ +public class Md5PasswordEncoder extends BaseDigestPasswordEncoder + implements PasswordEncoder { + //~ Methods ================================================================ + + /* (non-Javadoc) + * @see net.sf.acegisecurity.providers.dao.PasswordEncoder#isPasswordValid(java.lang.String, java.lang.String, java.lang.Object) + */ + public boolean isPasswordValid(String encPass, String rawPass, Object salt) { + String pass1 = "" + encPass; + String pass2 = encodeInternal("" + rawPass); + + return pass1.equals(pass2); + } + + /* (non-Javadoc) + * @see net.sf.acegisecurity.providers.dao.PasswordEncoder#encodePassword(java.lang.String, java.lang.Object) + */ + public String encodePassword(String rawPass, Object salt) { + return encodeInternal("" + rawPass); + } + + private String encodeInternal(String input) { + if (!getEncodeHashAsBase64()) { + return DigestUtils.md5Hex(input); + } + + byte[] encoded = Base64.encodeBase64(DigestUtils.md5(input)); + + return new String(encoded); + } +} diff --git a/core/src/main/java/org/acegisecurity/providers/encoding/PasswordEncoder.java b/core/src/main/java/org/acegisecurity/providers/encoding/PasswordEncoder.java new file mode 100644 index 0000000000..f577f565e9 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/providers/encoding/PasswordEncoder.java @@ -0,0 +1,87 @@ +/* Copyright 2004 Acegi Technology Pty Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sf.acegisecurity.providers.encoding; + +import org.springframework.dao.DataAccessException; + + +/** + *+ * Interface for performing authentication operations on a password, so that + * digest algorithms may be abstracted. + *
+ * + * @author colin sampaleanu + * @version $Id$ + */ +public interface PasswordEncoder { + //~ Methods ================================================================ + + /** + *+ * Validates a specified 'raw' password against an encoded password + * previously returned form {@link #encodePassword(String, Object)}. The + * raw password will first be encoded, and then both values will be + * compared. + *
+ * + *+ * The specified salt will potentially be used by the implementation to + * 'salt' the initial value before encoding. If a salt value is provided, + * it must be the same as the value used when calling {@link + * #encodePassword(String, Object)} to produce the first encoded value. + * Note that a specific implementation may choose to ignore the salt + * value, or provide its own. + *
+ * + * @param encPass a pre-encoded password + * @param rawPass a raw password to encode and compare against the + * pre-encoded password + * @param an object optionally used by the implementation to 'salt' the raw + * password before encoding. A null value is legal. + * + * @return DOCUMENT ME! + */ + public boolean isPasswordValid(String encPass, String rawPass, + Object saltSource) throws DataAccessException; + + /** + *+ * Encodes the specified raw password with an implementation specific + * algorithm. This will generally be a one-way message digest such as MD5 + * or SHA, but may also be a plaintext variant which does no encoding at + * all, but rather returns the same password it was fed. The latter is + * useful to plug in when the original password must be stored as-is. + *
+ * + *+ * The specified salt will potentially be used by the implementation to + * 'salt' the initial value before encoding, in order to prevent + * dictionary attacks. If a salt value is provided, the same salt value + * must be use when calling the {@link #isPasswordValid(String, String, + * Object)} function. Note that a specific implementation may choose to + * ignore the salt value, or provide its own. + *
+ * + * @param rawPass the password to encode + * @param an object optionally used by the implementation to 'salt' the raw + * password before encoding. A null value is legal. + * + * @return DOCUMENT ME! + */ + public String encodePassword(String rawPass, Object salt) + throws DataAccessException; +} diff --git a/core/src/main/java/org/acegisecurity/providers/encoding/PlaintextPasswordEncoder.java b/core/src/main/java/org/acegisecurity/providers/encoding/PlaintextPasswordEncoder.java new file mode 100644 index 0000000000..b5f8da9b8d --- /dev/null +++ b/core/src/main/java/org/acegisecurity/providers/encoding/PlaintextPasswordEncoder.java @@ -0,0 +1,68 @@ +/* Copyright 2004 Acegi Technology Pty Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sf.acegisecurity.providers.encoding; + +/** + *+ * Plaintext implementation of PasswordEncoder. + *
+ * + * @author colin sampaleanu + * @version $Id$ + */ +public class PlaintextPasswordEncoder implements PasswordEncoder { + //~ Instance fields ======================================================== + + private boolean ignorePasswordCase = false; + + //~ Methods ================================================================ + + /** + * Indicates whether the password comparison is case sensitive. Defaults to + *false, meaning an exact case match is required.
+ *
+ * @param ignorePasswordCase set to true for less stringent
+ * comparison
+ */
+ public void setIgnorePasswordCase(boolean ignorePasswordCase) {
+ this.ignorePasswordCase = ignorePasswordCase;
+ }
+
+ public boolean isIgnorePasswordCase() {
+ return ignorePasswordCase;
+ }
+
+ /* (non-Javadoc)
+ * @see net.sf.acegisecurity.providers.dao.PasswordEncoder#isPasswordValid(java.lang.String, java.lang.String, java.lang.Object)
+ */
+ public boolean isPasswordValid(String encPass, String rawPass, Object salt) {
+ String pass1 = "" + encPass;
+ String pass2 = "" + rawPass;
+
+ if (!ignorePasswordCase) {
+ return pass1.equals(pass2);
+ } else {
+ return pass1.equalsIgnoreCase(pass2);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see net.sf.acegisecurity.providers.dao.PasswordEncoder#encodePassword(java.lang.String, java.lang.Object)
+ */
+ public String encodePassword(String rawPass, Object salt) {
+ return rawPass;
+ }
+}
diff --git a/core/src/main/java/org/acegisecurity/providers/encoding/ShaPasswordEncoder.java b/core/src/main/java/org/acegisecurity/providers/encoding/ShaPasswordEncoder.java
new file mode 100644
index 0000000000..1ad7a8737b
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/providers/encoding/ShaPasswordEncoder.java
@@ -0,0 +1,64 @@
+/* Copyright 2004 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sf.acegisecurity.providers.encoding;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.codec.digest.DigestUtils;
+
+
+/**
+ * + * SHA implementation of PasswordEncoder. + *
+ * + *+ * A null password is encoded to the same value as an empty ("") password. + *
+ * + * @author colin sampaleanu + * @version $Id$ + */ +public class ShaPasswordEncoder extends BaseDigestPasswordEncoder + implements PasswordEncoder { + //~ Methods ================================================================ + + /* (non-Javadoc) + * @see net.sf.acegisecurity.providers.dao.PasswordEncoder#isPasswordValid(java.lang.String, java.lang.String, java.lang.Object) + */ + public boolean isPasswordValid(String encPass, String rawPass, Object salt) { + String pass1 = "" + encPass; + String pass2 = encodeInternal("" + rawPass); + + return pass1.equals(pass2); + } + + /* (non-Javadoc) + * @see net.sf.acegisecurity.providers.dao.PasswordEncoder#encodePassword(java.lang.String, java.lang.Object) + */ + public String encodePassword(String rawPass, Object salt) { + return encodeInternal("" + rawPass); + } + + private String encodeInternal(String input) { + if (!getEncodeHashAsBase64()) { + return DigestUtils.shaHex(input); + } + + byte[] encoded = Base64.encodeBase64(DigestUtils.sha(input)); + + return new String(encoded); + } +} diff --git a/core/src/main/java/org/acegisecurity/userdetails/UserDetailsService.java b/core/src/main/java/org/acegisecurity/userdetails/UserDetailsService.java index 057128acf2..d0e52197a8 100644 --- a/core/src/main/java/org/acegisecurity/userdetails/UserDetailsService.java +++ b/core/src/main/java/org/acegisecurity/userdetails/UserDetailsService.java @@ -36,9 +36,9 @@ public interface AuthenticationDao { /** * Locates the user based on the username. In the actual implementation, * the search may possibly be case insensitive, or case insensitive - * depending on how the implementaion instance is configured. In this case, - * the User object that comes back may have a username that is of a different - * case than what was actually requested.. + * depending on how the implementaion instance is configured. In this + * case, the User object that comes back may have a username that is of a + * different case than what was actually requested.. * * @param username the username presented to the {@link * DaoAuthenticationProvider} diff --git a/core/src/test/java/org/acegisecurity/providers/dao/DaoAuthenticationProviderTests.java b/core/src/test/java/org/acegisecurity/providers/dao/DaoAuthenticationProviderTests.java index eeeac4afc7..1b77d87a0e 100644 --- a/core/src/test/java/org/acegisecurity/providers/dao/DaoAuthenticationProviderTests.java +++ b/core/src/test/java/org/acegisecurity/providers/dao/DaoAuthenticationProviderTests.java @@ -37,7 +37,6 @@ import org.springframework.dao.DataRetrievalFailureException; * @version $Id$ */ public class DaoAuthenticationProviderTests extends TestCase { - //~ Methods ================================================================ public final void setUp() throws Exception { diff --git a/core/src/test/java/org/acegisecurity/providers/dao/MD5PasswordEncoderTest.java b/core/src/test/java/org/acegisecurity/providers/dao/MD5PasswordEncoderTest.java index 12a2dc33ff..ef2d0d1c07 100644 --- a/core/src/test/java/org/acegisecurity/providers/dao/MD5PasswordEncoderTest.java +++ b/core/src/test/java/org/acegisecurity/providers/dao/MD5PasswordEncoderTest.java @@ -12,10 +12,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package net.sf.acegisecurity.providers.dao; import junit.framework.TestCase; +import net.sf.acegisecurity.providers.encoding.*; + + /** ** TestCase for PlaintextPasswordEncoder. @@ -25,23 +29,22 @@ import junit.framework.TestCase; * @version $Id$ */ public class MD5PasswordEncoderTest extends TestCase { + //~ Methods ================================================================ - public void testBasicFunctionality() { - - MD5PasswordEncoder pe = new MD5PasswordEncoder(); - String raw = "abc123"; - String badRaw = "abc321"; - String encoded = pe.encodePassword(raw, null); // no SALT source - assertTrue(pe.isPasswordValid(encoded, raw, null)); - assertFalse(pe.isPasswordValid(encoded, badRaw, null)); - assertTrue(encoded.length() == 32); - - // now try Base64 - pe.setEncodeHashAsBase64(true); - encoded = pe.encodePassword(raw, null); // no SALT source - assertTrue(pe.isPasswordValid(encoded, raw, null)); - assertFalse(pe.isPasswordValid(encoded, badRaw, null)); - assertTrue(encoded.length() != 32); - } + public void testBasicFunctionality() { + Md5PasswordEncoder pe = new Md5PasswordEncoder(); + String raw = "abc123"; + String badRaw = "abc321"; + String encoded = pe.encodePassword(raw, null); // no SALT source + assertTrue(pe.isPasswordValid(encoded, raw, null)); + assertFalse(pe.isPasswordValid(encoded, badRaw, null)); + assertTrue(encoded.length() == 32); + // now try Base64 + pe.setEncodeHashAsBase64(true); + encoded = pe.encodePassword(raw, null); // no SALT source + assertTrue(pe.isPasswordValid(encoded, raw, null)); + assertFalse(pe.isPasswordValid(encoded, badRaw, null)); + assertTrue(encoded.length() != 32); + } } diff --git a/core/src/test/java/org/acegisecurity/providers/dao/PlaintextPasswordEncoderTest.java b/core/src/test/java/org/acegisecurity/providers/dao/PlaintextPasswordEncoderTest.java index 8646d4dda8..d4935312d4 100644 --- a/core/src/test/java/org/acegisecurity/providers/dao/PlaintextPasswordEncoderTest.java +++ b/core/src/test/java/org/acegisecurity/providers/dao/PlaintextPasswordEncoderTest.java @@ -12,10 +12,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package net.sf.acegisecurity.providers.dao; import junit.framework.TestCase; +import net.sf.acegisecurity.providers.encoding.*; + + /** *
* TestCase for PlaintextPasswordEncoder. @@ -25,35 +29,36 @@ import junit.framework.TestCase; * @version $Id$ */ public class PlaintextPasswordEncoderTest extends TestCase { + //~ Methods ================================================================ + + public void testBasicFunctionality() { + PlaintextPasswordEncoder pe = new PlaintextPasswordEncoder(); + + String raw = "abc123"; + String rawDiffCase = "AbC123"; + String badRaw = "abc321"; + + // should be able to validate even without encoding + String encoded = raw; + assertTrue(pe.isPasswordValid(encoded, raw, null)); // no SALT source + assertFalse(pe.isPasswordValid(encoded, badRaw, null)); + + // now make sure encoded version it gives us back is comparable as well + encoded = pe.encodePassword(raw, null); + assertTrue(pe.isPasswordValid(encoded, raw, null)); // no SALT source + assertFalse(pe.isPasswordValid(encoded, badRaw, null)); + + // make sure default is not to ignore password case + encoded = pe.encodePassword(rawDiffCase, null); + assertFalse(pe.isPasswordValid(encoded, raw, null)); + + // now check for ignore password case + pe = new PlaintextPasswordEncoder(); + pe.setIgnorePasswordCase(true); - public void testBasicFunctionality() { - PlaintextPasswordEncoder pe = new PlaintextPasswordEncoder(); - - String raw = "abc123"; - String rawDiffCase = "AbC123"; - String badRaw = "abc321"; - // should be able to validate even without encoding - String encoded = raw; - assertTrue(pe.isPasswordValid(encoded, raw, null)); // no SALT source - assertFalse(pe.isPasswordValid(encoded, badRaw, null)); - - // now make sure encoded version it gives us back is comparable as well - encoded = pe.encodePassword(raw, null); - assertTrue(pe.isPasswordValid(encoded, raw, null)); // no SALT source - assertFalse(pe.isPasswordValid(encoded, badRaw, null)); - - // make sure default is not to ignore password case - encoded = pe.encodePassword(rawDiffCase, null); - assertFalse(pe.isPasswordValid(encoded, raw, null)); - - // now check for ignore password case - pe = new PlaintextPasswordEncoder(); - pe.setIgnorePasswordCase(true); - - // should be able to validate even without encoding - encoded = pe.encodePassword(rawDiffCase, null); - assertTrue(pe.isPasswordValid(encoded, raw, null)); - assertFalse(pe.isPasswordValid(encoded, badRaw, null)); - } - -} \ No newline at end of file + // should be able to validate even without encoding + encoded = pe.encodePassword(rawDiffCase, null); + assertTrue(pe.isPasswordValid(encoded, raw, null)); + assertFalse(pe.isPasswordValid(encoded, badRaw, null)); + } +} diff --git a/core/src/test/java/org/acegisecurity/providers/dao/SHAPasswordEncoderTest.java b/core/src/test/java/org/acegisecurity/providers/dao/SHAPasswordEncoderTest.java index 67f1707dfa..bd6ca30354 100644 --- a/core/src/test/java/org/acegisecurity/providers/dao/SHAPasswordEncoderTest.java +++ b/core/src/test/java/org/acegisecurity/providers/dao/SHAPasswordEncoderTest.java @@ -12,36 +12,39 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package net.sf.acegisecurity.providers.dao; import junit.framework.TestCase; +import net.sf.acegisecurity.providers.encoding.*; + + /** *
- * TestCase for SHAPasswordEncoder. + * TestCase for ShaPasswordEncoder. *
* * @author colin sampaleanu * @version $Id$ */ public class SHAPasswordEncoderTest extends TestCase { + //~ Methods ================================================================ + + public void testBasicFunctionality() { + ShaPasswordEncoder pe = new ShaPasswordEncoder(); + String raw = "abc123"; + String badRaw = "abc321"; + String encoded = pe.encodePassword(raw, null); // no SALT source + assertTrue(pe.isPasswordValid(encoded, raw, null)); + assertFalse(pe.isPasswordValid(encoded, badRaw, null)); + assertTrue(encoded.length() == 40); - public void testBasicFunctionality() { - - SHAPasswordEncoder pe = new SHAPasswordEncoder(); - String raw = "abc123"; - String badRaw = "abc321"; - String encoded = pe.encodePassword(raw, null); // no SALT source - assertTrue(pe.isPasswordValid(encoded, raw, null)); - assertFalse(pe.isPasswordValid(encoded, badRaw, null)); - assertTrue(encoded.length() == 40); - - // now try Base64 - pe.setEncodeHashAsBase64(true); - encoded = pe.encodePassword(raw, null); // no SALT source - assertTrue(pe.isPasswordValid(encoded, raw, null)); - assertFalse(pe.isPasswordValid(encoded, badRaw, null)); - assertTrue(encoded.length() != 40); - - } + // now try Base64 + pe.setEncodeHashAsBase64(true); + encoded = pe.encodePassword(raw, null); // no SALT source + assertTrue(pe.isPasswordValid(encoded, raw, null)); + assertFalse(pe.isPasswordValid(encoded, badRaw, null)); + assertTrue(encoded.length() != 40); + } } diff --git a/samples/contacts/src/main/java/sample/contact/ClientApplication.java b/samples/contacts/src/main/java/sample/contact/ClientApplication.java index b1e4e80dad..319b003640 100644 --- a/samples/contacts/src/main/java/sample/contact/ClientApplication.java +++ b/samples/contacts/src/main/java/sample/contact/ClientApplication.java @@ -30,7 +30,7 @@ import java.util.Map; /** * Demonstrates accessing the {@link ContactManager} via remoting protocols. - * + * ** Based on Spring's JPetStore sample, written by Juergen Hoeller. *
diff --git a/test/acegisecuritytest.properties b/test/acegisecuritytest.properties index 832a797a7f..87a3e7d5a6 100644 --- a/test/acegisecuritytest.properties +++ b/test/acegisecuritytest.properties @@ -1,5 +1,5 @@ #HSQL database -#Thu Apr 15 12:20:59 EDT 2004 +#Thu Apr 15 23:43:47 EDT 2004 sql.strict_fk=true readonly=false sql.strong_fk=true