SpringSecurity 原理解析【2】:SecurityFilterChain的構建
在第一篇說Spring Security的總體佈局的時候就說起Spring Security嵌入Servlet的核心Bean爲一個名稱爲 springSecurityFilterChain 的過濾器。該過濾器是Spring Security的核心入口。java
不管何種方式的Web應用,Spring Security都是從IOC中獲取「springSecurityFilterChain」的,這個Bean的註冊入口只有一個,在類WebSecurityConfiguration中,該配置對象由註解@EnableWebSecurity開啓web
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME) public Filter springSecurityFilterChain() throws Exception { ... return webSecurity.build(); }
建造者模式:WebSecurity,建造的產品爲:Filter,具體類型爲FilterChainProxyspring
public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter, WebSecurity> implements SecurityBuilder<Filter>, ApplicationContextAware {...}
模板方法模式:AbstractConfiguredSecurityBuilder,模板方法爲建造者的build方法,模板化以後調用doBuild方法,doBuild仍然是一個模板方法json
@Override protected final O doBuild() throws Exception { synchronized (configurers) { buildState = BuildState.INITIALIZING; // ----- 初始化 ----- beforeInit(); init(); buildState = BuildState.CONFIGURING; // ----- 配置屬性 ----- beforeConfigure(); configure(); buildState = BuildState.BUILDING; // ----- 建造產品 ----- O result = performBuild(); buildState = BuildState.BUILT; return result; } }
核心構建方法 performBuild 方法在子類中具體實現,此處構建類爲WebSecurity後端
@Override protected Filter performBuild() throws Exception { // 鏈長度 int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size(); // SecurityFilterChain的構建 List<SecurityFilterChain> securityFilterChains = new ArrayList<>(chainSize); // 第一部分:來自不須要安全保護請求:ignoredRequest for (RequestMatcher ignoredRequest : ignoredRequests) { securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest)); } // 第二部分:來自須要安全保護請求:securityFilterChainBuilders for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) { securityFilterChains.add(securityFilterChainBuilder.build()); } // 構建代理類 FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains); // 防火牆 if (httpFirewall != null) { filterChainProxy.setFirewall(httpFirewall); } filterChainProxy.afterPropertiesSet(); Filter result = filterChainProxy; // 後置處理 postBuildAction.run(); // 構建完畢 return result; }
第一篇文章也說了,FilterChainProxy代理的多條SecurityFilterChain最終只能走一條,所以SecurityFilterChain來源十分重要,上面的代碼說明來源是2部分,這兩部分都是WebSecurity的私有屬性,也就是能夠在任何能夠得到WebSecurity實例的位置進行配置,可是通常都會按照規約進行,防止代理混亂、不易讀。如下WebSecurity的2個屬性分別表示2種來源跨域
private final List<RequestMatcher> ignoredRequests = new ArrayList<>(); private final List<SecurityBuilder<? extends SecurityFilterChain>> securityFilterChainBuilders = new ArrayList<>();
這兩種來源的SecurityFilterChain其實涉及了2種構建者:WebSecurity和HttpSecurity,前者基於Web應用(通常就是MVC)來構建安全,後者基於請求-響應(通常而言就是HTTP)來構建安全。緩存
WebSecurity
基於Servlet的Web應用的安全訪問控制,Spring Security與Spring MVC緊密集成。在WebSecurity能夠進行總體的配置,優先級最高安全
來源之一:ignoredRequests
WebSecurity內部類IgnoredRequestConfigurer提供了配置ignoredRequests的功能,而且提供了mvcMatchers(String... mvcPatterns)和mvcMatchers(HttpMethod method,String... mvcPatterns)兩種配置入口。經過WebSecurity#ignoring方法就能拿到該對象實例並完成配置:該配置符合鏈式調用,每一個IgnoredRequestConfigurer均可以構建一條SecurityFilterChain服務器
@Override public void configure(WebSecurity web) throws Exception { super.configure(web); // 獲取IgnoredRequest配置對象 web.ignoring() // 鏈式調用 .mvcMatchers("/login") // 鏈式調用 .mvcMatchers("/home") ; }
HttpSecurity
基於網絡請求的安全訪問控制,在Servlet中網絡請求主要在Filter中,這部分主要是配置的Http請求流轉過程當中的安全。cookie
來源之一:securityFilterChainBuilders
大部分開發使用的就是這種來源,每一個實例構建一條SecurityFilterChain。WebSecurity提供了addSecurityFilterChainBuilder方法來配置,所以經過WebSecurity實例也能完成配置,可是securityFilterChainBuilder構建流程複雜,通常都使用Spring Security提供的內置方法來簡化這個流程,例如在WebSecurityConfigurerAdapter#init中就默認調用了addSecurityFilterChainBuilder(HttpSecurity)
@Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .permitAll() .and() .logout() .permitAll(); }
Spring Security 認證、受權核心入口:FilterSecurityInterceptor
針對方法級別的攔截:區分爲前置攔截:beforeInvocation和後置攔截:afterInvocation,先後攔截原理基本一致。
這是開發者常常接觸方式,這種模式引入一個專有名詞:ConfigAttribute,直譯:屬性配置,可是對於開發而已能夠稱爲:安全訪問規則。
能夠對任何路徑配置(每一個Controller方法其實對應的就是訪問路徑)安全訪問規則。權限標識屬於一種安全訪問規則。
對安全訪問規則ConfigAttribute可否知足訪問資源的權限的決斷,由AccessDecisionManager處理,AccessDecisionManager特定訪問規則的web資源可否被訪問。
而AccessDecisionManager作出決斷須要當前帳號的認證信息,所以在作出決斷以前會嘗試經過AuthenticationManager獲取當前請求的身份認證信息。
這裏獲取認證信息、作出訪問決斷是Spring Security的另外一個核心,會單獨說明,本篇文章主要描述SecurityFilterChain的構建。
authorizeRequests --> ExpressionUrlAuthorizationConfigurer --> FilterSecurityInterceptor
功能:配置基於請求路徑的安全訪問規則:ConfigAttribute
HttpSecurity主要配置Filter鏈,也就是SecurityFilterChain的來源,也是最多見的,固然也是最重要的,下面爲默認的配置
http = new HttpSecurity(objectPostProcessor, authenticationBuilder,sharedObjects); if (!disableDefaults) { // @formatter:off http .csrf().and() .addFilter(new WebAsyncManagerIntegrationFilter()) .exceptionHandling().and() .headers().and() .sessionManagement().and() .securityContext().and() .requestCache().and() .anonymous().and() .servletApi().and() .apply(new DefaultLoginPageConfigurer<>()).and() .logout(); // @formatter:on ClassLoader classLoader = this.context.getClassLoader(); // 拿出IOC容器中全部AbstractHttpConfigurer具體類,逐個配置 List<AbstractHttpConfigurer> defaultHttpConfigurers = SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader); for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) { http.apply(configurer); } } // 常見的配置方法:protected void configure(HttpSecurity http) throws Exception { configure(http);
這裏默認會加入10個Configurer,每一個對應一個Filter,defaultHttpConfigurers默認爲空,能夠自定義Bean來註冊,會覆蓋默認配置,再加上配置的WebAsyncManagerIntegrationFilter和平時經常使用的formLogin總共12個Filter,各類Filter之間時有序的。
configure(http);方法則是開發者重寫的方法,用於擴展SecurityFilterChain中的Filter,一樣此處定義會覆蓋前面默認配置,每種configurer都與對應一種Filter
ChannelSecurityConfigurer --> ChannelProcessingFilter
功能:配置Web請求通道轉換後重定向,默認兩種:安全的https->44三、不安全的http->80,不區分的權限標識爲:"ANY_CHANNEL"。若是不須要https保護接口,則不用配置該過濾器。
WebAsyncManagerIntegrationFilter
功能:將SecurityContext配置到異步請求WebAsyncManager中,MVC支持異步Controller,返回對象爲:DeferredResult等
ConcurrentSessionFilter
功能:對於session存在的請求,驗證session是否過時,若是過時,則調用doLogout方法,不然更新session的訪問時間。默認不啓用該Filter,能夠在sessionManagement#maximumSessions控制。
securityContext --> SecurityContextPersistenceFilter
功能:持久化SecurityContext,即Security上下文,使得能從線程中獲取到當前登陸用戶的信息,持久化方式默認是HttpSessionSecurityContextRepository即服務器Session,key="SPRING_SECURITY_CONTEXT"。
headers --> HeadersConfigurer --> HeaderWriterFilter
功能:在請求響應的頭部添加自定義信息
cors -> CorsConfigurer -> CorsFilter
功能:跨域處理,注意這個過濾器是Spring Security內部過濾器,在優先級更高過濾器中就返回的響應不會具備跨域能力
csrf --> CsrfConfigurer --> CsrfFilter
功能:跨站請求攻擊防護,默認對"GET", "HEAD", "TRACE", "OPTIONS"請求進行CSRF保護,默認爲session中保存CsrfToken,先後端進行驗證
logout --> LogoutConfigurer --> LogoutFilter
功能:匹配路徑登出
formLogin --> FormLoginConfigurer --> UsernamePasswordAuthenticationFilter
功能:使用帳號、密碼模式的UsernamePasswordAuthenticationToken身份認證
DefaultLoginPageConfigurer --> DefaultLoginPageGeneratingFilter && DefaultLogoutPageGeneratingFilter
功能: 在未指定登陸登出頁面時,生成默認的頁面
httpBasic --> HttpBasicConfigurer --> BasicAuthenticationFilter
功能:經過UsernamePasswordAuthenticationToken處理請求頭中Basic格式的Authorization認證信息
requestCache --> RequestCacheConfigurer --> RequestCacheAwareFilter
功能:用來恢復登陸流程中被中斷(認證失敗)的請求。
在RequestCacheConfigurer中對HttpRequest緩存條件加了如下默認限制
- Csrf開啓時:只能爲GET請求
- 不緩存網站圖標請求:即路徑不能匹配「/**/favicon.*」
- 不緩存這三種媒體類型MediaType:"application/json"、"multipart/form-data"、"text/event-stream"
- 不緩存xjax請求,即請求頭"X-Requested-With"不能爲:"XMLHttpRequest"
servletApi --> ServletApiConfigurer --> SecurityContextHolderAwareRequestFilter
功能:實現Servlet3 API接口,主要在SecurityContextHolderAwareRequestWrapper重寫實現,例如getRemoteUser、getUserPrincipal、isUserInRole
rememberMe --> RememberMeConfigurer --> RememberMeAuthenticationFilter
功能:當用戶沒有登陸而訪問web資源時,能夠從cookie或者內存中找出身份識別信息從而靜默登陸
anonymous --> AnonymousConfigurer --> AnonymousAuthenticationFilter
功能:當Security Context中認證信息爲null時設置Security Context=AnonymousAuthenticationToken,默認權限爲"ROLE_ANONYMOUS"
sessionManagement --> SessionManagementConfigurer --> SessionManagementFilter
功能:針對未持久化過SecurityContext的請求進行session會話固定(Session fixation)保護和限制用戶打開會話數量。
exceptionHandling --> ExceptionHandlingConfigurer --> ExceptionTranslationFilter
功能:處理Spring Security流程中出現的異常,主要就是AuthenticationException和AccessDeniedException異常,獲取到異常通常都會交給對應的AuthenticationEntryPoint#commence去處理,可是非AnonymousAuthenticationToken或者RememberMeAuthenticationToken產生的AccessDeniedException會交給過濾器中的accessDeniedHandler來處理(例如:403)
過濾器有排序的,總體排序以下
FilterComparator() { Step order = new Step(INITIAL_ORDER, ORDER_STEP); put(ChannelProcessingFilter.class, order.next()); put(ConcurrentSessionFilter.class, order.next()); put(WebAsyncManagerIntegrationFilter.class, order.next()); put(SecurityContextPersistenceFilter.class, order.next()); put(HeaderWriterFilter.class, order.next()); put(CorsFilter.class, order.next()); put(CsrfFilter.class, order.next()); put(LogoutFilter.class, order.next()); filterToOrder.put("org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter",order.next()); filterToOrder.put("org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationRequestFilter",order.next()); put(X509AuthenticationFilter.class, order.next()); put(AbstractPreAuthenticatedProcessingFilter.class, order.next()); filterToOrder.put("org.springframework.security.cas.web.CasAuthenticationFilter",order.next()); filterToOrder.put("org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter",order.next()); filterToOrder.put("org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter",order.next()); put(UsernamePasswordAuthenticationFilter.class, order.next()); put(ConcurrentSessionFilter.class, order.next()); filterToOrder.put("org.springframework.security.openid.OpenIDAuthenticationFilter", order.next()); put(DefaultLoginPageGeneratingFilter.class, order.next()); put(DefaultLogoutPageGeneratingFilter.class, order.next()); put(ConcurrentSessionFilter.class, order.next()); put(DigestAuthenticationFilter.class, order.next()); filterToOrder.put("org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter", order.next()); put(BasicAuthenticationFilter.class, order.next()); put(RequestCacheAwareFilter.class, order.next()); put(SecurityContextHolderAwareRequestFilter.class, order.next()); put(JaasApiIntegrationFilter.class, order.next()); put(RememberMeAuthenticationFilter.class, order.next()); put(AnonymousAuthenticationFilter.class, order.next()); filterToOrder.put("org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter",order.next()); put(SessionManagementFilter.class, order.next()); put(ExceptionTranslationFilter.class, order.next()); put(FilterSecurityInterceptor.class, order.next()); put(SwitchUserFilter.class, order.next()); }
注意排序中FilterSecurityInterceptor的優先級別很低,防護的是Web中方法級別的安全。最終SecurityFilterChain來源如圖示下