「记住我」这一功能多出现在互联网应用中,其目的是为了减少用户的认证次数和访问门槛。在一般的内网应用、或者是安全性要求较高的管理后台中出现使用频度较低。
「记住我 Remember-me」也称为「持续登录 Persistent-login」, 主要用到了 Cookies 和 Token 技术,本节重点讨论如何通过 Spring Security 配合出「记住我」的自动认证功能。
「记住我」的核心思路是:将认证状态以安全的方式保存在客户端。
「记住我」需要通过向浏览器设置 Cookies 信息,这个 Cookies 信息未来会用于建立会话连接,并且提供自动登录的能力。
「记住我」的基本流程为:
通过前面描述我们看到,要实现「记住我」功能,关键在于如何安全的保护好用户的认证信息 Token。Spring Security 提供了两种「记住我」的实现方式:
使用 Hash 算法加密认证信息形成 Token,并将其保存在客户端中;
将认证信息保存在数据库中,并将查询条件保存在客户端中。
基于 Hash 的方式是一种相对简单的集成方式。这种方式利用 Hash 的特性,将「记住我」信息进行存档。每当用户认证通过,服务端便生成一条 Hash 记录,并发送给客户端浏览器,其中内容包括「用户名」、「Token 过期时间」、「密码」、「签名秘钥」。
发送的具体内容为:
base64(username + ":" + expirationTime + ":" + md5Hex(username + ":" + expirationTime + ":" password + ":" + key))
username: 根据 UserDetailsService 配置得到用户名信息。
password: 认证密码,确保 UserDetailsService 中可以匹配到目标用户。
expirationTime: 「记住我」Roken 的有效期,精确到毫秒。
key: 用于给 Token 签名的密钥信息,防止该 Token 被篡改。
发送出的 Token 只有到用户下次需要登录时才会被使用到,这期间,需要确保用户名、密码、密钥等信息不被改变。还需要注意的是,「记住我」Token 在过期之前,可以在任何地方使用,因此其安全性上有一定的问题,如果使用数字认证一样。当用户认为自己的 Token 不在安全时,最好的办法是立刻改变自己的认证密码,并且使全部的「记住我」Token 失效。
启动「记住我」功能仅需要一行配置,具体方式为:
<http>
...
<remember-me key="签名密钥"/>
</http>
当有多个 UserDetailsService 实例时,可以通过 user-service-ref
属性指定唯一实例。
使用数据库作为 Token 存储方式,需要在 <remember-me>
配置中增加 data-source-ref
属性,配置方式如下:
<http>
...
<remember-me data-source-ref="数据源实例"/>
</http>
所用到的数据源需要包含 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)
「记住我」需要配合「用户名密码认证过滤器」一起使用,触发 RememberMeServices
实例实现其效果。「记住我」接口中有三个主要方法,第一个名为 autoLogin
用于自动登录审核,另外两个是 loginFail
和 loginSuccess
分别在认证失败或成功时触发。
具体表现形式为:
// 自动认证
Authentication autoLogin(HttpServletRequest request, HttpServletResponse response);
自动认证方法,在「记住我」功能启用后,同时当前上下文中找不到用户信息时触发,我们需要根据不同的 Token 策略,实现「记住我」的判断逻辑。
// 登录失败时触发
void loginFail(HttpServletRequest request, HttpServletResponse response);
// 登录成功时触发
void loginSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication successfulAuthentication);
如前文所述,「记住我」有两种 Token 策略,对应了两种实现方法。
先上代码:
<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>
基于 Hash 的方式需要配置三个核心 Bean 对象,分别是「过滤器」、「记住我处理服务」和「认证管理器」。这其中 TokenBasedRememberMeServices
负责生成 Token 内容,并交给「认证管理器」使用。
最后,要把处理服务 RememberMeServices
设置到用户名密码认证过滤器 UsernamePasswordAuthenticationFilter.setRememberMeServices()
里,将记住我的认证管理器添加到 AuthenticationManager.setProviders()
之中,将记住我过滤器添加到安全过滤链之中。
使用数据存储方式,其实现代码与 Hash 方式基本相同,区别在于需要继续配置 PersistentTokenRepository
来存取 Token,有两个标准实现类:第一个是基于内存的 InMemoryTokenRepositoryImpl
,第二个是基于 JDBC 的 JdbcTokenRepositoryImpl
。通常情况下,第一种用于集成测试,第二种用于生产环境。
本节我们讨论了「记住我」的原理及快速集成方式:
下节我们讨论,当系统对认证有特殊需求且无法由 Spring Security 安全框架提供时,如何实现使用外部方式认证,使用 Spring Security 管理认证结果及鉴权的方法。
0/1000