一、前言
如SpringSecurity在用户名密码登录的示例所示:
在UsernamePasswordAuthenticationFilter
的父类Filter中的 doFilter() 方法,调用用户认证的方法认证用户成功后,会调用一个名为successfulAuthentication
的方法,它内部有几大过程;
- 1、将认证成功的信息存入
SecurityContextHolder
中。 - 2、如果
rememberMeServices
功能开启了,处理rememberMe的逻辑。 - 3、调用
successHandler
成功处理器。
其中第二点就是今天要说的 "记住我" 的功能。它的实现逻辑如下:
二、流程梳理
1、第一次赋值流程(假设已经开启了rememberMe认证流程)
在PersistentTokenBasedRememberMeServices
类中,先是生成一个PersistentRememberMeToken
类型的 token,并通过tokenReposority.createNewToken(token)
方法存储这个token,最后将token信息存入cookie中返回前端。
这里的
tokenReposority
是需要我们自己配置的,SpringSecurity提前提供好了两个可供使用的类
InMemoryTokenRepositoryImpl
顾名思义,将token存储在内存中,特点是快,但是消耗内存,用户量少的话可以使用。内部原理也很简单,就是将不同的token存储在一个HashMap里面。
@Bean
public PersistentTokenRepository persistentTokenRepository(){
InMemoryTokenRepositoryImpl tokenRepository = new InMemoryTokenRepositoryImpl ();
return tokenRepository;
}
和
JdbcTokenRepositoryImpl
这个是将token存储在数据库中的选择,下面setCreateTableOnStartup选中的ture会自动在数据库中创建一个表来存储数据(第二次启动项目记得改为false,因为表第一次启动已经创建了,第二次还是true会报错)
@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
tokenRepository.setCreateTableOnStartup(true);
tokenRepository.setDataSource(dataSource);
return tokenRepository;
}
其实,我们还可以自定义这个TokenRepository,只需要去实现上述说的两个类的接口PersistentTokenRepository
即可。
public interface PersistentTokenRepository {
void createNewToken(PersistentRememberMeToken token);
void updateToken(String series, String tokenValue, Date lastUsed);
PersistentRememberMeToken getTokenForSeries(String seriesId);
void removeUserTokens(String username);
}
2、过滤流程
第一次我登录了之后,因为会话的关系,我们可以访问一些资源。但是当我关闭页面,在会话消失后,我们的访问一个后台资源的话,按照以往的逻辑,应该是访问不到且会跳转到登录页面。但是如果我们之前的会话有RememberMe的话,cookie中带有一个名为remember-me
的信息,在通过前面几个过滤器之后,到了名为RememberMeAuthenticationFilter
的过滤器中的时候,它会从request中拿取cookie信息,并尝试通过token去获取用户信息,成功获取到之后会通过UserDetailService认证,认证通过即可放行。
上面的aotuLogin()方法如下
最后,附上开启记住我功能的配置
// tokenRepository配置
@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
tokenRepository.setCreateTableOnStartup(false);
tokenRepository.setDataSource(dataSource);
return tokenRepository;
}
// 默认的密码加密
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
// 这个自定义即可
@Bean
public UserDetailsService demoUser() {
return (username) -> new User(username, passwordEncoder().encode("123456"),
AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_USER"));
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginProcessingUrl("/testToLogin")
.loginPage("/needLogin")
.successHandler((request, response, authentication) -> {
PrintWriter writer = response.getWriter();
writer.print("强啊,成了得嘛");
}).failureHandler((request, response, exception) -> {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(om.writeValueAsString("login failure"));
})
.and()
.rememberMe() // -----------就是这里了
.alwaysRemember(true) // 最近发现新版本要多配置一个这个,否则也没有开启
.tokenValiditySeconds(3600)
.tokenRepository(persistentTokenRepository())
.userDetailsService(demoUser())
.and()
.authorizeRequests()
.antMatchers("/skip", "/needLogin").permitAll()
.anyRequest()
.authenticated()
.and()
.csrf().disable();
}
网友评论