SpringSecurity中文文档(Servlet RememberMe)

Remember-Me Authentication

Remember-me 或持久登录身份验证指的是网站能够在会话之间记住主体的标识。这通常是通过向浏览器发送 Cookie 来完成的,Cookie 将在以后的会话中被检测到,并导致自动登录的发生。Spring Security 为这些操作提供了必要的钩子,并且有两个具体的 remember-me 实现。一种方法使用哈希来保护基于 cookie 的令牌的安全性,另一种方法使用数据库或其他持久存储机制来存储生成的令牌。

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

Simple Hash-Based Token Approach

这种方法使用哈希来实现一个有用的"记住我"策略。本质上,在成功的交互式身份验证后,一个cookie会被发送到浏览器,该cookie的构成如下:

txt 复制代码
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

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

如果您熟悉本章中讨论的命名空间配置主题,您可以通过添加 元素来启用"记住我"身份验证:

java 复制代码
<http>
...
<remember-me key="myAppKey"/>
</http>

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

Persistent Token Approach

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

java 复制代码
<http>
...
<remember-me data-source-ref="someDataSource"/>
</http>

数据库应该包含一个持久化 _ login 表,该表是通过使用以下 SQL (或等效 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。以下列表显示了该接口:

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

我们之前已经看到,Spring Security 提供了两种实现。我们逐一查看这些实现。

TokenBasedRememberMeServices

这个实现支持之前描述的简单基于哈希的令牌方法。TokenBasedRememberMeServices 生成一个 RememberMeAuthenticationToken,该令牌由 RememberMeAuthenticationProvider 处理。这个身份验证提供者和 TokenBasedRememberMeServices 之间共享一个密钥。此外,TokenBasedRememberMeServices 需要 一个 UserDetailsService,从中它可以检索用户名和密码以用于签名比较目的,并生成包含正确 GrantedAuthority 实例的 RememberMeAuthenticationToken。TokenBasedRememberMeServices 还实现了 Spring Security 的 LogoutHandler 接口,因此它可以与 LogoutFilter 一起使用,以自动清除 cookie。

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

java 复制代码
@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;
}

应用程序上下文中需要以下 bean 来启用 remember-me 服务:

java 复制代码
@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;
}

请记得将您的 RememberMeServices 实现添加到您的 UsernamePasswordAuthenticationFilter.setRememberMeServices() 属性中,将 RememberMeAuthenticationProvider 包含在您的 AuthenticationManager.setProviders() 列表中,并在您的 FilterChainProxy 中添加 RememberMeAuthenticationFilter(通常紧随您的 UsernamePasswordAuthenticationFilter 之后)。

PersistentTokenBasedRememberMeServices

您可以按照与 TokenBasedRememberMeServices 相同的方式使用该类,但是它还需要配置一个 PersisentTokenRepository 来存储令牌。

  • InMemoryTokenRepositoryImpl which is intended for testing only.
  • JdbcTokenRepositoryImpl which stores the tokens in a database.

有关数据库架构,请参见持久性令牌方法。

相关推荐
大鸡腿同学26 分钟前
后端
IT_陈寒37 分钟前
Vite 凭什么比 Webpack 快50%?揭秘闪电构建背后的黑科技
前端·人工智能·后端
颜酱38 分钟前
Dijkstra 算法:从 BFS 到带权最短路径
javascript·后端·算法
aircrushin2 小时前
OpenClaw“养龙虾”现象的社会技术学分析
前端·后端
37手游后端团队2 小时前
全网最简单!从零开始,轻松把 openclaw 小龙虾装回家
人工智能·后端·openai
用户8307196840822 小时前
Spring Boot WebClient性能比RestTemplate高?看完秒懂!
java·spring boot
Apifox2 小时前
测试数据终于不用到处复制了,Apifox 自动化测试新增「共用测试数据」
前端·后端·测试
Gardener1722 小时前
OpenStack Instance ID 映射机制详解
后端
无责任此方_修行中3 小时前
拒绝 AI 焦虑!一个普通程序员的真实 AI 工作流(附成本账单)
后端·程序员·ai编程