123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191 |
- [[servlet-rememberme]]
- = Remember-Me Authentication
- [[remember-me-overview]]
- == 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 are using an authentication provider which doesn't use a `UserDetailsService` (for example, the LDAP provider) then it won't 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
- ----
- 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.
- This is the same issue as with digest authentication.
- If a principal is aware 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 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>
- ...
- <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.
- [[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[http://jaspan.com/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 unecessarily.
- 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>
- ...
- <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,
- 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 via hooks in the `AbstractAuthenticationProcessingFilter` superclass.
- 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);
- 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`.
- 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've seen above that Spring Security provides two implementations.
- We'll look at 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``s.
- 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.
- 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.
- [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:
- [source,xml]
- ----
- <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>
- ----
- 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
- 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.
- The database schema is described above in <<remember-me-persistent-token>>.
|