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萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章