记住我(Remember-Me) 认证

本站(springdoc.cn)中的内容来源于 spring.io ,原始版权归属于 spring.io。由 springdoc.cn 进行翻译,整理。可供个人学习、研究,未经许可,不得进行任何转载、商用或与之相关的行为。 商标声明:Spring 是 Pivotal Software, Inc. 在美国以及其他国家的商标。

Remember-me(记住我)或persistent-login认证是指网站能够在两次会话之间记住委托人的身份。这通常是通过向浏览器发送一个cookie来实现的,在未来的会话中可以检测到cookie,并导致自动登录的发生。Spring Security为这些操作提供了必要的钩子,并有两个具体的“记住我”的实现。一个使用散列法来保护基于cookie的令牌的安全性,另一个使用数据库或其他持久性存储机制来存储生成的令牌。

请注意,这两种实现都需要一个 UserDetailsService。如果你使用一个不使用 UserDetailsService 的认证 provider(例如,LDAP provider),除非你在你的应用程序上下文中也有一个 UserDetailsService Bean,否则它不会工作。

基于哈希简单令牌法

这种方法使用散列法来实现有用的“记住我”的策略。实质上,在成功的交互式认证后,会向浏览器发送一个cookie,cookie的组成如下。

base64(username + ":" + expirationTime + ":" + algorithmName + ":"
algorithmHex(username + ":" + expirationTime + ":" password + ":" + key))

username:          UserDetailsService 的 ID
password:          这与检索到的UserDetails中的内容相匹配。
expirationTime:    令牌过期的日期和时间,以毫秒表示。
key:               用于防止修改“记住我”令牌的私钥
algorithmName:     用于生成和验证 “记住我” 令牌签名的算法。

“remember-me” 令牌只在指定的时间内有效,并且只有在用户名、密码和密钥不改变的情况下才有效。值得注意的是,这有一个潜在的安全问题,因为捕获的“remember-me”令牌可以从任何用户代理处使用,直到令牌过期。这与摘要认证的问题相同。如果一个委托人意识到一个令牌已经被泄露,他们可以很容易地改变他们的密码,并立即使所有发行的“remember-me”令牌失效。如果需要更重要的安全性,你应该使用下一节中描述的方法。或者,根本就不应该使用 “remember-me” 服务。

如果你熟悉 命名空间配置一章中讨论的主题,你可以通过添加 <remember-me> 元素来启用 remember-me 认证。

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

UserDetailsService 通常会被自动选择。如果你的 application context 中有多个,你需要用 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)

Remember-Me 的接口和实现

Remember-me与 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() 方法。每当 SecurityContextHolder 不包含一个 Authentication 时, RememberMeAuthenticationFilter 就会调用 autoLogin() 方法。因此,这个接口为底层的 remember-me 实现提供了足够的认证相关事件的通知,并且每当候选web请求可能包含cookie并希望被记住时,都会委托给该实现。这种设计允许任何数量的 remember-me 实现策略。

我们在前面看到,Spring Security提供了两种实现方式。我们依次看一下这两个实现。

TokenBasedRememberMeServices

这个实现支持基于哈希简单令牌法中描述的更简单的方法。TokenBasedRememberMeServices 生成一个 RememberMeAuthenticationToken,由 RememberMeAuthenticationProvider 处理。这个认证提供者和 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>

在应用 application context 中需要以下 Bean 来启用 remember-me 服务。

<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 它将令牌存储在数据库中。

请看 持久化令牌法 的数据库 schema。