Lingmoumou's Blog

きっといつかって愿うまま

0%

Spring Security 记住我

在用户登陆一次以后,系统会记住用户一段时间,在这段时间,用户不用反复登陆就可以使用我们的系统。

RememberMe流程

  • 用户登录时,请求先进入UsernamePasswordAuthenticationFilter,当这个过滤器认证成功之后,会调用一个RemeberMeService服务,在RemeberMeService类里面有一个TokenRepository方法。RemeberMeService会生成一个token,然后将这个token存入到浏览器的Cookie中去。同时TokenRepository方法将这个Token写入到数据库中,因为这个动作是在通过UsernamePasswordAuthenticationFilter认证成功之后去做的,所以在存入数据的时候会将用户名和token存入进去,即token和用户名是一一对应的。
  • 当同一个用户再次访问系统的时候,不需要登录了,这个请求在经过过滤器链的时候会经过RememberMeAuthenticationFilter,这个过滤器的作用就是读取cookie中的token,然后交给RemeberMeServiceRemeberMeService会用TokenRepository到数据库中去查询这个token在数据库中有没有记录,如果有记录会将用户名取出来,取出来之后会调用UserDetailsService去获取用户信息,然后将用户信息存入到SecurityContext中去,这样用户就登录了。

SpringSecurity过滤器链
RememberMeAuthenticationFilter位于过滤器链中的倒数第二个位置,当其他用户认证都无法认证用户信息时,RememberMe会尝试做认证。

代码实现

在用户认证配置中,注入DataSourceUserDetailsService,在configure方法中添加rememberMe()配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// BrowserSecurityConfig.java

@EnableConfigurationProperties(SecurityProperties.class)
@ComponentScan(basePackages = {"net.ling.security"})
@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
private SecurityProperties securityProperties;

@Autowired
private DataSource dataSource;

@Autowired
private UserDetailsService userDetailsService;

@Autowired
private AuthenticationSuccessHandler imoocAuthenticationSuccessHandler;

@Autowired
private AuthenticationFailureHandler imoocAuthenticationFailureHandler;

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

@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl tokenRepository=new JdbcTokenRepositoryImpl();
tokenRepository.setDataSource(dataSource);
// 启动的时候自动创建表
// tokenRepository.setCreateTableOnStartup(true);
return tokenRepository;
}

@Override
protected void configure(HttpSecurity http) throws Exception {

ValidateCodeFilter validateCodeFilter=new ValidateCodeFilter();
validateCodeFilter.setAuthenticationFailureHandler(imoocAuthenticationFailureHandler);
validateCodeFilter.setSecurityProperties(securityProperties);
validateCodeFilter.afterPropertiesSet();

http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
.formLogin()
.loginPage("/authentication/require")
.loginProcessingUrl("/authentication/form")
.successHandler(imoocAuthenticationSuccessHandler)
.failureHandler(imoocAuthenticationFailureHandler)
.and()
.authorizeRequests()
.antMatchers("/authentication/require", "/swagger-ui.html","/webjars/**","/v2/**","/swagger-resources/**","/imooc-signIn.html",securityProperties.getBrowser().getLoginPage(),"/code/image").permitAll()
.anyRequest()
.authenticated()
.and()
.logout()
.and()
// rememberMe start
.rememberMe()
.tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(securityProperties.getBrowser().getRememberMeSeconds())
.userDetailsService(userDetailsService)
// rememberMe end
.and()
.csrf().disable();
}

}

在配置中,新增rememberMeSeconds的超时时间配置。

1
2
3
4
5
6
7
8
9
10
// BrowserProperties.java

@Data
public class BrowserProperties {
private String loginPage="/imooc-signIn.html";

private LoginResponseType loginType= LoginResponseType.JSON;

private int rememberMeSeconds=3600; // 一小时
}

基本原理

第一次登录

AbstractAuthenticationProcessingFilter.java

请求会进入UsernamePasswordAuthenticationFilter中,校验完用户名密码之后会调用rememberMeServices.loginSuccess(request, response, authResult);方法。

PersistentTokenBasedRememberMeServices.java:onLoginSuccess()

  1. 用tokenRepository去创建一个新的token存入到数据库中。
  2. 将生成的token存入到浏览器的cookie中去。

在有RememberMe的情况下再次登录

PersistentTokenBasedRememberMeServices.java-1:processAutoLoginCookie()

PersistentTokenBasedRememberMeServices.java-2:processAutoLoginCookie()

  1. 从请求的cookies中拿到token和tokenSeries。
  2. 从数据库中通过token和tokenSeries获取用户信息。
  3. 如果查不到相关的用户信息,就会抛出异常;如果有数据,则进行和一系列的判断,如:是否已经过期
  4. 如果检查都已经通过,通过找到的用户信息中的用户名,调用UerDetailsService获取详细的用户信息。

存储RememberMe获取到的用户信息

RememberMeAuthenticationFilter.java

将获取的用户信息存储至SecurityContextHolder中。

参考文献