Spring Security攔截器加載流程分析--練氣中期


寫在前面

上回我們講了spring security整合spring springmvc的流程,並且知道了spring security是通過過濾器鏈來進行認證授權操作的。今天我們來分析一下springsecurity過濾器鏈的加載流程。讀者在閱讀本文時可以邊閱讀邊跟着操作,這樣子會理解的更清楚一些。

Spring Security過濾器鏈

spring security的過濾器非常多,這里簡單介紹幾個常用的過濾器。

spring security常過濾器鏈介紹

org.springframework.security.web.context.SecurityContextPersistenceFilter

主要是使用SecurityContextRepository在session中保存或更新一個SecurityContext域對象(相當於一個容器),並將SecurityContext給以后的過濾器使用,來為后續filter建立所需的上下文。SecurityContext中存儲了當前用戶的認證以及權限信息。 其他的過濾器都需要依賴於它。在 Spring Security 中,雖然安全上下文信息被存儲於 Session 中,但我們在實際使用中不應該直接操作 Session,而應當使用 SecurityContextHolder。

org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter

這個過濾器用於集成SecurityContext到Spring異步執行機制的WebAsyncManager 中。如果想要與spring集成,就必須要使用此過濾器鏈。

org.springframework.security.web.csrf.CsrfFilter

csrf又稱跨域請求偽造,SpringSecurity會對所有post請求驗證是否包含系統生成的csrf的token信息,如果不包含,則報錯。起到防止csrf攻擊的效果。

題外話:csrf攻擊

CSRF(Cross-site request forgery)跨站請求偽造:攻擊者誘導受害者進入第三方網站,在第三方網站中,向被攻擊網站發送跨站請求。利用受害者在被攻擊網站已經獲取的注冊憑證,繞過后台的用戶驗證,達到冒充用戶對被攻擊的網站執行某項操作的目的。

一個例子:

比如你登陸了銀行網站A之后,瀏覽器cookie中將保存你的登錄信息。同時你沒有沒有注銷登錄的情況下又用同一瀏覽器訪問了視頻網站B,假設視頻網站B中含有csrf病毒,其將獲取你的cookie內容,拿到你銀行網站A的登錄信息,就可以進行不可描述之操作了。

而這個CsrfFilter通過簽發token的方式進行訪問驗證,如果token不是本網站簽發的或者訪問請求中不帶有這個token則拒絕訪問。

org.springframework.security.web.authentication.logout.LogoutFilter

匹配URL為/logout的請求,實現用戶退出,清除認證信息。

org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter

認證操作全靠這個過濾器,默認匹配URL為/login且必須為POST請求。

org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter

如果沒有在配置文件中指定認證頁面,則由該過濾器生成一個默認認證頁面

org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter

由此過濾器可以生產一個默認的退出登錄頁面

org.springframework.security.web.authentication.www.BasicAuthenticationFilter

此過濾器會自動解析HTTP請求中頭部名字為Authentication,且以Basic開頭的頭信息。

org.springframework.security.web.savedrequest.RequestCacheAwareFilter

通過HttpSessionRequestCache內部維護了一個RequestCache,用於緩存HttpServletRequest

org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter

針對ServletRequest進行了一次包裝,使得request具有更加豐富的API

org.springframework.security.web.authentication.AnonymousAuthenticationFilter

當SecurityContextHolder中認證信息為空,則會創建一個匿名用戶存入到SecurityContextHolder中。

spring security為了兼容未登錄的訪問,也走了一套認證流程,只不過是一個匿名的身份(游客)。

org.springframework.security.web.session.SessionManagementFilter

SecurityContextRepository限制同一用戶開啟多個會話的數量

org.springframework.security.web.access.ExceptionTranslationFilter

異常轉換過濾器位於整個springSecurityFilterChain的后方,用來轉換整個鏈路中出現的異常

org.springframework.security.web.access.intercept.FilterSecurityInterceptor

