记住我功能的基本原理
当用户登录发起认证请求时,会通过UsernamePasswordAuthenticationFilter
进行用户认证,认证成功之后,SpringSecurity 调用前期配置好的记住我功能,实际是调用了RememberMeService
接口,其接口的实现类会将用户的信息生成Token
并将它写入 response 的Cookie
中,在写入的同时,内部的TokenRepositoryTokenRepository
会将这份Token
再存入数据库一份。
当用户再次访问服务器资源的时候,首先会经过RememberMeAuthenticationFiler
过滤器,在这个过滤器里面会读取当前请求中携带的 Cookie,这里存着上次服务器保存 的Token
,然后去数据库中查找是否有相应的 Token,如果有,则再通过UserDetailsService
获取用户的信息。
记住我功能的过滤器
从图中可以得知记住我的过滤器在过滤链的中部,注意是在UsernamePasswordAuthenticationFilter
之后。
前端页面checkbox设置
在 html 中增加记住我复选框checkbox控件,注意其中复选框的name
一定必须为remember-me
1 | <input type="checkbox" name="remember-me" value="true"/> |
配置cookie存储数据库源
本例中使用了 springboot 管理的数据库源,所以注意要配置spring-boot-starter-jdbc
的依赖:
1 | <dependency> |
如果不配置会报编译异常:
1 | The type org.springframework.jdbc.core.support.JdbcDaoSupport cannot be resolved. It is indirectly referenced from required .class files |
记住我的安全认证配置:
1 |
|
注意:在数据库源配置之前,建议手动在数据库中新增一张保存的cookie
表,其数据库脚本在JdbcTokenRepositoryImpl
的静态属性中配置了:
1 | public class JdbcTokenRepositoryImpl extends JdbcDaoSupport implements |
因此可以事先执行以下sql 脚本创建表:
1 | create table persistent_logins (username varchar(64) not null, series varchar(64) primary key, token varchar(64) not null, last_used timestamp not null); |
当然,JdbcTokenRepositoryImpl
自身还有一个setCreateTableOnStartup()
方法进行开启自动建表操作,但是不建议使用。
当成功登录之后,RememberMeService
会将成功登录请求的cookie
存储到配置的数据库中:
源码分析
首次请求
首先进入到AbstractAuthenticationProcessingFilter
过滤器中的doFilter()
方法:
1 | public abstract class AbstractAuthenticationProcessingFilter { |
其中当用户认证成功之后,会进入successfulAuthentication()
方法,在用户信息被保存在了SecurityContextHolder
之后,其中就调用了rememberMeServices.loginSuccess()
:
1 | protected void successfulAuthentication(HttpServletRequest request, |
在这个RememberMeServices
有个抽象实现类,在抽象实现类loginSuccess()
方法中进行了记住我功能判断,为什么前端的复选框控件的 name 必须为remember-me
,原因就在此:
1 | public abstract class AbstractRememberMeServices implements RememberMeServices, |
当识别到记住我功能开启的时候,就会进入onLoginSuccess()
方法,其具体的方法实现在PersistentTokenBasedRememberMeServices
类中:
1 | public class PersistentTokenBasedRememberMeServices extends AbstractRememberMeServices { |
上面的tokenRepository.createNewToken()
和addCookie()
就将 cookie 保存到数据库并回显到响应中。
第二次请求
当第二次请求传到服务器的时候,请求会被RememberMeAuthenticationFilter
过滤器进行过滤:过滤器首先判定之前的过滤器都没有认证通过当前用户,也就是SecurityContextHolder
中没有已经认证的信息,所以会调用rememberMeServices.autoLogin()
的自动登录接口拿到已通过认证的rememberMeAuth
进行用户认证登录:
1 | public class RememberMeAuthenticationFilter extends GenericFilterBean implements |
这个自动登录的接口,又由其抽象实现类进行实现:
1 | public abstract class AbstractRememberMeServices implements RememberMeServices, |
processAutoLoginCookie()
的具体实现还是由PersistentTokenBasedRememberMeServices
来实现,总得来说就是一顿判定当前的cookieTokens
是不是在数据库中存在tokenRepository.getTokenForSeries(presentedSeries)
,并判断是不是一样的,如果一样,就是把当前请求的新 token 更新保存到数据库,最后通过当前请求token中的用户名调用UserDetailsService.loadUserByUsername()
进行用户认证。
1 | public class PersistentTokenBasedRememberMeServices extends AbstractRememberMeServices { |