记住我身份验证

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

请注意,两种实现都需要 UserDetailsService。如果您使用不使用 UserDetailsService 的身份验证提供程序(例如,LDAP 提供程序),则它将无法工作,除非您的应用程序上下文中也存在 UserDetailsService bean。

简单的基于哈希的令牌方法

这种方法使用哈希来实现有用的记住我策略。本质上,在成功进行交互式身份验证后,会向浏览器发送一个 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

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

如果您熟悉关于 命名空间配置 的章节中讨论的主题,您可以通过添加 <remember-me> 元素来启用记住我身份验证

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

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

持久令牌方法

这种方法基于名为 http://jaspan.com/improved_persistent_login_cookie_best_practice 的文章,并进行了一些小的修改。(本质上,用户名不包含在 cookie 中,以防止不必要地公开有效的登录名。在本文的评论部分对此进行了讨论。)要使用命名空间配置使用这种方法,请提供数据源引用

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

数据库应该包含一个名为 persistent_logins 的表,使用以下 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)

记住我接口和实现

记住我功能与 UsernamePasswordAuthenticationFilter 结合使用,并通过 AbstractAuthenticationProcessingFilter 超类的钩子实现。它也用于 BasicAuthenticationFilter 中。这些钩子在适当的时候调用具体的 RememberMeServices。以下列表显示了接口

Authentication autoLogin(HttpServletRequest request, HttpServletResponse response);

void loginFail(HttpServletRequest request, HttpServletResponse response);

void loginSuccess(HttpServletRequest request, HttpServletResponse response,
	Authentication successfulAuthentication);

有关方法功能的更详细讨论,请参阅 RememberMeServices 的 Javadoc,但请注意,在此阶段,AbstractAuthenticationProcessingFilter 仅调用 loginFail()loginSuccess() 方法。autoLogin() 方法由 RememberMeAuthenticationFilterSecurityContextHolder 不包含 Authentication 时调用。因此,此接口为底层的记住我实现提供了足够的身份验证相关事件通知,并在候选 Web 请求可能包含 cookie 并希望被记住时委托给实现。这种设计允许使用任意数量的记住我实现策略。

我们之前已经看到 Spring Security 提供了两种实现。我们依次查看每种实现。

TokenBasedRememberMeServices

此实现支持 简单基于哈希的令牌方法 中描述的更简单方法。TokenBasedRememberMeServices 生成一个 RememberMeAuthenticationToken,该令牌由 RememberMeAuthenticationProvider 处理。一个 key 在此身份验证提供者和 TokenBasedRememberMeServices 之间共享。此外,TokenBasedRememberMeServices 需要一个 UserDetailsService,它可以从中检索用户名和密码以进行签名比较,并生成 RememberMeAuthenticationToken 以包含正确的 GrantedAuthority 实例。TokenBasedRememberMeServices 还实现了 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() 属性中,将 RememberMeAuthenticationProvider 包含在您的 AuthenticationManager.setProviders() 列表中,并将 RememberMeAuthenticationFilter 添加到您的 FilterChainProxy 中(通常紧随您的 UsernamePasswordAuthenticationFilter 之后)。

PersistentTokenBasedRememberMeServices

您可以像使用 TokenBasedRememberMeServices 一样使用此类,但它还需要配置 PersistentTokenRepository 来存储令牌。

  • InMemoryTokenRepositoryImpl 仅用于测试。

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

有关数据库模式,请参见 持久令牌方法