You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
222 lines
11 KiB
222 lines
11 KiB
[[servlet-rememberme]] |
|
= Remember-Me Authentication |
|
|
|
[[remember-me-overview]] |
|
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. |
|
|
|
|
|
[[remember-me-hash-token]] |
|
== 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: |
|
|
|
[source,txt] |
|
---- |
|
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 xref:servlet/configuration/xml-namespace.adoc#ns-config[namespace configuration], you can enable remember-me authentication by adding the `<remember-me>` element: |
|
|
|
[source,xml] |
|
---- |
|
<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. |
|
|
|
[[remember-me-persistent-token]] |
|
== Persistent Token Approach |
|
This approach is based on the article https://web.archive.org/web/20180819014446/http://jaspan.com/improved_persistent_login_cookie_best_practice[Improved Persistent Login Cookie Best Practice] with some minor modifications footnote:[Essentially, the username is not included in the cookie, to prevent exposing a valid login name unnecessarily. |
|
There is a discussion on this in the comments section of this article.]. |
|
To use this approach with namespace configuration, you would supply a datasource reference: |
|
|
|
[source,xml] |
|
---- |
|
<http> |
|
... |
|
<remember-me data-source-ref="someDataSource"/> |
|
</http> |
|
---- |
|
|
|
The database should contain a `persistent_logins` table, created by using the following SQL (or equivalent): |
|
|
|
[source,ddl] |
|
---- |
|
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-impls]] |
|
== 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: |
|
|
|
[source,java] |
|
---- |
|
Authentication autoLogin(HttpServletRequest request, HttpServletResponse response); |
|
|
|
void loginFail(HttpServletRequest request, HttpServletResponse response); |
|
|
|
void loginSuccess(HttpServletRequest request, HttpServletResponse response, |
|
Authentication successfulAuthentication); |
|
---- |
|
|
|
See the Javadoc for javadoc:org.springframework.security.web.authentication.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 <<remember-me-hash-token>>. |
|
`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. |
|
|
|
[tabs] |
|
====== |
|
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: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
@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; |
|
} |
|
---- |
|
|
|
XML:: |
|
+ |
|
[source,xml,role="secondary"] |
|
---- |
|
<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 <<remember-me-persistent-token>> for the database schema.
|
|
|