SpringBoot + SpringSecurity “记住我”功能实现及相关源码分析

记住我功能的基本原理

记住我功能的基本原理

之前有讲过,当用户发起认证请求,会通过UsernamePasswordAuthenticationFilter,在认证成功之后,可以调用SpringSecurity提供的RememberMeService,它会生成一个Token并将它写入浏览器的Cookie中,同时这个它里面有一个TokenRepositoryTokenRepository会将Token放入数据库中。
当下次浏览器再请求的时候,会经过RememberMeAuthenticationFiler,在这个filter里面会读取Cookie中的Token,然后去数据库中查找是否有相应的Token,然后再通过UserDetailsService获取用户的信息。

记住我功能具体实现

我们就按照它的基本原理来挨着顺序做相应的配置就可以了

@Autowired
private DataSource dataSource;

@Autowired
private UserDetailsService myUserDetailsService;

/**
 * 配置TokenRepository
 * @return
 */
@Bean
public PersistentTokenRepository persistentTokenRepository() {
    JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
    // 配置数据源
    jdbcTokenRepository.setDataSource(dataSource);
    // 第一次启动的时候自动建表(可以不用这句话,自己手动建表,源码中有语句的)
    // jdbcTokenRepository.setCreateTableOnStartup(true);
    return jdbcTokenRepository;
}

这里注入了UserDetailsService,同时配置了一下TokenRepository,因为需要和数据库进行交互,所以DataSource是必须的,这里就不贴出配置相关的代码了。

接下来就是在config方法中做相应的配置了

 @Override
protected void configure(HttpSecurity http) throws Exception {
    http.formLogin()
            .rememberMe()                                   // 记住我相关配置
            .tokenRepository(persistentTokenRepository())   // 设置TokenRepository
            // 配置Cookie过期时间
            .tokenValiditySeconds(securityProperties.getBrowser().getRememberMeSeconds())
            // 配置UserDetailsService
            .userDetailsService(myUserDetailsService);
    // 省略其他配置
}

记住我功能SpringSecurity源码分析

根据最开始的原理分析,我们可以知道一般是在认证成功之后,才会去做记住我的操作,所以我们可以看successfulAuthentication方法中的相关代码

// AbstractAuthenticationProcessingFilter.java
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
    SecurityContextHolder.getContext().setAuthentication(authResult);
    // 调用rememberMeServices,进行登录操作
    this.rememberMeServices.loginSuccess(request, response, authResult);
    if (this.eventPublisher != null) {
        this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
    }

    this.successHandler.onAuthenticationSuccess(request, response, authResult);
}

这里在认证成功之后,调用了RememberMeServicesloginSuccess方法,该方法会进一步调用onLoginSuccess:

protected void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {
    String username= successfulAuthentication.getName();
    PersistentRememberMeToken persistentToken = new PersistentRememberMeToken(username, this.generateSeriesData(), this.generateTokenData(), new Date());

    try {
        // 进行TokenRepository操作
        this.tokenRepository.createNewToken(persistentToken);
        // 进行Cookie操作
        this.addCookie(persistentToken, request, response);
    } catch (Exception var7) {
        this.logger.error("Failed to save persistent token ", var7);
    }
}

这样相关的数据库操作和Token操作就完成了。

接下来看看下次登录的时候,是怎么进行记住我认证的。这里我们就直接看RememberMeAuthenticationFilter

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
    HttpServletRequest request = (HttpServletRequest)req;
    HttpServletResponse response = (HttpServletResponse)res;
    // 1.先判断之前是否已经通过认证了
    if (SecurityContextHolder.getContext().getAuthentication() == null) {
        // 2.通过RememberMeServices进行登录 
        Authentication rememberMeAuth = this.rememberMeServices.autoLogin(request, response);
        if (rememberMeAuth != null) {
            try {
                rememberMeAuth = this.authenticationManager.authenticate(rememberMeAuth);
                SecurityContextHolder.getContext().setAuthentication(rememberMeAuth);
                this.onSuccessfulAuthentication(request, response, rememberMeAuth);
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("SecurityContextHolder populated with remember-me token: '" + SecurityContextHolder.getContext().getAuthentication() + "'");
                }

                if (this.eventPublisher != null) {
                    this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(SecurityContextHolder.getContext().getAuthentication(), this.getClass()));
                }

                if (this.successHandler != null) {
                    this.successHandler.onAuthenticationSuccess(request, response, rememberMeAuth);
                    return;
                }
            } catch (AuthenticationException var8) {
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("SecurityContextHolder not populated with remember-me token, as AuthenticationManager rejected Authentication returned by RememberMeServices: '" + rememberMeAuth + "'; invalidating remember-me token", var8);
                }

                this.rememberMeServices.loginFail(request, response);
                this.onUnsuccessfulAuthentication(request, response, var8);
            }
        }

        chain.doFilter(request, response);
    } else {
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("SecurityContextHolder not populated with remember-me token, as it already contained: '" + SecurityContextHolder.getContext().getAuthentication() + "'");
        }

        chain.doFilter(request, response);
    }

}

代码下载

Spring-Security

发布了98 篇原创文章 · 获赞 97 · 访问量 27万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章