记住我认证

“记住我”或“持久登录”认证是指网站能够在会话之间记住主体的身份。这通常通过向浏览器发送一个 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 通常会自动选择。如果您的应用程序上下文中有一个以上的 UserDetailsService,则需要使用 user-service-ref 属性指定要使用的那个,其中值是您的 UserDetailsService bean 的名称。

持久令牌方法

此方法基于文章 改进的持久登录 Cookie 最佳实践,并进行了一些细微修改 [1]。要将此方法与命名空间配置一起使用,您需要提供一个数据源引用

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

记住我接口和实现

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

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

TokenBasedRememberMeServices

此实现支持 简单基于哈希的令牌方法 中描述的更简单的方法。TokenBasedRememberMeServices 生成一个 RememberMeAuthenticationToken,由 RememberMeAuthenticationProvider 处理。一个 key 在此认证提供程序和 TokenBasedRememberMeServices 之间共享。此外,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 中不包含用户名,以防止不必要地暴露有效的登录名。对此在本文的评论部分有讨论。
© . This site is unofficial and not affiliated with VMware.