web-dev-qa-db-ja.com

Spring SecurityでファイルアップロードMaxUploadSizeExceededExceptionをうまく処理する方法

Spring Web 4.0.5、Spring Security 3.2.4、Commons FileUpload 1.3.1、Tomcat 7を使用していますが、アップロードサイズの制限を超えたときに醜いMaxUploadSizeExceededExceptionが発生し、「 500内部サーバーエラー"。私はそれをNiceジェネリックポップアップで処理しますが、適切な説明メッセージとともに元のフォームに戻って、コントローラーに処理させたいと思います。

同様の質問が何度も尋ねられるのを見たことがありますが、Spring Securityを使用していないときに機能するいくつかのソリューションがあります。私が試したものはどれも私のために機能しませんでした。

問題は、Spring Securityを使用する場合、CommonsMultipartResolverが「multipartResolver」Beanとしてではなく、「filterMultipartResolver」として追加されることである可能性があります。

_@Bean(name="filterMultipartResolver")
CommonsMultipartResolver filterMultipartResolver() {
    CommonsMultipartResolver filterMultipartResolver = new CommonsMultipartResolver();
    filterMultipartResolver.setMaxUploadSize(MAXSIZE);
    return filterMultipartResolver;
}
_

filterMultipartResolver.setResolveLazily(true);を設定しても、違いはありません。

CommonsMultipartResolverを自分のクラスでサブクラス化し、parseRequest()メソッドをMaxUploadSizeExceededExceptionをトラップして空のMultipartParsingResultを返すものでオーバーライドすると、 " 403 Forbidden」エラー:

_public class ExtendedCommonsMultipartResolver extends CommonsMultipartResolver {
    protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException {
        String encoding = determineEncoding(request);
        try {
            return super.parseRequest(request);
        } catch (MaxUploadSizeExceededException e) {
            return parseFileItems(Collections.<FileItem> emptyList(), encoding);
        }
    }
}
_

最後に、なんらかのローカルまたはグローバルExceptionHandlerは決して呼び出されないため、実装しても意味がありません。

より良い解決策が見つからない場合は、アップロードサイズの制限を削除してコントローラーで自分で処理しますが、ファイルサイズに関するエラーメッセージが表示される前に、ユーザーにアップロードが完了するまで待機させるという欠点があります。この場合はイメージなので、適切な値にサイズを変更できるため、これらすべてを無視することもできます。

それでも、この問題の解決策を見たいのですが。

ありがとうございました

編集:

要求に応じてスタックトレースを追加します。これは、500が生成される場合です。

_May 30, 2014 12:47:17 PM org.Apache.catalina.core.StandardWrapperValve invoke
SEVERE: Servlet.service() for servlet [dispatcher] in context with path [/site] threw exception
org.springframework.web.multipart.MaxUploadSizeExceededException: Maximum upload size of 1000000 bytes exceeded; nested exception is org.Apache.commons.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (3403852) exceeds the configured maximum (1000000)
    at org.springframework.web.multipart.commons.CommonsMultipartResolver.parseRequest(CommonsMultipartResolver.Java:162)
    at org.springframework.web.multipart.commons.CommonsMultipartResolver.resolveMultipart(CommonsMultipartResolver.Java:142)
    at org.springframework.web.multipart.support.MultipartFilter.doFilterInternal(MultipartFilter.Java:110)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.Java:107)
    at org.Apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.Java:243)
    at org.Apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.Java:210)
    at org.Apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.Java:222)
    at org.Apache.catalina.core.StandardContextValve.invoke(StandardContextValve.Java:123)
    at org.Apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.Java:502)
    at org.Apache.catalina.core.StandardHostValve.invoke(StandardHostValve.Java:171)
    at org.Apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.Java:100)
    at org.Apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.Java:953)
    at org.Apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.Java:118)
    at org.Apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.Java:409)
    at org.Apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.Java:1044)
    at org.Apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.Java:607)
    at org.Apache.Tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.Java:315)
    at Java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.Java:1110)
    at Java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.Java:603)
    at Java.lang.Thread.run(Thread.Java:722)
