CSRF保护阻止我上传文件

时间:2015-09-30 14:49:06

标签: java spring-security spring-boot csrf

我使用Spring Boot和Spring安全性创建了一个简单的应用程序,其中包含:

  • 登录表单
  • “上传”表单(在后端侧有关联的控制器)

问题:Spring安全性具有内置的默认CSRF保护。它适用于常见的REST调用,但它阻止我上传文件:我收到此错误消息:

  

在请求参数'_csrf'或标题'X-XSRF-TOKEN'上找到无效的CSRF令牌'null'。

如果我停用CSRF保护,我可以成功上传文件。

我创建了一个SSCCE来说明问题。重现的步骤是:

  1. 启动应用程序(主类为com.denodev.Application
  2. 连接到localhost:8080
  3. 使用这些凭据进行身份验证:
    • 登录:user
    • 密码:password
  4. 重定向到“上传”表单时,请尝试上传任何文件。
  5. 在课程Application中,您可以随意激活/停用CSRF保护,重启应用并重试。
  6. 代码的相关部分是:

    @RestController
    @SpringBootApplication
    public class Application {
    
      public static void main(String[] args) {
        SpringApplication.run(Application.class);
      }
    
      @RequestMapping(value = "/upload-file", method = RequestMethod.POST)
      @ResponseBody
      public String uploadFile(@RequestParam("file") MultipartFile file) {
        return "Successfully received file "+file.getOriginalFilename();
      }
    
      @Configuration
      @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
      protected static class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
          http
              .authorizeRequests()
              .antMatchers("/", "/**/*.html", "login").permitAll()
              .anyRequest().authenticated()
              .and()
                .formLogin()
                .successHandler(successHandler())
                .failureHandler(failureHandler())
              .and()
                .exceptionHandling()
                .accessDeniedHandler(accessDeniedHandler())
                .authenticationEntryPoint(authenticationEntryPoint())
              .and()
    
              //1 : Uncomment to activate csrf protection
              .csrf()
              .csrfTokenRepository(csrfTokenRepository())
              .and()
              .addFilterAfter(csrfHeaderFilter(), CsrfFilter.class)
    
              //2 : Uncomment to disable csrf protection
              //.csrf().disable()
          ;
        }
    
        /**
         * Return HTTP 200 on authentication success instead of redirecting to a page.
         */
        private AuthenticationSuccessHandler successHandler() {
          return new AuthenticationSuccessHandler() {
            @Override
            public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
              httpServletResponse.setStatus(HttpServletResponse.SC_OK);
            }
          };
        }
    
        /**
         * Return HTTP 401 on authentication failure instead of redirecting to a page.
         */
        private AuthenticationFailureHandler failureHandler() {
          return new AuthenticationFailureHandler() {
            @Override
            public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
              httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
              httpServletResponse.getWriter().write(e.getMessage());
            }
          };
        }
    
        /**
         * Return HTTP 403 on "access denied" instead of redirecting to a page.
         */
        private AccessDeniedHandler accessDeniedHandler() {
          return new AccessDeniedHandler() {
            @Override
            public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
              httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
              httpServletResponse.getWriter().write(e.getMessage());
            }
          };
        }
    
        private AuthenticationEntryPoint authenticationEntryPoint() {
          return new AuthenticationEntryPoint() {
            @Override
            public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
              httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
              httpServletResponse.getWriter().write(e.getMessage());
            }
          };
        }
    

    我尝试了什么:

    关于在Spring安全性之前放置MultipartFilter的Multipart建议的Spring security's documentation。它通过编辑web.xml文件很好地解释了如何使用普通的旧webapp。这不适用于Spring Boot,我无法确定等效语法。

    我尝试使用注释MultipartFilter@Bean公开Order几个选项,但我仍然在努力解决这个问题。

    有什么想法吗?

1 个答案:

答案 0 :(得分:2)

这对我有用:

添加指令以在客户端上传文件:

app.directive('fileModel', function ($parse) {

        return {

            restrict: 'A',

            link: function(scope, element, attrs) {

                var model = $parse(attrs.fileModel);
                var modelSetter = model.assign;

                element.bind('change', function(){

                    scope.$apply(function(){
                        modelSetter(scope, element[0].files[0]);
                    });

                });

            }
    };
})

上传文件:

<input type="file" file-model="fileToUpload"/>

这是我将文件上传到服务器的方式:

var formData = new FormData();

formData.append("file", fileToUpload);

$http({

        method: 'POST',
        url: 'the URL',
        headers: {'Content-Type': undefined},
        data: formData,
        transformRequest: angular.identity

})

.success(function(data, status){

})