记住我身份验证

记住我或持久登录身份验证是指网站能够在会话之间记住主体身份。这通常通过向浏览器发送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的名称。

持久令牌方法

这种方法基于文章改进的持久登录cookie最佳实践,并进行了一些小的修改[1]。要在命名空间配置中使用此方法,您需要提供一个数据源引用:

<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()方法在SecurityContextHolder不包含Authentication时由RememberMeAuthenticationFilter调用。因此,此接口为底层的记住我实现提供了足够的与身份验证相关的事件通知,并在候选 Web 请求可能包含 Cookie 并希望被记住时委托给实现。这种设计允许任意数量的记住我实现策略。

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

TokenBasedRememberMeServices

此实现支持简单的基于哈希的令牌方法中描述的更简单的方法。TokenBasedRememberMeServices生成一个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()属性中,将RememberMeAuthenticationProvider添加到您的AuthenticationManager.setProviders()列表中,并将RememberMeAuthenticationFilter添加到您的FilterChainProxy中(通常紧跟在您的UsernamePasswordAuthenticationFilter之后)。

PersistentTokenBasedRememberMeServices

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

  • InMemoryTokenRepositoryImpl仅用于测试。

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

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


1。基本上,用户名不包含在 Cookie 中,以防止不必要地暴露有效的登录名。本文的评论部分对此进行了讨论。