Spring Security 中的 RememberMe 登录,so easy!
创始人
2024-04-12 22:02:25
0

1. RememberMe简介

RememberMe 这个功能非常常见,图 6-1 所示就是 QQ 邮箱登录时的“记住我”选项。

提到 RememberMe,一些初学者往往会有一些误解,认为 RememberMe 功能就是把用户名/密码用 Cookie 保存在浏览器中,下次登录时不用再次输入用户名/密码。这个理解显然是不对的。

我们这里所说的 RememberMe 是一种服务器端的行为。传统的登录方式基于  Session 会话,一旦用户关闭浏览器重新打开,就要再次登录,这样太过于烦琐。如果能有一种机制,让用户关闭并重新打开浏览器之后,还能继续保持认证状态,就会方便很多,RememberMe 就是为了解决这一需求而生的。

具体的实现思路就是通过 Cookie 来记录当前用户身份。当用户登录成功之后,会通过一定的算法,将用户信息、时间戳等进行加密,加密完成后,通过响应头带回前端存储在 Cookie 中,当浏览器关闭之后重新打开,如果再次访问该网站,会自动将 Cookie 中的信息发送给服务器,服务器对 Cookie 中的信息进行校验分析,进而确定出用户的身份,Cookie 中所保存的用户信息也是有时效的,例如三天、一周等。敏锐的读者可能已经发现这种方式是存在安全隐患的。所谓鱼与熊掌不可兼得,要想使用便利,就要牺牲一定的安全性,不过在本章中,我们将会介绍通过持久化令牌以及二次校验来降低使用 RememberMe 所带来的安全风险。

2. RememberMe基本用法

我们先来看一种最简单的用法。

首先创建一个 Spring Boot 工程,引入 spring-boot-starter-security 依赖。工程创建成功后,添加一个 HelloController 并创建一个测试接口,代码如下:

@RestController
public class HelloController {@GetMapping("/hello")public String hello() {return "hello";}
}

然后创建 SecurityConfig 配置文件:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@BeanPasswordEncoder passwordEncoder() {return NoOpPasswordEncoder.getInstance();}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.inMemoryAuthentication().withUser("javaboy").password("123").roles("admin");}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().rememberMe().key("javaboy").and().csrf().disable();}
}

这里我们主要是调用了 HttpSecurity 中的 rememberMe 方法并配置了一个 key,该方法最终会向过滤器链中添加 RememberMeAuthenticationFilter 过滤器。

配置完成后,启动项目,当我们访问 /hello 接口时,会自动重定向到登录页面,如图 6-2 所示。

可以看到,此时的默认登录页面多了一个 RememberMe 选项,勾选上 RememberMe,登录成功之后,我们就可以访问 /hello 接口了。访问完成后,关闭浏览器再重新打开,此时不需要登录就可以直接访问 /hello 接口;同时,如果关闭掉服务端重新打开,再去访问 /hello接口,发现此时也不需要登录了。

那么这一切是怎么实现的呢?打开浏览器控制台,我们来分析整个登录过程。

首先,当我们单击登录按钮时,多了一个请求参数 remember-me,如图6-3所示。

很明显,remember-me 参数就是用来告诉服务端是否开启 RememberMe 功能,如果开发者自定义登录页面,那么默认情况下,是否开启 RememberMe 的参数就是 remember-me。

当请求成功后,在响应头中多出了一个 Set-Cookie,如图 6-4 所示。

在响应头中给出了一个 remember-me 字符串。以后所有请求的请求头 Cookie 字段,都会自动携带上这个令牌,服务端利用该令牌可以校验用户身份是否合法。

大致的流程就是这样,但是大家发现这种方式安全隐患很大,一旦 remember-me 令牌泄漏,恶意用户就可以拿着这个令牌去随意访问系统资源。持久化令牌和二次校验可以在一定程度上降低该问题带来的风险。

3. 持久化令牌

使用持久化令牌实现 RememberMe 的体验和使用普通令牌的登录体验是一样的,不同的是服务端所做的事情变了。

持久化令牌在普通令牌的基础上,新增了 series 和 token 两个校验参数,当使用用户名/密码的方式登录时,series 才会自动更新;而一旦有了新的会话,token 就会重新生成。所以,如果令牌被人盗用,一旦对方基于 RememberMe 登录成功后,就会生成新的 token,你自己的登录令牌就会失效,这样就能及时发现账户泄漏并作出处理,比如清除自动登录令牌、通知用户账户泄漏等。

Spring Security中对于持久化令牌提供了两种实现:

  • JdbcTokenRepositoryImpl

  • InMemoryTokenRepositoryImpl

