5.17. ファイルアップロード¶
Caution
本バージョンの内容は既に古くなっています。最新のガイドラインはこちらからご参照ください。
目次
5.17.1. Overview¶
Note
本節では、Servlet3.0でサポートされたファイルアップロード機能を使用しているため、Servletのバージョンは、3.0以上であることが前提となる。
Note
WebLogicなど一部のアプリケーションサーバー上でServlet 3.0のファイルアップロード機能を使用すると、 リクエストパラメータやファイル名のマルチバイト文字が文字化けすることがある。
問題が発生するアプリケーションサーバーを使用する場合は、Commons FileUploadを使用することで問題を回避することができる。 Commons FileUploadを使用するための設定方法については、「Commons FileUpload を使用したファイルのアップロード」を参照されたい。
Warning
使用するアプリケーションサーバのファイルアップロードの実装が、Apache Commons FileUploadの実装に依存している場合、CVE-2014-0050およびCVE-2016-3092で報告されているセキュリティの脆弱性が発生する可能性がある。 使用するアプリケーションサーバに同様の脆弱性がない事を確認されたい。
Tomcatを使用する場合、7.0系は7.0.70以上、8.0系は8.0.36以上、8.5系は8.5.3以上を使用する必要がある。
5.17.1.1. アップロード処理の基本フロー¶
Servlet3.0からサポートされたファイルアップロード機能と、Spring Webのクラスを使って、ファイルをアップロードする際の基本フローを、以下に示す。
項番 説明 multipart/form-dataリクエストを受け取り、org.springframework.web.servlet.DispatcherServletを呼び出す。DispatcherServletは、org.springframework.web.multipart.support.StandardServletMultipartResolverのメソッドを呼び出し、Servlet3.0のファイルアップロード機能を、Spring MVCで扱えるようにする。StandardServletMultipartResolverは、Servlet3.0から導入されたAPI(javax.servlet.http.Part)をラップするorg.springframework.web.multipart.MultipartFileのオブジェクトを生成する。DispatcherServletは、Controllerの処理メソッドを呼び出す。(3)で生成されたMultipartFileオブジェクトは、 Controllerの引数またはフォームオブジェクトに、バインドされる。MultipartFileオブジェクトのメソッドを呼び出し、アップロードされたファイルの中身と、メタ情報(ファイル名など)を取得する。MultipartFileは、Servlet3.0から導入されたPartオブジェクトのメソッドを呼び出し、アップロードされたファイルの中身と、メタ情報(ファイル名など)を取得し、Controllerに返却する。MultipartFileオブジェクトより取得した、ファイルの中身と、メタ情報(ファイル名など)は、Serviceのメソッドの引数として、引き渡す。DispatcherServletは、StandardServletMultipartResolverを呼び出し、Servlet3.0のファイルアップロード機能で使用される一時ファイルを削除する。StandardServletMultipartResolverは、Servlet3.0から導入されたPartオブジェクトのメソッドを呼び出し、ディスクに保存されている一時ファイルを削除する。Note
Controllerでは、Spring Webから提供されている
MultipartFileオブジェクトに対して処理を行うため、Servlet3.0から提供されたファイルアップロード用のAPIに依存した実装を、排除することができる。
5.17.1.2. Spring Webから提供されているクラスについて¶
Spring Webから提供されているファイルアップロード用のクラスについて、説明する。
MultipartFileクラス。Servlet3.0から導入されたPartオブジェクトに、処理を委譲している。
multipart/form-dataリクエストの解析方法を解決するためのインタフェース。ファイルアップロード機能の、実装に対応するMultipartFileオブジェクトを生成する役割をもつ。
MultipartResolverクラス。
Tip
本ガイドラインでは、Servlet3.0から導入されたファイルアップロード機能を使うことを前提としているが、Spring Webでは、「Apache Commons FileUpload」用の実装クラスも提供している。 アップロード処理の実装の違いは、
MultipartResolverと、MultipartFileオブジェクトによって吸収されるため、Controllerの実装に影響を与えることはない。
5.17.2. How to use¶
5.17.2.1. アプリケーションの設定¶
5.17.2.1.1. Servlet3.0のアップロード機能を有効化するための設定¶
Servlet3.0のアップロード機能を有効化するために、以下の設定を行う。
- web.xml
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> <!-- (1) (2) --> <servlet> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <!-- omitted --> <multipart-config> <!-- (3) --> <max-file-size>5242880</max-file-size> <!-- (4) --> <max-request-size>27262976</max-request-size> <!-- (5) --> <file-size-threshold>0</file-size-threshold> <!-- (6) --> </multipart-config> </servlet> <!-- omitted --> </web-app>
項番 説明 <web-app>要素のxsi:schemaLocation属性に、Servlet3.0以上のXSDファイルを指定する。<web-app>要素のversion属性に、3.0以上のバージョンを指定する。<servlet>要素に、<multipart-config>要素を追加する。org.springframework.web.multipart.MultipartExceptionが発生する。上記例では、 5MBを指定している。multipart/form-dataリクエストのContent-Lengthの最大値を指定する。指定がない場合、-1 (制限なし)が設定される。指定した値を超えた場合、org.springframework.web.multipart.MultipartExceptionが発生する。本パラメータに設定する値は、以下の計算式で算出される値を設定する必要がある。(「アップロードを許可する1ファイルの最大バイト数」 * 「同時にアップロードを許可するファイル数」 ) + 「その他のフォーム項目のデータサイズ」 + 「multipart/form-dataリクエストのメタ情報サイズ」上記例では、 26MBを指定している。内訳は、25MB(5MB * 5 files)と、1MB(メタ情報のバイト数 + フォーム項目のバイト数)である。<max-file-size>要素や<max-request-size>要素で指定した値が有効にならないアプリケーションサーバが存在するため、デフォルト値(0)を明示的に指定している。Warning
Dos攻撃に対する攻撃耐性を高めるため、
max-file-sizeと、max-request-sizeは、かならず指定すること。Dos攻撃については、アップロード機能に対するDos攻撃を参照されたい。
Note
デフォルトの設定では、アップロードされたファイルは必ず一時ファイルに出力されるが、
<multipart-config>の子要素である<file-size-threshold>要素の設定値によって、出力有無を制御することができる。<!-- omitted --> <multipart-config> <!-- omitted --> <file-size-threshold>32768</file-size-threshold> <!-- (7) --> </multipart-config> <!-- omitted -->
項番 説明 Warning
本パラメータは、以下の点でトレードオフの関係となっているため、システム特性にあった設定値を指定すること。
- 設定値を大きくすると、メモリ内で処理が完結するため、処理性能は向上するが、 Dos攻撃などによって
OutOfMemoryErrorが発生する可能性が高くなる。- 設定値を小さくすると、メモリを使用率を最小限に抑えることができるため、Dos攻撃などによって
OutOfMemoryErrorが発生する可能性を抑えることができるが、 ディスクIOの発生頻度が高くなるため、性能劣化が発生する可能性が高くなる。一時ファイルの出力ディレクトリを変更したい場合は、
<multipart-config>の子要素である<location>要素にディレクトリパスを指定する。<!-- omitted --> <multipart-config> <location>/tmp</location> <!-- (8) --> <!-- omitted --> </multipart-config> <!-- omitted -->
項番 説明 /tmpを指定している。Warning
<location>要素で指定するディレクトリは、アプリケーションサーバ(サーブレットコンテナ)が利用するディレクトリであり、アプリケーションからアクセスする場所ではない。アプリケーションとしてアップロードされたファイルを一時的なファイルとして保存しておきたい場合は、
<location>要素で指定するディレクトリとは、別のディレクトリに出力すること。
5.17.2.1.2. Servlet Filterの処理内でリクエストパラメータを取得できるようにするための設定¶
multipart/form-dataリクエストの時に、Servlet Filterの処理内でリクエストパラメータを取得できるようにするために、以下の設定を行う。
- web.xml
<!-- (1) --> <filter> <filter-name>MultipartFilter</filter-name> <filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class> </filter> <!-- (2) --> <filter-mapping> <filter-name>MultipartFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
項番 説明 MultipartFilterを定義する。MultipartFilterを適用するURLのパターンを指定する。Warning
MultipartFilterは、リクエストパラメータにアクセスするServlet Filterより前に定義する必要がある。
Spring Securityを使ってセキュリティ対策を行う場合は、
springSecurityFilterChainより前に定義すること。 また、プロジェクト独自で作成するServlet Filterでリクエストパラメータにアクセスするものがある場合は、そのServlet Filterより前に定義すること。
5.17.2.1.3. Servlet3.0のアップロード機能とSpring MVCを連携するための設定¶
Servlet3.0のアップロード機能と、Spring MVCを連携するために、以下の設定を行う。
- spring-mvc.xml
<bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver"> <!-- (1) --> </bean>
項番 説明 StandardServletMultipartResolverを、bean定義する。beanIDは、"multipartResolver"とすること。この設定を行うことで、アップロードされたファイルをorg.springframework.web.multipart.MultipartFileとして、Controllerの引数およびフォームオブジェクトのプロパティとして、受け取ることができる。
5.17.2.1.4. 例外ハンドリングの設定¶
許可されないサイズのファイルやマルチパートのリクエストが行われた際に発生するMultipartExceptionの例外ハンドリングの定義を追加する。
MultipartExceptionは、クライアントが指定するファイルサイズに起因して発生する例外なので、クライアントエラー(HTTPレスポンスコード=4xx)として扱うことを推奨する。MultipartExceptionをハンドリングするための設定は、MultipartFilterを使用するか否かによって異なる。MultipartFilterを使用する場合は、サーブレットコンテナの<error-page>機能を使って例外ハンドリングを行う。- web.xml
<error-page> <!-- (1) --> <exception-type>org.springframework.web.multipart.MultipartException</exception-type> <!-- (2) --> <location>/WEB-INF/views/common/error/fileUploadError.jsp</location> </error-page>
項番 説明 MultipartExceptionを指定する。MultipartExceptionが発生した際に表示するファイルを指定する。上記例では、"/WEB-INF/views/common/error/fileUploadError.jsp"を指定している。
- fileUploadError.jsp
<%-- (3) --%> <% response.setStatus(HttpServletResponse.SC_BAD_REQUEST); %> <!DOCTYPE html> <html> <!-- omitted --> </html>
項番 説明 HttpServletResponseのAPIを呼び出して設定する。上記例では、"400"(Bad Request) を設定している。明示的に設定しない場合、HTTPステータスコードは"500"(Internal Server Error)となる。
MultipartFilterを使用しない場合は、SystemExceptionResolverを使用して例外ハンドリングを行う。- spring-mvc.xml
<bean class="org.terasoluna.gfw.web.exception.SystemExceptionResolver"> <!-- omitted --> <property name="exceptionMappings"> <map> <!-- omitted --> <!-- (4) --> <entry key="MultipartException" value="common/error/fileUploadError" /> </map> </property> <property name="statusCodes"> <map> <!-- omitted --> <!-- (5) --> <entry key="common/error/fileUploadError" value="400" /> </map> </property> <!-- omitted --> </bean>
項番 説明 SystemExceptionResolverのexceptionMappingsに、MultipartExceptionが発生した際に表示するView(JSP)の定義を追加する。上記例では、"common/error/fileUploadError"を指定している。MultipartExceptionが発生した際に応答するHTTPステータスコードの定義を追加する。上記例では、"400"(Bad Request) を指定している。クライアントエラー(HTTPレスポンスコード = 4xx)を指定することで、共通ライブラリの例外ハンドリング機能から提供しているクラス(HandlerExceptionResolverLoggingInterceptor)によって出力されるログは、ERRORレベルではなく、WARNレベルとなる。
MultipartExceptionに対する例外コードを設ける場合は、例外コードの設定を追加する。- applicationContext.xml
<bean id="exceptionCodeResolver" class="org.terasoluna.gfw.common.exception.SimpleMappingExceptionCodeResolver"> <property name="exceptionMappings"> <map> <!-- (6) --> <entry key="MultipartException" value="e.xx.fw.6001" /> <!-- omitted --> </map> </property> <property name="defaultExceptionCode" value="e.xx.fw.9001" /> <!-- omitted --> </bean>
項番 説明 SimpleMappingExceptionCodeResolverのexceptionMappingsに、MultipartExceptionが発生した際に適用する、例外コードを追加する。上記例では、"e.xx.fw.6001"を指定している。個別に定義を行わない場合は、defaultExceptionCodeに指定した例外コードが適用される。
5.17.2.2. 単一ファイルのアップロード¶
単一ファイルをアップロードする方法について、説明する。
org.springframework.web.multipart.MultipartFileオブジェクトを、フォームオブジェクトにバインドして受け取る方法と、Controllerの引数として直接受け取る2つの方法があるが、本ガイドラインでは、フォームオブジェクトにバインドして受け取る方法を推奨する。以下に、フォームオブジェクトにバインドして受け取る方法について、説明する。
5.17.2.2.1. フォームの実装¶
public class FileUploadForm implements Serializable { // omitted private MultipartFile file; // (1) @NotNull @Size(min = 0, max = 100) private String description; // omitted getter/setter methods. }
項番 説明 org.springframework.web.multipart.MultipartFileのプロパティを定義する。
5.17.2.2.2. JSPの実装¶
<form:form action="${pageContext.request.contextPath}/article/uploadFile" method="post" modelAttribute="fileUploadForm" enctype="multipart/form-data"> <!-- (1) (2) --> <table> <tr> <th width="35%">File to upload</th> <td width="65%"> <form:input type="file" path="file" /> <!-- (3) --> <form:errors path="file" /> </td> </tr> <tr> <th width="35%">Description</th> <td width="65%"> <form:input path="description" /> <form:errors path="description" /> </td> </tr> <tr> <td> </td> <td><form:button>Upload</form:button></td> </tr> </table> </form:form>
項番 説明 <form:form>要素のenctype属性に、"multipart/form-data"を指定する。<form:form>要素のmodelAttribute属性に、フォームオブジェクトの属性名を指定する。上記例では、"fileUploadForm"を指定している。<form:input>要素type属性に、"file"を指定し、path属性に、MultipartFileプロパティ名を指定する。上記例では、アップロードされたファイルは、FileUploadFormオブジェクトの"file"プロパティに格納される。
5.17.2.2.3. Controllerの実装¶
@RequestMapping("article") @Controller public class ArticleController { @Value("${upload.allowableFileSize}") private int uploadAllowableFileSize; // omitted // (1) @ModelAttribute public FileUploadForm setFileUploadForm() { return new FileUploadForm(); } // (2) @RequestMapping(value = "upload", method = RequestMethod.GET, params = "form") public String uploadForm() { return "article/uploadForm"; } // (3) @RequestMapping(value = "upload", method = RequestMethod.POST) public String upload(@Validated FileUploadForm form, BindingResult result, RedirectAttributes redirectAttributes) { if (result.hasErrors()) { return "article/uploadForm"; } MultipartFile uploadFile = form.getFile(); // (4) if (!StringUtils.hasLength(uploadFile.getOriginalFilename())) { result.rejectValue(uploadFile.getName(), "e.xx.at.6002"); return "article/uploadForm"; } // (5) if (uploadFile.isEmpty()) { result.rejectValue(uploadFile.getName(), "e.xx.at.6003"); return "article/uploadForm"; } // (6) if (uploadAllowableFileSize < uploadFile.getSize()) { result.rejectValue(uploadFile.getName(), "e.xx.at.6004", new Object[] { uploadAllowableFileSize }, null); return "article/uploadForm"; } // (7) // omit processing of upload. // (8) redirectAttributes.addFlashAttribute(ResultMessages.success().add( "i.xx.at.0001")); // (9) return "redirect:/article/upload?complete"; } @RequestMapping(value = "upload", method = RequestMethod.GET, params = "complete") public String uploadComplete() { return "article/uploadComplete"; } // omitted }
項番 説明 Modelに格納するためのメソッド。上記例では、Modelに格納するための属性名は、"fileUploadForm"となる。MultipartFile#getOriginalFilenameメソッドを呼び出し、ファイル名の指定有無で判断する。上記例では、ファイルが選択されていない場合は、入力チェックエラーとしている。MultipartFile#isEmptyメソッドを呼び出し、中身の存在チェックを行う。上記例では、 空のファイルが選択されている場合は、入力チェックエラーとしている。MultipartFile#getSizeメソッドを呼び出し、サイズが許容範囲内かチェックを行う。上記例では、 ファイルのサイズが許容サイズを超えている場合は、入力チェックエラーとしている。Note
重複アップロードの防止
ファイルのアップロードを行う場合は、PRGパターンによる画面遷移と、トランザクショントークンチェックを行うことを推奨する。 PRGパターンによる画面遷移と、トランザクショントークンチェックを行うことで、重複送信に伴う、同一ファイルのアップロードを防ぐことができる。
重複送信の防止方法について、詳細は、二重送信防止を参照されたい。
Note
MultipartFileについて
MultipartFileには、アップロードされたファイルを操作するためのメソッドが用意されている。 各メソッドの利用方法については、MultipartFileクラスのJavaDocを参照されたい。
5.17.2.3. ファイルアップロードのBean Validation¶
Note
Bean Validationの仕組みでチェックすることで、Controllerの処理をシンプルに保つことができるため、Bean Validationの仕組みを使うことを推奨する。
5.17.2.3.1. ファイルが選択されていることを検証するためのバリデーションの実装¶
// (1) @Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE }) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = UploadFileRequiredValidator.class) public @interface UploadFileRequired { String message() default "{com.examples.upload.UploadFileRequired.message}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; @Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @interface List { UploadFileRequired[] value(); } }// (2) public class UploadFileRequiredValidator implements ConstraintValidator<UploadFileRequired, MultipartFile> { @Override public void initialize(UploadFileRequired constraint) { } @Override public boolean isValid(MultipartFile multipartFile, ConstraintValidatorContext context) { return multipartFile != null && StringUtils.hasLength(multipartFile.getOriginalFilename()); } }
項番 説明 
5.17.2.3.2. ファイルが空でないことを検証するためのバリデーションの実装¶
// (3) @Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE }) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = UploadFileNotEmptyValidator.class) public @interface UploadFileNotEmpty { String message() default "{com.examples.upload.UploadFileNotEmpty.message}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; @Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @interface List { UploadFileNotEmpty[] value(); } }// (4) public class UploadFileNotEmptyValidator implements ConstraintValidator<UploadFileNotEmpty, MultipartFile> { @Override public void initialize(UploadFileNotEmpty constraint) { } @Override public boolean isValid(MultipartFile multipartFile, ConstraintValidatorContext context) { if (multipartFile == null || !StringUtils.hasLength(multipartFile.getOriginalFilename())) { return true; } return !multipartFile.isEmpty(); } }
項番 説明 
5.17.2.3.3. ファイルのサイズが許容サイズ内であることを検証するためのバリデーションの実装¶
// (5) @Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE }) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = UploadFileMaxSizeValidator.class) public @interface UploadFileMaxSize { String message() default "{com.examples.upload.UploadFileMaxSize.message}"; long value() default (1024 * 1024); Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; @Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @interface List { UploadFileMaxSize[] value(); } }// (6) public class UploadFileMaxSizeValidator implements ConstraintValidator<UploadFileMaxSize, MultipartFile> { private UploadFileMaxSize constraint; @Override public void initialize(UploadFileMaxSize constraint) { this.constraint = constraint; } @Override public boolean isValid(MultipartFile multipartFile, ConstraintValidatorContext context) { if (constraint.value() < 0 || multipartFile == null) { return true; } return multipartFile.getSize() <= constraint.value(); } }
項番 説明 
5.17.2.3.4. フォームの実装¶
public class FileUploadForm implements Serializable { // omitted // (7) @UploadFileRequired @UploadFileNotEmpty @UploadFileMaxSize private MultipartFile file; @NotNull @Size(min = 0, max = 100) private String description; // omitted getter/setter methods. }
項番 説明 MultipartFileのフィールドに、アップロードファイルのバリデーションを行うための、アノテーションを付与する。
5.17.2.3.5. Controllerの実装¶
@RequestMapping(value = "uploadFile", method = RequestMethod.POST) public String uploadFile(@Validated FileUploadForm form, BindingResult result, RedirectAttributes redirectAttributes) { // (8) if (result.hasErrors()) { return "article/uploadForm"; } MultipartFile uploadFile = form.getFile(); // omit processing of upload. redirectAttributes.addFlashAttribute(ResultMessages.success().add( "i.xx.at.0001")); return "redirect:/article/upload"; }
項番 説明 BindingResultに格納される。
5.17.2.4. 複数ファイルのアップロード¶
複数ファイルを同時にアップロードする方法について説明する。
複数ファイルを同時にアップロードする場合は、org.springframework.web.multipart.MultipartFileオブジェクトを、フォームオブジェクトにバインドして受け取る必要がある。
以降の説明では、単一ファイルのアップロードと重複する箇所の説明については、省略する。
5.17.2.4.1. フォームの実装¶
// (1) public class FileUploadForm implements Serializable { // omitted @UploadFileRequired @UploadFileNotEmpty @UploadFileMaxSize private MultipartFile file; @NotNull @Size(min = 0, max = 100) private String description; // omitted getter/setter methods. }public class FilesUploadForm implements Serializable { // omitted @Valid // (2) private List<FileUploadForm> fileUploadForms; // (3) // omitted getter/setter methods. }
項番 説明 @Validアノテーションを付与する。Note
ファイルのみアップロードする場合は、
MultipartFileオブジェクトを、List型のプロパティとして定義することもできるが、 Bean Validationを使用してアップロードファイルの入力チェックを行う場合は、ファイル単位の情報を保持するオブジェクトを、List型のプロパティとして定義する方が相性がよい。
5.17.2.4.2. JSPの実装¶
<form:form action="${pageContext.request.contextPath}/article/uploadFiles" method="post" modelAttribute="filesUploadForm" enctype="multipart/form-data"> <table> <tr> <th width="35%">File to upload</th> <td width="65%"> <form:input type="file" path="fileUploadForms[0].file" /> <!-- (1) --> <form:errors path="fileUploadForms[0].file" /> </td> </tr> <tr> <th width="35%">Description</th> <td width="65%"> <form:input path="fileUploadForms[0].description" /> <form:errors path="fileUploadForms[0].description" /> </td> </tr> </table> <table> <tr> <th width="35%">File to upload</th> <td width="65%"> <form:input type="file" path="fileUploadForms[1].file" /> <!-- (1) --> <form:errors path="fileUploadForms[1].file" /> </td> </tr> <tr> <th width="35%">Description</th> <td width="65%"> <form:input path="fileUploadForms[1].description" /> <form:errors path="fileUploadForms[1].description" /> </td> </tr> </table> <div> <form:button>Upload</form:button> </div> </form:form>
項番 説明 []の中に指定する。開始位置は、0開始となる。
5.17.2.4.3. Controllerの実装¶
@RequestMapping(value = "uploadFiles", method = RequestMethod.POST) public String uploadFiles(@Validated FilesUploadForm form, BindingResult result, RedirectAttributes redirectAttributes) { if (result.hasErrors()) { return "article/uploadForm"; } // (1) for (FileUploadForm fileUploadForm : form.getFileUploadForms()) { MultipartFile uploadFile = fileUploadForm.getFile(); // omit processing of upload. } redirectAttributes.addFlashAttribute(ResultMessages.success().add( "i.xx.at.0001")); return "redirect:/article/upload?complete"; }
項番 説明 MultipartFileを取得し、アップロード処理を実装する。上記例では、具体的な実装は省略しているが、共有ディスクやデータベースへ保存する処理を行うことになる。
5.17.2.5. HTML5のmultiple属性を使った複数ファイルのアップロード¶
HTML5でサポートされたinputタグのmultiple属性を使用して、複数ファイルを同時にアップロードする方法について説明する。
以降の説明では、単一ファイルのアップロード及び複数ファイルのアップロードと重複する箇所の説明については、省略する。
5.17.2.5.1. フォームの実装¶
HTML5のinputタグのmultiple属性を使用して、複数ファイルを同時にアップロードする場合は、org.springframework.web.multipart.MultipartFileオブジェクトのコレクションを、フォームオブジェクトにバインドして受け取る必要がある。
// (1) public class FilesUploadForm implements Serializable { // omitted // (2) @UploadFileNotEmpty private List<MultipartFile> files; // omitted getter/setter methods. }
項番 説明 MultipartFileクラスをリストとして宣言する。上記例では、入力チェックとして、ファイルが空でないことを検証するためのアノテーションを指定している。本来は他の必須チェックやファイルのサイズチェックなども必要であるが、上記例では割愛している。
5.17.2.5.2. Validatorの実装¶
コレクションに格納されている複数の MultipartFile オブジェクトに対して入力チェックを行う場合は、コレクション用のValidatorを実装する必要がある。
以下では、単一ファイル用に作成したValidatorを利用してコレクション用のValidatorを作成する方法について説明する。
// (1) public class UploadFileNotEmptyForCollectionValidator implements ConstraintValidator<NotEmptyUploadFile, Collection<MultipartFile>> { // (2) private final UploadFileNotEmptyValidator validator = new UploadFileNotEmptyValidator(); // (3) @Override public void initialize(NotEmptyUploadFile constraintAnnotation) { validator.initialize(constraintAnnotation); } // (4) @Override public boolean isValid(Collection<MultipartFile> values, ConstraintValidatorContext context) { for (MultipartFile file : values) { if (!validator.isValid(file, context)) { return false; } } return true; } }
項番 説明 Collection<MultipartFile>を指定する。
項番 説明 @ConstraintアノテーションのvalidatedBy属性に、(1)で作成したクラスを指定する。こうすることで、@NotEmptyUploadFileアノテーションを付与したプロパティに対する妥当性チェックを行う際に、(1)で作成したクラスが実行される。
5.17.2.5.3. JSPの実装¶
<form:form action="${pageContext.request.contextPath}/article/uploadFiles" method="post" modelAttribute="filesUploadForm2" enctype="multipart/form-data"> <table> <tr> <th width="35%">File to upload</th> <td width="65%"> <form:input type="file" path="files" multiple="multiple" /> <!-- (1) --> <form:errors path="files" /> </td> </tr> </table> <div> <form:button>Upload</form:button> </div> </form:form>
項番 説明 
5.17.2.5.4. Controllerの実装¶
@RequestMapping(value = "uploadFiles", method = RequestMethod.POST) public String uploadFiles(@Validated FilesUploadForm form, BindingResult result, RedirectAttributes redirectAttributes) { if (result.hasErrors()) { return "article/uploadForm"; } // (1) for (MultipartFile file : form.getFiles()) { // omit processing of upload. } redirectAttributes.addFlashAttribute(ResultMessages.success().add( "i.xx.at.0001")); return "redirect:/article/upload?complete"; }
項番 説明 MultipartFileオブジェクトが格納されているリストを取得し、アップロード処理を実装する。上記例では、具体的な実装は省略しているが、共有ディスクやデータベースへ保存する処理を行うことになる。
5.17.2.6. 仮アップロード¶
アップロード結果の確認画面など、画面遷移の途中でファイルをアップロードする場合、仮アップロードという考え方が必要になる。
Note
MultipartFileオブジェクトで保持しているファイルの中身は、アップロードしたリクエストが完了した時点で消滅する可能性がある。 そのため、ファイルの中身をリクエストを跨いで扱いたい場合は、MultipartFileオブジェクトで保持しているファイルの中身や、メタ情報(ファイル名など)をファイルやフォームに退避する必要がある。
MultipartFileオブジェクトで保持しているファイルの中身は、下記処理フローの(3)が完了した時点で、消滅する。
項番 説明 Note
仮アップロードの処理は、アプリケーション層の役割なので、Controller又はHelperクラスで実装することになる。
5.17.2.6.1. Controllerの実装¶
以下に、アップロードされたファイルを仮ディレクトリに一時保存する実装例を示す。
@Component public class UploadHelper { // (2) @Value("${app.upload.temporaryDirectory}") private File uploadTemporaryDirectory; // (1) public String saveTemporaryFile(MultipartFile multipartFile) throws IOException { String uploadTemporaryFileId = UUID.randomUUID().toString(); File uploadTemporaryFile = new File(uploadTemporaryDirectory, uploadTemporaryFileId); // (2) FileUtils.copyInputStreamToFile(multipartFile.getInputStream(), uploadTemporaryFile); return uploadTemporaryFileId; } }
項番 説明 org.apache.commons.io.FileUtilsクラスの copyInputStreamToFileメソッドを呼び出し、アップロードしたファイルの中身をファイルに保存している。// omitted @Inject UploadHelper uploadHelper; @RequestMapping(value = "upload", method = RequestMethod.POST, params = "confirm") public String uploadConfirm(@Validated FileUploadForm form, BindingResult result) throws IOException { if (result.hasErrors()) { return "article/uploadForm"; } // (3) String uploadTemporaryFileId = uploadHelper.saveTemporaryFile(form .getFile()); // (4) form.setUploadTemporaryFileId(uploadTemporaryFileId); form.setFileName(form.getFile().getOriginalFilename()); return "article/uploadConfirm"; }
項番 説明 Note
仮ディレクトリのディレクトリは、アプリケーションをデプロイする環境によって異なる可能性があるため、外部プロパティから取得すること。 外部プロパティの詳細については、プロパティ管理を参照されたい。
Warning
上記例では、アプリケーションサーバ上のローカルディスクに一時保存する例としているが、アプリケーションサーバがクラスタ化されている場合は、 データベース又は共有ディスクに保存する必要がでてくるので、非機能要件も考慮して保存先を設計する必要がある。
データベースに保存する場合は、トランザクション管理が必要となるため、 データベースに保存す処理をServiceのメソッドに委譲することになる。
5.17.3. How to extend¶
5.17.3.1. 仮アップロード時の不要ファイルのHousekeeping¶
- 仮アップロード後の画面操作を中止した場合
- 仮アップロード後の画面操作中にシステムエラーが発生した場合
- 仮アップロード後の画面操作中にサーバが停止した場合
- etc …
Warning
不要なファイルを残したままにすると、ディスクを圧迫する可能性があるため、必ず不要なファイルを削除する仕組みを用意すること。
本ガイドラインでは、Spring Frameworkから提供されている「Task Scheduler」機能を使用して、不要なファイルを削除する方法について説明する。 「Task Scheduler」の詳細については、公式リファレンスの”Task Execution and Scheduling”を参照されたい。
Note
ガイドラインとしては、Spring Frameworkから提供されている「Task Scheduler」機能を使用する方法について説明するが、使用を強制するものではない。 実際のプロジェクトでは、インフラチームによって不要なファイルを削除するバッチアプリケーション(Shellアプリケーション)が提供されるケースがある。 その場合は、インフラチーム作成のバッチアプリケーションを使用して不要なファイルを削除することを推奨する。
5.17.3.1.1. 不要ファイルを削除するコンポーネントクラスの実装¶
不要なファイルを削除するコンポーネントクラスを実装する。
package com.examples.common.upload; import java.io.File; import java.util.Collection; import java.util.Date; import javax.inject.Inject; import org.apache.commons.io.FileUtils; import org.apache.commons.io.filefilter.FileFilterUtils; import org.apache.commons.io.filefilter.IOFileFilter; import org.springframework.beans.factory.annotation.Value; import org.terasoluna.gfw.common.date.DateFactory; // (1) public class UnnecessaryFilesCleaner { @Inject DateFactory dateFactory; @Value("${app.upload.temporaryFileSavedPeriodMinutes}") private int savedPeriodMinutes; @Value("${app.upload.temporaryDirectory}") private File targetDirectory; // (2) public void cleanup() { // calculate cutoff date. Date cutoffDate = dateFactory.newDateTime().minusMinutes( savedPeriodMinutes).toDate(); // collect target files. IOFileFilter fileFilter = FileFilterUtils.ageFileFilter(cutoffDate); Collection<File> targetFiles = FileUtils.listFiles(targetDirectory, fileFilter, null); if (targetFiles.isEmpty()) { return; } // delete files. for (File targetFile : targetFiles) { FileUtils.deleteQuietly(targetFile); } } }
項番 説明 Note
削除対象ファイルが格納されているディレクトリのパスや、削除基準となる時間などは、アプリケーションをデプロイする環境によって異なる可能性があるため、外部プロパティから取得すること。 外部プロパティの詳細については、プロパティ管理を参照されたい。
5.17.3.1.2. 不要ファイルを削除する処理のスケジューリング設定¶
不要ファイルを削除するPOJOクラスを、bean登録とタスクスケジュールの設定を行う。
- applicationContext.xml
<!-- omitted --> <!-- (3) --> <bean id="uploadTemporaryFileCleaner" class="com.examples.common.upload.UnnecessaryFilesCleaner" /> <!-- (4) --> <task:scheduler id="fileCleanupTaskScheduler" /> <!-- (5) --> <task:scheduled-tasks scheduler="fileCleanupTaskScheduler"> <!-- (6)(7)(8) --> <task:scheduled ref="uploadTemporaryFileCleaner" method="cleanup" cron="${app.upload.temporaryFilesCleaner.cron}"/> </task:scheduled-tasks> <!-- omitted -->
項番 説明 "uploadTemporaryFileCleaner"というIDで登録している。Note
cron属性の設定値は、「秒 分 時 月 年 曜日」の形式で指定する。
設定例)
0 */15 * * * *: 毎時 0分,15分,30分,45分に実行される。
0 0 * * * *: 毎時 0分に実行される。
0 0 9-17 * * MON-FRI: 平日9時~17時の間の毎時0分に実行される。cronの指定値の詳細については、CronSequenceGeneratorのJavaDocを参照されたい。
実行タイミングは、アプリケーションをデプロイする環境によって異なる可能性があるため、外部プロパティから取得すること。 外部プロパティの詳細については、プロパティ管理を参照されたい。
Tip
上記例では、タスクの実行トリガーとしてcronを使用しているが、cron以外に、fixed-delayとfixed-rateが、デフォルトで用意されているので、要件に応じて使い分けること。
デフォルトで用意されているトリガーでは要件を満たせない場合は、trigger属性に
org.springframework.scheduling.Triggerを実装したbeanを指定することで、独自のトリガーを設けることもできる。
5.17.4. Appendix¶
5.17.4.1. ファイルアップロードに関するセキュリティ問題への考慮¶
以下に、対策方針について説明する。
5.17.4.1.1. アップロード機能に対するDos攻撃¶
アップロード機能に対するDos攻撃とは、巨大なサイズのファイルを連続してアップロードしてサーバに対して負荷を掛けることで、 サーバのダウンや、レスポンス速度の低下を狙った攻撃方法のことである。
<multipart-config>要素を用いて、リクエストのサイズ制限を設ける必要がある。5.17.4.1.2. アップロードしたファイルをWebサーバ上で実行する攻撃¶
この攻撃への対策方法は、以下の通りである。
- アップロードされたファイルを、Webサーバ(アプリケーションサーバ)上の公開ディレクトリに配置せず、ファイルの中身を表示するための処理を経由して、アップロードしたファイルの中身を閲覧させる。
- アップロード可能なファイルの拡張子を制限し、Webサーバ(アプリケーションサーバ)で実行可能なスクリプトファイルが、アップロードされないようにする。
いずれかの対策を行うことで攻撃を防ぐことができるが、両方とも対策しておくことを推奨する。
5.17.4.2. Commons FileUpload を使用したファイルのアップロード¶
一部のアプリケーションサーバー上でServlet 3.0のファイルアップロード機能を使用すると、 リクエストパラメータやファイル名のマルチバイト文字が文字化けすることがある。
具体例としては、WebLogic(検証バージョンは12.1.3)でServlet 3.0のファイルアップロード機能を使用すると、 ファイルと一緒に送信するフィールドのマルチバイト文字が文字化けすることが確認されている。 アプリケーションサーバーの問題であると思われるが、アプリケーションサーバー側で修正されない限り、 ファイルとマルチバイト文字を同時に送信する事ができない。
この問題は、Commons FileUploadを使用することで回避できるため、 本ガイドラインでは、アプリケーションサーバーが修正されるまでの暫定対処として、 Commons FileUploadを使用したファイルのアップロードについて説明する。
Commons FileUploadを使用する場合は以下の設定を行う。
xxx-web/pom.xml
<!-- (1) -->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.2</version>
</dependency>
| 項番 | 説明 | 
|---|---|
| (1) | commons-fileuploadへの依存関係を追加する。 | 
Warning
Apache Commons FileUploadを使用する場合、 CVE-2014-0050およびCVE-2016-3092で報告されているセキュリティの脆弱性が発生する可能性がある。 使用するApache Commons FileUploadのバージョンに脆弱性がない事を確認されたい。
Apache Commons FileUploadを使用する場合、1.3.2以上を使用する必要がある。
xxx-web/src/main/resources/META-INF/spring/applicationContext.xml
<!-- (1) -->
<bean id="filterMultipartResolver"
    class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <property name="maxUploadSize" value="10240000" /><!-- (2) -->
</bean>
<!-- ... -->
| 項番 | 説明 | 
|---|---|
| (1) | Commons FileUploadを使用した MultipartResolver実装であるCommonsMultipartResolverのbean定義を行う。bean IDには "filterMultipartResolver"を指定する。 | 
| (2) | ファイルアップロードで許容する最大サイズを設定する。 Commons FileUploadに場合、最大値はヘッダ含めたリクエスト全体のサイズであることに注意すること。 また、デフォルト値は-1(無制限)なので、必ず値を設定すること。 その他のプロパティはJavaDocを参照されたい。 | 
Warning
Commons Fileuploadを使用する場合は、MultipartResolverの定義をspring-mvc.xmlではなく、applicationContext.xmlに行う必要がある。
spring-mvc.xmlに定義がある場合は削除すること。
xxx-web/src/main/webapp/WEB-INF/web.xml
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">
    <servlet>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- omitted -->
        <!-- (1) -->
        <!-- <multipart-config>...</multipart-config> -->
    </servlet>
    <!-- (2) -->
    <filter>
        <filter-name>MultipartFilter</filter-name>
        <filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>MultipartFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!-- omitted -->
</web-app>
| 項番 | 説明 | 
|---|---|
| (1) | Commons FileUploadを使用する場合、Servlet 3.0のアップロード機能を無効にする必要がある。 DispatcherServletの定義の中に<multipart-config>要素がある場合は、必ず削除すること。 | 
| (2) | Commons Fileuploadを使用する場合、CSRF対策を有効にするために MultipartFilterを定義する必要がある。MultipartFilterのマッピング定義は、springSecurityFilterChain(Spring SecurityのServlet Filter)の定義より前に行うこと。 | 
Tip
MultipartFilterは、DIコンテナ(applicationContext.xml)から"filterMultipartResolver"というbean IDで登録されているMultipartResolverを取得して、
ファイルアップロード処理を行う仕組みになっている。