獲取所配置資源訪問的授權信息,根據SecurityContextHolder中存儲的用戶信息來決定其是否有權限。

spring security加載流程

在web.xml中,我們配置了一個名字為“springSecurityFilterChain”的過濾器org.springframework.web.filter.DelegatingFilterProxy。以下是部分源碼。

public class DelegatingFilterProxy extends GenericFilterBean {
    @Nullable
    private String contextAttribute;
    @Nullable
    private WebApplicationContext webApplicationContext;
    @Nullable
    private String targetBeanName;
    private boolean targetFilterLifecycle;
    /** 真正加載的過濾器*/
    @Nullable
    private volatile Filter delegate;
    private final Object delegateMonitor;

    protected void initFilterBean() throws ServletException {
        synchronized(this.delegateMonitor) {
            if (this.delegate == null) {
                if (this.targetBeanName == null) {
                    this.targetBeanName = this.getFilterName();
                }

                WebApplicationContext wac = this.findWebApplicationContext();
                if (wac != null) {
                    this.delegate = this.initDelegate(wac);
                }
            }

        }
    }
	/** 過濾器的入口 */
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        Filter delegateToUse = this.delegate;
        if (delegateToUse == null) {
            synchronized(this.delegateMonitor) {
                delegateToUse = this.delegate;
                if (delegateToUse == null) {
                    WebApplicationContext wac = this.findWebApplicationContext();
                    if (wac == null) {
                        throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener or DispatcherServlet registered?");
                    }
                     /** 在做完一系列判斷之后,真正要做的就是這一步,初始化delegate*/
                    delegateToUse = this.initDelegate(wac);
                }
                this.delegate = delegateToUse;
            }
        }

        this.invokeDelegate(delegateToUse, request, response, filterChain);
    }

   

    protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
        String targetBeanName = this.getTargetBeanName();
        Assert.state(targetBeanName != null, "No target bean name set");
        Filter delegate = (Filter)wac.getBean(targetBeanName, Filter.class);
        if (this.isTargetFilterLifecycle()) {
            delegate.init(this.getFilterConfig());
        }

        return delegate;
    }

    protected void invokeDelegate(Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        delegate.doFilter(request, response, filterChain);
    }

   
}

接下來我們在initDelegate方法上打個斷點調試一下

最終我們發現,通過initDelegate方法給delegate初始化,得到一個FilterChainProxy對象。接下來我們進入FilterChainProxy這個類看看

public class FilterChainProxy extends GenericFilterBean {
。。。。
}

可以發現這也是一個過濾器,那么我們找dofilter方法。

接下來我們在doFilterInternal里面打個斷點試試,看看能不能得到過濾器鏈

我們在這一步得到了一個List ,點開可以發現正好是前文講到的一些常用的過濾器。

我們再進入到getFilters中看看這些過濾器鏈在哪

private List<Filter> getFilters(HttpServletRequest request) {
        Iterator var2 = this.filterChains.iterator();

        SecurityFilterChain chain;
        do {
            if (!var2.hasNext()) {
                return null;
            }

            chain = (SecurityFilterChain)var2.next();
        } while(!chain.matches(request));

        return chain.getFilters();
    }

從該方法我們知道過濾器鏈來自於SecurityFilterChain的getFilters方法,接下來,我們看看這個類

public interface SecurityFilterChain {
    boolean matches(HttpServletRequest var1);

    List<Filter> getFilters();
}

可以發現這是一個接口,在idea中ctrl+H可以發現它只有一個實現類,那就是DefaultSecurityFilterChain

到這一步,我們可以發現,springSecurity的過濾器鏈存在於SecurityFilterChain中(springSecurity的過濾器鏈由SecurityFilterChain負責封裝),它只有一個實現類那就是DefaultSecurityFilterChain。也就是說其過濾器鏈由SecurityFilterChain封裝。

至此,springSecurity的過濾器鏈加載流程我們就說完了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM