13.SpringSecurity-添加记住我功能

前言

记住我功能主要讲解为3点:

  1. 记住我功能基本原理
  2. 记住我功能具体实现
  3. 记住我功能SpringSecurity源码解析

内容

1.记住我功能基本原理

13.SpringSecurity-添加记住我功能_第1张图片
在用户发送认证请求之后,或调用我们之前说过的usernamePasswordAuthenticationFilter这个过滤器,认证成功之后会调用一个RemeberMeService服务;负责针对每一用户生成一个Token,然后将token写入到浏览器的Cookie里面,同时会使用:TokenRepository将这个token写入数据库中。将Token写入数据库时候,同时会把用户认证成功的用户名一并写入数据库(此时用户名和token是一一对应的)中因为我们是在用户认证成功之后做的,所以会将用户信息写入,下次用户访问的时候就不需要再次登录了。当用户下次请求的时候会经过过滤器链中的RemeberMeAuthenticationFilter(这个过滤器作用就是读取cookie中token)然后交给RemeberMeService,RemeberMeService通过TokenRepository到数据库去查询这个Token数据库里面有没有记录。如果有记录就去除用户名,取出用户名之后,就会去调用UserDetailsService,获取用户信息,然后把获取的当前用户信息放到SecurityContext里面。这样就把用户登录上了。

另外再说一下:RemeberMeAuthenticationFilter在我们的过滤器链中绿色过滤器中,他是在倒数第二个位置。前面是其他的认证,其他的认证都没法认证用户信息的时候RemeberMeAuthenticationFilter尝试去做认证。

13.SpringSecurity-添加记住我功能_第2张图片

2.记住我功能具体实现

2.1前端页面添加功能

13.SpringSecurity-添加记住我功能_第3张图片

2.2 配置TokenRepository

配置TokenRepository读取数据库,TokenRepository基本配置信息是在我们spring-security-demo里面的,我们在application.yml文件中配置了基本的数据源信息的。
在spring-security-web项目中添加:
13.SpringSecurity-添加记住我功能_第4张图片

  @Bean
    public PersistentTokenRepository persistentTokenRepository(){
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        //
        //因为是Jdbc操作,所以我们需要注入数据源:org.springframework.jdbc.core.support.JdbcDaoSupport
        //tokenRepository继承org.springframework.jdbc.core.support.JdbcDaoSupport
        tokenRepository.setDataSource(dataSource);
        System.out.println("PersistentTokenRepository--dataSource:>dataSource");
        //tokenRepository.setCreateTableOnStartup(true);//系统启动的时候创建:CREATE_TABLE_SQL表
        return tokenRepository;
    }

我们点进去:JdbcTokenRepositoryImpl
里面有一个变量,

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,在数据库创建表,我们有两种思路:

  1. 我们直接拷贝出sql语句,然后在数据库执行
  2. 我们不拷贝sql语句,在JdbcTokenRepositoryImpl自己执行,此时我们开启:
tokenRepository.setCreateTableOnStartup(true);//系统启动的时候创建:CREATE_TABLE_SQL表

2.3 配置Token记住我的过期秒数

因为记住我功能也需要有时间限制,所以我们需要配置token过期时间,
我们在spring-security-core里面的属性配置文件:

13.SpringSecurity-添加记住我功能_第5张图片

2.4 配置查询Token的UserDetailsService

我们在最后一步需要,查找用户进行登录,查找的时候是根据token去查找的,所以我们需要配置UserDetailsService

在spring-security-web里面的WebSecurityConfig注入:

@Autowired
private UserDetailsService userDetailsService;

