记住我(remember-me)或持久登录(persistent-login)认证,指的是网站可以在不同会话之间记忆验证的身份。 通常情况是发送一个cookie给浏览器,在以后的session里检测cookie,进行自动登录。 Spring Security为remember-me实现提供了必要的调用钩子,并提供了两个remember-me的具体实现。 其中一个使用散列来保护基于cookie标记的安全性,另一个使用了数据库或其他持久化存储机制来保存生成的标记。
注意,两个实现方式,都需要UserDetailsService
。
如果你使用了认证提供器,没有使用UserDetailsService
(比如LDAP供应器),那它就没法工作,除非你在application context里设置了一个UserDetailsService
。
这种方法使用散列来完成remember-me策略。 本质上,在成功进行认证的之后,把一个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
这个remember-me标记只适用于指定范围,提供用户名,密码和关键字都不会改变。 值得注意,这里有一个潜在的安全问题,来自任何一个用户代理的remember-me标记,直到标记过期都是可用的。 这个问题和摘要式认证相同。 如果一个用户发现标记已经设置了,他们可以轻易修改他们的密码,并且立即注销所有的remember-me标记。 如果需要更好的安全性,你应该使用下一章描述的方法。 或者不应该使用remember-me服务。
如果你还记得在命名空间配置中讨论的主题,你只要添加<remember-me>
元素就可以使用remember-me认证:
<http> ... <remember-me key="myAppKey"/> </http>
这个UserDetailsService
会自动选上。
如果你在application context中配置了多个,你需要使用user-service-ref
属性指定应该使用哪一个,这里的值要放上你的UserDetailsService
bean的名字。
这个方法是基于这篇文章 http://jaspan.com/improved_persistent_login_cookie_best_practice 进行了一些小修改 [10]。 要用在命名空间配置里使用这个方法,你应该提供一个datasource引用:
<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)
Remember-me认证不能和基本认证一起使用,因为基本认证往往不使用HttpSession
。
Remember-me使用在UsernamePasswordAuthenticationFilter
中,通过在它的超类AbstractAuthenticationProcessingFilter
里实现的一个调用钩子。
这个钩子会在合适的时候调用一个具体的RememberMeServices
。
这个接口看起来像这样:
Authentication autoLogin(HttpServletRequest request, HttpServletResponse response); void loginFail(HttpServletRequest request, HttpServletResponse response); void loginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication);
请参考JavaDocs获得有关这些方法的完整讨论,不过注意在这里,AbstractAuthenticationProcessingFilter
只调用loginFail()
和 loginSuccess()
方法。
当SecurityContextHolder
没有包含Authentication
的时候,RememberMeProcessingFilter
才去调用autoLogin()
。
因此,这个接口通过使用完整的认证相关事件的提醒提供了下面remember-me实现,然后在可能包含一个cookie希望被记得的申请web请求中调用这个实现。
这个设计允许任何数目的remember-me实现策略。
我们在下面看看上面介绍过的两个Spring Security提供的实现。
这个实现支持在Section 10.2, “简单基于散列标记的方法”里描述的简单方法。
TokenBasedRememberMeServices
被
RememberMeAuthenticationProvider
执行的时候生成一个RememberMeAuthenticationToken
。
认证提供器和TokenBasedRememberMeServices
之间共享一个key
。
另外TokenBasedRememberMeServices
需要一个UserDetailsService,用它来获得用户名和密码,进行比较,然后生成RememberMeAuthenticationToken
来包含正确的GrantedAuthority[]
。
如果用户请求注销,让cookie失效,就应该使用系统提供的一系列注销命令。
TokenBasedRememberMeServices
也实现Spring Security的LogoutHandler
接口,这样可以使用LogoutFilter
自动清除cookie。
这些bean要求在application context里启用remember-me服务,像下面一样:
<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.web.authentication.rememberme.RememberMeAuthenticationProvider"> <property name="key" value="springRocks"/> </bean>
不要忘记把你的RememberMeServices
实现添加到UsernamePasswordAuthenticationFilter.setRememberMeServices()
属性中,包括把RememberMeAuthenticationProvider
添加到你的UsernamePasswordAuthenticationFilter.setProviders()
队列中,把RememberMeProcessingFilter
添加到你的FilterChainProxy
中(要放到AuthenticationProcessingFilter
后面)。
这个类可以像TokenBasedRememberMeServices
一样使用,但是它还需要配置一个PersistentTokenRepository
来保存标记。
这里有两个标准实现。
InMemoryTokenRepositoryImpl
最好是只用来测试。
JdbcTokenRepositoryImpl
把标记保存到数据库里。
数据库表结构在 Section 10.3, “持久化标记方法”.