Spring Security 之 AbstractAuthenticationProcessingFilter 源碼解析

 SpringSecurity對Spring Web項目提供支持,AbstractAuthenticationProcessingFilte 作爲驗證請求入口的。

AbstractAuthenticationProcessingFilter 繼承自 GenericFilterBean,而 GenericFilterBean spring 框架中的過濾器類,實現了接口 javax.servlet.Filter

public abstract class AbstractAuthenticationProcessingFilter
 extends GenericFilterBean implements ApplicationEventPublisherAware, MessageSourceAware {

 AbstractAuthenticationProcessingFilte 成員變量

    protected ApplicationEventPublisher eventPublisher;
    protected AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
    private AuthenticationManager authenticationManager;
    protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
    private RememberMeServices rememberMeServices = new NullRememberMeServices();
    private RequestMatcher requiresAuthenticationRequestMatcher;
    private boolean continueChainBeforeSuccessfulAuthentication = false;
    private SessionAuthenticationStrategy sessionStrategy = new NullAuthenticatedSessionStrategy();
    private boolean allowSessionCreation = true;
    private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
    private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();

 AbstractAuthenticationProcessingFilter的主要職責和依賴組件

abstractAuthenticationProcessingFilter的職責也就非常明確——處理所有HTTP Request和Response對象,並將其封裝成AuthenticationMananger可以處理的Authentication。並且在身份驗證成功或失敗之後將對應的行爲轉換爲HTTP的Response。同時還要處理一些Web特有的資源比如Session和Cookie。總結成一句話,就是替AuthenticationMananger把所有和Authentication沒關係的事情全部給包圓了。

 

 AbstractAuthenticationProcessingFilter爲了完成組織上交代的與瀏覽器和HTTP請求的驗證任務,它將大任務拆成了幾個子任務並交給了以下組件完成

  • AuthenticationManager用於處理身份驗證的核心邏輯;
  • AuthenticationSuccessHandler用於處理驗證成功的後續流程;
  • AuthenticationFailureHandler用於處理失敗的後續流程;
  • 在驗證成功後發佈一個名爲InteractiveAuthenticationSuccessEvent的事件通知給到應用上下文,用於告知身份驗證已經成功;
  • 因爲是基於瀏覽器所以相關的會話管理行爲交由 SessionAuthenticationStrategy來進行實現。
  • 文檔上還有一點沒有寫出來的是,如果用戶開啓了類似“記住我”之類的免密碼登錄,AbstractAuthenticationProcessingFilter還有一個名爲RememberMeServices來進行管理。

流程分析

因爲 AbstractAuthenticationProcessingFilter 爲本質上是一個 servlet 過濾器,因此找到其入口函數 doFilter()

try代碼快中的 --> 1 處代碼表示該處會調用 attemptAuthentication()方法進行身份校驗處理。attemptAuthentication()方法本體如下所示:

public abstract Authentication attemptAuthentication(HttpServletRequest request,
            HttpServletResponse response) throws AuthenticationException, IOException,
            ServletException;

該方法是個等待子類實現的虛擬方法,對於用戶帳號密碼的校驗在該方法中進行,最後放回的是Authentication 對象。

catch 代碼塊中的 --> 2 和 --> 3 處代碼表示身份校驗失敗之後調用方法 unsuccessfulAuthentication(),該方法本體如下所示:

protected void unsuccessfulAuthentication(HttpServletRequest request,
            HttpServletResponse response, AuthenticationException failed)
            throws IOException, ServletException {
        SecurityContextHolder.clearContext();

        if (logger.isDebugEnabled()) {
            logger.debug("Authentication request failed: " + failed.toString(), failed);
            logger.debug("Updated SecurityContextHolder to contain null Authentication");
            logger.debug("Delegating to authentication failure handler " + failureHandler);
        }

        rememberMeServices.loginFail(request, response);

        failureHandler.onAuthenticationFailure(request, response, failed);
    }

該方法中最重要的一條語句是failureHandler.onAuthenticationFailure(request, response, failed);,表明驗證身份信息失敗之後調用類 failureHandleronAuthenticationFailure() 方法。而 failureHandlerAuthenticationFailureHandler 的實例變量。

  • --> 4 處代碼表示驗證身份信息成功後,調用 successfulAuthentication() 方法,其方法本體如下:
    protected void successfulAuthentication(HttpServletRequest request,
            HttpServletResponse response, FilterChain chain, Authentication authResult)
            throws IOException, ServletException {

        if (logger.isDebugEnabled()) {
            logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
                    + authResult);
        }

        SecurityContextHolder.getContext().setAuthentication(authResult);

        rememberMeServices.loginSuccess(request, response, authResult);

        // Fire event
        if (this.eventPublisher != null) {
            eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
                    authResult, this.getClass()));
        }

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

其中,最重要的一行代碼是 successHandler.onAuthenticationSuccess(request, response, authResult);,表示身份驗證成功後調用 successHandleronAuthenticationSuccess 方法。而 successHandlerAuthenticationSuccessHandler 的實現變量。

 


接下來我們討論幾個問題。

問題1. 怎麼判斷當前的請求是需要被驗證訪問的?

在正式進行身份之前,doFilter會通過Security中的MatcherRequest。嘗試查找是否有匹配記錄。
訪問控制的代碼:

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                // inde.html對應的url允許所任人訪問
                .antMatchers("/").permitAll()
                // user.html對應的url,則需要用戶有USER的角色纔可以訪問
                .antMatchers("/user").hasRole("USER")
                .and()
                .formLogin();
    }

 其中的matcher的規則便會在這個流程中預先被檢查,如果需要進行身份驗證則會進行寫一個階段:對請求進行必要的身份驗證。

問題2. 如何進行身份驗證?

doFilter中通過調用自己的attemptAuthentication方法,但並不進行身份驗證的邏輯處理,而是委託AuthenticationManager去完成相關的身份驗證流程。AbstractAuthenticationProcessingFilter將HttpServletRequest包裝成了Authentication對象與核心的AuthenticationManager進行交互。這樣的設計可以使AuthenticationManager不感知外部的Web環境,從而使Security不僅可以支持Web應用,同時也可以被所有Java應用進行使用——只要客製化外部參與並將其封裝成Authentication與AuthenticationManager的進行身份驗證。

這裏還需要注意的是在AuthenticationManager中實際完成身份驗證任務並不是AuthenticationManager它自己身。而是將相關的任務針對每一種身份驗證協議的AuthenticationProvider去完成相關的身份驗證工作。

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章