Add SHA256 as an algorithm option for Remember Me token hashing
Closes gh-8549
This commit is contained in:
parent
5dff157755
commit
f45c4d4b8e
|
@ -18,16 +18,19 @@ If you are using an authentication provider which doesn't use a `UserDetailsServ
|
||||||
This approach uses hashing to achieve a useful remember-me strategy.
|
This approach uses hashing to achieve a useful remember-me strategy.
|
||||||
In essence a cookie is sent to the browser upon successful interactive authentication, with the cookie being composed as follows:
|
In essence a cookie is sent to the browser upon successful interactive authentication, with the cookie being composed as follows:
|
||||||
|
|
||||||
|
====
|
||||||
[source,txt]
|
[source,txt]
|
||||||
----
|
----
|
||||||
base64(username + ":" + expirationTime + ":" +
|
base64(username + ":" + expirationTime + ":" + algorithmName + ":"
|
||||||
md5Hex(username + ":" + expirationTime + ":" password + ":" + key))
|
algorithmHex(username + ":" + expirationTime + ":" password + ":" + key))
|
||||||
|
|
||||||
username: As identifiable to the UserDetailsService
|
username: As identifiable to the UserDetailsService
|
||||||
password: That matches the one in the retrieved UserDetails
|
password: That matches the one in the retrieved UserDetails
|
||||||
expirationTime: The date and time when the remember-me token expires, expressed in milliseconds
|
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
|
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
|
||||||
----
|
----
|
||||||
|
====
|
||||||
|
|
||||||
As such the remember-me token is valid only for the period specified, and provided that the username, password and key does not change.
|
As such the remember-me token is valid only for the period specified, and provided that the username, password and key does not change.
|
||||||
Notably, this has a potential security issue in that a captured remember-me token will be usable from any user agent until such time as the token expires.
|
Notably, this has a potential security issue in that a captured remember-me token will be usable from any user agent until such time as the token expires.
|
||||||
|
@ -38,6 +41,7 @@ Alternatively, remember-me services should simply not be used at all.
|
||||||
|
|
||||||
If you are familiar with the topics discussed in the chapter on xref:servlet/configuration/xml-namespace.adoc#ns-config[namespace configuration], you can enable remember-me authentication just by adding the `<remember-me>` element:
|
If you are familiar with the topics discussed in the chapter on xref:servlet/configuration/xml-namespace.adoc#ns-config[namespace configuration], you can enable remember-me authentication just by adding the `<remember-me>` element:
|
||||||
|
|
||||||
|
====
|
||||||
[source,xml]
|
[source,xml]
|
||||||
----
|
----
|
||||||
<http>
|
<http>
|
||||||
|
@ -45,6 +49,7 @@ If you are familiar with the topics discussed in the chapter on xref:servlet/con
|
||||||
<remember-me key="myAppKey"/>
|
<remember-me key="myAppKey"/>
|
||||||
</http>
|
</http>
|
||||||
----
|
----
|
||||||
|
====
|
||||||
|
|
||||||
The `UserDetailsService` will normally be selected automatically.
|
The `UserDetailsService` will normally be selected automatically.
|
||||||
If you have more than one in your application context, you need to specify which one should be used with the `user-service-ref` attribute, where the value is the name of your `UserDetailsService` bean.
|
If you have more than one in your application context, you need to specify which one should be used with the `user-service-ref` attribute, where the value is the name of your `UserDetailsService` bean.
|
||||||
|
@ -55,6 +60,7 @@ This approach is based on the article https://web.archive.org/web/20180819014446
|
||||||
There is a discussion on this in the comments section of this article.].
|
There is a discussion on this in the comments section of this article.].
|
||||||
To use the this approach with namespace configuration, you would supply a datasource reference:
|
To use the this approach with namespace configuration, you would supply a datasource reference:
|
||||||
|
|
||||||
|
====
|
||||||
[source,xml]
|
[source,xml]
|
||||||
----
|
----
|
||||||
<http>
|
<http>
|
||||||
|
@ -62,9 +68,11 @@ To use the this approach with namespace configuration, you would supply a dataso
|
||||||
<remember-me data-source-ref="someDataSource"/>
|
<remember-me data-source-ref="someDataSource"/>
|
||||||
</http>
|
</http>
|
||||||
----
|
----
|
||||||
|
====
|
||||||
|
|
||||||
The database should contain a `persistent_logins` table, created using the following SQL (or equivalent):
|
The database should contain a `persistent_logins` table, created using the following SQL (or equivalent):
|
||||||
|
|
||||||
|
====
|
||||||
[source,ddl]
|
[source,ddl]
|
||||||
----
|
----
|
||||||
create table persistent_logins (username varchar(64) not null,
|
create table persistent_logins (username varchar(64) not null,
|
||||||
|
@ -72,6 +80,7 @@ create table persistent_logins (username varchar(64) not null,
|
||||||
token varchar(64) not null,
|
token varchar(64) not null,
|
||||||
last_used timestamp not null)
|
last_used timestamp not null)
|
||||||
----
|
----
|
||||||
|
====
|
||||||
|
|
||||||
[[remember-me-impls]]
|
[[remember-me-impls]]
|
||||||
== Remember-Me Interfaces and Implementations
|
== Remember-Me Interfaces and Implementations
|
||||||
|
@ -80,6 +89,7 @@ It is also used within `BasicAuthenticationFilter`.
|
||||||
The hooks will invoke a concrete `RememberMeServices` at the appropriate times.
|
The hooks will invoke a concrete `RememberMeServices` at the appropriate times.
|
||||||
The interface looks like this:
|
The interface looks like this:
|
||||||
|
|
||||||
|
====
|
||||||
[source,java]
|
[source,java]
|
||||||
----
|
----
|
||||||
Authentication autoLogin(HttpServletRequest request, HttpServletResponse response);
|
Authentication autoLogin(HttpServletRequest request, HttpServletResponse response);
|
||||||
|
@ -89,6 +99,7 @@ void loginFail(HttpServletRequest request, HttpServletResponse response);
|
||||||
void loginSuccess(HttpServletRequest request, HttpServletResponse response,
|
void loginSuccess(HttpServletRequest request, HttpServletResponse response,
|
||||||
Authentication successfulAuthentication);
|
Authentication successfulAuthentication);
|
||||||
----
|
----
|
||||||
|
====
|
||||||
|
|
||||||
Please refer to the Javadoc for a fuller discussion on what the methods do, although note at this stage that `AbstractAuthenticationProcessingFilter` only calls the `loginFail()` and `loginSuccess()` methods.
|
Please refer to the Javadoc for a fuller discussion on what the methods do, although note at this stage that `AbstractAuthenticationProcessingFilter` only calls the `loginFail()` and `loginSuccess()` methods.
|
||||||
The `autoLogin()` method is called by `RememberMeAuthenticationFilter` whenever the `SecurityContextHolder` does not contain an `Authentication`.
|
The `autoLogin()` method is called by `RememberMeAuthenticationFilter` whenever the `SecurityContextHolder` does not contain an `Authentication`.
|
||||||
|
@ -105,8 +116,56 @@ In addition, `TokenBasedRememberMeServices` requires A UserDetailsService from w
|
||||||
Some sort of logout command should be provided by the application that invalidates the cookie if the user requests this.
|
Some sort of logout command should be provided by the application that invalidates the cookie if the user requests this.
|
||||||
`TokenBasedRememberMeServices` also implements Spring Security's `LogoutHandler` interface so can be used with `LogoutFilter` to have the cookie cleared automatically.
|
`TokenBasedRememberMeServices` also implements Spring Security's `LogoutHandler` interface so can be used with `LogoutFilter` to have the cookie cleared automatically.
|
||||||
|
|
||||||
The beans required in an application context to enable remember-me services are as follows:
|
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
|
||||||
|
.authorizeHttpRequests((authorize) -> authorize
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
)
|
||||||
|
.rememberMe((remember) -> remember
|
||||||
|
.rememberMeServices(rememberMeServices)
|
||||||
|
);
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
RememberMeServices rememberMeServices(UserDetailsService userDetailsService) {
|
||||||
|
RememberMeTokenAlgorithm encodingAlgorithm = RememberMeTokenAlgorithm.SHA256;
|
||||||
|
TokenBasedRememberMeServices rememberMe = new TokenBasedRememberMeServices(myKey, userDetailsService, encodingAlgorithm);
|
||||||
|
rememberMe.setMatchingAlgorithm(RememberMeTokenAlgorithm.MD5);
|
||||||
|
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:
|
||||||
|
|
||||||
|
====
|
||||||
[source,xml]
|
[source,xml]
|
||||||
----
|
----
|
||||||
<bean id="rememberMeFilter" class=
|
<bean id="rememberMeFilter" class=
|
||||||
|
@ -126,13 +185,13 @@ The beans required in an application context to enable remember-me services are
|
||||||
<property name="key" value="springRocks"/>
|
<property name="key" value="springRocks"/>
|
||||||
</bean>
|
</bean>
|
||||||
----
|
----
|
||||||
|
====
|
||||||
|
|
||||||
Don't forget to add your `RememberMeServices` implementation to your `UsernamePasswordAuthenticationFilter.setRememberMeServices()` property, include the `RememberMeAuthenticationProvider` in your `AuthenticationManager.setProviders()` list, and add `RememberMeAuthenticationFilter` into your `FilterChainProxy` (typically immediately after your `UsernamePasswordAuthenticationFilter`).
|
Don't forget to add your `RememberMeServices` implementation to your `UsernamePasswordAuthenticationFilter.setRememberMeServices()` property, include the `RememberMeAuthenticationProvider` in your `AuthenticationManager.setProviders()` list, and add `RememberMeAuthenticationFilter` into your `FilterChainProxy` (typically immediately after your `UsernamePasswordAuthenticationFilter`).
|
||||||
|
|
||||||
|
|
||||||
=== PersistentTokenBasedRememberMeServices
|
=== PersistentTokenBasedRememberMeServices
|
||||||
This class can be used in the same way as `TokenBasedRememberMeServices`, but it additionally needs to be configured with a `PersistentTokenRepository` to store the tokens.
|
You can use this class in the same way as `TokenBasedRememberMeServices`, but it additionally needs to be configured with a `PersistentTokenRepository` to store the tokens.
|
||||||
There are two standard implementations.
|
|
||||||
|
|
||||||
* `InMemoryTokenRepositoryImpl` which is intended for testing only.
|
* `InMemoryTokenRepositoryImpl` which is intended for testing only.
|
||||||
* `JdbcTokenRepositoryImpl` which stores the tokens in a database.
|
* `JdbcTokenRepositoryImpl` which stores the tokens in a database.
|
||||||
|
|
|
@ -54,11 +54,21 @@ import org.springframework.util.StringUtils;
|
||||||
* The cookie encoded by this implementation adopts the following form:
|
* The cookie encoded by this implementation adopts the following form:
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* username + ":" + expiryTime + ":"
|
* username + ":" + expiryTime + ":" + algorithmName + ":"
|
||||||
* + Md5Hex(username + ":" + expiryTime + ":" + password + ":" + key)
|
* + algorithmHex(username + ":" + expiryTime + ":" + password + ":" + key)
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* <p>
|
* <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.
|
* 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
|
* 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
|
* changing the key. This provides some reasonable approaches to recovering from a
|
||||||
|
@ -80,19 +90,43 @@ import org.springframework.util.StringUtils;
|
||||||
* not be stored when the browser is closed.
|
* not be stored when the browser is closed.
|
||||||
*
|
*
|
||||||
* @author Ben Alex
|
* @author Ben Alex
|
||||||
|
* @author Marcus Da Coregio
|
||||||
*/
|
*/
|
||||||
public class TokenBasedRememberMeServices extends AbstractRememberMeServices {
|
public class TokenBasedRememberMeServices extends AbstractRememberMeServices {
|
||||||
|
|
||||||
|
private static final RememberMeTokenAlgorithm DEFAULT_MATCHING_ALGORITHM = RememberMeTokenAlgorithm.MD5;
|
||||||
|
|
||||||
|
private static final RememberMeTokenAlgorithm DEFAULT_ENCODING_ALGORITHM = RememberMeTokenAlgorithm.MD5;
|
||||||
|
|
||||||
|
private final RememberMeTokenAlgorithm encodingAlgorithm;
|
||||||
|
|
||||||
|
private RememberMeTokenAlgorithm matchingAlgorithm = DEFAULT_MATCHING_ALGORITHM;
|
||||||
|
|
||||||
public TokenBasedRememberMeServices(String key, UserDetailsService userDetailsService) {
|
public TokenBasedRememberMeServices(String key, UserDetailsService userDetailsService) {
|
||||||
|
this(key, userDetailsService, DEFAULT_ENCODING_ALGORITHM);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct the instance with the parameters provided
|
||||||
|
* @param key the signature key
|
||||||
|
* @param userDetailsService the {@link UserDetailsService}
|
||||||
|
* @param encodingAlgorithm the {@link RememberMeTokenAlgorithm} used to encode the
|
||||||
|
* signature
|
||||||
|
* @since 5.8
|
||||||
|
*/
|
||||||
|
public TokenBasedRememberMeServices(String key, UserDetailsService userDetailsService,
|
||||||
|
RememberMeTokenAlgorithm encodingAlgorithm) {
|
||||||
super(key, userDetailsService);
|
super(key, userDetailsService);
|
||||||
|
Assert.notNull(encodingAlgorithm, "encodingAlgorithm cannot be null");
|
||||||
|
this.encodingAlgorithm = encodingAlgorithm;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request,
|
protected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request,
|
||||||
HttpServletResponse response) {
|
HttpServletResponse response) {
|
||||||
if (cookieTokens.length != 3) {
|
if (!isValidCookieTokensLength(cookieTokens)) {
|
||||||
throw new InvalidCookieException(
|
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);
|
long tokenExpiryTime = getTokenExpiryTime(cookieTokens);
|
||||||
if (isTokenExpired(tokenExpiryTime)) {
|
if (isTokenExpired(tokenExpiryTime)) {
|
||||||
|
@ -110,15 +144,27 @@ public class TokenBasedRememberMeServices extends AbstractRememberMeServices {
|
||||||
// only called once per HttpSession - if the token is valid, it will cause
|
// only called once per HttpSession - if the token is valid, it will cause
|
||||||
// SecurityContextHolder population, whilst if invalid, will cause the cookie to
|
// SecurityContextHolder population, whilst if invalid, will cause the cookie to
|
||||||
// be cancelled.
|
// 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(),
|
String expectedTokenSignature = makeTokenSignature(tokenExpiryTime, userDetails.getUsername(),
|
||||||
userDetails.getPassword());
|
userDetails.getPassword(), actualAlgorithm);
|
||||||
if (!equals(expectedTokenSignature, cookieTokens[2])) {
|
if (!equals(expectedTokenSignature, actualTokenSignature)) {
|
||||||
throw new InvalidCookieException("Cookie token[2] contained signature '" + cookieTokens[2]
|
throw new InvalidCookieException("Cookie contained signature '" + actualTokenSignature + "' but expected '"
|
||||||
+ "' but expected '" + expectedTokenSignature + "'");
|
+ expectedTokenSignature + "'");
|
||||||
}
|
}
|
||||||
return userDetails;
|
return userDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isValidCookieTokensLength(String[] cookieTokens) {
|
||||||
|
return cookieTokens.length == 3 || cookieTokens.length == 4;
|
||||||
|
}
|
||||||
|
|
||||||
private long getTokenExpiryTime(String[] cookieTokens) {
|
private long getTokenExpiryTime(String[] cookieTokens) {
|
||||||
try {
|
try {
|
||||||
return new Long(cookieTokens[1]);
|
return new Long(cookieTokens[1]);
|
||||||
|
@ -130,17 +176,33 @@ public class TokenBasedRememberMeServices extends AbstractRememberMeServices {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates the digital signature to be put in the cookie. Default value is MD5
|
* Calculates the digital signature to be put in the cookie. Default value is
|
||||||
* ("username:tokenExpiryTime:password:key")
|
* {@link #encodingAlgorithm} applied to ("username:tokenExpiryTime:password:key")
|
||||||
*/
|
*/
|
||||||
protected String makeTokenSignature(long tokenExpiryTime, String username, String password) {
|
protected String makeTokenSignature(long tokenExpiryTime, String username, String password) {
|
||||||
String data = username + ":" + tokenExpiryTime + ":" + password + ":" + getKey();
|
String data = username + ":" + tokenExpiryTime + ":" + password + ":" + getKey();
|
||||||
try {
|
try {
|
||||||
MessageDigest digest = MessageDigest.getInstance("MD5");
|
MessageDigest digest = MessageDigest.getInstance(this.encodingAlgorithm.getDigestAlgorithm());
|
||||||
return new String(Hex.encode(digest.digest(data.getBytes())));
|
return new String(Hex.encode(digest.digest(data.getBytes())));
|
||||||
}
|
}
|
||||||
catch (NoSuchAlgorithmException ex) {
|
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.
|
||||||
|
* @since 5.8
|
||||||
|
*/
|
||||||
|
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 +234,25 @@ public class TokenBasedRememberMeServices extends AbstractRememberMeServices {
|
||||||
long expiryTime = System.currentTimeMillis();
|
long expiryTime = System.currentTimeMillis();
|
||||||
// SEC-949
|
// SEC-949
|
||||||
expiryTime += 1000L * ((tokenLifetime < 0) ? TWO_WEEKS_S : tokenLifetime);
|
expiryTime += 1000L * ((tokenLifetime < 0) ? TWO_WEEKS_S : tokenLifetime);
|
||||||
String signatureValue = makeTokenSignature(expiryTime, username, password);
|
String signatureValue = makeTokenSignature(expiryTime, username, password, this.encodingAlgorithm);
|
||||||
setCookie(new String[] { username, Long.toString(expiryTime), signatureValue }, tokenLifetime, request,
|
setCookie(new String[] { username, Long.toString(expiryTime), this.encodingAlgorithm.name(), signatureValue },
|
||||||
response);
|
tokenLifetime, request, response);
|
||||||
if (this.logger.isDebugEnabled()) {
|
if (this.logger.isDebugEnabled()) {
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
"Added remember-me cookie for user '" + username + "', expiry: '" + new Date(expiryTime) + "'");
|
"Added remember-me cookie for user '" + username + "', expiry: '" + new Date(expiryTime) + "'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the algorithm to be used to match the token signature
|
||||||
|
* @param matchingAlgorithm the matching algorithm
|
||||||
|
* @since 5.8
|
||||||
|
*/
|
||||||
|
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.
|
* 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
|
* After this period (from the current time) the remember-me login will be considered
|
||||||
|
@ -190,7 +262,7 @@ public class TokenBasedRememberMeServices extends AbstractRememberMeServices {
|
||||||
* <p>
|
* <p>
|
||||||
* The returned value will be used to work out the expiry time of the token and will
|
* 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.
|
* also be used to set the <tt>maxAge</tt> property of the cookie.
|
||||||
*
|
* <p>
|
||||||
* See SEC-485.
|
* See SEC-485.
|
||||||
* @param request the request passed to onLoginSuccess
|
* @param request the request passed to onLoginSuccess
|
||||||
* @param authentication the successful authentication object.
|
* @param authentication the successful authentication object.
|
||||||
|
@ -234,4 +306,20 @@ public class TokenBasedRememberMeServices extends AbstractRememberMeServices {
|
||||||
return (s != null) ? Utf8.encode(s) : null;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,11 @@
|
||||||
|
|
||||||
package org.springframework.security.test.web;
|
package org.springframework.security.test.web;
|
||||||
|
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
|
|
||||||
|
import org.springframework.security.crypto.codec.Hex;
|
||||||
import org.springframework.util.DigestUtils;
|
import org.springframework.util.DigestUtils;
|
||||||
|
|
||||||
public final class CodecTestUtils {
|
public final class CodecTestUtils {
|
||||||
|
@ -52,4 +55,14 @@ public final class CodecTestUtils {
|
||||||
return DigestUtils.md5DigestAsHex(data.getBytes());
|
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.UserDetailsService;
|
||||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
import org.springframework.security.test.web.CodecTestUtils;
|
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 org.springframework.util.StringUtils;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
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.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.BDDMockito.given;
|
import static org.mockito.BDDMockito.given;
|
||||||
|
@ -47,6 +50,7 @@ import static org.mockito.Mockito.mock;
|
||||||
* .
|
* .
|
||||||
*
|
*
|
||||||
* @author Ben Alex
|
* @author Ben Alex
|
||||||
|
* @author Marcus Da Coregio
|
||||||
*/
|
*/
|
||||||
public class TokenBasedRememberMeServicesTests {
|
public class TokenBasedRememberMeServicesTests {
|
||||||
|
|
||||||
|
@ -77,8 +81,8 @@ public class TokenBasedRememberMeServicesTests {
|
||||||
|
|
||||||
private long determineExpiryTimeFromBased64EncodedToken(String validToken) {
|
private long determineExpiryTimeFromBased64EncodedToken(String validToken) {
|
||||||
String cookieAsPlainText = CodecTestUtils.decodeBase64(validToken);
|
String cookieAsPlainText = CodecTestUtils.decodeBase64(validToken);
|
||||||
String[] cookieTokens = StringUtils.delimitedListToStringArray(cookieAsPlainText, ":");
|
String[] cookieTokens = getCookieTokens(cookieAsPlainText);
|
||||||
if (cookieTokens.length == 3) {
|
if (isValidCookieTokensLength(cookieTokens)) {
|
||||||
try {
|
try {
|
||||||
return Long.parseLong(cookieTokens[1]);
|
return Long.parseLong(cookieTokens[1]);
|
||||||
}
|
}
|
||||||
|
@ -88,15 +92,52 @@ public class TokenBasedRememberMeServicesTests {
|
||||||
return -1;
|
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:
|
// format is:
|
||||||
// username + ":" + expiryTime + ":" + Md5Hex(username + ":" + expiryTime + ":" +
|
// username + ":" + expiryTime + ":" + Md5Hex(username + ":" + expiryTime + ":" +
|
||||||
// password + ":" + key)
|
// 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;
|
String tokenValue = username + ":" + expiryTime + ":" + signatureValue;
|
||||||
return CodecTestUtils.encodeBase64(tokenValue);
|
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
|
@Test
|
||||||
public void autoLoginReturnsNullIfNoCookiePresented() {
|
public void autoLoginReturnsNullIfNoCookiePresented() {
|
||||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
@ -120,8 +161,8 @@ public class TokenBasedRememberMeServicesTests {
|
||||||
@Test
|
@Test
|
||||||
public void autoLoginReturnsNullForExpiredCookieAndClearsCookie() {
|
public void autoLoginReturnsNullForExpiredCookieAndClearsCookie() {
|
||||||
Cookie cookie = new Cookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY,
|
Cookie cookie = new Cookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY,
|
||||||
generateCorrectCookieContentForToken(System.currentTimeMillis() - 1000000, "someone", "password",
|
generateCorrectCookieContentForTokenNoAlgorithmName(System.currentTimeMillis() - 1000000, "someone",
|
||||||
"key"));
|
"password", "key"));
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
request.setCookies(cookie);
|
request.setCookies(cookie);
|
||||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
@ -161,8 +202,8 @@ public class TokenBasedRememberMeServicesTests {
|
||||||
public void autoLoginClearsCookieIfSignatureBlocksDoesNotMatchExpectedValue() {
|
public void autoLoginClearsCookieIfSignatureBlocksDoesNotMatchExpectedValue() {
|
||||||
udsWillReturnUser();
|
udsWillReturnUser();
|
||||||
Cookie cookie = new Cookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY,
|
Cookie cookie = new Cookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY,
|
||||||
generateCorrectCookieContentForToken(System.currentTimeMillis() + 1000000, "someone", "password",
|
generateCorrectCookieContentForTokenNoAlgorithmName(System.currentTimeMillis() + 1000000, "someone",
|
||||||
"WRONG_KEY"));
|
"password", "WRONG_KEY"));
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
request.setCookies(cookie);
|
request.setCookies(cookie);
|
||||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
@ -189,8 +230,8 @@ public class TokenBasedRememberMeServicesTests {
|
||||||
public void autoLoginClearsCookieIfUserNotFound() {
|
public void autoLoginClearsCookieIfUserNotFound() {
|
||||||
udsWillThrowNotFound();
|
udsWillThrowNotFound();
|
||||||
Cookie cookie = new Cookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY,
|
Cookie cookie = new Cookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY,
|
||||||
generateCorrectCookieContentForToken(System.currentTimeMillis() + 1000000, "someone", "password",
|
generateCorrectCookieContentForTokenNoAlgorithmName(System.currentTimeMillis() + 1000000, "someone",
|
||||||
"key"));
|
"password", "key"));
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
request.setCookies(cookie);
|
request.setCookies(cookie);
|
||||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
@ -204,8 +245,8 @@ public class TokenBasedRememberMeServicesTests {
|
||||||
public void autoLoginClearsCookieIfUserServiceMisconfigured() {
|
public void autoLoginClearsCookieIfUserServiceMisconfigured() {
|
||||||
udsWillReturnNull();
|
udsWillReturnNull();
|
||||||
Cookie cookie = new Cookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY,
|
Cookie cookie = new Cookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY,
|
||||||
generateCorrectCookieContentForToken(System.currentTimeMillis() + 1000000, "someone", "password",
|
generateCorrectCookieContentForTokenNoAlgorithmName(System.currentTimeMillis() + 1000000, "someone",
|
||||||
"key"));
|
"password", "key"));
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
request.setCookies(cookie);
|
request.setCookies(cookie);
|
||||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
@ -216,8 +257,8 @@ public class TokenBasedRememberMeServicesTests {
|
||||||
public void autoLoginWithValidTokenAndUserSucceeds() {
|
public void autoLoginWithValidTokenAndUserSucceeds() {
|
||||||
udsWillReturnUser();
|
udsWillReturnUser();
|
||||||
Cookie cookie = new Cookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY,
|
Cookie cookie = new Cookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY,
|
||||||
generateCorrectCookieContentForToken(System.currentTimeMillis() + 1000000, "someone", "password",
|
generateCorrectCookieContentForTokenNoAlgorithmName(System.currentTimeMillis() + 1000000, "someone",
|
||||||
"key"));
|
"password", "key"));
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
request.setCookies(cookie);
|
request.setCookies(cookie);
|
||||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
@ -226,6 +267,68 @@ public class TokenBasedRememberMeServicesTests {
|
||||||
assertThat(result.getPrincipal()).isEqualTo(this.user);
|
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
|
@Test
|
||||||
public void testGettersSetters() {
|
public void testGettersSetters() {
|
||||||
assertThat(this.services.getUserDetailsService()).isEqualTo(this.uds);
|
assertThat(this.services.getUserDetailsService()).isEqualTo(this.uds);
|
||||||
|
@ -293,6 +396,37 @@ public class TokenBasedRememberMeServicesTests {
|
||||||
assertThat(new Date().before(new Date(determineExpiryTimeFromBased64EncodedToken(cookie.getValue())))).isTrue();
|
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
|
// SEC-933
|
||||||
@Test
|
@Test
|
||||||
public void obtainPasswordReturnsNullForTokenWithNullCredentials() {
|
public void obtainPasswordReturnsNullForTokenWithNullCredentials() {
|
||||||
|
@ -318,4 +452,19 @@ public class TokenBasedRememberMeServicesTests {
|
||||||
assertThat(CodecTestUtils.isBase64(cookie.getValue().getBytes())).isTrue();
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue