はじめに
2017/9/22現在のSpring Securityのバージョンは4.2.3のため、この問題は起こりません。
2018/9/12 追記:
Spring Security 4.2.5, 5.0.2で、再びフィルタ適用→ヘッダ書き込みの順になったので、同じ問題が発生しています。
また記事内で取り上げているissueに続報があり、APサーバによって挙動が異なるのはflush buffer size
とcommit size
の違いっぽいです。
ただ、どっちの仕様を正とするのかSpring Securityでも揺れているようなので、バージョンアップの際は注意しましょう。
確認環境
- Spring Security
- 4.1.4.RELEASE(TERASOLUNA Server Framework for Java (5.3.0.RELEASE)を使っています)
- APサーバ
- JBoss EAP 7.0.0
そもそもSpring SecurityでCache-Controlヘッダを出力するには
TERASOLUNAで出力する場合ですが、spring-security.xml
にSpring Securityのheaders要素を記載します。
以下の例では、デフォルトで適用されるヘッダを無効にして、Cache-Control
とX-Content-Type-Options
ヘッダだけを出力するコンポーネントのみを登録しています。
<sec:http>
<sec:headers defaults-disabled="true">
<sec:cache-control />
<sec:content-type-options />
</sec:headers>
なんで4.1だけレスポンスヘッダが出力されない場合があるの?
ヘッダ書き込み処理の手順の違い
ヘッダ書き込みをするクラスはorg.springframework.security.web.header.HeaderWriterFilter
で、doFilterInternal
メソッドで処理しているのですが、Spring Security 4.1.0 RC1で実装が変更されました。
関連issue:
SEC-2728: Only write cache related headers if no other cache headers found #2953
↓4.0.1.RELEASE時のdoFilterInternal
メソッド
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
for (HeaderWriter factory : headerWriters) {
factory.writeHeaders(request, response);
}
filterChain.doFilter(request, response);
}
↓4.1.0 RC1時のdoFilterInternal
メソッド
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
HeaderWriterResponse headerWriterResponse = new HeaderWriterResponse(request,
response, this.headerWriters);
try {
filterChain.doFilter(request, headerWriterResponse);
}
finally {
headerWriterResponse.writeHeaders();
}
}
4.0.1ではヘッダ書き込み→フィルタ適用の順で処理していますが、4.1.0ではフィルタ適用→ヘッダ書き込みの順になっていますね。
なにが問題?
try-finallyを使うことで、フィルタ適用処理で例外が発生した場合もヘッダ書き込みをするようになっていますが、フィルタ適用処理でhttpストリームがフラッシュされてしまうと、ヘッダ書き込みしてもクライアント側に送られません。(バグ報告のイシューを呼んでも、具体的にどのようなケースで問題になるのかは読み解けませんでした)
以下が対象のイシューですので、詳しくはこちらを参照してください。
どのような対応が必要?
Spring Securityのバージョンを上げましょう
簡単にバージョンをあげられない場合はHeaderWriterFilter
を差し替えましょう。
↑のバグ報告のイシューでHeaderWriterFilter
の変更はRevertされているので、Spring Security 4.1以外のHeaderWriterFilter
を使えばOKです。
HeaderWriterFilterの差し替え方
Spring Security 4.1以外のHeaderWriterFilter
をcom.neriudon.sample.filter
に配置して、CustomizedHeaderWriterFilter
と命名した場合、以下のようになります。
<bean id="secureCacheControlHeadersWriter"
class="org.springframework.security.web.header.writers.DelegatingRequestMatcherHeaderWriter">
<constructor-arg>
<bean
class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
<constructor-arg value="/**" />
</bean>
</constructor-arg>
<constructor-arg>
<bean
class="org.springframework.security.web.header.writers.CacheControlHeadersWriter" />
</constructor-arg>
</bean>
<bean id="customizedHeaderWriterFilter" class="com.neriudon.sample.filter.CustomizedHeaderWriterFilter">
<constructor-arg ref="secureCacheControlHeadersWriter" />
</bean>
<sec:http>
<sec:headers disabled="true" />
<sec:custom-filter position="HEADERS_FILTER"
ref="customizedHeaderWriterFilter" />
ポイントは
<sec:headers disabled="true" />
<sec:custom-filter position="HEADERS_FILTER"
ref="customizedHeaderWriterFilter" />
の部分です。
position
を使うと独自で用意したフィルタと差し替えることが出来ますが、既存のフィルタと競合するため、headers disabled="true"
としなければなりません。
そのため、差し替えるフィルタに出力するヘッダを指定しています。この部分は、TERASOLUNAのガイドラインにあるリクエストパターン毎のセキュリティヘッダの出力を引用しました。
この記事を投稿した経緯
TERASOLUNAのバージョンをあげたところ、ヘッダが出力されなくなってしまったので
バグ報告のイシューでもTomcat/JBossで挙動が異なるというようなコメントがありますが……奥が深いです