Remember-Me Authentication

此身份验证可使用两种实现:

  • 散列令牌方法:使用散列保护基于 cookie 的令牌,但可能存在安全问题。

  • 持久令牌方法:使用数据库或其他机制存储生成的令牌,提供更高的安全性。

这两种方法都需要 UserDetailsService,并且可以用在使用 UsernamePasswordAuthenticationFilter、BasicAuthenticationFilter 和 RememberMeAuthenticationFilter 的身份验证流程中。Spring Security 提供了 TokenBasedRememberMeServices 和 PersistentTokenBasedRememberMeServices 实现,用户可以根据需要进行配置。

记住我或持久登录身份验证是指网站能够在会话之间记住主体的身份。这通常通过向浏览器发送 cookie 来完成,并且在未来的会话中检测到该 cookie 并导致自动登录发生。Spring Security 提供了这些操作发生的必要钩子,并具有两种具体的记住我实现。一种使用散列来保护基于 cookie 令牌的安全性,另一种使用数据库或其他持久存储机制来存储生成的令牌。

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.

请注意,这两种实现都需要 UserDetailsService。如果您使用不使用 UserDetailsService 的身份验证提供程序(例如 LDAP 提供程序),则在应用程序上下文中没有 UserDetailsService bean 的情况下它不起作用。

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

此方法使用散列来实现有用的记住我策略。从本质上讲,在成功进行交互式身份验证后,会将一个 cookie 发送到浏览器,该 cookie 的组成如下:

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.

如果你熟悉 namespace configuration 章节中讨论的话题,你可以通过添加 <remember-me> 元素来启用记住我身份验证:

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>

UserDetailsService 通常会自动选择。如果您在应用程序上下文中有多个 UserDetailsService ,则需要使用 user-service-ref 属性指定应使用哪一个,其中该值是 UserDetailsService bean 的名称。

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

这种 方法基于@ {s4} 一文,并做了一些小改动。(从本质上讲,用户名不包含在 Cookie 中,以防止不必要地暴露有效的登录名。本文评论部分对此进行了讨论。)若要将此方法与名称空间配置一起使用,请提供一个数据源参考:

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 unnecessarily. 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>

数据库中应包含 persistent_logins 表,可使用以下 SQL(或等效 SQL)创建:

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

记住我服务与 UsernamePasswordAuthenticationFilter 一起使用,并通过 AbstractAuthenticationProcessingFilter 超类的挂钩实现。它还在 BasicAuthenticationFilter 中使用。这些挂钩会在适当的时候调用具体的 RememberMeServices。以下代码列出了这项操作:

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);

请参阅 {security-api-url}org/springframework/security/web/authentication/RememberMeServices.html@ {s5} 上的 Javadoc,以更全面地了解这些方法的作用,但请注意,在这个阶段,@ {s6} 只调用@ {s7}@ {s8} 方法。@ {s10}@ {s11} 不包含@ {s12} 时调用@ {s9} 方法。因此,此接口为基础的记住我实施提供了有关身份验证相关事件的足够通知,并在候选 Web 请求可能包含 Cookie 并希望被记住时委托实现。此设计允许任何数量的记住我实施策略。

See the Javadoc for {security-api-url}org/springframework/security/web/authentication/RememberMeServices.html[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.

我们之前已经了解过 Spring Security 提供了两种实现。我们将逐一查看它们。

We have seen earlier that Spring Security provides two implementations. We look at each of these in turn.

TokenBasedRememberMeServices

此实现支持 Simple Hash-Based Token Approach 中描述的更简单的方法。TokenBasedRememberMeServices 会生成一个 RememberMeAuthenticationToken,该 RememberMeAuthenticationToken 会由 RememberMeAuthenticationProvider 处理。此身份验证提供程序和 TokenBasedRememberMeServices 之间会共享一个 key。此外,TokenBasedRememberMeServices 需要一个 UserDetailsService,它可以从中获取用户名和密码以进行签名比较,并生成包含正确 GrantedAuthority 实例的 RememberMeAuthenticationTokenTokenBasedRememberMeServices 还实现了 Spring Security 的 LogoutHandler 接口,以便将其与 LogoutFilter 一起使用,这样一来,cookie 就会自动清除。

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.

默认情况下,此实现使用 SHA-256 算法对令牌签名进行编码。它会解析并使用从 algorithmName 检索到的算法来验证令牌签名。如果`algorithmName` 不存在,则会使用默认匹配算法,即 SHA-256。您可以分别为签名编码和签名匹配指定不同的算法,这允许用户安全地升级到不同的编码算法,同时在没有 algorithmName 的情况下仍然能够验证旧算法。为此,您可以将自定义的 TokenBasedRememberMeServices 指定为 Bean,并在配置中使用它。

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>

在应用程序上下文中需要以下 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>

将您的 RememberMeServices 实现添加到您的 UsernamePasswordAuthenticationFilter.setRememberMeServices() 属性中,在 AuthenticationManager.setProviders() 列表中包含 RememberMeAuthenticationProvider,并且将 RememberMeAuthenticationFilter 添加到 FilterChainProxy 中(通常紧跟在您的 UsernamePasswordAuthenticationFilter 之后)。

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

您可以与 TokenBasedRememberMeServices 以相同的方式使用此类,但还需要使用 PersistentTokenRepository 来配置它以存储令牌。

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.

有关数据库模式,请参阅 Persistent Token Approach

See Persistent Token Approach for the database schema.