然后我们添加rememberMe相关配置:

  1. tokenRepository
  2. tokenValiditySeconds(验证码过期时间)
  3. 配置操作数据库用户的service(userDetailsService)
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private SecurityProperties securityProperties;

    @Autowired
    private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;

    @Autowired
    private MyAuthenticationFailureHandler myAuthenticationFailureHandler;

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private DataSource dataSource;

    @Bean
    public PasswordEncoder  passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Bean
    public PersistentTokenRepository persistentTokenRepository(){
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        //
        //因为是Jdbc操作,所以我们需要注入数据源:org.springframework.jdbc.core.support.JdbcDaoSupport
        //tokenRepository继承org.springframework.jdbc.core.support.JdbcDaoSupport
        tokenRepository.setDataSource(dataSource);
        System.out.println("PersistentTokenRepository--dataSource:>dataSource");
        //tokenRepository.setCreateTableOnStartup(true);//系统启动的时候创建:CREATE_TABLE_SQL表
        return tokenRepository;
    }
    /**
     * 定义web安全配置类:覆盖config方法
     * 1.参数为HttpSecurity
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        /**
         * 定义了任何请求都需要表单认证
         */
        ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
        validateCodeFilter.setAuthenticationFailureHandler(myAuthenticationFailureHandler);
        validateCodeFilter.setSecurityProperties(securityProperties);//传递securityProperties
        validateCodeFilter.afterPropertiesSet();

        http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)//自定义的额过滤器加到UsernamePasswordAuthenticationFilter前面去
               .formLogin()//表单登录---指定了身份认证方式
                      // .loginPage("/login.html")
                       .loginPage("/authentication/require")
                       .loginProcessingUrl("/authentication/form")//配置UsernamePasswordAuthenticationFilter需要拦截的请求
                       .successHandler(myAuthenticationSuccessHandler)//表单登录成功之后用自带的处理器
                       .failureHandler(myAuthenticationFailureHandler)//表单登录失败之后用自带的处理器
                   // http.httpBasic()//http的basic登录
                      .and()
                .rememberMe()
                      .tokenRepository(persistentTokenRepository())//配置remeberMe的token操作
                      .tokenValiditySeconds(securityProperties.getBrowser().getRememberMeSeconds())//配置token失效秒数
                      .userDetailsService(userDetailsService)//配置操作数据库用户的service
                      .and()
          .authorizeRequests()//对请求进行授权
                      .antMatchers("/authentication/require",securityProperties.getBrowser().getLoginPage(),"/code/image").permitAll()//对匹配login.html的请求允许访问
                      .anyRequest()//任何请求
                      .authenticated()
                       .and()
           .csrf()
                     .disable();//都需要认证
    }
}

此时我们多一张表:叫做persistent_logins
13.SpringSecurity-添加记住我功能_第6张图片
这里面的用户名和token是一一对应关系。

第一次访问,提示需要身份认证。

image.png

此时我们数据库会所以多记录。

由于我们的信息是存放在session里面的,那么我现在全部停止服务器,然后重启服务时候,按理说session丢失了,我们需要在访问之前接口的时候按理说是需要重新认证的。但是我们现在不登录,直接访问:http://127.0.0.1:8088/user接口的时候,确实是访问到了,后端接口。 这是因为之前做认证过过滤器链的时候已经把Token写入到浏览器cookie了,那么现在我们再次请求时候,会通过RememberMeAuthenticationFilter,然后做了数据库查找,然后做了登录。座椅最后能够获取到用户信息。
image.png

3. 记住我SpringSecurity源码解析

我们将按照上面的源码图,我们打断点,然后分析下源码。
先到UsernamePasswordAuthenticationFilter
13.SpringSecurity-添加记住我功能_第7张图片

然后,跳转到
AbstractAuthenticationProcessingFilter里面的认证成功之后的方法:successfulAuthentication.
在这里我们把结果放到:SecurityContext之后,调用了remeberMeService的loginSccess。

进入
13.SpringSecurity-添加记住我功能_第8张图片

然后返回到前端cookie里面去:
对应图上这一步:
13.SpringSecurity-添加记住我功能_第9张图片

以上相当于我们登录进去了,我们重启服务,查看到底做了什么?
我们直接访问:http://127.0.0.1:8088/user
然后我们会进入RememberMeAuthenticationFilter:
判断中:

SecurityContextHolder.getContext().getAuthentication() == null

如果从前面获取的已经认证的Authentication为空,那么就会调用remeberMeServices的autoLogin自动登录方法。也就是下面的这一段代码:
13.SpringSecurity-添加记住我功能_第10张图片

在这个方法里面做了:
13.SpringSecurity-添加记住我功能_第11张图片

然后就进入我们的user服务了:
image.png

你可能感兴趣的:(spring-security)