Add SHA256 as an algorithm option for Remember Me token hashing
Closes gh-8549
This commit is contained in:
@@ -16,8 +16,11 @@
|
||||
|
||||
package org.springframework.security.test.web;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Base64;
|
||||
|
||||
import org.springframework.security.crypto.codec.Hex;
|
||||
import org.springframework.util.DigestUtils;
|
||||
|
||||
public final class CodecTestUtils {
|
||||
@@ -52,4 +55,14 @@ public final class CodecTestUtils {
|
||||
return DigestUtils.md5DigestAsHex(data.getBytes());
|
||||
}
|
||||
|
||||
public static String algorithmHex(String algorithmName, String data) {
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance(algorithmName);
|
||||
return new String(Hex.encode(digest.digest(data.getBytes())));
|
||||
}
|
||||
catch (NoSuchAlgorithmException ex) {
|
||||
throw new IllegalStateException("No " + algorithmName + " algorithm available!");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -33,9 +33,12 @@ import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.test.web.CodecTestUtils;
|
||||
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices.RememberMeTokenAlgorithm;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
@@ -47,6 +50,7 @@ import static org.mockito.Mockito.mock;
|
||||
* .
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @author Marcus Da Coregio
|
||||
*/
|
||||
public class TokenBasedRememberMeServicesTests {
|
||||
|
||||
@@ -77,8 +81,8 @@ public class TokenBasedRememberMeServicesTests {
|
||||
|
||||
private long determineExpiryTimeFromBased64EncodedToken(String validToken) {
|
||||
String cookieAsPlainText = CodecTestUtils.decodeBase64(validToken);
|
||||
String[] cookieTokens = StringUtils.delimitedListToStringArray(cookieAsPlainText, ":");
|
||||
if (cookieTokens.length == 3) {
|
||||
String[] cookieTokens = getCookieTokens(cookieAsPlainText);
|
||||
if (isValidCookieTokensLength(cookieTokens)) {
|
||||
try {
|
||||
return Long.parseLong(cookieTokens[1]);
|
||||
}
|
||||
@@ -88,15 +92,52 @@ public class TokenBasedRememberMeServicesTests {
|
||||
return -1;
|
||||
}
|
||||
|
||||
private String generateCorrectCookieContentForToken(long expiryTime, String username, String password, String key) {
|
||||
private String[] getCookieTokens(String cookieAsPlainText) {
|
||||
return StringUtils.delimitedListToStringArray(cookieAsPlainText, ":");
|
||||
}
|
||||
|
||||
private String determineAlgorithmNameFromBase64EncodedToken(String validToken) {
|
||||
String cookieAsPlainText = CodecTestUtils.decodeBase64(validToken);
|
||||
String[] cookieTokens = getCookieTokens(cookieAsPlainText);
|
||||
if (isValidCookieTokensLength(cookieTokens)) {
|
||||
return cookieTokens[2];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isValidCookieTokensLength(String[] cookieTokens) {
|
||||
return cookieTokens.length == 3 || cookieTokens.length == 4;
|
||||
}
|
||||
|
||||
private String generateCorrectCookieContentForTokenNoAlgorithmName(long expiryTime, String username,
|
||||
String password, String key) {
|
||||
return generateCorrectCookieContentForTokenWithAlgorithmName(expiryTime, username, password, key,
|
||||
RememberMeTokenAlgorithm.MD5);
|
||||
}
|
||||
|
||||
private String generateCorrectCookieContentForTokenNoAlgorithmName(long expiryTime, String username,
|
||||
String password, String key, RememberMeTokenAlgorithm algorithm) {
|
||||
// format is:
|
||||
// username + ":" + expiryTime + ":" + Md5Hex(username + ":" + expiryTime + ":" +
|
||||
// password + ":" + key)
|
||||
String signatureValue = CodecTestUtils.md5Hex(username + ":" + expiryTime + ":" + password + ":" + key);
|
||||
String signatureValue = CodecTestUtils.algorithmHex(algorithm.getDigestAlgorithm(),
|
||||
username + ":" + expiryTime + ":" + password + ":" + key);
|
||||
String tokenValue = username + ":" + expiryTime + ":" + signatureValue;
|
||||
return CodecTestUtils.encodeBase64(tokenValue);
|
||||
}
|
||||
|
||||
private String generateCorrectCookieContentForTokenWithAlgorithmName(long expiryTime, String username,
|
||||
String password, String key, RememberMeTokenAlgorithm algorithm) {
|
||||
// format is:
|
||||
// username + ":" + expiryTime + ":" + algorithmName + ":" + algorithmHex(username
|
||||
// + ":" + expiryTime + ":" +
|
||||
// password + ":" + key)
|
||||
String signatureValue = CodecTestUtils.algorithmHex(algorithm.getDigestAlgorithm(),
|
||||
username + ":" + expiryTime + ":" + password + ":" + key);
|
||||
String tokenValue = username + ":" + expiryTime + ":" + algorithm.name() + ":" + signatureValue;
|
||||
return CodecTestUtils.encodeBase64(tokenValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void autoLoginReturnsNullIfNoCookiePresented() {
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
@@ -120,8 +161,8 @@ public class TokenBasedRememberMeServicesTests {
|
||||
@Test
|
||||
public void autoLoginReturnsNullForExpiredCookieAndClearsCookie() {
|
||||
Cookie cookie = new Cookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY,
|
||||
generateCorrectCookieContentForToken(System.currentTimeMillis() - 1000000, "someone", "password",
|
||||
"key"));
|
||||
generateCorrectCookieContentForTokenNoAlgorithmName(System.currentTimeMillis() - 1000000, "someone",
|
||||
"password", "key"));
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setCookies(cookie);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
@@ -161,8 +202,8 @@ public class TokenBasedRememberMeServicesTests {
|
||||
public void autoLoginClearsCookieIfSignatureBlocksDoesNotMatchExpectedValue() {
|
||||
udsWillReturnUser();
|
||||
Cookie cookie = new Cookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY,
|
||||
generateCorrectCookieContentForToken(System.currentTimeMillis() + 1000000, "someone", "password",
|
||||
"WRONG_KEY"));
|
||||
generateCorrectCookieContentForTokenNoAlgorithmName(System.currentTimeMillis() + 1000000, "someone",
|
||||
"password", "WRONG_KEY"));
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setCookies(cookie);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
@@ -189,8 +230,8 @@ public class TokenBasedRememberMeServicesTests {
|
||||
public void autoLoginClearsCookieIfUserNotFound() {
|
||||
udsWillThrowNotFound();
|
||||
Cookie cookie = new Cookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY,
|
||||
generateCorrectCookieContentForToken(System.currentTimeMillis() + 1000000, "someone", "password",
|
||||
"key"));
|
||||
generateCorrectCookieContentForTokenNoAlgorithmName(System.currentTimeMillis() + 1000000, "someone",
|
||||
"password", "key"));
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setCookies(cookie);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
@@ -204,8 +245,8 @@ public class TokenBasedRememberMeServicesTests {
|
||||
public void autoLoginClearsCookieIfUserServiceMisconfigured() {
|
||||
udsWillReturnNull();
|
||||
Cookie cookie = new Cookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY,
|
||||
generateCorrectCookieContentForToken(System.currentTimeMillis() + 1000000, "someone", "password",
|
||||
"key"));
|
||||
generateCorrectCookieContentForTokenNoAlgorithmName(System.currentTimeMillis() + 1000000, "someone",
|
||||
"password", "key"));
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setCookies(cookie);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
@@ -216,8 +257,8 @@ public class TokenBasedRememberMeServicesTests {
|
||||
public void autoLoginWithValidTokenAndUserSucceeds() {
|
||||
udsWillReturnUser();
|
||||
Cookie cookie = new Cookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY,
|
||||
generateCorrectCookieContentForToken(System.currentTimeMillis() + 1000000, "someone", "password",
|
||||
"key"));
|
||||
generateCorrectCookieContentForTokenNoAlgorithmName(System.currentTimeMillis() + 1000000, "someone",
|
||||
"password", "key"));
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setCookies(cookie);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
@@ -226,6 +267,68 @@ public class TokenBasedRememberMeServicesTests {
|
||||
assertThat(result.getPrincipal()).isEqualTo(this.user);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void autoLoginWhenTokenNoAlgorithmAndDifferentMatchingAlgorithmThenReturnsNullAndClearCookie() {
|
||||
udsWillReturnUser();
|
||||
Cookie cookie = new Cookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY,
|
||||
generateCorrectCookieContentForTokenNoAlgorithmName(System.currentTimeMillis() + 1000000, "someone",
|
||||
"password", "key", RememberMeTokenAlgorithm.MD5));
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setCookies(cookie);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
this.services.setMatchingAlgorithm(RememberMeTokenAlgorithm.SHA256);
|
||||
Authentication result = this.services.autoLogin(request, response);
|
||||
Cookie returnedCookie = response.getCookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY);
|
||||
assertThat(result).isNull();
|
||||
assertThat(returnedCookie).isNotNull();
|
||||
assertThat(returnedCookie.getMaxAge()).isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void autoLoginWhenTokenNoAlgorithmAndSameMatchingAlgorithmThenSucceeds() {
|
||||
udsWillReturnUser();
|
||||
Cookie cookie = new Cookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY,
|
||||
generateCorrectCookieContentForTokenNoAlgorithmName(System.currentTimeMillis() + 1000000, "someone",
|
||||
"password", "key", RememberMeTokenAlgorithm.SHA256));
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setCookies(cookie);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
this.services.setMatchingAlgorithm(RememberMeTokenAlgorithm.SHA256);
|
||||
Authentication result = this.services.autoLogin(request, response);
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.getPrincipal()).isEqualTo(this.user);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void autoLoginWhenTokenHasAlgorithmAndSameMatchingAlgorithmThenUsesTokenAlgorithmAndSucceeds() {
|
||||
udsWillReturnUser();
|
||||
Cookie cookie = new Cookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY,
|
||||
generateCorrectCookieContentForTokenWithAlgorithmName(System.currentTimeMillis() + 1000000, "someone",
|
||||
"password", "key", RememberMeTokenAlgorithm.SHA256));
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setCookies(cookie);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
this.services.setMatchingAlgorithm(RememberMeTokenAlgorithm.SHA256);
|
||||
Authentication result = this.services.autoLogin(request, response);
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.getPrincipal()).isEqualTo(this.user);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void autoLoginWhenTokenHasAlgorithmAndDifferentMatchingAlgorithmThenUsesTokenAlgorithmAndSucceeds() {
|
||||
udsWillReturnUser();
|
||||
Cookie cookie = new Cookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY,
|
||||
generateCorrectCookieContentForTokenWithAlgorithmName(System.currentTimeMillis() + 1000000, "someone",
|
||||
"password", "key", RememberMeTokenAlgorithm.SHA256));
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setCookies(cookie);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
this.services.setMatchingAlgorithm(RememberMeTokenAlgorithm.MD5);
|
||||
Authentication result = this.services.autoLogin(request, response);
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.getPrincipal()).isEqualTo(this.user);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGettersSetters() {
|
||||
assertThat(this.services.getUserDetailsService()).isEqualTo(this.uds);
|
||||
@@ -293,6 +396,37 @@ public class TokenBasedRememberMeServicesTests {
|
||||
assertThat(new Date().before(new Date(determineExpiryTimeFromBased64EncodedToken(cookie.getValue())))).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loginSuccessWhenDefaultEncodingAlgorithmThenContainsAlgorithmName() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.addParameter(AbstractRememberMeServices.DEFAULT_PARAMETER, "true");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
this.services.loginSuccess(request, response,
|
||||
new TestingAuthenticationToken("someone", "password", "ROLE_ABC"));
|
||||
Cookie cookie = response.getCookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY);
|
||||
assertThat(cookie).isNotNull();
|
||||
assertThat(cookie.getMaxAge()).isEqualTo(this.services.getTokenValiditySeconds());
|
||||
assertThat(CodecTestUtils.isBase64(cookie.getValue().getBytes())).isTrue();
|
||||
assertThat(new Date().before(new Date(determineExpiryTimeFromBased64EncodedToken(cookie.getValue())))).isTrue();
|
||||
assertThat("MD5").isEqualTo(determineAlgorithmNameFromBase64EncodedToken(cookie.getValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loginSuccessWhenCustomEncodingAlgorithmThenContainsAlgorithmName() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.addParameter(AbstractRememberMeServices.DEFAULT_PARAMETER, "true");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
this.services = new TokenBasedRememberMeServices("key", this.uds, RememberMeTokenAlgorithm.SHA256);
|
||||
this.services.loginSuccess(request, response,
|
||||
new TestingAuthenticationToken("someone", "password", "ROLE_ABC"));
|
||||
Cookie cookie = response.getCookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY);
|
||||
assertThat(cookie).isNotNull();
|
||||
assertThat(cookie.getMaxAge()).isEqualTo(this.services.getTokenValiditySeconds());
|
||||
assertThat(CodecTestUtils.isBase64(cookie.getValue().getBytes())).isTrue();
|
||||
assertThat(new Date().before(new Date(determineExpiryTimeFromBased64EncodedToken(cookie.getValue())))).isTrue();
|
||||
assertThat("SHA256").isEqualTo(determineAlgorithmNameFromBase64EncodedToken(cookie.getValue()));
|
||||
}
|
||||
|
||||
// SEC-933
|
||||
@Test
|
||||
public void obtainPasswordReturnsNullForTokenWithNullCredentials() {
|
||||
@@ -318,4 +452,19 @@ public class TokenBasedRememberMeServicesTests {
|
||||
assertThat(CodecTestUtils.isBase64(cookie.getValue().getBytes())).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenEncodingAlgorithmNullThenException() {
|
||||
assertThatExceptionOfType(IllegalArgumentException.class)
|
||||
.isThrownBy(() -> new TokenBasedRememberMeServices("key", this.uds, null))
|
||||
.withMessage("encodingAlgorithm cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenNoEncodingAlgorithmSpecifiedThenMd5() {
|
||||
TokenBasedRememberMeServices rememberMeServices = new TokenBasedRememberMeServices("key", this.uds);
|
||||
RememberMeTokenAlgorithm encodingAlgorithm = (RememberMeTokenAlgorithm) ReflectionTestUtils
|
||||
.getField(rememberMeServices, "encodingAlgorithm");
|
||||
assertThat(encodingAlgorithm).isSameAs(RememberMeTokenAlgorithm.MD5);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user