From da5101cfb4aab76105c63bc95fd41c25f2f74cb2 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sat, 17 Apr 2004 01:29:52 +0000 Subject: [PATCH] Make salt sources pluggable. --- .../dao/DaoAuthenticationProvider.java | 29 +++++- .../providers/dao/SaltSource.java | 35 +++++++ .../dao/salt/ReflectionSaltSource.java | 89 ++++++++++++++++++ .../dao/salt/SystemWideSaltSource.java | 61 ++++++++++++ .../dao/salt/ReflectionSaltSourceTests.java | 94 +++++++++++++++++++ .../dao/salt/SystemWideSaltSourceTests.java | 70 ++++++++++++++ 6 files changed, 376 insertions(+), 2 deletions(-) create mode 100644 core/src/main/java/org/acegisecurity/providers/dao/SaltSource.java create mode 100644 core/src/main/java/org/acegisecurity/providers/dao/salt/ReflectionSaltSource.java create mode 100644 core/src/main/java/org/acegisecurity/providers/dao/salt/SystemWideSaltSource.java create mode 100644 core/src/test/java/org/acegisecurity/providers/dao/salt/ReflectionSaltSourceTests.java create mode 100644 core/src/test/java/org/acegisecurity/providers/dao/salt/SystemWideSaltSourceTests.java 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 b4a1e910e0..36b58f1aba 100644 --- a/core/src/main/java/org/acegisecurity/providers/dao/DaoAuthenticationProvider.java +++ b/core/src/main/java/org/acegisecurity/providers/dao/DaoAuthenticationProvider.java @@ -22,7 +22,8 @@ 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 net.sf.acegisecurity.providers.encoding.PasswordEncoder; +import net.sf.acegisecurity.providers.encoding.PlaintextPasswordEncoder; import org.springframework.beans.factory.InitializingBean; @@ -48,6 +49,7 @@ public class DaoAuthenticationProvider implements AuthenticationProvider, private AuthenticationDao authenticationDao; private PasswordEncoder passwordEncoder = new PlaintextPasswordEncoder(); + private SaltSource saltSource; //~ Methods ================================================================ @@ -74,6 +76,23 @@ public class DaoAuthenticationProvider implements AuthenticationProvider, return passwordEncoder; } + /** + * The source of salts to use when decoding passwords. null + * is a valid value, meaning the DaoAuthenticationProvider + * will present null to the relevant + * PasswordEncoder. + * + * @param saltSource to use when attempting to decode passwords via the + * PasswordEncoder + */ + public void setSaltSource(SaltSource saltSource) { + this.saltSource = saltSource; + } + + public SaltSource getSaltSource() { + return saltSource; + } + public void afterPropertiesSet() throws Exception { if (this.authenticationDao == null) { throw new IllegalArgumentException( @@ -95,8 +114,14 @@ public class DaoAuthenticationProvider implements AuthenticationProvider, .getMessage(), repositoryProblem); } + Object salt = null; + + if (this.saltSource != null) { + salt = this.saltSource.getSalt(user); + } + if (!passwordEncoder.isPasswordValid(user.getPassword(), - authentication.getCredentials().toString(), user)) { + authentication.getCredentials().toString(), salt)) { throw new BadCredentialsException("Bad credentials presented"); } diff --git a/core/src/main/java/org/acegisecurity/providers/dao/SaltSource.java b/core/src/main/java/org/acegisecurity/providers/dao/SaltSource.java new file mode 100644 index 0000000000..a9ad278364 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/providers/dao/SaltSource.java @@ -0,0 +1,35 @@ +/* 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.dao; + +/** + * Provides alternative sources of the salt to use for encoding passwords. + * + * @author Ben Alex + * @version $Id$ + */ +public interface SaltSource { + //~ Methods ================================================================ + + /** + * Returns the salt to use for the indicated user. + * + * @param user from the AuthenticationDao + * + * @return the salt to use for this USer + */ + public Object getSalt(User user); +} diff --git a/core/src/main/java/org/acegisecurity/providers/dao/salt/ReflectionSaltSource.java b/core/src/main/java/org/acegisecurity/providers/dao/salt/ReflectionSaltSource.java new file mode 100644 index 0000000000..a9a708e35b --- /dev/null +++ b/core/src/main/java/org/acegisecurity/providers/dao/salt/ReflectionSaltSource.java @@ -0,0 +1,89 @@ +/* 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.dao.salt; + +import net.sf.acegisecurity.AuthenticationServiceException; +import net.sf.acegisecurity.providers.dao.SaltSource; +import net.sf.acegisecurity.providers.dao.User; + +import org.springframework.beans.factory.InitializingBean; + +import java.lang.reflect.Method; + + +/** + * Obtains a salt from a specified property of the {@link User} object. + * + *

