SpringSecurity记住我,分为两种方式
1、基于简单加密token的方法
2、持久化记住我两种实现:基于内存的;基于数据库的
记住我功能基本原理
流程:
1、 当用户发起认证请求,会被UsernamePasswordAuthenticationFilter拦截,然后身份认证;
2、 认证成功后,在AbstracAuthenticationProcessingFilter中有个RememberMeServices接口。该接口默认实现类是NullRememberMeServices,使用数据库实现记住我,配置后会调用另一个实现抽象类AbstractRememberMeServices
对于RememberMeService负责针对每一用户生成一个Token,然后将token写入到浏览器的Cookie里面,同时会使用TokenRepository将这个token写入数据库中。将Token写入数据库时候,同时会把用户认证成功的用户名一并写入数据库(此时用户名和token是一一对应的)。
当用户下次请求的时候会经过过滤器链中的RemeberMeAuthenticationFilter(这个过滤器作用就是读取cookie中token)然后交给RemeberMeService,RemeberMeService通过TokenRepository到数据库去查询这个Token数据库里面有没有记录。如果有记录就去除用户名,取出用户名之后,就会去调用UserDetailsService,获取用户信息,然后把获取的当前用户信息放到SecurityContext里面。这样用户就直接登录上了。
对于RemeberMeAuthenticationFilter在我们的过滤器链中绿色过滤器中,他是在倒数第二个位置。前面是其他的认证,其他的认证都没法认证用户信息的时候RemeberMeAuthenticationFilter尝试去做认证。

