【问题标题】:CSRF protection prevents me from uploading a fileCSRF 保护阻止我上传文件
【发布时间】:2015-10-01 00:01:40
【问题描述】:

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

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

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

在请求参数“_csrf”或标头“X-XSRF-TOKEN”上发现无效的 CSRF 令牌“null”。

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

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

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

代码的相关部分是:

@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());
        }
      };
    }

我尝试了什么:

关于 Multipart 的 Spring security's documentation 建议将 MultipartFilter 放在 Spring 安全性之前。它很好地解释了如何通过编辑 web.xml 文件来使用普通的旧 web 应用程序来做到这一点。这不适用于 Spring Boot,我无法弄清楚等效语法是什么。

我尝试使用注释@BeanOrder 公开MultipartFilter,但我仍然在努力解决它。

有什么想法吗?

【问题讨论】:

  • 您是否在 AngularJs HTTP 请求中发送上传的文件?
  • @BillBilal 在这个 SSCCE 的上下文中,不,但我尝试了同样的 Angular/AJAX 调用并得到了同样的问题。
  • 就我而言,它有效。我添加了一个指令来在客户端上传文件,然后我在 AngularJs POST 请求中发送它。 AngularJs 将 X-XSRF-TOKEN 令牌添加到每个 HTTP 请求中
  • 你的例子行不通。您不会像以前那样将 X-XSRF-TOKEN 发送到服务器。这就是 CSRF 保护应该如何保护您的方式。
  • 另一种解决方案是在上传文件的表单中添加CSRF令牌(_csrf是隐藏的),但我不知道你是否需要做一些额外的配置。

标签: java spring-security spring-boot csrf


【解决方案1】:

这对我有用:

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

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){

})

【讨论】:

  • 谢谢。我进行了测试,感谢'Content-Type': undefined,它似乎可以正常工作。这不是一个坏习惯吗?
猜你喜欢
  • 2013-12-19
  • 1970-01-01
  • 2018-07-12
  • 2013-04-15
  • 2013-02-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多