前者是基于 JdbcTemplate 来操作数据库,后者则是操作存储在内存中的数据。由于 InMemoryTokenRepositoryImpl 的使用场景很少,因此这里主要介绍基于 JdbcTokenRepositoryImpl 的配置。

首先创建一个 security06 数据库,然后我们需要一张表来记录令牌信息,创建表的 SQL 脚本在在 JdbcTokenRepositoryImpl 类中的 CREATE_TABLE_SQL 变量上已经定义好了,代码如下:

public 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)";

我们直接将变量中定义的 SQL 脚本拷贝出来到数据库中执行,生成一张 persistent_logins 表用来记录令牌信息。persistent_logins 表一共就四个字段:username 表示登录用户名、series 表示生成的 series 字符串、token 表示生成的 token 字符串、last_used 则表示上次使用时间。

接下来,在项目中引入 JdbcTemplate 依赖和 MySQL 数据库驱动依赖:

org.springframework.bootspring-boot-starter-jdbc

mysqlmysql-connector-java

然后在 application.properties 中配置数据库连接信息:

spring.datasource.url=jdbc:mysql:///security06?useUnicode=true&characterEncod
ing=UTF-8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=123

最后修改 SecurityConfig:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@AutowiredDataSource dataSource;@BeanJdbcTokenRepositoryImpl jdbcTokenRepository() {JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();jdbcTokenRepository.setDataSource(dataSource);return jdbcTokenRepository;}@BeanPasswordEncoder passwordEncoder() {return NoOpPasswordEncoder.getInstance();}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.inMemoryAuthentication().withUser("javaboy").password("123").roles("admin");}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().rememberMe().tokenRepository(jdbcTokenRepository()).and().csrf().disable();}
}

在配置中我们提供了一个 JdbcTokenRepositoryImpl 实例,并为其配置了数据源,最后在配置 RememberMe 时通过 tokenRepository 方法指定 JdbcTokenRepositoryImpl 实例。

配置完成后,启动项目并进行登录测试。登录成功后,我们发现数据库表中多了一条记录,如图6-5所示。

此时如果关闭浏览器重新打开,再去访问 /hello 接口,访问时并不需要登录,但是访问成功之后,数据库中的 token 字段会发生变化。同时,如果服务端重启之后,浏览器再去访问 /hello 接口,依然不需要登录,但是 token 字段也会更新,因为这两种情况中都有新会话的建立,所以 token 会更新,而 series 则不会更新。当然,如果用户注销登录,则数据库中和该用户相关的登录记录会自动清除。

可以看到,持久化令牌比前面的普通令牌安全系数提高了不少,但是依然存在风险。安全问题和用户的使用便捷性就像一个悖论,想要用户使用方便,不可避免地要牺牲一点安全性。对于开发者而言,要做的就是如何将系统存在的安全风险降到最低。

那么怎么办呢?二次校验可以帮助我们进一步降低风险。。。

相关内容

热门资讯

个人销售住房增值税政策来了 12月29日,财政部、国家税务总局发布“关于个人销售住房增值税政策的公告”。 个人(不含个体工商...
南京拟修订法规加强历史文化名城... 原题:保护利用将设“正负面清单” 《南京市历史文化名城保护条例》将迎大修 南京市历史文化名城保护法规...
《深圳公益诉讼检察工作白皮书(... 深圳新闻网2025年12月31日讯(深圳特区报记者 上官文复 通讯员 李俞青)12月30日,深圳市人...
了解招生政策要选择官方渠道 石向阳 绘 日前,清华大学招生办公室发布声明称,有部分机构与个人冒用学校名义开展招生宣讲,散布不实招...
视频丨日学者:高市言行和政策令... 日本上智大学政治学教授中野晃一近日在接受总台记者采访时指出,高市早苗有关对华立场的言论将进一步破坏日...
10年受理公益诉讼案件线索超8... 深圳特区报讯(记者 上官文复 通讯员 李俞青)12月30日,深圳市人民检察院举办《深圳公益诉讼检察工...
日学者:高市言行和政策令日本面... 日本上智大学政治学教授中野晃一近日在接受总台记者采访时指出,高市早苗有关对华立场的言论将进一步破坏日...
袁家军、胡衡华、刘明胜、徐树彪... 据重庆日报消息,12月30日下午, 国家电投集团水电股份有限公司揭牌。重庆市委书记袁家军,市委副书记...
“3女带4孩续面”被改编成动画... 极目新闻记者 詹钘 近日,有网友发现,郑州续面事件已经被人改编成动画,在短视频平台和短剧平台播放。视...
征兵政策解读之二:征集条件篇 一、年龄条件 1.义务兵。男青年年满18 至22 周岁,普通高等学校本专科毕业生、符合毕业条件的毕业...