基于简单加密token的方法
简单加密token的方法思想
摘抄自:https://www.iteye.com/blog/elim-2163997#_Toc405748626
当用户选择了记住我成功登录后,Spring Security将会生成一个cookie发送给客户端浏览器。cookie值由如下方式组成:
base64(username+":"+expirationTime+":"+md5Hex(username+":"+expirationTime+":"+password+":"+key))
username:登录的用户名。
password:登录的密码。
expirationTime:token失效的日期和时间,以毫秒表示。
key:用来防止修改token的一个key。
这样用来实现Remember-Me功能的token只能在指定的时间内有效,且必须保证token中所包含的username、password和key没有被改变才行。
需要注意的是,这样做其实是存在安全隐患的,那就是在用户获取到实现记住我功能的token后,任何用户都可以在该token过期之前通过该token进行自动登录。如果用户发现自己的token被盗用了,那么他可以通过改变自己的登录密码来立即使其所有的记住我token失效。如果希望我们的应用能够更安全一点,可以使用接下来要介绍的持久化token方式,或者不使用Remember-Me功能,因为Remember-Me功能总是有点不安全的。
记住我代码具体实现(简单加密)
http.rememberMe().rememberMeParameter("remember-me") //接收页面传过来的参数默认就是remember-me,这里可以指定更换
.rememberMeCookieName(keyName) //设置cookie的名称
.tokenValiditySeconds(1209600)
.key(
.key(key) //用于加密
前端登录加
<input name="remember-me" type="checkbox" value="true"/>记住我
基于持久化token的方法
持久化token的方法思想
摘抄自:https://www.iteye.com/blog/elim-2163997#_Toc405748626持久化token的方法跟简单加密token的方法在实现Remember-Me功能上大体相同,都是在用户选择了“记住我”成功登录后,将生成的token存入cookie中并发送到客户端浏览器,待到下次用户访问系统时,系统将直接从客户端cookie中读取token进行认证。
不同的是基于简单加密token的方法,一旦用户登录成功后,生成的token将在客户端保存一段时间,如果用户不点击退出登录,或者不修改密码,那么在cookie失效之前,他都可以使用该token进行登录,哪怕该token被别人盗用了,用户与盗用者都同样可以进行登录。而基于持久化token的方法采用这样的实现逻辑:
(1)用户选择了“记住我”成功登录后,将会把username、随机产生的序列号、生成的token存入一个数据库表中,同时将它们的组合生成一个cookie发送给客户端浏览器。
(2)当下一次没有登录的用户访问系统时,首先检查cookie,如果对应cookie中包含的username、序列号和token与数据库中保存的一致,则表示其通过验证,系统将重新生成一个新的token替换数据库中对应组合的旧token,序列号保持不变,同时删除旧的cookie,重新生成包含新生成的token,就的序列号和username的cookie发送给客户端。
(3)如果检查cookie时,cookie中包含的username和序列号跟数据库中保存的匹配,但是token不匹配。这种情况极有可能是因为你的cookie被人盗用了,由于盗用者使用你原本通过认证的cookie进行登录了导致旧的token失效,而产生了新的token。这个时候Spring Security就可以发现cookie被盗用的情况,它将删除数据库中与当前用户相关的所有token记录,这样盗用者使用原有的cookie将不能再登录,同时提醒用户其帐号有被盗用的可能性。
(4)如果对应cookie不存在,或者包含的username和序列号与数据库中保存的不一致,那么将会引导用户到登录页面。
从以上逻辑我们可以看出持久化token的方法比简单加密token的方法更安全,因为一旦你的cookie被人盗用了,你只要再利用原有的cookie试图自动登录一次,原有的token将失效导致盗用者不能再使用原来盗用的cookie进行登录了,同时用户可以发现自己的cookie有被盗用的可能性。
但因为cookie被盗用后盗用者还可以在用户下一次登录前顺利的进行登录,所以如果你的应用对安全性要求比较高就不要使用Remember-Me功能了。
使用持久化token方法时需要我们的数据库中拥有如下表及其表结构。
复制收展Javapublic static final String CREATE_TABLE_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)";
- 1
=================================
基于简单加密token的方法,当我们重启服务器后Remember Me将失效。这是因为用户的session已经丢失了。
Spring Security提供了Remember Me Toke持久化技术来解决这个问题。RememberMeServices默认实现类是TokenBasedRememberMeServices,该类将Remember Me Token存储到内存,当重启服务后,内存数据将丢失,也是无法验证用户的有效Token。
PersistentTokenBasedRememberMeServices是RememberMeServices的另一个实现,其通过PersistentTokenRepository将Remember Me Token存储到数据库。当用户再次登入,通过对比cookie和数据库就可以完成认证过程。
记住我代码具体实现(数据库)
复制收展Java/**
* 可持久化的cookie token服务
*/
@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
//InMemoryTokenRepositoryImpl tokenRepository = new InMemoryTokenRepositoryImpl();
//配置数据源,因为要在数据库中创建表保存token信息
tokenRepository.setDataSource(dataSource);
//启动的时候是否创建该表 CREATE_TABLE_SQL,这个表格是保存用户登录信息的。
//1、我们直接拷贝出sql语句,然后在数据库执行;
//2、我们不拷贝sql语句,在JdbcTokenRepositoryImpl自己执行,此时我们开启:
// 如果token表不存在,使用下面语句可以初始化 persistent_logins表;若存在,请注释掉这条语句,否则会报错。
//tokenRepository.setCreateTableOnStartup(true);
return tokenRepository;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
注意:如果token表不存在,使用下面语句可以初始化 persistent_logins表;若存在,请注释掉这条语句,否则会报错。 //tokenRepository.setCreateTableOnStartup(true);
复制收展Java@Resource(name="smsUserDetailsService")
private UserDetailsService smsUserDetailsService;
@Resource
private DataSource dataSource;
@Override
public void configure(HttpSecurity http) throws Exception {
//记住我的配置,
//问题:AbstractAuthenticationProcessingFilter中loginSuccess()调用的是NullRememberMeServices(默认实现,且方法都是空的)中loginSuccess(),走不到抽象类 AbstractRememberMeServices
// rememberMe需要的配置包含TokenRepository对象以及token过期时间
http.rememberMe().tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(60 * 60 * 24)
.userDetailsService(smsUserDetailsService);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
前端登录
<input name="remember-me" type="checkbox" value="true"/>记住我
这样就配置好了,配置挺简单。
记住我代码具体实现(内存)
基于内存的和基于数据库方式差不多,区别是
PersistentTokenRepository的实现类持久化方式不同,默认使用InMemoryTokenRepositoryImpl。
public class InMemoryTokenRepositoryImpl implements PersistentTokenRepository {}
public class JdbcTokenRepositoryImpl extends JdbcDaoSupport implements PersistentTokenRepository {}
复制收展Javapackage org.springframework.security.web.authentication.rememberme;
import java.util.Date;
public interface PersistentTokenRepository {
void createNewToken(PersistentRememberMeToken var1);
void updateToken(String var1, String var2, Date var3);
PersistentRememberMeToken getTokenForSeries(String var1);
void removeUserTokens(String var1);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
看一看源码请查看