Spring Framework Advent Calendar 2011 part.2 - Spring Security の FilterChainProxy を使って ServletFilter の設定を Spring に移管する

Spring Framework Advent Calendar の 2 日目です。本日は FilterChainProxy についてです。

Spring Framework を使っていると、協調する各プロダクトの設定ファイルを Spring の設定ファイルに統合したくなることでしょう。今回は、いつも web.xml に記述しているであろう ServletFilter の設定を Spring に移管する方法を紹介します。以前にも SpringFramework と Velocity の統合 - 倖せの迷う森 にて、Velocity の設定情報を Spring に統合する手法を紹介しました。

Spring Security には FilterChainProxy というクラスがあり、これを使用すると、サーブレットコンテナの FilterChain と同じような動作をさせることができます。こういったクラスを Spring が提供しているのは、フィルタークラスをも Spring Bean として取り扱いたいという野望があるのでしょうか。

FilterChainProxy は、Spring Security のアクセス制御を実現するためのエンジンとして用いられています*1が、もちろん他の用途に利用することもできます。たとえば、自作の Filter クラスを FilterChainProxy で束ねて取り扱いを簡単にする、といった方法が考えられます。以下のような web.xml のフィルター定義は、

  <filter>
    <filter-name>filter1</filter-name>
    <filter-class>path.to.your.package.Filter1</filter-class>
  </filter>
  <filter>
    <filter-name>filter2</filter-name>
    <filter-class>path.to.your.package.Filter2</filter-class>
  </filter>
  <filter>
    <filter-name>filter2</filter-name>
    <filter-class>path.to.your.package.Filter3</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>filter1</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  <filter-mapping>
    <filter-name>filter1</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  <filter-mapping>
    <filter-name>filter1</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

filterChainProxy を使うと以下のようにまとめられます。

web.xml
  <filter>
    <filter-name>myFilterChainProxy</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>myFilterChainProxy</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
applicationContext.xml*2
 <bean id="myfilterChainProxy" class="org.springframework.security.util.FilterChainProxy">
     <security:filter-chain-map pathType="ant">
         <security:filter-chain pattern="/do/not/filter" filters="none"/>
         <security:filter-chain pattern="/**" filters="filter1,filter2,filter3"/>
     </security:filter-chain-map>
 </bean>

フィルターのマッピング部分が1カ所に集約されていますね。また、FilterChainProxy のフィルタールールは pattern を複数組み合わせるなど柔軟に定義できるため、URL によって細かい制御が必要な Filter などは重宝するでしょう*3。pattern の記述方式は ant か regex を選べます。
ただし、web.xml からフィルターの定義が完全に消せるわけではありません。FilterChainProxy をkick するための Filter として DelegatingFilterProxy を記述しておく必要があります。
filter1, filter2, filter3 は通常の ServletFilter でもかまいませんが、新規に書き起こすのであれば org.springframework.web.filter.OncePerRequestFilter のサブクラスとして作成しておくことをオススメします。これらは Spring Bean として扱えますので、通常通り Bean 定義ファイルに記述してもよいですし、@Component や @Service アノテーションを与えて component-scan するアプローチも可能です。

*1:Spring Security は、認証プロセスを何層にも連なる ServletFilter の集まりで実現しています。 https://sites.google.com/site/soracane/home/springnitsuite/spring-security/spring-securityno-naibu-dousa に、より詳細な解説があります。

*2:filter="none" は、次期リリース予定の Spring Security 3.1 からサポートされなくなります

*3:そのような URL 設計が果たして適切なのかどうかはここでは言及しませんが