Remember-Me Authentication

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

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

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

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

记住我或持久登录身份验证是指网站能够在会话之间记住主体的身份。这通常通过向浏览器发送 cookie 来完成,并且在未来的会话中检测到该 cookie 并导致自动登录发生。Spring Security 提供了这些操作发生的必要钩子,并具有两种具体的记住我实现。一种使用散列来保护基于 cookie 令牌的安全性,另一种使用数据库或其他持久存储机制来存储生成的令牌。 请注意,这两种实现都需要 UserDetailsService。如果您使用不使用 UserDetailsService 的身份验证提供程序(例如 LDAP 提供程序),则在应用程序上下文中没有 UserDetailsService bean 的情况下它不起作用。

Simple Hash-Based Token Approach

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

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

记住我令牌仅在指定期间有效,并且仅当用户名、密码和密钥保持不变时才有效。值得注意的是,这存在潜在的安全问题,因为捕获的记住我令牌可以在任何用户代理使用,直到令牌到期为止。这与摘要身份验证是一样的问题。如果主体知道令牌已被捕获,他们可以很容易地更改密码并立即使所有发布的记住我令牌失效。如果需要更重要的安全性,则应使用下一部分中描述的方法。或者,根本不应使用记住我服务。

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

<http>
...
<remember-me key="myAppKey"/>
</http>

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

Persistent Token Approach

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

<http>
...
<remember-me data-source-ref="someDataSource"/>
</http>

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

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。以下代码列出了这项操作:

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 并希望被记住时委托实现。此设计允许任何数量的记住我实施策略。

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

TokenBasedRememberMeServices

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

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

  • 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 才能启用记住我服务:

  • 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 之后)。

PersistentTokenBasedRememberMeServices

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

  • InMemoryTokenRepositoryImpl 仅用于测试。

  • JdbcTokenRepositoryImpl 将令牌存储到数据库中。

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