记住我 认证
记住我(remember-me)或持久化登录(persistent-login)认证是指网站能够在不同会话之间记住用户的身份。这通常通过向浏览器发送一个 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 的名称。
持久化令牌方法
这种方法基于文章 Improved Persistent Login Cookie Best Practice,并做了一些小修改 [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()
方法。每当 SecurityContextHolder
不包含 Authentication
时,RememberMeAuthenticationFilter
都会调用 autoLogin()
方法。因此,此接口为底层记住我实现提供了足够的认证相关事件通知,并在候选 web 请求可能包含 cookie 并希望被记住时委托给实现。这种设计允许使用任意数量的记住我实现策略。
我们之前看到,Spring Security 提供了两种实现。我们将依次介绍它们。
TokenBasedRememberMeServices
此实现支持简单的基于哈希令牌的方法中描述的更简单的方法。TokenBasedRememberMeServices
生成一个 RememberMeAuthenticationToken
,由 RememberMeAuthenticationProvider
处理。认证提供者和 TokenBasedRememberMeServices
之间共享一个 key
。此外,TokenBasedRememberMeServices
需要一个 UserDetailsService
,从中可以检索用户名和密码用于签名比较,并生成包含正确 GrantedAuthority
实例的 RememberMeAuthenticationToken
。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>
在应用程序上下文中需要以下 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
,将令牌存储在数据库中。
有关数据库模式,请参阅持久化令牌方法。