Caused by: org.Apache.commons.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (3403852) exceeds the configured maximum (1000000)
    at org.Apache.commons.fileupload.FileUploadBase$FileItemIteratorImpl.<init>(FileUploadBase.Java:965)
    at org.Apache.commons.fileupload.FileUploadBase.getItemIterator(FileUploadBase.Java:310)
    at org.Apache.commons.fileupload.FileUploadBase.parseRequest(FileUploadBase.Java:334)
    at org.Apache.commons.fileupload.servlet.ServletFileUpload.parseRequest(ServletFileUpload.Java:115)
    at org.springframework.web.multipart.commons.CommonsMultipartResolver.parseRequest(CommonsMultipartResolver.Java:158)
    ... 19 more
_
18
xtian

MaxUploadSizeExceededExceptionを処理するには、追加のフィルターを追加して例外をキャッチし、エラーページにリダイレクトします。たとえば、次のようなMultipartExceptionHandler Filterを作成できます。

public class MultipartExceptionHandler extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request,
            HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        try {
            filterChain.doFilter(request, response);
        } catch (MaxUploadSizeExceededException e) {
            handle(request, response, e);
        } catch (ServletException e) {
            if(e.getRootCause() instanceof MaxUploadSizeExceededException) {
                handle(request, response, (MaxUploadSizeExceededException) e.getRootCause());
            } else {
                throw e;
            }
        }
    }

    private void handle(HttpServletRequest request,
            HttpServletResponse response, MaxUploadSizeExceededException e) throws ServletException, IOException {

        String redirect = UrlUtils.buildFullRequestUrl(request) + "?error";
        response.sendRedirect(redirect);
    }

}

