【问题标题】:Uploading File Returns 403 Error - Spring MVC上传文件返回 403 错误 - Spring MVC
【发布时间】:2015-11-10 21:26:48
【问题描述】:

在我的 Spring MVC 项目中,我试图通过一个简单的表单上传文件。

HTML 表单:

<form method="POST" enctype="multipart/form-data" action="/upload">
    <label>Select File</label> 
    <input type="file" name="file"/>
</form>

我的控制器:

@Controller
public class FileController {
    @RequestMapping(value="/upload", method=RequestMethod.POST)
        public @ResponseBody String handleFileUpload(
                @RequestParam("name") String name,
                @RequestParam("file") MultipartFile file){
            if (!file.isEmpty()) {
                try {
                    //do stuff
                } catch (Exception e) {
                    return "You failed to upload " + name + " => " + e.getMessage();
                }
            } else {
                return "You failed to upload " + name + " because the file was empty.";
            }
        }
}

安全配置:

@Configuration
@EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/upload").permitAll()
            .and()
               .exceptionHandling().accessDeniedPage("/403")
    }
}

但是我得到一个403: Forbidden 错误并且每次都被重定向到我的 403.html 视图

到目前为止,我已经尝试在 Spring Security 过滤器在单独的类中初始化之前指定 MultipartFilter,但没有成功

public class SecurityApplicationInitializer extends AbstractSecurityWebApplicationInitializer {

    @Override
    protected void beforeSpringSecurityFilterChain(ServletContext servletContext) {
        insertFilters(servletContext, new MultipartFilter());
    }
}

有什么想法吗?

更新:包括我的 WebAppInitializer

@Configuration
@Import({ WebSecurityConfig.class })
public class WebAppInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        System.out.println(":::Starting My App:::");
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.register(WebMVCConfig.class);
        context.setServletContext(servletContext);
        context.setConfigLocation("com.myApp.configuration");
    }

}

我有一个 servlet 请求属性列表,它返回以下内容并出现 403 错误:

javax.servlet.forward.request_uri
javax.servlet.forward.context_path
javax.servlet.forward.servlet_path
__spring_security_scpf_applied
org.springframework.web.servlet.DispatcherServlet.THEME_SOURCE
SPRING_SECURITY_403_EXCEPTION
org.springframework.web.servlet.DispatcherServlet.THEME_RESOLVER
springMacroRequestContext
themes
thymeleafEvaluationContext
org.springframework.security.web.FilterChainProxy.APPLIED
_csrf
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.FILTERED
org.springframework.security.web.csrf.CsrfFilter@539743f9.FILTERED
beans
springRequestContext
org.springframework.web.servlet.HandlerMapping.introspectTypeLevelMapping
org.springframework.web.servlet.DispatcherServlet.FLASH_MAP_MANAGER
org.springframework.web.servlet.DispatcherServlet.CONTEXT
org.springframework.core.convert.ConversionService
execInfo
org.springframework.web.servlet.HandlerMapping.pathWithinHandlerMapping
org.springframework.web.context.request.async.WebAsyncManager.WEB_ASYNC_MANAGER
org.springframework.web.servlet.resource.ResourceUrlProvider
org.springframework.web.servlet.DispatcherServlet.OUTPUT_FLASH_MAP
org.springframework.web.servlet.HandlerMapping.bestMatchingPattern
org.springframework.security.web.csrf.CsrfToken
org.springframework.web.servlet.DispatcherServlet.LOCALE_RESOLVER

更新 #2:这肯定是 CSRF 问题;当我在WebSecurityConfig 中包含以下内容时,我没有得到403

.csrf().disable()

【问题讨论】:

  • 你是如何配置你的 web.xml 或者你使用 WebApplicationInitializer 来实现 DispatcherServlet 的?你可以发布那个代码吗?还要提到你用来加载 html 表单的 url。
  • @Raghu 我已经包含了我的 WebAppInitializer - 我的表单的 url 位于 /WEB-INF/html 文件夹中,我所有的 Thymeleaf 视图都在该文件夹中得到解决。 holmis83 我相信是的

标签: java spring spring-mvc spring-security


【解决方案1】:

这在 Spring Security 参考的CSRF - Multipart (File Upload) 部分中有介绍。你有两个选择:

在 Spring Security 之前放置 MultipartFilter

第一个选项是确保在 Spring Security 过滤器之前指定 MultipartFilter。在 Spring Security 过滤器之前指定 MultipartFilter 意味着没有调用 MultipartFilter 的授权,这意味着任何人都可以在您的服务器上放置临时文件。但是,只有授权用户才能提交由您的应用程序处理的文件。一般来说,这是推荐的方法,因为临时文件上传对大多数服务器的影响应该可以忽略不计。

为确保在java配置的Spring Security过滤器之前指定MultipartFilter,用户可以覆盖beforeSpringSecurityFilterChain,如下所示:

public class SecurityApplicationInitializer extends AbstractSecurityWebApplicationInitializer {

