mirror of
				https://github.com/spring-projects/spring-security.git
				synced 2025-10-27 20:58:45 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			223 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			223 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
| [[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 unecessarily.
 | |
| 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.
 |