[〜#〜]注[〜#〜]:このリダイレクトは、フォームとアップロードについての仮定を行います。リダイレクト先を変更する必要がある場合があります。具体的には、GETにあるフォームのパターンに従い、それがPOSTで処理される場合、これは機能します。

次に、このフィルターをMultipartFilterの前に追加するようにします。たとえば、web.xmlを使用している場合は、次のようになります。

<filter>
    <filter-name>meh</filter-name>
    <filter-class>org.example.web.MultipartExceptionHandler</filter-class>
</filter>
<filter>
    <description>
        Allows the application to accept multipart file data.
    </description>
    <display-name>springMultipartFilter</display-name>
    <filter-name>springMultipartFilter</filter-name>
    <filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
    <!--init-param>
        <param-name>multipartResolverBeanName</param-name>
        <param-value>multipartResolver</param-value>
    </init-param-->
</filter>
<filter>
    <description>
        Secures access to web resources using the Spring Security framework.
    </description>
    <display-name>springSecurityFilterChain</display-name>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>

<filter-mapping>
    <filter-name>meh</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>springMultipartFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>ERROR</dispatcher>
    <dispatcher>REQUEST</dispatcher>
</filter-mapping>

フォームでは、HTTPパラメータエラーが存在するかどうかを検査することで、エラーが発生したかどうかを検出できます。たとえば、JSPでは次のようにします。

<c:if test="${param.error != null}">
    <p>Failed to upload...too big</p>
</c:if>

PS: SEC-2614 を作成して、エラー処理について説明するためにドキュメントを更新しました

10
Rob Winch

私はパーティーに遅れるのはわかっていますが、私はずっとエレガントな解決法を見つけました。

マルチパートリゾルバー用のフィルターを追加する代わりに、コントローラーメソッドにthrows MaxUploadSizeExceededExceptionを追加し、web.xmlDelegatingFilterProxyのフィルターを追加するだけで、例外ハンドラーをリクエストをリダイレクトする必要のないコントローラ。

例えば。:

メソッド(コントローラー内):

@RequestMapping(value = "/uploadFile", method = RequestMethod.POST)
public ResponseEntity<String> uploadFile(MultipartHttpServletRequest request) throws MaxUploadSizeExceededException {
    //code
}

例外ハンドラー(同じコントローラー内):

@ExceptionHandler(MaxUploadSizeExceededException.class)
public ResponseEntity handleSizeExceededException(HttpServletRequest request, Exception ex) {
    //code
}

Web.xml(Rob Winchに感謝):

<filter>
    <description>
        Secures access to web resources using the Spring Security framework.
    </description>
    <display-name>springSecurityFilterChain</display-name>
    <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>
    <dispatcher>ERROR</dispatcher>
    <dispatcher>REQUEST</dispatcher>
</filter-mapping>

そして、それだけで十分です。

3
Taugenichts

事はspringSecurityFilterChainを追加する必要がある後にマルチパートフィルター。そのため、403ステータスを取得しています。ここに:

http://docs.spring.io/spring-security/site/docs/3.2.0.CI-SNAPSHOT/reference/html/csrf.html#csrf-multipartfilter

そうすると、FileUploadBase.SizeLimitExceededExceptionアノテーション付きメソッドを含む@ControllerAdviceアノテーション付きクラスで@ExceptionHandlerをキャッチできるようになると思います。

1
px5x2

実験中に思いついた解決策は次のとおりです。

  1. 例外を飲み込むためにCommonsMultipartResolverを拡張します。コントローラで使用したい場合に備えて、リクエストに例外を追加しましたが、必要ではないと思います

    package org.springframework.web.multipart.commons;
    
    import Java.util.Collections;
    
    import javax.servlet.http.HttpServletRequest;
    
    import org.Apache.commons.fileupload.FileItem;
    import org.springframework.web.multipart.MaxUploadSizeExceededException;
    import org.springframework.web.multipart.MultipartException;
    
    public class ExtendedCommonsMultipartResolver extends CommonsMultipartResolver {
        @Override
        protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException {
            try {
                return super.parseRequest(request);
            } catch (MaxUploadSizeExceededException e) {
                request.setAttribute("MaxUploadSizeExceededException", e);
                return parseFileItems(Collections.<FileItem> emptyList(), null);
            }
        }
    }
    
  2. CommonsMultipartResolverの代わりに、WebSecurityConfigurerAdapterでリゾルバーを宣言します(どのような場合でもfilterMultipartResolverを宣言する必要があるため、ここでは何も新しいものはありません)。

    @Bean(name="filterMultipartResolver")
    CommonsMultipartResolver filterMultipartResolver() {
        CommonsMultipartResolver filterMultipartResolver = new ExtendedCommonsMultipartResolver();
        filterMultipartResolver.setMaxUploadSize(MAXBYTES);
        return filterMultipartResolver;
    }
    
  3. ドキュメントに記載されているように、AbstractSecurityWebApplicationInitializerで正しいフィルタの優先順位を定義することを忘れないでください(どの場合でもこれを行います)。

    @Order(1)
    public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
        @Override
        protected void beforeSpringSecurityFilterChain(ServletContext servletContext) {
            insertFilters(servletContext, new MultipartFilter());
        }
    }
    
  4. _csrfトークンをフォームアクションURLに追加します(ここではthymeleafを使用しています)

    <form th:action="@{|/submitImage?${_csrf.parameterName}=${_csrf.token}|}" 
    
  5. コントローラーで、MultipartFileのnullを確認します。(スニペットはエラーを確認していません)のようなものです。

    @RequestMapping(value = "/submitImage", method = RequestMethod.POST)
    public String submitImage(MyFormBean myFormBean, BindingResult bindingResult, HttpServletRequest request, Model model) {
        MultipartFile multipartFile = myFormBean.getImage();
        if (multipartFile==null) {
            bindingResult.rejectValue("image", "validation.image.filesize");
        } else if (multipartFile.isEmpty()) {
            bindingResult.rejectValue("image", "validation.image.missing");
    

これにより、サイズを超えた場合でも、通常のControllerメソッドを使用してフォーム送信を処理できます。

このアプローチについて私が気に入らないのは、外部ライブラリパッケージ(MultipartParsingResultは保護されています)をいじる必要があり、フォームのURLにトークンを設定することも覚えておく必要があることです(これも安全性が低いです)。

私が好きなのは、フォームの送信をコントローラーの1か所だけで処理することです。

大きなファイルがユーザーに戻る前に完全にダウンロードされるという問題も解決されませんが、すでに別の場所で対処されていると思います。

0
xtian