昨天有小夥伴加鬆哥微信,說他把鬆哥的 Spring Security 系列擼完了。。java
but 鬆哥這個系列還沒發完呢,在個人計劃中,Spring Security 系列目前應該能更新一半,還剩一半,雖然有的小夥伴可能以爲好像已經沒啥了,其實還有不少東西。。。web
鬆哥最近也是特別忙,Security 更新慢下來了,可是秉持前面說的,要學就成系列的學,要學就學透徹,這個系列我還會繼續更下去。瀏覽器
今天咱們就來聊一聊 Spring Security 系列中的 FilterChainProxy。安全
這是一個很是重要的代理對象。微信
咱們先來回顧一下前面文章講的:ide
在一個 Web 項目中,請求流程大概以下圖所示:工具
請求從客戶端發起(例如瀏覽器),而後穿過層層 Filter,最終來到 Servlet 上,被 Servlet 所處理。源碼分析
上圖中的 Filter 咱們能夠稱之爲 Web Filter,Spring Security 中的 Filter 咱們能夠稱之爲 Security Filter,它們之間的關係以下圖:ui
能夠看到,Spring Security Filter 並非直接嵌入到 Web Filter 中的,而是經過 FilterChainProxy 來統一管理 Spring Security Filter,FilterChainProxy 自己則經過 Spring 提供的 DelegatingFilterProxy 代理過濾器嵌入到 Web Filter 之中。this
DelegatingFilterProxy 不少小夥伴應該比較熟悉,在 Spring 中手工整合 Spring Session、Shiro 等工具時都離不開它,如今用了 Spring Boot,不少事情 Spring Boot 幫咱們作了,因此有時候會感受 DelegatingFilterProxy 的存在感有所下降,實際上它一直都在。
FilterChainProxy 中能夠存在多個過濾器鏈,以下圖:
能夠看到,當請求到達 FilterChainProxy 以後,FilterChainProxy 會根據請求的路徑,將請求轉發到不一樣的 Spring Security Filters 上面去,不一樣的 Spring Security Filters 對應了不一樣的過濾器,也就是不一樣的請求將通過不一樣的過濾器。
這是 FilterChainProxy 的一個大體功能,今天咱們就從源碼上理解 FilterChainProxy 中這些功能究竟是怎麼實現的。
先把 FilterChainProxy 源碼亮出來,這個源碼比較上,咱們一部分一部分來,先從它聲明的全局屬性上開始:
private final static String FILTER_APPLIED = FilterChainProxy.class.getName().concat( ".APPLIED"); private List<SecurityFilterChain> filterChains; private FilterChainValidator filterChainValidator = new NullFilterChainValidator(); private HttpFirewall firewall = new StrictHttpFirewall();
接下來咱們來看一個過濾器中最重要的 doFilter 方法:
@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { boolean clearContext = request.getAttribute(FILTER_APPLIED) == null; if (clearContext) { try { request.setAttribute(FILTER_APPLIED, Boolean.TRUE); doFilterInternal(request, response, chain); } finally { SecurityContextHolder.clearContext(); request.removeAttribute(FILTER_APPLIED); } } else { doFilterInternal(request, response, chain); } }
在 doFilter 方法中,正常來講,clearContext 參數每次都是 true,因而每次都先給 request 標記上 FILTER_APPLIED 屬性,而後執行 doFilterInternal 方法去走過濾器,執行完畢後,最後在 finally 代碼塊中清除 SecurityContextHolder 中保存的用戶信息,同時移除 request 中的標記。
按着這個順序,咱們來看 doFilterInternal 方法:
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; }
doFilterInternal 方法就比較重要了:
web.ignoring().antMatchers("/hello");
,那麼當你請求 /hello 接口時就會走到這裏來,也就是說這個不通過 Spring Security Filter。這個鬆哥以前也專門寫文章介紹過:Spring Security 兩種資源放行策略,千萬別用錯了!。那麼接下來咱們就來看看 VirtualFilterChain:
private static class VirtualFilterChain implements FilterChain { private final FilterChain originalChain; private final List<Filter> additionalFilters; private final FirewalledRequest firewalledRequest; private final int size; private int currentPosition = 0; private VirtualFilterChain(FirewalledRequest firewalledRequest, FilterChain chain, List<Filter> additionalFilters) { this.originalChain = chain; this.additionalFilters = additionalFilters; this.size = additionalFilters.size(); this.firewalledRequest = firewalledRequest; } @Override public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { if (currentPosition == size) { if (logger.isDebugEnabled()) { logger.debug(UrlUtils.buildRequestUrl(firewalledRequest) + " reached end of additional filter chain; proceeding with original chain"); } // Deactivate path stripping as we exit the security filter chain this.firewalledRequest.reset(); originalChain.doFilter(request, response); } else { currentPosition++; Filter nextFilter = additionalFilters.get(currentPosition - 1); if (logger.isDebugEnabled()) { logger.debug(UrlUtils.buildRequestUrl(firewalledRequest) + " at position " + currentPosition + " of " + size + " in additional filter chain; firing Filter: '" + nextFilter.getClass().getSimpleName() + "'"); } nextFilter.doFilter(request, response, this); } } }
currentPosition == size
,表示過濾器鏈已經執行完畢,此時經過調用 originalChain.doFilter 進入到原生過濾鏈方法中,同時也退出了 Spring Security 過濾器鏈。不然就從 additionalFilters 取出 Spring Security 過濾器鏈中的一個個過濾器,挨個調用 doFilter 方法。最後,FilterChainProxy 中還定義了 FilterChainValidator 接口及其實現:
public interface FilterChainValidator { void validate(FilterChainProxy filterChainProxy); } private static class NullFilterChainValidator implements FilterChainValidator { @Override public void validate(FilterChainProxy filterChainProxy) { } }
實際上這個實現並未作任何事情。
這就是 FilterChainProxy 中的整個邏輯。
其實本文中的不少知識點鬆哥在以前的文章中都和你們介紹過,例如配置多個過濾器鏈、StrictHttpFireWall、兩種資源放行方式等等,可是以前主要是和你們分享用法,沒有去細究他的原理,今天的文章,咱們經過源碼把這個問題捋了一遍,相信你們對於這些功能細節也更清晰了。
好啦,感興趣的小夥伴記得點個在看鼓勵下鬆哥哦~