认证和授权学习4:springboot+springsecurity实现记住我下次自动登录的功能

本文使用的springboot版本是22.1.3.RELEASE

[上一篇:springsecurity实现方法级的权限控制](https://www.cnblogs.com/chengxuxiaoyuan/p/13973495.html)

一、原理分析

第一次登陆时,如果用户勾选了readme选项,登陆成功后springsecurity会生成一个cookie,返回给浏览器端,并且在服务端生成一个登录成功的标识token和这个cookie绑定,待登录失效后,浏览器下次访问时如果携带了这个cookie,springsecurity如果根据cookie能够查询到登录成功的token,就放行这次登录。

二、工程依赖

<!-- 以下是>spring boot依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- 以下是>spring security依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <!--数据库相关-->
        <!--数据库驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.32</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.11</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

三、登录页面

<form action="login" method="post">
    用户名:<input type="text" name="username"><br>
    密&nbsp;&nbsp;&nbsp;码:
    <input type="password" name="password"><br>
    <input type="checkbox" name="remembermeParamater" value="true">记住我
    <input type="submit" value="登录">
</form>

页面上增加一个checkbox表示记住我,向服务端传递true或者false,name要和后端指定的参数名保持一致。

四、服务端简单实现,把token放在内存中

在springsecurity的配置类中配置使用remember

@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)//开启方法权限控制
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    //用户配置
    ...
    //安全配置
        http.csrf().disable();
        //匹配路径时越具体的路径要先匹配
        http.authorizeRequests().anyRequest().permitAll();
        //注意自定义安全配置时一定要把登录页面和登录url配上,不然访问任何页面都是403,除非最后一行调用父类的config方法
        http.formLogin().loginPage("/login.html").loginProcessingUrl("/login");
        http.rememberMe().rememberMeParameter("remembermeParamater")
        http.rememberMe().tokenValiditySeconds(60*2);//有效时间,已秒为单位
}

注意这里这个rememberMeParameter方法指定的参数要和登录页面上保持一致。

这样当用户第一次登录成功后,关闭浏览器,打开浏览器再次访问系统,即可直接登录成功,但这种实现方式,因为服务端生成的token是保存在内存中的,所以服务端重启后用户的记住我数据就会失效。

五、持久化存储token实现

在数据库中创建一张表,用来存储token,这张表的字段是springsecurity指定的

-- 创建记录rememberme记录的表
CREATE TABLE persistent_logins
(
  username  VARCHAR(64),
  series   VARCHAR(64),
  token     VARCHAR(64),
  last_used DATETIME
 );

针对上边简单实现的缺陷,可以配置让springsecurity把用户登录后生成的记住我token存储在数据库中。这种方式需要指定tokenRepository


@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)//开启方法权限控制
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
     //用户配置
    ...
    //安全配置
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        //匹配路径时越具体的路径要先匹配
        http.authorizeRequests().anyRequest().permitAll();
        //注意自定义安全配置时一定要把登录页面和登录url配上,不然访问任何页面都是403,除非最后一行调用父类的config方法
        http.formLogin().loginPage("/login.html").loginProcessingUrl("/login");
        http.rememberMe().rememberMeParameter("remembermeParamater").tokenRepository(persistentTokenRepository);
        http.rememberMe().tokenValiditySeconds(60*2);//有效时间,已秒为单位
    }

    @Autowired
    private DataSource dataSource;

    @Autowired
    private PersistentTokenRepository persistentTokenRepository;
    
    @Bean
    public PersistentTokenRepository persistentTokenRepository(@Qualifier("dataSource") DataSource dataSource) {
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        tokenRepository.setDataSource(dataSource); // 设置数据源
        tokenRepository.setCreateTableOnStartup(false); // 不让自动创建表
        return tokenRepository;
    }
}

这里创建了一个PersistentTokenRepository的bean,注入给springsecurity的rememberme模块。对于PersistentTokenRepository接口,springsecurity提供了一个基于内存的实现和基于jdbc的实现,这里使用的是基于jdbc的实现来把token持久化到数据库中,所以工程中引入了jdbc的依赖。

六、总结

springboot+springsecuri实现记住我功能的原理就是第一次登陆成功后生成一个标识保存在服务端,并且和发送给客户端的cookie绑定,客户端下次访问时查询这个标识。标识可以保存在内存中,也可以保存在数据库中。

完整的示例工程

示例工程