Remember-Me Authentication
Remember-me or persistent-login authentication refers to web sites being able to remember the identity of a principal between sessions. This is typically accomplished by sending a cookie to the browser, with the cookie being detected during future sessions and causing automated login to take place. Spring Security provides the necessary hooks for these operations to take place and has two concrete remember-me implementations. One uses hashing to preserve the security of cookie-based tokens and the other uses a database or other persistent storage mechanism to store the generated tokens.
Note that both implementations require a UserDetailsService
.
If you use an authentication provider that does not use a UserDetailsService
(for example, the LDAP provider), it does not work unless you also have a UserDetailsService
bean in your application context.
Simple Hash-Based Token Approach
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:
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
The remember-me token is valid only for the period specified and only if the username, password, and key do not change. Notably, this has a potential security issue, in that a captured remember-me token is usable from any user agent until such time as the token expires. This is the same issue as with digest authentication. If a principal is aware that a token has been captured, they can easily change their password and immediately invalidate all remember-me tokens on issue. If more significant security is needed, you should use the approach described in the next section. Alternatively, remember-me services should not be used at all.
If you are familiar with the topics discussed in the chapter on namespace configuration, you can enable remember-me authentication by adding the <remember-me>
element:
<http>
...
<remember-me key="myAppKey"/>
</http>
The UserDetailsService
is normally 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.
Persistent Token Approach
This approach is based on the article titled http://jaspan.com/improved_persistent_login_cookie_best_practice, with some minor modifications. (Essentially, the username is not included in the cookie, to prevent exposing a valid login name unecessarily. There is a discussion on this in the comments section of this article.) To use the this approach with namespace configuration, supply a datasource reference:
<http>
...
<remember-me data-source-ref="someDataSource"/>
</http>
The database should contain a persistent_logins
table, created by using the following SQL (or equivalent):
create table persistent_logins (username varchar(64) not null,
series varchar(64) primary key,
token varchar(64) not null,
last_used timestamp not null)
Remember-Me Interfaces and Implementations
Remember-me is used with UsernamePasswordAuthenticationFilter
and is implemented through hooks in the AbstractAuthenticationProcessingFilter
superclass.
It is also used within BasicAuthenticationFilter
.
The hooks invoke a concrete RememberMeServices
at the appropriate times.
The following listing shows the interface:
Authentication autoLogin(HttpServletRequest request, HttpServletResponse response);
void loginFail(HttpServletRequest request, HttpServletResponse response);
void loginSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication successfulAuthentication);
See the Javadoc for RememberMeServices
for a fuller discussion on what the methods do, although note that, at this stage, AbstractAuthenticationProcessingFilter
calls only the loginFail()
and loginSuccess()
methods.
The autoLogin()
method is called by RememberMeAuthenticationFilter
whenever the SecurityContextHolder
does not contain an Authentication
.
This interface, therefore, provides the underlying remember-me implementation with sufficient notification of authentication-related events and delegates to the implementation whenever a candidate web request might contain a cookie and wish to be remembered.
This design allows any number of remember-me implementation strategies.
We have seen earlier that Spring Security provides two implementations. We look at each of these in turn.
TokenBasedRememberMeServices
This implementation supports the simpler approach described in Simple Hash-Based Token Approach.
TokenBasedRememberMeServices
generates a RememberMeAuthenticationToken
, which is processed by RememberMeAuthenticationProvider
.
A key
is shared between this authentication provider and the TokenBasedRememberMeServices
.
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 SHA-256 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 SHA-256.
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
-
XML
@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;
}
<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:
-
Java
-
XML
@Bean
RememberMeAuthenticationFilter rememberMeFilter() {
RememberMeAuthenticationFilter rememberMeFilter = new RememberMeAuthenticationFilter();
rememberMeFilter.setRememberMeServices(rememberMeServices());
rememberMeFilter.setAuthenticationManager(theAuthenticationManager);
return rememberMeFilter;
}
@Bean
TokenBasedRememberMeServices rememberMeServices() {
TokenBasedRememberMeServices rememberMeServices = new TokenBasedRememberMeServices();
rememberMeServices.setUserDetailsService(myUserDetailsService);
rememberMeServices.setKey("springRocks");
return rememberMeServices;
}
@Bean
RememberMeAuthenticationProvider rememberMeAuthenticationProvider() {
RememberMeAuthenticationProvider rememberMeAuthenticationProvider = new RememberMeAuthenticationProvider();
rememberMeAuthenticationProvider.setKey("springRocks");
return rememberMeAuthenticationProvider;
}
<bean id="rememberMeFilter" class=
"org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter">
<property name="rememberMeServices" ref="rememberMeServices"/>
<property name="authenticationManager" ref="theAuthenticationManager" />
</bean>
<bean id="rememberMeServices" class=
"org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
<property name="userDetailsService" ref="myUserDetailsService"/>
<property name="key" value="springRocks"/>
</bean>
<bean id="rememberMeAuthenticationProvider" class=
"org.springframework.security.authentication.RememberMeAuthenticationProvider">
<property name="key" value="springRocks"/>
</bean>
Remember 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
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.
See Persistent Token Approach for the database schema.