    @Override
    protected void beforeSpringSecurityFilterChain(ServletContext servletContext) {
        insertFilters(servletContext, new MultipartFilter());
    }
}

为确保MultipartFilter在带有XML配置的Spring Security过滤器之前被指定,用户可以确保MultipartFilter的元素被放置在web.xml中的springSecurityFilterChain之前,如下所示:

<filter>
    <filter-name>MultipartFilter</filter-name>
    <filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
</filter>
<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>MultipartFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

在行动中包含 CSRF 令牌

如果不允许未经授权的用户上传临时文件,另一种方法是将 MultipartFilter 放在 Spring Security 过滤器之后,并将 CSRF 作为查询参数包含在表单的 action 属性中。下面是一个带有jsp的例子

<form action="./upload?${_csrf.parameterName}=${_csrf.token}" 
      method="post" 
      enctype="multipart/form-data">

这种方法的缺点是查询参数可能会泄露。更一般地说,将敏感数据放在正文或标头中被认为是最佳实践,以确保它不会泄露。更多信息请参见RFC 2616 Section 15.1.3 Encoding Sensitive Information in URI’s

【讨论】:

  • 感谢 Rob,我已阅读 Spring 文档并尝试了这些方法中的每一种,但均无济于事。我会再试一次并回复你
【解决方案2】:

对我来说快速的解决方案如下

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
<head>
<title>Upload File Request Page</title>
</head>
<body>
    <form method="POST" action="file/uploadFile?${_csrf.parameterName}=${_csrf.token}" enctype="multipart/form-data">
        File to upload: <input type="file" name="file"><br /> 
        Name: <input type="text" name="name"><br /> <br /> 
        <input type="submit" value="Upload"> Press here to upload the file!
    </form> 
</body>
</html>

控制器代码如下:

package com.student.controller;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.security.Principal;
import javax.servlet.http.HttpServletRequest;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

@Controller
@RequestMapping("/file")
public class FileUploadController {

    @RequestMapping(value = "", method = RequestMethod.GET)
    public String index(ModelMap modelMap,Principal principal,HttpServletRequest request) {
        return "uploadfile";
    }

    @RequestMapping(value = "/uploadFile", method = RequestMethod.POST)
    public @ResponseBody String uploadFileHandler(@RequestParam("name") String name, @RequestParam("file") MultipartFile file) {

        if (!file.isEmpty()) {
            try {
                byte[] bytes = file.getBytes();

                // Creating the directory to store file
                String rootPath = System.getProperty("catalina.home");
                File dir = new File(rootPath + File.separator + "tmpFiles");
                if (!dir.exists())
                    dir.mkdirs();

                // Create the file on server
                File serverFile = new File(dir.getAbsolutePath()
                        + File.separator + name);
                BufferedOutputStream stream = new BufferedOutputStream(
                        new FileOutputStream(serverFile));
                stream.write(bytes);
                stream.close();



                return "You successfully uploaded file=" + rootPath+name;
            } catch (Exception e) {
                return "You failed to upload " + name + " => " + e.getMessage();
            }
        } else {
            return "You failed to upload " + name
                    + " because the file was empty.";
        }
    }

}

我在 spring dispatcher 文件中添加了以下代码

<!-- upload files -->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">

         <!-- setting maximum upload size -->
        <property name="maxUploadSize" value="100000" />

    </bean>

【讨论】:

    【解决方案3】:

    对我来说,即使使用 csrf().disable() 它也不起作用。 一旦我禁用它,我得到了 200,但文件没有上传,我没有看到任何错误。 一旦我设置了调试标志 logging.level.org.springframework.web: DEBUG 我看到了根本原因:

    [org.springframework.web.multipart.MultipartException:解析多部分servlet请求失败;嵌套异常为 java.io.IOException: 临时上传位置 [/target/tomcat/work/Tomcat/localhost/ROOT] 无效]","exception":""}

    我尝试在 application.yaml 上设置位置: spring.servlet.http.multipart.location:“/tmp” 但它并没有改变位置,所以我所做的是添加下面的代码,它成功了:

    @豆

    MultipartConfigElement multipartConfigElement() {
        MultipartConfigFactory factory = new MultipartConfigFactory();
        String location = "/tmp";
        LOGGER.debug("Multipart location file:" + location);
        File tmpFile = new File(location);
        if (!tmpFile.exists()) {
            tmpFile.mkdirs();
        }
        factory.setLocation(location);
        return factory.createMultipartConfig();
    }
    

    【讨论】:

      【解决方案4】:

      我的解决方案是在我的WebSecurityConfig 中禁用csrf(),例如:

       @EnableWebSecurity
       @Configuration
      public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
      
          @Override
          protected void configure(HttpSecurity http) throws Exception {
              http.cors().and().csrf().disable();
          }
      }
      

      现在我可以简单地上传多部分文件:)

      【讨论】:

        猜你喜欢
        • 2019-02-28
        • 2016-06-10
        • 2016-11-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多