DelegatingFilterProxy的原理及使用
DelegatingFilterProxy就是一個對於servlet filter的代理,用這個類的好處主要是通過Spring容器來管理servlet filter的生命周期,還有就是如果filter中需要一些Spring容器的實例,可以通過spring直接注入,另外讀取一些配置文件這些便利的操作都可以通過Spring來配置實現。
DelegatingFilterProxy的使用方法,
首先在web.xml中配置:
<filter>
< filter-name>myFilter</filter-name>
< filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
< filter-name>myFilter</filter-name>
< url-pattern>/*</url-pattern>
</filter-mapping>
然后在Spring的配置文件中,配置具體的Filter類的實例。
<bean name="myFilter" class="com.*.MyFilter"></bean>
在Spring中配置的bean的name要和web.xml中的<filter-name>一樣
或者在DelegatingFilterProxy的filter配置中配置初始參數:targetBeanName,對應到Spring配置中的beanname
如果要保留Filter原有的init,destroy方法的調用,還需要配置初始化參數targetFilterLifecycle為true,該參數默認為false
----------------------------------------------------
使用過springSecurity的朋友都知道,首先需要在web.xml進行以下配置,
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
從這個配置中,可能會給我們造成一個錯覺,以為DelegatingFilterProxy類就是springSecurity的入口,但其實這個類位於spring-web-3.0.5.RELEASE.jar這個jar下面,說明這個類本身是和springSecurity無關。DelegatingFilterProxy類繼承於抽象類GenericFilterBean,間接地implement 了javax.servlet.Filter接口,Servlet容器在啟動時,首先會調用Filter的init方法,GenericFilterBean的作用主要是可以把Filter的初始化參數自動地set到繼承於GenericFilterBean類的Filter中去。在其init方法的如下代碼就是做了這個事:
1
2
3
4
5
6
|
PropertyValues pvs = new FilterConfigPropertyValues(filterConfig, this .requiredProperties);
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess( this );
ResourceLoader resourceLoader = new ServletContextResourceLoader(filterConfig.getServletContext());
bw.registerCustomEditor(Resource. class , new ResourceEditor(resourceLoader));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true );
|
另外在init方法中調用了initFilterBean()方法,該方法是GenericFilterBean類是特地留給子類擴展用的,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
protected void initFilterBean() throws ServletException {
// 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.
synchronized ( this .delegateMonitor) {
WebApplicationContext wac = findWebApplicationContext();
if (wac != null ) {
this .delegate = initDelegate(wac);
}
}
}
|
可以看出上述代碼首先看Filter是否提供了targetBeanName初始化參數,如果沒有提供則直接使用filter的name做為beanName,產生了beanName后,由於我們在web.xml的filter的name是springSecurityFilterChain,從spring的IOC容器中取出bean的代碼是initDelegate方法,下面是該方法代碼:
1
2
3
4
5
6
7
|
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
Filter delegate = wac.getBean(getTargetBeanName(), Filter. class );
if (isTargetFilterLifecycle()) {
delegate.init(getFilterConfig());
}
return delegate;
}
|
通過跟蹤代碼,發現取出的bean是org.springframework.security.FilterChainProxy,該類也是繼承於GenericFilterBean,取出bean后,判斷targetFilterLifecycle屬性是false還是true,決定是否調用該類的init方法。這個FilterChainProxy bean實例最終被保存在DelegatingFilterProxy類的delegate屬性里,
下面看一下DelegatingFilterProxy類的doFilter方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// Lazily initialize the delegate if necessary.
Filter delegateToUse = null ;
synchronized ( this .delegateMonitor) {
if ( this .delegate == null ) {
WebApplicationContext wac = findWebApplicationContext();
if (wac == null ) {
throw new IllegalStateException( "No WebApplicationContext found: no ContextLoaderListener registered?" );
}
this .delegate = initDelegate(wac);
}
delegateToUse = this .delegate;
}
// Let the delegate perform the actual doFilter operation.
invokeDelegate(delegateToUse, request, response, filterChain);
}
|
真正要關注invokeDelegate(delegateToUse, request, response, filterChain);這句代碼,在下面可以看出DelegatingFilterProxy類實際是用其delegate屬性即org.springframework.security.FilterChainProxy實例的doFilter方法來響應請求。
1
2
3
4
5
6
|
protected void invokeDelegate(
Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
delegate.doFilter(request, response, filterChain);
}
|
以上就是DelegatingFilterProxy類的一些內部運行機制,其實主要作用就是一個代理模式的應用,可以把servlet 容器中的filter同spring容器中的bean關聯起來。
----------------------------------------------------------------
安全過濾器鏈
Spring Security的web架構是完全基於標准的servlet過濾器的。 它沒有在內部使用servlet或任何其他基於servlet的框架(比如spring mvc), 所以它沒有與任何特定的web技術強行關聯。 它只管處理HttpServletRequest 和HttpServletResponse,不關心請求時來自瀏覽器,web服務客戶端,HttpInvoker還是一個AJAX應用。
Spring Security維護了一個過濾器鏈,每個過濾器擁有特定的功能,過濾器需要服務也會對應添加和刪除。 過濾器的次序是非常重要的,它們之間都有依賴關系。 如果你已經使用了命名空間配置,過濾器會自動幫你配置, 你不需要定義任何Spring Bean,但是有時候你需要完全控制Spring過濾器鏈, 因為你使用了命名空間沒有提供的特性,或者你需要使用你自己自定義的類。
1. DelegatingFilterProxy
當使用servlet過濾器時,你很需要在你的web.xml中聲明它們, 它們可能被servlet容器忽略。在Spring Security,過濾器類也是定義在xml中的spring bean, 因此可以獲得Spring的依賴注入機制和生命周期接口。 spring的DelegatingFilterProxy提供了在 web.xml和application context之間的聯系。
當使用DelegatingFilterProxy,你會看到像 web.xml文件中的這樣內容:
<filter> <filter-name>myFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>myFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
注意這個過濾器其實是一個DelegatingFilterProxy,這個過濾器里沒有實現過濾器的任何邏輯。 DelegatingFilterProxy做的事情是代理Filter的方法,從application context里獲得bean。 這讓bean可以獲得spring web application context的生命周期支持,使配置較為輕便。 bean必須實現javax.servlet.Filter接口,它必須和filter-name里定義的名稱是一樣的。查看DelegatingFilterProxy的javadoc獲得更多信息。
2. FilterChainProxy
現在應該清楚了,你可以聲明每個Spring Security過濾器bean,你在application context中需要的。 把一個DelegatingFilterProxy入口添加到web.xml, 確認它們的次序是正確的。 這是一種繁瑣的方式,會讓web.xml顯得十分雜亂,如果我們配置了太多過濾器的話。 我們最好添加一個單獨的入口,在web.xml中,然后在application context中處理實體, 管理我們的web安全bean。 這就是FilterChainProxy所做的事情。它使用DelegatingFilterProxy (就像上面例子中那樣),但是對應的class是org.springframework.security.web.FilterChainProxy。 過濾器鏈是在application context中聲明的。這里有一個例子:
<bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy"> <sec:filter-chain-map path-type="ant"> <sec:filter-chain pattern="/webServices/**" filters=" securityContextPersistenceFilterWithASCFalse, basicAuthenticationFilter, exceptionTranslationFilter, filterSecurityInterceptor" /> <sec:filter-chain pattern="/**" filters=" securityContextPersistenceFilterWithASCFalse, formLoginFilter, exceptionTranslationFilter, filterSecurityInterceptor" /> </sec:filter-chain-map> </bean>
你可能注意到FilterSecurityInterceptor聲明的不同方式。 命名空間元素filter-chain-map被用來設置安全過濾器鏈。 它映射一個特定的URL模式,到過濾器鏈中,從bean名稱來定義的filters元素。 它同時支持正則表達式和ant路徑,並且只使用第一個出現的匹配URI。 在運行階段FilterChainProxy會定位當前web請求匹配的第一個URI模式,由filters屬性指定的過濾器bean列表將開始處理請求。 過濾器會按照定義的順序依次執行,所以你可以對處理特定URL的過濾器鏈進行完全的控制。
你可能注意到了,我們在過濾器鏈里聲明了兩個SecurityContextPersistenceFilter(ASC是allowSessionCreation的簡寫,是SecurityContextPersistenceFilter的一個屬性)。 因為web服務從來不會在請求里帶上jsessionid,為每個用戶代理都創建一個HttpSession完全是一種浪費。 如果你需要構建一個高等級最高可擴展性的系統,我們推薦你使用上面的配置方法。 對於小一點兒的項目,使用一個HttpSessionContextIntegrationFilter(讓它的allowSessionCreation默認為true)就足夠了。
在有關聲明周期的問題上,如果這些方法被FilterChainProxy自己調用,FilterChainProxy會始終根據下一層的Filter代理init(FilterConfig)和destroy()方法。 這時,FilterChainProxy會保證初始化和銷毀操作只會在Filter上調用一次, 而不管它在過濾器鏈中被聲明了多少次)。你控制着所有的抉擇,比如這些方法是否被調用 或targetFilterLifecycle初始化參數DelegatingFilterProxy。 默認情況下,這個參數是false,servlet容器生命周期調用不會傳播到 DelegatingFilterProxy。
當我們了解如何使用命名控制配置構建web安全。 我們使用一個DelegatingFilterProxy,它的名字是“springSecurityFilterChain”。 你應該現在可以看到FilterChainProxy的名字,它是由命名空間創建的。
2.1. 繞過過濾器鏈
通過命名空間,你可以使用filters = "none",來提供一個過濾器bean列表。 這會朝向請求模式,使用安全過濾器鏈整體。注意任何匹配這個模式的路徑不會有任何授權或校驗的服務 起作用,它們是可以自由訪問的。
3. 過濾器順序
定義在web.xml里的過濾器的順序是非常重要的。 不論你實際使用的是哪個過濾器,<filter-mapping>的順序應該像下面這樣:
ChannelProcessingFilter,因為它可能需要重定向到其他協議。
ConcurrentSessionFilter,因為它不使用SecurityContextHolder功能,但是需要更新 SessionRegistry 來從主體中放映正在進行的請求。
SecurityContextPersistenceFilter,這樣 SecurityContext可以在web請求的開始階段通過 SecurityContextHolder建立,然后 SecurityContext的任何修改都會在web請求結束的時候(為下一個web請求做准備)復制到 HttpSession中。
驗證執行機制 - UsernamePasswordAuthenticationFilter, CasAuthenticationFilter, BasicAuthenticationFilter 等等 - 這樣 SecurityContextHolder 可以被修改,並包含一個合法的 Authentication 請求標志。
SecurityContextHolderAwareRequestFilter,如果,你使用它,把一個Spring Security提醒HttpServletRequestWrapper安裝到你的servlet容器里。
RememberMeAuthenticationFilter,這樣如果之前的驗證執行機制沒有更新 SecurityContextHolder,這個請求提供了一個可以使用的remember-me服務的cookie,一個對應的已保存的 Authentication對象會被創建出來。
AnonymousAuthenticationFilter,這樣如果之前的驗證執行機制沒有更新 SecurityContextHolder,會創建一個匿名 Authentication對象。
ExceptionTranslationFilter,用來捕捉 Spring Security異常,這樣,可能返回一個HTTP錯誤響應,或者執行一個對應的 AuthenticationEntryPoint。
FilterSecurityInterceptor,保護web URI。
4. 使用其他過濾器 —— 基於框架
如果你在使用SiteMesh,確認Spring Security過濾器在SiteMesh過濾器之前調用。 這可以保證SecurityContextHolder為每個SiteMesh渲染器及時創建。
5. 其他配置例子
方法一:
web.xml配置一個
<filter>
<filter-name>DelegatingFilterProxy</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetBeanName</param-name>
<param-value>myFilter</param-value> //自己過濾器的名字
</init-param>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>DelegatingFilterProxy</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
方法二:
web.xml配置一個
<filter>
<filter-name>myFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>myFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
方法一或者二不同的地方就是在web.xml中的寫法不同而已沒有太大的區別,配完web.xml之后還要配置applicationContext.xml中的bean。
applicationContext.xml配置:
<bean id="myFilter" class="com.bjtu.filter"> //指名具體的filter類
<property name="service"> //需要注入的具體參數
<ref bean="service"/>
</property>
</bean>
<bean id="service" parent="baseTransactionProxy">//這里的service封裝了所有對數據庫的操作
<property name="target">
<bean class="com.maimaiche.service.MaiMaiCheServiceImpl">
......
</bean>
</property>
</bean>
--------------------------------------------------
1、web.xml
- <!-- 所有filter,委托給spring -->
- <filter>
- <filter-name>appFilters</filter-name>
- <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
- <init-param>
- <param-name>targetFilterLifecycle</param-name>
- <param-value>true</param-value>
- </init-param>
- </filter>
- <filter-mapping>
- <filter-name>appFilters</filter-name>
- <url-pattern>/*</url-pattern>
- </filter-mapping>
2、applicationContext-filter.xml
- <bean id="appFilters" class="org.springframework.security.util.FilterChainProxy">
- <security:filter-chain-map path-type="ant">
- <security:filter-chain filters="characterEncodingFilter,commonParamsFilter"
- pattern="/**" />
- </security:filter-chain-map>
- </bean>
- <bean id="characterEncodingFilter" class="org.springframework.web.filter.CharacterEncodingFilter">
- <property name="encoding" value="UTF-8" />
- <property name="forceEncoding" value="true" />
- </bean>
- <bean id="commonParamsFilter" class="com.renren.wap.fuxi.filter.CommonParamsFilter" />