Spring Security的過濾器

Spring Security文檔的The Security Filter Chain一章指出Spring Security完全基于標(biāo)準(zhǔn)的servlet過濾器悬包,本文結(jié)合筆者的看法對該文檔進(jìn)行一些補(bǔ)充說明魄梯。

DelegatingFilterProxy類

DelegatingFilterProxy用于代理其他的過濾器,位于spring-web.jar中,這使得Spring可以通過它方便地使用Spring容器管理的過濾器。DelegatingFilterProxy的部分代碼如下所示,它繼承自GenericFilterBean類计寇,GenericFilterBean的作用與DispatcherServlet的初始化過程這篇文章中介紹的HttpServletBean相似。

public class DelegatingFilterProxy extends GenericFilterBean {
    private String contextAttribute;
    private WebApplicationContext webApplicationContext;
    private String targetBeanName;
    private boolean targetFilterLifecycle = false;
    private volatile Filter delegate;
    private final Object delegateMonitor = new Object();

    // 省略一些代碼

    @Override
    protected void initFilterBean() throws ServletException {
        synchronized (this.delegateMonitor) {
            if (this.delegate == null) {
                // If no target bean name specified, use filter name.
                if (this.targetBeanName == null) {
                    this.targetBeanName = getFilterName();
                }
                // Fetch Spring root application context and initialize the delegate early,
                // if possible. If the root application context will be started after this
                // filter proxy, we'll have to resort to lazy initialization.
                WebApplicationContext wac = findWebApplicationContext();
                if (wac != null) {
                    this.delegate = initDelegate(wac);
                }
            }
        }
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        // Lazily initialize the delegate if necessary.
        Filter delegateToUse = this.delegate;
        if (delegateToUse == null) {
            synchronized (this.delegateMonitor) {
                delegateToUse = this.delegate;
                if (delegateToUse == null) {
                    WebApplicationContext wac = findWebApplicationContext();
                    if (wac == null) {
                        throw new IllegalStateException("No WebApplicationContext found: " +
                                "no ContextLoaderListener or DispatcherServlet registered?");
                    }
                    delegateToUse = initDelegate(wac);
                }
                this.delegate = delegateToUse;
            }
        }

        // Let the delegate perform the actual doFilter operation.
        invokeDelegate(delegateToUse, request, response, filterChain);
    }

    protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
        Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
        if (isTargetFilterLifecycle()) {
            delegate.init(getFilterConfig());
        }
        return delegate;
    }
    // 省略一些代碼
}
  • 在initFilterBean方法初始化DelegatingFilterProxy對象或者第一次執(zhí)行doFilter方法時(shí)會(huì)調(diào)用findWebApplicationContext方法查找上下文;
  • targetBeanName表示被代理的過濾器bean的名稱番宁;
  • delegate保存被代理的過濾器實(shí)例元莫,該實(shí)例即是initDelegate方法從上下文中查找的名為targetBeanName的過濾器bean;
  • delegateMonitor是初始化時(shí)用的鎖蝶押。

doFilter方法利用invokeDelegate方法將調(diào)用委托給被代理的過濾器執(zhí)行踱蠢。

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

    delegate.doFilter(request, response, filterChain);
}

FilterChainProxy類

文檔提到“Spring Security的基礎(chǔ)設(shè)施只應(yīng)該委托給一個(gè)FilterChainProxy實(shí)例,如果在web.xml中配置每個(gè)Spring Security的過濾器那就會(huì)顯得很笨拙”棋电。
FilterChainProxy繼承自GenericFilterBean類茎截,可以看成一個(gè)過濾器的集合。

public class FilterChainProxy extends GenericFilterBean {
    private static final Log logger = LogFactory.getLog(FilterChainProxy.class);
    private final static String FILTER_APPLIED = FilterChainProxy.class.getName().concat(
            ".APPLIED");
    private List<SecurityFilterChain> filterChains;
    private FilterChainValidator filterChainValidator = new NullFilterChainValidator();
    private HttpFirewall firewall = new DefaultHttpFirewall();

    public FilterChainProxy() {
    }

    public FilterChainProxy(SecurityFilterChain chain) {
        this(Arrays.asList(chain));
    }

    public FilterChainProxy(List<SecurityFilterChain> filterChains) {
        this.filterChains = filterChains;
    }

    @Override
    public void afterPropertiesSet() {
        filterChainValidator.validate(this);
    }

    // 省略一些代碼
}
  • filterChains字段可以看成保存了所有與Spring Security相關(guān)的過濾器赶盔,這些過濾器由SecurityFilterChain組織起來企锌。
    public interface SecurityFilterChain {
    
        boolean matches(HttpServletRequest request);
    
        List<Filter> getFilters();
    }
    

doFilter方法則調(diào)用了doFilterInternal方法,該方法及相關(guān)代碼如下:

private void doFilterInternal(ServletRequest request, ServletResponse response,
        FilterChain chain) throws IOException, ServletException {

    FirewalledRequest fwRequest = firewall
            .getFirewalledRequest((HttpServletRequest) request);
    HttpServletResponse fwResponse = firewall
            .getFirewalledResponse((HttpServletResponse) response);

    List<Filter> filters = getFilters(fwRequest);

    if (filters == null || filters.size() == 0) {
        if (logger.isDebugEnabled()) {
            logger.debug(UrlUtils.buildRequestUrl(fwRequest)
                    + (filters == null ? " has no matching filters"
                            : " has an empty filter list"));
        }

        fwRequest.reset();

        chain.doFilter(fwRequest, fwResponse);

        return;
    }

    VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
    vfc.doFilter(fwRequest, fwResponse);
}

