Remember-Me认证

10.1. 概述

记住我(remember-me)或持久登录(persistent-login)认证,指的是网站可以在不同会话之间记忆验证的身份。 通常情况是发送一个cookie给浏览器,在以后的session里检测cookie,进行自动登录。 Spring Security为remember-me实现提供了必要的调用钩子,并提供了两个remember-me的具体实现。 其中一个使用散列来保护基于cookie标记的安全性,另一个使用了数据库或其他持久化存储机制来保存生成的标记。

注意,两个实现方式,都需要UserDetailsService。 如果你使用了认证提供器,没有使用UserDetailsService(比如LDAP供应器),那它就没法工作,除非你在application context里设置了一个UserDetailsService

10.2. 简单基于散列标记的方法

这种方法使用散列来完成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的名字。

10.3. 持久化标记方法

这个方法是基于这篇文章 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)

10.4. Remember-Me接口和实现

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提供的实现。

10.4.1. TokenBasedRememberMeServices

这个实现支持在Section 10.2, “简单基于散列标记的方法”里描述的简单方法。 TokenBasedRememberMeServicesRememberMeAuthenticationProvider执行的时候生成一个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后面)。

10.4.2. PersistentTokenBasedRememberMeServices

这个类可以像TokenBasedRememberMeServices一样使用,但是它还需要配置一个PersistentTokenRepository来保存标记。 这里有两个标准实现。

  • InMemoryTokenRepositoryImpl最好是只用来测试。

  • JdbcTokenRepositoryImpl把标记保存到数据库里。

数据库表结构在 Section 10.3, “持久化标记方法”.



[10] 基本上,为了防止暴露有效登录名,用户名没有包含在cookie里。在这个文章的评论里有一个相关的讨论。