+ * This allows you to subclass User and provide an additional bean + * getter for a salt. You should use a synthetic value that does not change, + * such as a database primary key. Do not use username if it is + * likely to change. + *

+ * + * @author Ben Alex + * @version $Id$ + */ +public class ReflectionSaltSource implements SaltSource, InitializingBean { + //~ Instance fields ======================================================== + + private String userPropertyToUse; + + //~ Methods ================================================================ + + /** + * Performs reflection on the passed User to obtain the salt. + * + *

+ * The property identified by userPropertyToUse must be + * available from the passed User object. If it is not + * available, an {@link AuthenticationServiceException} will be thrown. + *

+ * + * @param user which contains the method identified by + * userPropertyToUse + * + * @return the result of invoking user.userPropertyToUse() + * + * @throws AuthenticationServiceException if reflection fails + */ + public Object getSalt(User user) { + try { + Method reflectionMethod = user.getClass().getMethod(this.userPropertyToUse, + null); + + return reflectionMethod.invoke(user, null); + } catch (Exception exception) { + throw new AuthenticationServiceException(exception.getMessage()); + } + } + + public void setUserPropertyToUse(String userPropertyToUse) { + this.userPropertyToUse = userPropertyToUse; + } + + public String getUserPropertyToUse() { + return userPropertyToUse; + } + + public void afterPropertiesSet() throws Exception { + if ((this.getUserPropertyToUse() == null) + || "".equals(this.getUserPropertyToUse())) { + throw new IllegalArgumentException( + "A userPropertyToUse must be set"); + } + } +} diff --git a/core/src/main/java/org/acegisecurity/providers/dao/salt/SystemWideSaltSource.java b/core/src/main/java/org/acegisecurity/providers/dao/salt/SystemWideSaltSource.java new file mode 100644 index 0000000000..c765dc292f --- /dev/null +++ b/core/src/main/java/org/acegisecurity/providers/dao/salt/SystemWideSaltSource.java @@ -0,0 +1,61 @@ +/* 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.dao.salt; + +import net.sf.acegisecurity.providers.dao.SaltSource; +import net.sf.acegisecurity.providers.dao.User; + +import org.springframework.beans.factory.InitializingBean; + + +/** + * Uses a static system-wide String as the salt. + * + *

+ * Does not supply a different salt for each {@link User}. This means users + * sharing the same password will still have the same digested password. Of + * benefit is the digested passwords will at least be more protected than if + * stored without any salt. + *

+ * + * @author Ben Alex + * @version $Id$ + */ +public class SystemWideSaltSource implements SaltSource, InitializingBean { + //~ Instance fields ======================================================== + + private String systemWideSalt; + + //~ Methods ================================================================ + + public Object getSalt(User user) { + return this.systemWideSalt; + } + + public void setSystemWideSalt(String systemWideSalt) { + this.systemWideSalt = systemWideSalt; + } + + public String getSystemWideSalt() { + return this.systemWideSalt; + } + + public void afterPropertiesSet() throws Exception { + if ((this.systemWideSalt == null) || "".equals(this.systemWideSalt)) { + throw new IllegalArgumentException("A systemWideSalt must be set"); + } + } +} diff --git a/core/src/test/java/org/acegisecurity/providers/dao/salt/ReflectionSaltSourceTests.java b/core/src/test/java/org/acegisecurity/providers/dao/salt/ReflectionSaltSourceTests.java new file mode 100644 index 0000000000..744d4aa0c3 --- /dev/null +++ b/core/src/test/java/org/acegisecurity/providers/dao/salt/ReflectionSaltSourceTests.java @@ -0,0 +1,94 @@ +/* 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.dao.salt; + +import junit.framework.TestCase; + +import net.sf.acegisecurity.AuthenticationServiceException; +import net.sf.acegisecurity.GrantedAuthority; +import net.sf.acegisecurity.GrantedAuthorityImpl; +import net.sf.acegisecurity.providers.dao.User; + + +/** + * Tests {@link ReflectionSaltSource}. + * + * @author Ben Alex + * @version $Id$ + */ +public class ReflectionSaltSourceTests extends TestCase { + //~ Constructors =========================================================== + + public ReflectionSaltSourceTests() { + super(); + } + + public ReflectionSaltSourceTests(String arg0) { + super(arg0); + } + + //~ Methods ================================================================ + + public final void setUp() throws Exception { + super.setUp(); + } + + public static void main(String[] args) { + junit.textui.TestRunner.run(ReflectionSaltSourceTests.class); + } + + public void testDetectsMissingUserPropertyToUse() throws Exception { + ReflectionSaltSource saltSource = new ReflectionSaltSource(); + + try { + saltSource.afterPropertiesSet(); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + assertEquals("A userPropertyToUse must be set", + expected.getMessage()); + } + } + + public void testExceptionWhenInvalidPropertyRequested() { + ReflectionSaltSource saltSource = new ReflectionSaltSource(); + saltSource.setUserPropertyToUse("getDoesNotExist"); + + User user = new User("scott", "wombat", true, + new GrantedAuthority[] {new GrantedAuthorityImpl("HOLDER")}); + + try { + saltSource.getSalt(user); + fail("Should have thrown AuthenticationServiceException"); + } catch (AuthenticationServiceException expected) { + assertTrue(true); + } + } + + public void testGettersSetters() { + ReflectionSaltSource saltSource = new ReflectionSaltSource(); + saltSource.setUserPropertyToUse("getUsername"); + assertEquals("getUsername", saltSource.getUserPropertyToUse()); + } + + public void testNormalOperation() { + ReflectionSaltSource saltSource = new ReflectionSaltSource(); + saltSource.setUserPropertyToUse("getUsername"); + + User user = new User("scott", "wombat", true, + new GrantedAuthority[] {new GrantedAuthorityImpl("HOLDER")}); + assertEquals("scott", saltSource.getSalt(user)); + } +} diff --git a/core/src/test/java/org/acegisecurity/providers/dao/salt/SystemWideSaltSourceTests.java b/core/src/test/java/org/acegisecurity/providers/dao/salt/SystemWideSaltSourceTests.java new file mode 100644 index 0000000000..9840202055 --- /dev/null +++ b/core/src/test/java/org/acegisecurity/providers/dao/salt/SystemWideSaltSourceTests.java @@ -0,0 +1,70 @@ +/* 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.dao.salt; + +import junit.framework.TestCase; + + +/** + * Tests {@link SystemWideSaltSource}. + * + * @author Ben Alex + * @version $Id$ + */ +public class SystemWideSaltSourceTests extends TestCase { + //~ Constructors =========================================================== + + public SystemWideSaltSourceTests() { + super(); + } + + public SystemWideSaltSourceTests(String arg0) { + super(arg0); + } + + //~ Methods ================================================================ + + public final void setUp() throws Exception { + super.setUp(); + } + + public static void main(String[] args) { + junit.textui.TestRunner.run(SystemWideSaltSourceTests.class); + } + + public void testDetectsMissingSystemWideSalt() throws Exception { + SystemWideSaltSource saltSource = new SystemWideSaltSource(); + + try { + saltSource.afterPropertiesSet(); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + assertEquals("A systemWideSalt must be set", expected.getMessage()); + } + } + + public void testGettersSetters() { + SystemWideSaltSource saltSource = new SystemWideSaltSource(); + saltSource.setSystemWideSalt("helloWorld"); + assertEquals("helloWorld", saltSource.getSystemWideSalt()); + } + + public void testNormalOperation() { + SystemWideSaltSource saltSource = new SystemWideSaltSource(); + saltSource.setSystemWideSalt("helloWorld"); + assertEquals("helloWorld", saltSource.getSalt(null)); + } +}