Spring Security之持久化令牌

305 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第20天,点击查看活动详情

\qquad在上篇文章讲了Spring Security的RememberMe,但是使用普通令牌的登录依旧存在一些安全问题,Spring Security通过持久化令牌和二次校验来进一步提高系统的安全性。

持久化令牌

\qquad使用持久化令牌实现RememberMe的登录和使用普通令牌的登录的不同之处在于服务端所做的事情变了,主要在持久化这三个字上。持久化一般是与数据库有关,持久化令牌在普通令牌的基础上,新增了两个校验参数--series 和token,当使用用户名/密码的方式登录时,series 才会自动更新,而一旦有了新的会话,token 就会重新生成。所以,如果令牌被人盗用,一旦他人通过RememberMe登录后,就会产生新的token,那自己的登录令牌就会失效,这样就能及时发现账户泄漏并作出处理,可以通知用户账户泄漏等信息,方便用户进行信息的修改。
\qquadSpring Security中对于持久化令牌提供了两种实现: JdbcTokenRepositoryImpl(通过JdbcTemplate来将数据持久化到数据库)和InMemoryTokenRepositorylmpl(操作内存里的数据),通过对比就自然知道使用基于JdbcTokenRepositoryImpl的配置会更好。
\qquad既然要操作数据库,首先需要创建一张表来记录令牌信息,创建表的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)";

\qquad在RememberMe的基础上,我们需要引入mysql和Jdbc的依赖,在配置文件上配置好数据库的连接,然后直接将变量中定义的SQL脚本拷贝出来到数据库中执行,生成数据表 persistent_ logins用来记录令牌信息。

persistent_ logins 表一共就四个字段:
username:表示登录用户名、
series:表示生成的series字符串、
token:表示生成的token字符串、
last_used:则表示上次使用时间。

\qquad对原来的项目进行修改,向spring注入了一个JdbcTokenRepositoryImpl 实例,并为其配置了数据源,最后在配置RememberMe时通过tokenRepository方法指定 JdbcTokenRepositoryImpl 实例,开启持久化令牌:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    PasswordEncoder passwordEncoder(){
        return NoOpPasswordEncoder.getInstance();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("jacks")
                .password("1234")
                .roles("admin");
    }

    @Autowired
    DataSource dataSource;
    @Bean
    JdbcTokenRepositoryImpl jdbcTokenRepository(){
        JdbcTokenRepositoryImpl repository = new JdbcTokenRepositoryImpl();
        repository.setDataSource(dataSource);
        return repository;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .and()
                .rememberMe()
                .tokenRepository(jdbcTokenRepository()) //开启二次校验
                //.key("jacks") 这里不要设置标识remember-me身份验证创建的令牌的键
                .and()
                .csrf().disable();
    }
}

\qquad当我们重新启动项目并用RememberMe进行登录时,数据库表会生产一条记录,记录series和token的值,那么如果关闭浏览器再重新打开,接着去访问/hello接口,访问时并不需要登录,但是访问成功之后,数据库中的token字段会发生变化。这样的话,即使是服务端重启,通过浏览器再去访问/hello接口,依然不需要登录,但是token字段会更新,因为这两种情况中都有新会话的建立,所以token会更新,而series则不会更新。
\qquad当然,如果用户注销登录,则数据库中和该用户相关的登录记录会自动清除。可以看到,持久化令牌比前面的普通令牌安全系数提高了不少,但是依然存在风险。对于开发者而言,要做的就是将系统存在的安全风险降到最低。