1
0
mirror of synced 2026-08-05 09:47:05 +00:00

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
parent 5dff157755
commit f45c4d4b8e
4 changed files with 344 additions and 35 deletions
@@ -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.
In essence a cookie is sent to the browser upon successful interactive authentication, with the cookie being composed as follows:
====
[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
----
====
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.
@@ -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:
====
[source,xml]
----
<http>
@@ -45,6 +49,7 @@ If you are familiar with the topics discussed in the chapter on xref:servlet/con
<remember-me key="myAppKey"/>
</http>
----
====
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.
@@ -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.].
To use the this approach with namespace configuration, you would supply a datasource reference:
====
[source,xml]
----
<http>
@@ -62,9 +68,11 @@ To use the this approach with namespace configuration, you would supply a dataso
<remember-me data-source-ref="someDataSource"/>
</http>
----
====
The database should contain a `persistent_logins` table, created using the following SQL (or equivalent):
====
[source,ddl]
----
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,
last_used timestamp not null)
----
====
[[remember-me-impls]]
== 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 interface looks like this:
====
[source,java]
----
Authentication autoLogin(HttpServletRequest request, HttpServletResponse response);
@@ -89,6 +99,7 @@ void loginFail(HttpServletRequest request, HttpServletResponse response);
void loginSuccess(HttpServletRequest request, HttpServletResponse response,
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.
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.
`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]
----
<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"/>
</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`).
=== 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.
There are two standard implementations.
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.
* `InMemoryTokenRepositoryImpl` which is intended for testing only.
* `JdbcTokenRepositoryImpl` which stores the tokens in a database.