在用户登陆一次以后,系统会记住用户一段时间,在这段时间,用户不用反复登陆就可以使用我们的系统。
- 用户登录时,请求先进入
UsernamePasswordAuthenticationFilter
,当这个过滤器认证成功之后,会调用一个RemeberMeService
服务,在RemeberMeService
类里面有一个TokenRepository
方法。RemeberMeService
会生成一个token
,然后将这个token
存入到浏览器的Cookie
中去。同时TokenRepository
方法将这个Token
写入到数据库中,因为这个动作是在通过UsernamePasswordAuthenticationFilter
认证成功之后去做的,所以在存入数据的时候会将用户名和token存入进去,即token和用户名是一一对应的。 - 当同一个用户再次访问系统的时候,不需要登录了,这个请求在经过过滤器链的时候会经过
RememberMeAuthenticationFilter
,这个过滤器的作用就是读取cookie
中的token
,然后交给RemeberMeService
,RemeberMeService
会用TokenRepository
到数据库中去查询这个token
在数据库中有没有记录,如果有记录会将用户名取出来,取出来之后会调用UserDetailsService
去获取用户信息,然后将用户信息存入到SecurityContext
中去,这样用户就登录了。
RememberMeAuthenticationFilter
位于过滤器链中的倒数第二个位置,当其他用户认证都无法认证用户信息时,RememberMe会尝试做认证。
代码实现
在用户认证配置中,注入DataSource
与UserDetailsService
,在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
.class) (SecurityProperties
@ComponentScan(basePackages = {"net.ling.security"})
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
private SecurityProperties securityProperties;
private DataSource dataSource;
private UserDetailsService userDetailsService;
private AuthenticationSuccessHandler imoocAuthenticationSuccessHandler;
private AuthenticationFailureHandler imoocAuthenticationFailureHandler;
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl tokenRepository=new JdbcTokenRepositoryImpl();
tokenRepository.setDataSource(dataSource);
// 启动的时候自动创建表
// tokenRepository.setCreateTableOnStartup(true);
return tokenRepository;
}
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
public class BrowserProperties {
private String loginPage="/imooc-signIn.html";
private LoginResponseType loginType= LoginResponseType.JSON;
private int rememberMeSeconds=3600; // 一小时
}
基本原理
第一次登录
请求会进入UsernamePasswordAuthenticationFilter
中,校验完用户名密码之后会调用rememberMeServices.loginSuccess(request, response, authResult);
方法。
- 用tokenRepository去创建一个新的token存入到数据库中。
- 将生成的token存入到浏览器的cookie中去。
在有RememberMe的情况下再次登录
- 从请求的cookies中拿到token和tokenSeries。
- 从数据库中通过token和tokenSeries获取用户信息。
- 如果查不到相关的用户信息,就会抛出异常;如果有数据,则进行和一系列的判断,如:是否已经过期
- 如果检查都已经通过,通过找到的用户信息中的用户名,调用
UerDetailsService
获取详细的用户信息。
存储RememberMe获取到的用户信息
将获取的用户信息存储至SecurityContextHolder
中。