Spring SecurityのCSRF対策とServlet 3.0のMultipartをJava Configで設定する
はまったのでメモ。
やりたかったこと
HTMLのFORMをPOSTするとき <input hidden name="_csrf" value="..." />
でCSRFトークンを送りつつMultipartでファイルアップロードしたい。
Springの設定はJava Configでやっていて、web.xmlは作っていない。今さらいろいろな設定をxmlにしたくない。
やってみたこと
Springのリファレンス 16.11 Spring’s multipart (file upload) support にある通り、MultipartResolver
をコンテキストに登録してみた。
@Bean public MultipartResolver multipartResolver() { return new StandardServletMultipartResolver(); }
HTML側からPOSTすると、サーバ側のログに Invalid csrf token
というメッセージが出て、ステータスコード403が返ってきた。
原因は、Spring SecurityのCsrfFilterがリクエストパラメータからトークンを取得するときに、Multipartリクエストに対し getParameter をしているため。
解決
しばらくググってみたところ、 TERASOLUNA Global Framework のCSRF対策 に方法が書いてあった。
6.7.2.4.1. MultipartFilterを使用する方法
通常、マルチパートリクエストの場合、formから送信された値はFilter内で取得できない。 org.springframework.web.multipart.support.MultipartFilterを使用することで、マルチパートリクエストでも、Filter内で、 formから送信された値を取得することができる。
サイトにはweb.xmlでの設定例が載っているが、要はspringSecurityFilterChain
より前にMultipartFilter
を動かす必要があるということ。
Java Configでやるには、SecurityWebApplicationInitializer#beforeSpringSecurityFilterChain()
でフィルタを登録する。
public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer { @Override protected void beforeSpringSecurityFilterChain(ServletContext servletContext) { final MultipartFilter multipartFilter = new MultipartFilter(); servletContext.addFilter("multipartFilter", multipartFilter) .addMappingForUrlPatterns( EnumSet.allOf(DispatcherType.class), false, "/*"); } }
MultipartFilter
は、デフォルトでServlet 3.0のAPIを使うStandardServletMultipartResolver
を使うようになっているため、自前でコンテキストに登録する必要はない。もしcommons-fileuploadを使いたい場合は、MultipartFilter#DEFAULT_MULTIPART_RESOLVER_BEAN_NAME
で定義された名前 ("filterMultipartResolver") でコンテキストに CommonsMultipartResolver
を登録すればよい。
Servlet 3.0のMultipart有効化
web.xmlでの設定例や、自前Servletにつけるアノテーションはすぐに見つかるのだけど、DispatcherServlet
のJava Configの設定例が見当たらない。けど、サーブレットに <multipart-config>
をつければ良いだろうということで、WebApplicationInitializer
に設定を追加。
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected void customizeRegistration(Dynamic registration) { registration.setMultipartConfig(new MultipartConfigElement( System.getProperty("java.io.tmpdir"), -1, -1, 1024 * 1024)); } }
MultipartConfigElement
で、アップロード可能な最大ファイルサイズや、添付ファイルに書きだすサイズ閾値など指定可能。