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.

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

相关推荐
Q_19284999066 分钟前
基于Spring Boot的个人健康管理系统
java·spring boot·后端
liutaiyi86 分钟前
Redis可视化工具 RDM mac安装使用
redis·后端·macos
Q_192849990613 分钟前
基于Springcloud的智能社区服务系统
后端·spring·spring cloud
xiaocaibao77716 分钟前
Java语言的网络编程
开发语言·后端·golang
m0_748245171 小时前
Web第一次作业
java
小码的头发丝、1 小时前
Java进阶学习笔记|面向对象
java·笔记·学习
m0_548514771 小时前
前端Pako.js 压缩解压库 与 Java 的 zlib 压缩与解压 的互通实现
java·前端·javascript
坊钰1 小时前
【Java 数据结构】移除链表元素
java·开发语言·数据结构·学习·链表
chenziang12 小时前
leetcode hot100 LRU缓存
java·开发语言