private List<Filter> getFilters(HttpServletRequest request) {
    for (SecurityFilterChain chain : filterChains) {
        if (chain.matches(request)) {
            return chain.getFilters();
        }
    }

    return null;
}
  • getFilters方法根據(jù)請求查找第一個(gè)匹配的SecurityFilterChain于未,因此SecurityFilterChain的添加順序非常重要撕攒;
  • VirtualFilterChain依次調(diào)用匹配的SecurityFilterChain中的每個(gè)過濾器。

Spring Security的過濾器

眾所周知烘浦,Spring Security的實(shí)現(xiàn)基于servlet的過濾器打却,以下按照在SecurityFilterChain中的排列順序列出了Spring Security中主要的過濾器:

  • SecurityContextPersistenceFilter;
  • CorsFilter谎倔;
  • LogoutFilter;
  • UsernamePasswordAuthenticationFilter猿推、CasAuthenticationFilter和BasicAuthenticationFilter等認(rèn)證過濾器片习;
  • RememberMeAuthenticationFilter;
  • AnonymousAuthenticationFilter蹬叭;
  • ExceptionTranslationFilter藕咏;
  • FilterSecurityInterceptor。

更多過濾器可以參見Filter OrderingCore Security Filters秽五,下面簡要分析各過濾器的功能孽查。

1. SecurityContextPersistenceFilter

SecurityContextPersistenceFilter從所配置的SecurityContextRepository中獲取與該請求關(guān)聯(lián)的SecurityContext,并在請求結(jié)束后清除SecurityContextHolder坦喘。一般需要將該過濾器配置在其他認(rèn)證過濾器前盲再,因?yàn)檎J(rèn)證過濾器如Basic、CAS等要求SecurityContextHolder包含有效的SecurityContext瓣铣。

2. CorsFilter

CorsFilter根據(jù)CORS配置處理預(yù)檢請求答朋、簡單請求和非簡單請求,CORS配置由CorsConfiguration類表示棠笑。

3. LogoutFilter

若收到注銷的請求梦碗,LogoutFilter會(huì)首先清除認(rèn)證信息,然后依次執(zhí)行配置的所有注銷處理器LogoutHandler的logout方法,最后執(zhí)行配置的注銷成功處理器LogoutSuccessHandler的onLogoutSuccess方法洪规。

4. UsernamePasswordAuthenticationFilter

若收到登錄的請求印屁,UsernamePasswordAuthenticationFilter執(zhí)行基于表單提交的認(rèn)證,默認(rèn)配置下表單的用戶名和密碼字段分別是username和password斩例,但是可以通過setUsernameParameter和setPasswordParameter進(jìn)行配置雄人,用戶提交的用戶名和密碼則是通過obtainUsername和obtainPassword方法分別得到。具體的認(rèn)證過程在該Filter的超類AbstractAuthenticationProcessingFilter中執(zhí)行樱拴。

5. ExceptionTranslationFilter

ExceptionTranslationFilter處理位于它之后的過濾器如FilterSecurityInterceptor拋出的AccessDeniedException或AuthenticationException異常柠衍,它只是一個(gè)Java異常和HTTP響應(yīng)之間的橋梁,并不做任何實(shí)際的安全認(rèn)證晶乔。

  • 處理AuthenticationException異常:該過濾器會(huì)委托給配置的AuthenticationEntryPoint處理珍坊;
  • 處理AccessDeniedException異常:如果是匿名用戶則委托給配置的AuthenticationEntryPoint處理,否則委托給配置的AccessDeniedHandler正罢。

6. FilterSecurityInterceptor

FilterSecurityInterceptor用來保護(hù)HTTP資源阵漏,若沒有認(rèn)證則拋出AuthenticationException異常,若權(quán)限不足則拋出AccessDeniedException異常翻具,這些異常都會(huì)被ExceptionTranslationFilter捕獲履怯。

參考文獻(xiàn)

https://stackoverflow.com/questions/41480102/how-spring-security-filter-chain-works
跨域資源共享 CORS 詳解

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市裆泳,隨后出現(xiàn)的幾起案子叹洲,更是在濱河造成了極大的恐慌,老刑警劉巖工禾,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件运提,死亡現(xiàn)場離奇詭異,居然都是意外死亡闻葵,警方通過查閱死者的電腦和手機(jī)民泵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來槽畔,“玉大人栈妆,你說我怎么就攤上這事∠峋” “怎么了鳞尔?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長坏快。 經(jīng)常有香客問我铅檩,道長,這世上最難降的妖魔是什么莽鸿? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任昧旨,我火速辦了婚禮拾给,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘兔沃。我一直安慰自己蒋得,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布乒疏。 她就那樣靜靜地躺著额衙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪怕吴。 梳的紋絲不亂的頭發(fā)上窍侧,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天,我揣著相機(jī)與錄音转绷,去河邊找鬼伟件。 笑死,一個(gè)胖子當(dāng)著我的面吹牛议经,可吹牛的內(nèi)容都是我干的斧账。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼煞肾,長吁一口氣:“原來是場噩夢啊……” “哼咧织!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起籍救,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤习绢,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后蝙昙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體毯炮,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年耸黑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片篮幢。...
    茶點(diǎn)故事閱讀 39,779評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡大刊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出三椿,到底是詐尸還是另有隱情缺菌,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布搜锰,位于F島的核電站伴郁,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蛋叼。R本人自食惡果不足惜焊傅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧授帕,春花似錦巷查、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至暴浦,卻和暖如春溅话,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背歌焦。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工飞几, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人同规。 一個(gè)月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓循狰,卻偏偏與公主長得像,于是被迫代替她去往敵國和親券勺。 傳聞我的和親對象是個(gè)殘疾皇子绪钥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評論 2 354