Add SHA256 as an algorithm option for Remember Me token hashing

Closes gh-8549
This commit is contained in:
Marcus Da Coregio 2022-06-30 14:25:09 -03:00 committed by Marcus Hert Da Coregio
parent 0e291a3295
commit e17fe8ced9
4 changed files with 303 additions and 32 deletions

View File

@ -19,13 +19,14 @@ In essence, a cookie is sent to the browser upon successful interactive authenti
====
[source,txt]
----
base64(username + ":" + expirationTime + ":" +
md5Hex(username + ":" + expirationTime + ":" password + ":" + key))
base64(username + ":" + expirationTime + ":" + algorithmName + ":"
algorithmHex(username + ":" + expirationTime + ":" password + ":" + key))
username: As identifiable to the UserDetailsService
password: That matches the one in the retrieved UserDetails
expirationTime: The date and time when the remember-me token expires, expressed in milliseconds
key: A private key to prevent modification of the remember-me token
algorithmName: The algorithm used to generate and to verify the remember-me token signature
----
====
@ -113,6 +114,53 @@ A `key` is shared between this authentication provider and the `TokenBasedRememb
In addition, `TokenBasedRememberMeServices` requires a `UserDetailsService`, from which it can retrieve the username and password for signature comparison purposes and generate the `RememberMeAuthenticationToken` to contain the correct `GrantedAuthority` instances.
`TokenBasedRememberMeServices` also implements Spring Security's `LogoutHandler` interface so that it can be used with `LogoutFilter` to have the cookie cleared automatically.
By default, this implementation uses the MD5 algorithm to encode the token signature.
To verify the token signature, the algorithm retrieved from `algorithmName` is parsed and used.
If no `algorithmName` is present, the default matching algorithm will be used, which is MD5.
You can specify different algorithms for signature encoding and for signature matching, this allows users to safely upgrade to a different encoding algorithm while still able to verify old ones if there is no `algorithmName` present.
To do that you can specify your customized `TokenBasedRememberMeServices` as a Bean and use it in the configuration.
====
.Java
[source,java,role="primary"]
----
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http, RememberMeServices rememberMeServices) throws Exception {
http
.authorizeRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.rememberMe((remember) -> remember
.rememberMeServices(rememberMeServices)
);
return http.build();
}
@Bean
RememberMeServices rememberMeServices(UserDetailsService userDetailsService) {
TokenBasedRememberMeServices rememberMe = new TokenBasedRememberMeServices(myKey, userDetailsService);
rememberMe.setMatchingAlgorithm(RememberMeTokenAlgorithm.MD5);
rememberMe.setEncodingAlgorithm(RememberMeTokenAlgorithm.SHA256);
return rememberMe;
}
----
.XML
[source,xml,role="secondary"]
----
<http>
<remember-me services-ref="rememberMeServices"/>
</http>
<bean id="rememberMeServices" class=
"org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
<property name="userDetailsService" ref="myUserDetailsService"/>
<property name="key" value="springRocks"/>
<property name="matchingAlgorithm" value="MD5"/>
<property name="encodingAlgorithm" value="SHA256"/>
</bean>
----
====
The following beans are required in an application context to enable remember-me services:
====

View File

@ -54,11 +54,21 @@ import org.springframework.util.StringUtils;
* The cookie encoded by this implementation adopts the following form:
*
* <pre>
* username + &quot;:&quot; + expiryTime + &quot;:&quot;
* + Md5Hex(username + &quot;:&quot; + expiryTime + &quot;:&quot; + password + &quot;:&quot; + key)
* username + &quot;:&quot; + expiryTime + &quot;:&quot; + algorithmName + &quot;:&quot;
* + algorithmHex(username + &quot;:&quot; + expiryTime + &quot;:&quot; + password + &quot;:&quot; + key)
* </pre>
*
* <p>
* This implementation uses the algorithm configured in {@link #encodingAlgorithm} to
* encode the signature. It will try to use the algorithm retrieved from the
* {@code algorithmName} to validate the signature. However, if the {@code algorithmName}
* is not present in the cookie value, the algorithm configured in
* {@link #matchingAlgorithm} will be used to validate the signature. This allows users to
* safely upgrade to a different encoding algorithm while still able to verify old ones if
* there is no {@code algorithmName} present.
* </p>
*
* <p>
* As such, if the user changes their password, any remember-me token will be invalidated.
* Equally, the system administrator may invalidate every remember-me token on issue by
* changing the key. This provides some reasonable approaches to recovering from a
@ -83,6 +93,14 @@ import org.springframework.util.StringUtils;
*/
public class TokenBasedRememberMeServices extends AbstractRememberMeServices {
private static final RememberMeTokenAlgorithm DEFAULT_MATCHING_ALGORITHM = RememberMeTokenAlgorithm.MD5;
private static final RememberMeTokenAlgorithm DEFAULT_ENCODING_ALGORITHM = RememberMeTokenAlgorithm.MD5;
private RememberMeTokenAlgorithm matchingAlgorithm = DEFAULT_MATCHING_ALGORITHM;
private RememberMeTokenAlgorithm encodingAlgorithm = DEFAULT_ENCODING_ALGORITHM;
public TokenBasedRememberMeServices(String key, UserDetailsService userDetailsService) {
super(key, userDetailsService);
}
@ -90,9 +108,9 @@ public class TokenBasedRememberMeServices extends AbstractRememberMeServices {
@Override
protected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request,
HttpServletResponse response) {
if (cookieTokens.length != 3) {
if (!isValidCookieTokensLength(cookieTokens)) {
throw new InvalidCookieException(
"Cookie token did not contain 3" + " tokens, but contained '" + Arrays.asList(cookieTokens) + "'");
"Cookie token did not contain 3 or 4 tokens, but contained '" + Arrays.asList(cookieTokens) + "'");
}
long tokenExpiryTime = getTokenExpiryTime(cookieTokens);
if (isTokenExpired(tokenExpiryTime)) {
@ -110,15 +128,27 @@ public class TokenBasedRememberMeServices extends AbstractRememberMeServices {
// only called once per HttpSession - if the token is valid, it will cause
// SecurityContextHolder population, whilst if invalid, will cause the cookie to
// be cancelled.
String actualTokenSignature = cookieTokens[2];
RememberMeTokenAlgorithm actualAlgorithm = this.matchingAlgorithm;
// If the cookie value contains the algorithm, we use that algorithm to check the
// signature
if (cookieTokens.length == 4) {
actualTokenSignature = cookieTokens[3];
actualAlgorithm = RememberMeTokenAlgorithm.valueOf(cookieTokens[2]);
}
String expectedTokenSignature = makeTokenSignature(tokenExpiryTime, userDetails.getUsername(),
userDetails.getPassword());
if (!equals(expectedTokenSignature, cookieTokens[2])) {
throw new InvalidCookieException("Cookie token[2] contained signature '" + cookieTokens[2]
+ "' but expected '" + expectedTokenSignature + "'");
userDetails.getPassword(), actualAlgorithm);
if (!equals(expectedTokenSignature, actualTokenSignature)) {
throw new InvalidCookieException("Cookie contained signature '" + actualTokenSignature + "' but expected '"
+ expectedTokenSignature + "'");
}
return userDetails;
}
private boolean isValidCookieTokensLength(String[] cookieTokens) {
return cookieTokens.length == 3 || cookieTokens.length == 4;
}
private long getTokenExpiryTime(String[] cookieTokens) {
try {
return new Long(cookieTokens[1]);
@ -130,17 +160,32 @@ public class TokenBasedRememberMeServices extends AbstractRememberMeServices {
}
/**
* Calculates the digital signature to be put in the cookie. Default value is MD5
* ("username:tokenExpiryTime:password:key")
* Calculates the digital signature to be put in the cookie. Default value is
* {@link #encodingAlgorithm} applied to ("username:tokenExpiryTime:password:key")
*/
protected String makeTokenSignature(long tokenExpiryTime, String username, String password) {
String data = username + ":" + tokenExpiryTime + ":" + password + ":" + getKey();
try {
MessageDigest digest = MessageDigest.getInstance("MD5");
MessageDigest digest = MessageDigest.getInstance(this.encodingAlgorithm.getDigestAlgorithm());
return new String(Hex.encode(digest.digest(data.getBytes())));
}
catch (NoSuchAlgorithmException ex) {
throw new IllegalStateException("No MD5 algorithm available!");
throw new IllegalStateException("No " + this.encodingAlgorithm.name() + " algorithm available!");
}
}
/**
* Calculates the digital signature to be put in the cookie.
*/
protected String makeTokenSignature(long tokenExpiryTime, String username, String password,
RememberMeTokenAlgorithm algorithm) {
String data = username + ":" + tokenExpiryTime + ":" + password + ":" + getKey();
try {
MessageDigest digest = MessageDigest.getInstance(algorithm.getDigestAlgorithm());
return new String(Hex.encode(digest.digest(data.getBytes())));
}
catch (NoSuchAlgorithmException ex) {
throw new IllegalStateException("No " + algorithm.name() + " algorithm available!");
}
}
@ -172,15 +217,33 @@ public class TokenBasedRememberMeServices extends AbstractRememberMeServices {
long expiryTime = System.currentTimeMillis();
// SEC-949
expiryTime += 1000L * ((tokenLifetime < 0) ? TWO_WEEKS_S : tokenLifetime);
String signatureValue = makeTokenSignature(expiryTime, username, password);
setCookie(new String[] { username, Long.toString(expiryTime), signatureValue }, tokenLifetime, request,
response);
String signatureValue = makeTokenSignature(expiryTime, username, password, this.encodingAlgorithm);
setCookie(new String[] { username, Long.toString(expiryTime), this.encodingAlgorithm.name(), signatureValue },
tokenLifetime, request, response);
if (this.logger.isDebugEnabled()) {
this.logger.debug(
"Added remember-me cookie for user '" + username + "', expiry: '" + new Date(expiryTime) + "'");
}
}
/**
* Sets the algorithm to be used to encode the token signature
* @param encodingAlgorithm the encoding algorithm
*/
public void setEncodingAlgorithm(RememberMeTokenAlgorithm encodingAlgorithm) {
Assert.notNull(encodingAlgorithm, "encodingAlgorithm cannot be null");
this.encodingAlgorithm = encodingAlgorithm;
}
/**
* Sets the algorithm to be used to match the token signature
* @param matchingAlgorithm the matching algorithm
*/
public void setMatchingAlgorithm(RememberMeTokenAlgorithm matchingAlgorithm) {
Assert.notNull(matchingAlgorithm, "matchingAlgorithm cannot be null");
this.matchingAlgorithm = matchingAlgorithm;
}
/**
* Calculates the validity period in seconds for a newly generated remember-me login.
* After this period (from the current time) the remember-me login will be considered
@ -190,7 +253,7 @@ public class TokenBasedRememberMeServices extends AbstractRememberMeServices {
* <p>
* The returned value will be used to work out the expiry time of the token and will
* also be used to set the <tt>maxAge</tt> property of the cookie.
*
* <p>
* See SEC-485.
* @param request the request passed to onLoginSuccess
* @param authentication the successful authentication object.
@ -234,4 +297,20 @@ public class TokenBasedRememberMeServices extends AbstractRememberMeServices {
return (s != null) ? Utf8.encode(s) : null;
}
public enum RememberMeTokenAlgorithm {
MD5("MD5"), SHA256("SHA-256");
private final String digestAlgorithm;
RememberMeTokenAlgorithm(String digestAlgorithm) {
this.digestAlgorithm = digestAlgorithm;
}
public String getDigestAlgorithm() {
return this.digestAlgorithm;
}
}
}

View File

@ -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!");
}
}
}

View File

@ -32,6 +32,7 @@ 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.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat;
@ -76,8 +77,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]);
}
@ -87,15 +88,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();
@ -119,8 +157,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();
@ -160,8 +198,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();
@ -188,8 +226,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();
@ -203,8 +241,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();
@ -215,8 +253,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();
@ -225,6 +263,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);
@ -292,6 +392,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.setEncodingAlgorithm(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() {