形成天才的决定因素应该是勤奋。—— 郭沫若
Spring Security为用户提供了方便的记住密码功能,将生成的rememberMeToken放入了cookie中,这也意味着只要token没有过期,一旦泄漏后就会产生安全风险。所以应该提供其它的校验进一步来加强系统的安全性。
二次校验
在用户自动登录后,可以通过对密码进行二次校验进而确保用户的真实性。尽管Spring Security提供了记住密码功能,但是也为我们提供了二次校验的功能。
思路是对一些查询等不进行修改操作的接口,此时我们允许通过二次登录的用户直接进行查看,但是对于一些修改数据的接口,此时要求验证密码后才允许操作。
假设有一个Controller,拥有三个接口,getData作为公开接口,登录后任何用户都可以访问。updateData接口需要验证密码后才可以访问,即使通过rememberMe登录,访问后仍然需要验证密码。getData需要通过rememberMe登录后才可以进行访问,直接通过密码登录是无法访问的。
@RestController
public class HelloController {
@GetMapping("/getData")
public String getData() {
return "hello world";
}
@GetMapping("/updateData")
public String updateData() {
return "hello updateData";
}
@GetMapping("/rememberData")
public String rememberData() {
return "hello rememberData";
}
}
配置:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/rememberData").rememberMe()
.antMatchers("/updateData").fullyAuthenticated()
.anyRequest().authenticated()
.and()
...
}
/rememberData是通过rememberMe方式登录后才可以访问
/updateData是通过账号密码登录后才可以访问
authenticated 和 fullyAuthenticated的区别:authenticated 表示登录后可以访问,不论登录方式,fullyAuthenticated表示只允许账号密码登录方式才可以访问,rememberMe登录是无法访问的。
持久化令牌
当用户勾选remember后进行登录时,我们可以生成一个持久化的令牌,当用户下次自动登录时,获取令牌进行验证。
这里有两个关键的值:series 和 tokenValue,它们都是用MD5散列过的随机字符串。用户登录成功后,产生的series是不会更新的,只有用户再次使用密码登录时才会进行更新。而tokenValue会在每个session中重新生成。由于每次session会更新token,因此也可以很好的解决多端登录问题。
PersistentRememberMeToken是持久化令牌的实体类。提供了四个字段:用户名、序列号、令牌和最后一次自动登录时间。
对应的Sql也在JdbcTokenRepositoryImpl进行了定义。
根据大佬Luke Taylor提供的实体类和Sql,对于持久化令牌的表结构有了更清楚的了解。
配置:
首先需要创建张persistent_logins表
CREATE TABLE `persistent_logins` (
`username` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL,
`series` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL,
`token` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL,
`last_used` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`series`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
注入数据库配置,创建JdbcTokenRepositoryImpl 实例并注入数据库。
@Autowired
DataSource dataSource;
@Bean
JdbcTokenRepositoryImpl jdbcTokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
return jdbcTokenRepository;
}
将JdbcTokenRepositoryImpl 注入配置文件中。
@Override
protected void configure(HttpSecurity http) throws Exception {
http.rememberMe()
.tokenRepository(jdbcTokenRepository())
.and()
....
}