diff --git a/crypto/src/main/java/org/springframework/security/crypto/factory/PasswordEncoderFactories.java b/crypto/src/main/java/org/springframework/security/crypto/factory/PasswordEncoderFactories.java new file mode 100644 index 0000000000..0787840240 --- /dev/null +++ b/crypto/src/main/java/org/springframework/security/crypto/factory/PasswordEncoderFactories.java @@ -0,0 +1,65 @@ +/* + * Copyright 2002-2017 the original author or authors. + * + * 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 org.springframework.security.crypto.factory; + +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.DelegatingPasswordEncoder; +import org.springframework.security.crypto.password.NoOpPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder; +import org.springframework.security.crypto.password.StandardPasswordEncoder; +import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder; + +import java.util.HashMap; +import java.util.Map; + +/** + * Used for creating {@link PasswordEncoder} instances + * @author Rob Winch + * @since 5.0 + */ +public class PasswordEncoderFactories { + + /** + * Creates a {@link DelegatingPasswordEncoder} with default mappings. Additional + * mappings may be added and the encoding will be updated to conform with best + * practices. However, due to the nature of {@link DelegatingPasswordEncoder} the + * updates should not impact users. The mappings current are: + * + * + * + * @return the {@link PasswordEncoder} to use + */ + public static PasswordEncoder createDelegatingPasswordEncoder() { + String encodingId = "bcrypt"; + Map encoders = new HashMap<>(); + encoders.put(encodingId, new BCryptPasswordEncoder()); + encoders.put("noop", NoOpPasswordEncoder.getInstance()); + encoders.put("pbkdf2", new Pbkdf2PasswordEncoder()); + encoders.put("scrypt", new SCryptPasswordEncoder()); + encoders.put("sha256", new StandardPasswordEncoder()); + + return new DelegatingPasswordEncoder(encodingId, encoders); + } + + private PasswordEncoderFactories() {} +} diff --git a/crypto/src/main/java/org/springframework/security/crypto/password/DelegatingPasswordEncoder.java b/crypto/src/main/java/org/springframework/security/crypto/password/DelegatingPasswordEncoder.java index c42f020467..4161ce94b0 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/password/DelegatingPasswordEncoder.java +++ b/crypto/src/main/java/org/springframework/security/crypto/password/DelegatingPasswordEncoder.java @@ -27,6 +27,9 @@ import java.util.Map; * *

Constructing an instance

* + * You can easily construct an instance using + * {@link org.springframework.security.crypto.factory.PasswordEncoderFactories}. + * Alternatively, you may create your own custom instance. For example: * *
  * String idForEncode = "bcrypt";
@@ -73,7 +76,7 @@ import java.util.Map;
  * When matching it would delegate to
  * {@link org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder}
  * 
  • The second password would have a {@code PasswordEncoder} id of "noop" and - * encodedPassword of "thisisextremelyunsafe". When matching it would delegate to + * encodedPassword of "password". When matching it would delegate to * {@link NoOpPasswordEncoder}
  • *
  • The third password would have a {@code PasswordEncoder} id of "pbkdf2" and * encodedPassword of @@ -113,6 +116,8 @@ import java.util.Map; * {@link IllegalArgumentException}. This behavior can be customized using * {@link #setDefaultPasswordEncoderForMatches(PasswordEncoder)}. * + * @see org.springframework.security.crypto.factory.PasswordEncoderFactories + * * @author Rob Winch * @since 5.0 */ diff --git a/crypto/src/test/java/org/springframework/security/crypto/factory/PasswordEncoderFactoriesTests.java b/crypto/src/test/java/org/springframework/security/crypto/factory/PasswordEncoderFactoriesTests.java new file mode 100644 index 0000000000..7d796bb214 --- /dev/null +++ b/crypto/src/test/java/org/springframework/security/crypto/factory/PasswordEncoderFactoriesTests.java @@ -0,0 +1,71 @@ +/* + * Copyright 2002-2017 the original author or authors. + * + * 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 org.springframework.security.crypto.factory; + +import org.junit.Test; +import org.springframework.security.crypto.password.PasswordEncoder; + +import static org.assertj.core.api.Assertions.*; + +/** + * @author Rob Winch + * @since 5.0 + */ +public class PasswordEncoderFactoriesTests { + private PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder(); + + private String rawPassword = "password"; + + @Test + public void encodeWhenDefaultThenBCryptUsed() { + String encodedPassword = this.encoder.encode(this.rawPassword); + + assertThat(encodedPassword).startsWith("{bcrypt}"); + assertThat(this.encoder.matches(this.rawPassword, encodedPassword)).isTrue(); + } + + @Test + public void matchesWhenBCryptThenWorks() { + String encodedPassword = "{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG"; + assertThat(this.encoder.matches(this.rawPassword, encodedPassword)).isTrue(); + } + + @Test + public void matchesWhenNoopThenWorks() { + String encodedPassword = "{noop}password"; + assertThat(this.encoder.matches(this.rawPassword, encodedPassword)).isTrue(); + } + + @Test + public void matchesWhenPbkdf2ThenWorks() { + String encodedPassword = "{pbkdf2}5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc"; + assertThat(this.encoder.matches(this.rawPassword, encodedPassword)).isTrue(); + } + + @Test + public void matchesWhenSCryptThenWorks() { + String encodedPassword = "{scrypt}$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc="; + assertThat(this.encoder.matches(this.rawPassword, encodedPassword)).isTrue(); + } + + @Test + public void matchesWhenSha256ThenWorks() { + String encodedPassword = "{sha256}97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0"; + assertThat(this.encoder.matches(this.rawPassword, encodedPassword)).isTrue(); + } + +}