【发布时间】:2015-10-01 00:01:40
【问题描述】:
我使用 Spring Boot 和 Spring security 创建了一个简单的应用程序,其中包含:
- 登录表单
- “上传”表单(在后端带有关联的控制器)
问题:Spring security 内置了默认的 CSRF 保护。它适用于常见的 REST 调用,但它阻止我上传文件:我收到此错误消息:
在请求参数“_csrf”或标头“X-XSRF-TOKEN”上发现无效的 CSRF 令牌“null”。
如果我停用 CSRF 保护,我可以成功上传文件。
我创建了一个SSCCE 来说明问题。重现的步骤是:
- 启动应用程序(主类是
com.denodev.Application) - 连接到
localhost:8080 - 使用这些凭据进行身份验证:
- 登录:
user - 密码:
password
- 登录:
- 当重定向到“上传”表单时,尝试上传任何文件。
- 在课堂
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,我无法弄清楚等效语法是什么。
我尝试使用注释@Bean 和Order 公开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