Parcourir la source

Make salt sources pluggable.

Ben Alex il y a 21 ans
Parent
commit
da5101cfb4

+ 27 - 2
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.  <code>null</code>
+     * is a valid value, meaning the <code>DaoAuthenticationProvider</code>
+     * will present <code>null</code> to the relevant
+     * <code>PasswordEncoder</code>.
+     *
+     * @param saltSource to use when attempting to decode passwords via  the
+     *        <code>PasswordEncoder</code>
+     */
+    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");
         }
 

+ 35 - 0
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 <code>AuthenticationDao</code>
+     *
+     * @return the salt to use for this <code>USer</code>
+     */
+    public Object getSalt(User user);
+}

+ 89 - 0
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.
+ * 
+ * <P>
+ * This allows you to subclass <code>User</code> 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 <code>username</code> if it is
+ * likely to change.
+ * </p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class ReflectionSaltSource implements SaltSource, InitializingBean {
+    //~ Instance fields ========================================================
+
+    private String userPropertyToUse;
+
+    //~ Methods ================================================================
+
+    /**
+     * Performs reflection on the passed <code>User</code> to obtain the salt.
+     * 
+     * <P>
+     * The property identified by <code>userPropertyToUse</code> must be
+     * available from the passed <code>User</code> object. If it is not
+     * available, an {@link AuthenticationServiceException} will be thrown.
+     * </p>
+     *
+     * @param user which contains the method identified by
+     *        <code>userPropertyToUse</code>
+     *
+     * @return the result of invoking <code>user.userPropertyToUse()</code>
+     *
+     * @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");
+        }
+    }
+}

+ 61 - 0
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 <code>String</code> as the salt.
+ * 
+ * <P>
+ * 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.
+ * </p>
+ *
+ * @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");
+        }
+    }
+}

+ 94 - 0
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));
+    }
+}

+ 70 - 0
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));
+    }
+}