Support for changing prefix and suffix in DelegatingPasswordEncoder
Closes gh-10273
This commit is contained in:
+41
-12
@@ -116,14 +116,19 @@ import java.util.Map;
|
|||||||
*
|
*
|
||||||
* @author Rob Winch
|
* @author Rob Winch
|
||||||
* @author Michael Simons
|
* @author Michael Simons
|
||||||
|
* @author heowc
|
||||||
* @since 5.0
|
* @since 5.0
|
||||||
* @see org.springframework.security.crypto.factory.PasswordEncoderFactories
|
* @see org.springframework.security.crypto.factory.PasswordEncoderFactories
|
||||||
*/
|
*/
|
||||||
public class DelegatingPasswordEncoder implements PasswordEncoder {
|
public class DelegatingPasswordEncoder implements PasswordEncoder {
|
||||||
|
|
||||||
private static final String PREFIX = "{";
|
private static final String DEFAULT_PREFIX = "{";
|
||||||
|
|
||||||
private static final String SUFFIX = "}";
|
private static final String DEFAULT_SUFFIX = "}";
|
||||||
|
|
||||||
|
private final String prefix;
|
||||||
|
|
||||||
|
private final String suffix;
|
||||||
|
|
||||||
private final String idForEncode;
|
private final String idForEncode;
|
||||||
|
|
||||||
@@ -142,9 +147,31 @@ public class DelegatingPasswordEncoder implements PasswordEncoder {
|
|||||||
* {@link #matches(CharSequence, String)}
|
* {@link #matches(CharSequence, String)}
|
||||||
*/
|
*/
|
||||||
public DelegatingPasswordEncoder(String idForEncode, Map<String, PasswordEncoder> idToPasswordEncoder) {
|
public DelegatingPasswordEncoder(String idForEncode, Map<String, PasswordEncoder> idToPasswordEncoder) {
|
||||||
|
this(idForEncode, idToPasswordEncoder, DEFAULT_PREFIX, DEFAULT_SUFFIX);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance
|
||||||
|
* @param idForEncode the id used to lookup which {@link PasswordEncoder} should be
|
||||||
|
* used for {@link #encode(CharSequence)}
|
||||||
|
* @param idToPasswordEncoder a Map of id to {@link PasswordEncoder} used to determine
|
||||||
|
* which {@link PasswordEncoder} should be used for
|
||||||
|
* @param prefix the prefix that denotes the start of an {@code idForEncode}
|
||||||
|
* @param suffix the suffix that denotes the end of an {@code idForEncode}
|
||||||
|
* {@link #matches(CharSequence, String)}
|
||||||
|
*/
|
||||||
|
public DelegatingPasswordEncoder(String idForEncode, Map<String, PasswordEncoder> idToPasswordEncoder,
|
||||||
|
String prefix, String suffix) {
|
||||||
if (idForEncode == null) {
|
if (idForEncode == null) {
|
||||||
throw new IllegalArgumentException("idForEncode cannot be null");
|
throw new IllegalArgumentException("idForEncode cannot be null");
|
||||||
}
|
}
|
||||||
|
if (prefix == null) {
|
||||||
|
throw new IllegalArgumentException("prefix cannot be null");
|
||||||
|
}
|
||||||
|
if (suffix == null || suffix.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("suffix cannot be empty");
|
||||||
|
}
|
||||||
|
|
||||||
if (!idToPasswordEncoder.containsKey(idForEncode)) {
|
if (!idToPasswordEncoder.containsKey(idForEncode)) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"idForEncode " + idForEncode + "is not found in idToPasswordEncoder " + idToPasswordEncoder);
|
"idForEncode " + idForEncode + "is not found in idToPasswordEncoder " + idToPasswordEncoder);
|
||||||
@@ -153,16 +180,18 @@ public class DelegatingPasswordEncoder implements PasswordEncoder {
|
|||||||
if (id == null) {
|
if (id == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (id.contains(PREFIX)) {
|
if (!prefix.isEmpty() && id.contains(prefix)) {
|
||||||
throw new IllegalArgumentException("id " + id + " cannot contain " + PREFIX);
|
throw new IllegalArgumentException("id " + id + " cannot contain " + prefix);
|
||||||
}
|
}
|
||||||
if (id.contains(SUFFIX)) {
|
if (id.contains(suffix)) {
|
||||||
throw new IllegalArgumentException("id " + id + " cannot contain " + SUFFIX);
|
throw new IllegalArgumentException("id " + id + " cannot contain " + suffix);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.idForEncode = idForEncode;
|
this.idForEncode = idForEncode;
|
||||||
this.passwordEncoderForEncode = idToPasswordEncoder.get(idForEncode);
|
this.passwordEncoderForEncode = idToPasswordEncoder.get(idForEncode);
|
||||||
this.idToPasswordEncoder = new HashMap<>(idToPasswordEncoder);
|
this.idToPasswordEncoder = new HashMap<>(idToPasswordEncoder);
|
||||||
|
this.prefix = prefix;
|
||||||
|
this.suffix = suffix;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -188,7 +217,7 @@ public class DelegatingPasswordEncoder implements PasswordEncoder {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String encode(CharSequence rawPassword) {
|
public String encode(CharSequence rawPassword) {
|
||||||
return PREFIX + this.idForEncode + SUFFIX + this.passwordEncoderForEncode.encode(rawPassword);
|
return this.prefix + this.idForEncode + this.suffix + this.passwordEncoderForEncode.encode(rawPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -209,15 +238,15 @@ public class DelegatingPasswordEncoder implements PasswordEncoder {
|
|||||||
if (prefixEncodedPassword == null) {
|
if (prefixEncodedPassword == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
int start = prefixEncodedPassword.indexOf(PREFIX);
|
int start = prefixEncodedPassword.indexOf(this.prefix);
|
||||||
if (start != 0) {
|
if (start != 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
int end = prefixEncodedPassword.indexOf(SUFFIX, start);
|
int end = prefixEncodedPassword.indexOf(this.suffix, start);
|
||||||
if (end < 0) {
|
if (end < 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return prefixEncodedPassword.substring(start + 1, end);
|
return prefixEncodedPassword.substring(start + this.prefix.length(), end);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -233,8 +262,8 @@ public class DelegatingPasswordEncoder implements PasswordEncoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String extractEncodedPassword(String prefixEncodedPassword) {
|
private String extractEncodedPassword(String prefixEncodedPassword) {
|
||||||
int start = prefixEncodedPassword.indexOf(SUFFIX);
|
int start = prefixEncodedPassword.indexOf(this.suffix);
|
||||||
return prefixEncodedPassword.substring(start + 1);
|
return prefixEncodedPassword.substring(start + this.suffix.length());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+62
@@ -36,6 +36,7 @@ import static org.mockito.Mockito.verifyZeroInteractions;
|
|||||||
/**
|
/**
|
||||||
* @author Rob Winch
|
* @author Rob Winch
|
||||||
* @author Michael Simons
|
* @author Michael Simons
|
||||||
|
* @author heowc
|
||||||
* @since 5.0
|
* @since 5.0
|
||||||
*/
|
*/
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
@@ -64,12 +65,16 @@ public class DelegatingPasswordEncoderTests {
|
|||||||
|
|
||||||
private DelegatingPasswordEncoder passwordEncoder;
|
private DelegatingPasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
private DelegatingPasswordEncoder onlySuffixPasswordEncoder;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void setup() {
|
public void setup() {
|
||||||
this.delegates = new HashMap<>();
|
this.delegates = new HashMap<>();
|
||||||
this.delegates.put(this.bcryptId, this.bcrypt);
|
this.delegates.put(this.bcryptId, this.bcrypt);
|
||||||
this.delegates.put("noop", this.noop);
|
this.delegates.put("noop", this.noop);
|
||||||
this.passwordEncoder = new DelegatingPasswordEncoder(this.bcryptId, this.delegates);
|
this.passwordEncoder = new DelegatingPasswordEncoder(this.bcryptId, this.delegates);
|
||||||
|
|
||||||
|
this.onlySuffixPasswordEncoder = new DelegatingPasswordEncoder(this.bcryptId, this.delegates, "", "$");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -83,6 +88,49 @@ public class DelegatingPasswordEncoderTests {
|
|||||||
.isThrownBy(() -> new DelegatingPasswordEncoder(this.bcryptId + "INVALID", this.delegates));
|
.isThrownBy(() -> new DelegatingPasswordEncoder(this.bcryptId + "INVALID", this.delegates));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenPrefixIsNull() {
|
||||||
|
assertThatIllegalArgumentException()
|
||||||
|
.isThrownBy(() -> new DelegatingPasswordEncoder(this.bcryptId, this.delegates, null, "$"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenSuffixIsNull() {
|
||||||
|
assertThatIllegalArgumentException()
|
||||||
|
.isThrownBy(() -> new DelegatingPasswordEncoder(this.bcryptId, this.delegates, "$", null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenPrefixIsEmpty() {
|
||||||
|
assertThat(new DelegatingPasswordEncoder(this.bcryptId, this.delegates, "", "$")).isNotNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenSuffixIsEmpty() {
|
||||||
|
assertThatIllegalArgumentException()
|
||||||
|
.isThrownBy(() -> new DelegatingPasswordEncoder(this.bcryptId, this.delegates, "$", ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenPrefixAndSuffixAreEmpty() {
|
||||||
|
assertThatIllegalArgumentException()
|
||||||
|
.isThrownBy(() -> new DelegatingPasswordEncoder(this.bcryptId, this.delegates, "", ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenIdContainsPrefixThenIllegalArgumentException() {
|
||||||
|
this.delegates.put('$' + this.bcryptId, this.bcrypt);
|
||||||
|
assertThatIllegalArgumentException()
|
||||||
|
.isThrownBy(() -> new DelegatingPasswordEncoder(this.bcryptId, this.delegates, "$", "$"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenIdContainsSuffixThenIllegalArgumentException() {
|
||||||
|
this.delegates.put(this.bcryptId + '$', this.bcrypt);
|
||||||
|
assertThatIllegalArgumentException()
|
||||||
|
.isThrownBy(() -> new DelegatingPasswordEncoder(this.bcryptId, this.delegates, "", "$"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void setDefaultPasswordEncoderForMatchesWhenNullThenIllegalArgumentException() {
|
public void setDefaultPasswordEncoderForMatchesWhenNullThenIllegalArgumentException() {
|
||||||
assertThatIllegalArgumentException()
|
assertThatIllegalArgumentException()
|
||||||
@@ -104,6 +152,12 @@ public class DelegatingPasswordEncoderTests {
|
|||||||
assertThat(this.passwordEncoder.encode(this.rawPassword)).isEqualTo(this.bcryptEncodedPassword);
|
assertThat(this.passwordEncoder.encode(this.rawPassword)).isEqualTo(this.bcryptEncodedPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void encodeWhenValidBySpecifyDelegatingPasswordEncoderThenUsesIdForEncode() {
|
||||||
|
given(this.bcrypt.encode(this.rawPassword)).willReturn(this.encodedPassword);
|
||||||
|
assertThat(this.onlySuffixPasswordEncoder.encode(this.rawPassword)).isEqualTo("bcrypt$" + this.encodedPassword);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void matchesWhenBCryptThenDelegatesToBCrypt() {
|
public void matchesWhenBCryptThenDelegatesToBCrypt() {
|
||||||
given(this.bcrypt.matches(this.rawPassword, this.encodedPassword)).willReturn(true);
|
given(this.bcrypt.matches(this.rawPassword, this.encodedPassword)).willReturn(true);
|
||||||
@@ -112,6 +166,14 @@ public class DelegatingPasswordEncoderTests {
|
|||||||
verifyZeroInteractions(this.noop);
|
verifyZeroInteractions(this.noop);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void matchesWhenBCryptBySpecifyDelegatingPasswordEncoderThenDelegatesToBCrypt() {
|
||||||
|
given(this.bcrypt.matches(this.rawPassword, this.encodedPassword)).willReturn(true);
|
||||||
|
assertThat(this.onlySuffixPasswordEncoder.matches(this.rawPassword, "bcrypt$" + this.encodedPassword)).isTrue();
|
||||||
|
verify(this.bcrypt).matches(this.rawPassword, this.encodedPassword);
|
||||||
|
verifyZeroInteractions(this.noop);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void matchesWhenNoopThenDelegatesToNoop() {
|
public void matchesWhenNoopThenDelegatesToNoop() {
|
||||||
given(this.noop.matches(this.rawPassword, this.encodedPassword)).willReturn(true);
|
given(this.noop.matches(this.rawPassword, this.encodedPassword)).willReturn(true);
|
||||||
|
|||||||
Reference in New Issue
Block a user