前言
记住我功能主要讲解为3点:
- 记住我功能基本原理
- 记住我功能具体实现
- 记住我功能SpringSecurity源码解析
内容
1.记住我功能基本原理
在用户发送认证请求之后,或调用我们之前说过的usernamePasswordAuthenticationFilter这个过滤器,认证成功之后会调用一个RemeberMeService服务;负责针对每一用户生成一个Token,然后将token写入到浏览器的Cookie里面,同时会使用:TokenRepository将这个token写入数据库中。将Token写入数据库时候,同时会把用户认证成功的用户名一并写入数据库(此时用户名和token是一一对应的)中因为我们是在用户认证成功之后做的,所以会将用户信息写入,下次用户访问的时候就不需要再次登录了。当用户下次请求的时候会经过过滤器链中的RemeberMeAuthenticationFilter(这个过滤器作用就是读取cookie中token)然后交给RemeberMeService,RemeberMeService通过TokenRepository到数据库去查询这个Token数据库里面有没有记录。如果有记录就去除用户名,取出用户名之后,就会去调用UserDetailsService,获取用户信息,然后把获取的当前用户信息放到SecurityContext里面。这样就把用户登录上了。
另外再说一下:RemeberMeAuthenticationFilter在我们的过滤器链中绿色过滤器中,他是在倒数第二个位置。前面是其他的认证,其他的认证都没法认证用户信息的时候RemeberMeAuthenticationFilter尝试去做认证。
2.记住我功能具体实现
2.1前端页面添加功能
2.2 配置TokenRepository
配置TokenRepository读取数据库,TokenRepository基本配置信息是在我们spring-security-demo里面的,我们在application.yml文件中配置了基本的数据源信息的。
在spring-security-web项目中添加:
@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,在数据库创建表,我们有两种思路:
- 我们直接拷贝出sql语句,然后在数据库执行
- 我们不拷贝sql语句,在JdbcTokenRepositoryImpl自己执行,此时我们开启:
tokenRepository.setCreateTableOnStartup(true);//系统启动的时候创建:CREATE_TABLE_SQL表
2.3 配置Token记住我的过期秒数
因为记住我功能也需要有时间限制,所以我们需要配置token过期时间,
我们在spring-security-core里面的属性配置文件:
2.4 配置查询Token的UserDetailsService
我们在最后一步需要,查找用户进行登录,查找的时候是根据token去查找的,所以我们需要配置UserDetailsService
在spring-security-web里面的WebSecurityConfig注入:
@Autowired
private UserDetailsService userDetailsService;
然后我们添加rememberMe相关配置:
- tokenRepository
- tokenValiditySeconds(验证码过期时间)
- 配置操作数据库用户的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
这里面的用户名和token是一一对应关系。
第一次访问,提示需要身份认证。
此时我们数据库会所以多记录。
由于我们的信息是存放在session里面的,那么我现在全部停止服务器,然后重启服务时候,按理说session丢失了,我们需要在访问之前接口的时候按理说是需要重新认证的。但是我们现在不登录,直接访问:http://127.0.0.1:8088/user接口的时候,确实是访问到了,后端接口。 这是因为之前做认证过过滤器链的时候已经把Token写入到浏览器cookie了,那么现在我们再次请求时候,会通过RememberMeAuthenticationFilter,然后做了数据库查找,然后做了登录。座椅最后能够获取到用户信息。
3. 记住我SpringSecurity源码解析
我们将按照上面的源码图,我们打断点,然后分析下源码。
先到UsernamePasswordAuthenticationFilter
然后,跳转到
AbstractAuthenticationProcessingFilter里面的认证成功之后的方法:successfulAuthentication.
在这里我们把结果放到:SecurityContext之后,调用了remeberMeService的loginSccess。
以上相当于我们登录进去了,我们重启服务,查看到底做了什么?
我们直接访问:http://127.0.0.1:8088/user
然后我们会进入RememberMeAuthenticationFilter:
判断中:
SecurityContextHolder.getContext().getAuthentication() == null
如果从前面获取的已经认证的Authentication为空,那么就会调用remeberMeServices的autoLogin自动登录方法。也就是下面的这一段代码: