On this page
17. Remember-Me Authentication
17.1 Overview
“记住我”或“持久登录”身份验证是指能够记住会话之间的主体身份的网站。通常,这是通过向浏览器发送一个 cookie 来实现的,该 cookie 在以后的会话中被检测到并导致自动登录。 Spring Security 提供了进行这些操作所需的钩子,并具有两个具体的“记住我”实现。一种使用散列来保留基于 cookie 的令牌的安全性,另一种使用数据库或其他持久性存储机制来存储生成的令牌。
请注意,两个实现都需要UserDetailsService
。如果您使用的身份验证提供程序不使用UserDetailsService
(例如 LDAP 提供程序),那么除非您的应用程序上下文中还包含UserDetailsService
bean,否则它将无法工作。
17.2 基于哈希的简单令牌方法
这种方法使用哈希来实现有用的“记住我”策略。本质上,在成功进行交互式身份验证后,会将 cookie 发送到浏览器,该 cookie 的组成如下:
base64(username + ":" + expirationTime + ":" +
md5Hex(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
因此,记住我令牌仅在指定的时间内有效,并且前提是用户名,密码和密钥不变。值得注意的是,这存在潜在的安全问题,因为捕获的“记住我”令牌将可从任何用户代理使用,直到令牌到期为止。这与摘要身份验证相同。如果委托人知道已捕获令牌,则他们可以轻松更改密码并立即使所有出现的“记住我”令牌失效。如果需要更重要的安全性,则应使用下一节所述的方法。另外,根本不应该使用“记住我”服务。
如果您熟悉namespace configuration章节中讨论的主题,则只需添加<remember-me>
元素即可启用“记住我”身份验证:
<http>
...
<remember-me key="myAppKey"/>
</http>
UserDetailsService
通常会自动选择。如果您的应用程序上下文中有多个,则需要指定user-service-ref
属性应使用哪一个,其中值是UserDetailsService
bean 的名称。
17.3 永久令牌方法
此方法基于文章http://jaspan.com/improved_persistent_login_cookie_best_practice并作了一些较小的修改[16]。要将这种方法与名称空间配置一起使用,您将提供一个数据源参考:
<http>
...
<remember-me data-source-ref="someDataSource"/>
</http>
数据库应包含使用以下 SQL(或等效 SQL)创建的persistent_logins
表:
create table persistent_logins (username varchar(64) not null,
series varchar(64) primary key,
token varchar(64) not null,
last_used timestamp not null)
17.4 记住我的接口和实现
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);
请参阅 Javadoc,以更全面地讨论方法的作用,尽管在此阶段请注意AbstractAuthenticationProcessingFilter
仅调用loginFail()
和loginSuccess()
方法。每当SecurityContextHolder
不包含Authentication
时,RememberMeAuthenticationFilter
就会调用autoLogin()
方法。因此,此接口为底层的“记住我”实现提供了与身份验证相关的事件的充分通知,并在候选 Web 请求可能包含 cookie 并希望被记住时委托给该实现。这种设计允许使用任何数量的“记住我”实施策略。上面我们已经看到,Spring Security 提供了两种实现。我们将依次研究这些。
17.4.1 TokenBasedRememberMeServices
此实现支持第 17.2 节“基于简单哈希的令牌方法”中描述的更简单的方法。 TokenBasedRememberMeServices
生成RememberMeAuthenticationToken
,由RememberMeAuthenticationProvider
处理。在此身份验证提供程序和TokenBasedRememberMeServices
之间共享key
。另外,TokenBasedRememberMeServices
需要一个 UserDetailsService,从中可以检索用户名和密码以进行签名比较,并生成RememberMeAuthenticationToken
以包含正确的GrantedAuthority
。应用程序应提供某种注销命令,如果用户请求,该命令会使 cookie 无效。 TokenBasedRememberMeServices
还实现了 Spring Security 的LogoutHandler
接口,因此可以与LogoutFilter
一起使用以自动清除 cookie。
在应用程序上下文中启用“记住我”服务所需的 bean 如下:
<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
之后)。
17.4.2 PersistentTokenBasedRememberMeServices
可以使用与TokenBasedRememberMeServices
相同的方式来使用此类,但还需要为其配置PersistentTokenRepository
来存储令牌。有两种标准实现。
InMemoryTokenRepositoryImpl
仅用于测试。JdbcTokenRepositoryImpl
将令牌存储在数据库中。
上面的第 17.3 节“持久令牌方法”中描述了数据库模式。
[16]本质上,用户名不包含在 cookie 中,以防止不必要地公开有效的登录名。本文的 Comment 部分对此进行了讨论。