当要发送的请求是多部分请求时,Spring CSRF 令牌不起作用
Posted
技术标签:
【中文标题】当要发送的请求是多部分请求时,Spring CSRF 令牌不起作用【英文标题】:Spring CSRF token does not work, when the request to be sent is a multipart request 【发布时间】:2014-02-26 03:43:05 【问题描述】:我用过,
Spring Framework 4.0.0 发布(GA) Spring Security 3.2.0 发布(GA) Struts 2.3.16其中,我使用内置的安全令牌来防范 CSRF 攻击。
Struts 表单如下所示。
<s:form namespace="/admin_side"
action="Category"
enctype="multipart/form-data"
method="POST"
validate="true"
id="dataForm"
name="dataForm">
<s:hidden name="%#attr._csrf.parameterName"
value="%#attr._csrf.token"/>
</s:form>
生成的html代码如下。
<form id="dataForm"
name="dataForm"
action="/TestStruts/admin_side/Category.action"
method="POST"
enctype="multipart/form-data">
<input type="hidden"
name="_csrf"
value="3748c228-85c6-4c3f-accf-b17d1efba1c5"
id="dataForm__csrf">
</form>
这很好用,除非请求是多部分的,在这种情况下,请求以状态码 403 结束。
HTTP 状态 403 - 在请求中发现无效的 CSRF 令牌“null” 参数“_csrf”或标头“X-CSRF-TOKEN”。
类型状态报告
message 在请求参数中发现无效的 CSRF Token 'null' '_csrf' 或标题 'X-CSRF-TOKEN'。
说明已禁止访问指定资源。
spring-security.xml
文件如下。
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.2.xsd">
<http pattern="/Login.jsp*" security="none"></http>
<http auto-config='true' use-expressions="true" disable-url-rewriting="true" authentication-manager-ref="authenticationManager">
<session-management session-fixation-protection="newSession">
<concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
</session-management>
<csrf/>
<headers>
<xss-protection />
<frame-options />
<!--<cache-control />-->
<!--<hsts />-->
<content-type-options /> <!--content sniffing-->
</headers>
<intercept-url pattern="/admin_side/**" access="hasRole('ROLE_ADMIN')" requires-channel="any"/>
<form-login login-page="/admin_login/Login.action" authentication-success-handler-ref="loginSuccessHandler" authentication-failure-handler-ref="authenticationFailureHandler"/>
<logout logout-success-url="/admin_login/Login.action" invalidate-session="true" delete-cookies="JSESSIONID"/>
</http>
<beans:bean id="encoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
<beans:bean id="daoAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<beans:property name="userDetailsService" ref="userDetailsService"/>
<beans:property name="passwordEncoder" ref="encoder" />
</beans:bean>
<beans:bean id="authenticationManager" class="org.springframework.security.authentication.ProviderManager">
<beans:property name="providers">
<beans:list>
<beans:ref bean="daoAuthenticationProvider" />
</beans:list>
</beans:property>
</beans:bean>
<authentication-manager>
<authentication-provider user-service-ref="userDetailsService">
</authentication-provider>
</authentication-manager>
<beans:bean id="loginSuccessHandler" class="loginsuccesshandler.LoginSuccessHandler"/>
<beans:bean id="authenticationFailureHandler" class="loginsuccesshandler.AuthenticationFailureHandler" />
<global-method-security secured-annotations="enabled" proxy-target-class="false" authentication-manager-ref="authenticationManager">
<protect-pointcut expression="execution(* admin.dao.*.*(..))" access="ROLE_ADMIN"/>
</global-method-security>
</beans:beans>
那么,当请求是多部分的时,在哪里寻找这个令牌? (这根本不应该与 Struts 相关。)
UserDetailsService
的实现可以在我之前的问题this 中找到,如果需要的话。
Placing MultipartFilter
before Spring Security 也没有帮助。
web.xml
文件如下所示。
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/applicationContext.xml
/WEB-INF/spring-security.xml
</param-value>
</context-param>
<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>
<servlet-name>/*</servlet-name>
</filter-mapping>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>AdminLoginNocacheFilter</filter-name>
<filter-class>filter.AdminLoginNocacheFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>AdminLoginNocacheFilter</filter-name>
<url-pattern>/admin_login/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>NoCacheFilter</filter-name>
<filter-class>filter.NoCacheFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>NoCacheFilter</filter-name>
<url-pattern>/admin_side/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<description>Description</description>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
</listener>
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
<init-param>
<param-name>struts.devMode</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
仅当令牌作为查询字符串参数附加时才有效,但不鼓励这样做。
<s:form namespace="/admin_side"
action="Category?%#attr._csrf.parameterName=%#attr._csrf.token"
enctype="multipart/form-data"
method="POST"
validate="true"
id="dataForm"
name="dataForm">
...
<s:form>
【问题讨论】:
【参考方案1】:如果你使用@annotations,并且jsp视图是这样的:
<form:form id="profileForm" action="profile?id=$param.id" method="POST"
modelAttribute="appUser" enctype="multipart/form-data" >
...
<input type="file" name="file">
...
<input type="hidden" name="$_csrf.parameterName"
value="$_csrf.token" />
</form:form>
这可能会有所帮助:
AppConfig.java:
@EnableWebMvc
@Configuration
@Import( SecurityConfig.class )
public class AppConfig
@Bean(name = "filterMultipartResolver")
public CommonsMultipartResolver filterMultipartResolver()
CommonsMultipartResolver filterMultipartResolver = new CommonsMultipartResolver();
filterMultipartResolver.setDefaultEncoding("utf-8");
// resolver.setMaxUploadSize(512000);
return filterMultipartResolver;
...
SecurityConfig.java 扩展了 WebSecurityConfigurerAdapter 并且是 SpringSecurity 的配置
multipart/form-data 过滤器(MultipartFilter)需要在启用 CSRF 的 SecurityConfig 之前注册。你可以这样做:
SecurityInitializer.java:
public class SecurityInitializer extends
AbstractSecurityWebApplicationInitializer
@Override
protected void beforeSpringSecurityFilterChain(ServletContext servletContext)
super.beforeSpringSecurityFilterChain(servletContext);
// CSRF for multipart form data filter:
FilterRegistration.Dynamic springMultipartFilter;
springMultipartFilter = servletContext.addFilter(
"springMultipartFilter", new MultipartFilter());
springMultipartFilter.addMappingForUrlPatterns(null, false, "/*");
【讨论】:
【参考方案2】:在这种情况下,因为它是一个多部分请求,其中 CSRF 令牌对 Spring 安全性不可用,除非正确配置 MultipartFilter
和 MultipartResolver
,以便 Spring 可以处理多部分请求。
applicationContext.xml
文件中的MulipartResolver
必须注册如下
<bean id="filterMultipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="-1" />
</bean>
maxUploadSize
的属性值-1
对上传的文件大小没有限制。该值可能会因要求而异。如果是多个文件,则文件大小为所有上传文件的大小。
还有,
<servlet-name>/*</servlet-name>
<filter-mapping>
的 MultipartFilter
需要更改为
<url-pattern>/*</url-pattern>
这是documentation 中的bug。
这可以正常工作,以防万一它是单独的 Spring MVC。
但如果它是 Spring 和 Struts(2) 的集成,则会在关联的 Struts 动作类中引发另一个问题。上传文件的信息将在关联的 Struts 动作类中为null
。
要解决此特定问题,请参阅this 答案以自定义multipart request。
【讨论】:
【参考方案3】:我通过以下方式解决了这个问题:
使用普通 javascript 发送多部分文件,如 Mozilla's guide 在 HTML 标头中的元标记中添加 _csrf 标记,就像在 sending the CSRF token with Ajax 的 Spring 指南中一样不使用jquery,直接添加到XHR对象中
var csrfToken = $("meta[name='_csrf']").attr("content");
var csrfHeader = $("meta[name='_csrf_header']").attr("content");
XHR.setRequestHeader(csrfHeader, csrfToken);
XHR.setRequestHeader('Content-Type','multipart/form-data; boundary=' + boundary);
XHR.send(data);
【讨论】:
【参考方案4】:在 Spring Boot + Security + CSRF + Multipart 的情况下,multipart 文件既不绑定 ModelAttribure 也不绑定 RequestParam(MultipartFile 文件)
下面的代码对我来说效果很好。
1.MvcConfiguration.java
@Configuration
@EnableWebMvc
@ComponentScan
public class MvcConfiguration extends WebMvcConfigurerAdapter
.......
......
/*
* Case : Spring Boot + Security + CSRF + Mulitpart
* In this case, since it is a multipart request in which the CSRF token is unavailable to Spring security unless MultipartFilter along with MultipartResolver
* is properly configured so that the multipart request can be processed by Spring.
*
* And
*
* The multipart/form-data filter (MultipartFilter) needs to be registered before the SecurityConfig that enables the CSRF.
* So that's why
* 1. reg.setOrder(1); //below
* 2. security.filter-order=2 // in application.properties
*/
@Bean
public FilterRegistrationBean registerMultipartFilter()
FilterRegistrationBean reg = new FilterRegistrationBean(new MultipartFilter());
reg.setOrder(1);
return reg;
@Bean(name = "filterMultipartResolver")
public CommonsMultipartResolver filterMultipartResolver()
CommonsMultipartResolver filterMultipartResolver = new CommonsMultipartResolver();
filterMultipartResolver.setDefaultEncoding("utf-8");
// resolver.setMaxUploadSize(512000);
return filterMultipartResolver;
.....
.....
2。应用程序.properties
security.filter-order=2
【讨论】:
【参考方案5】:您可以禁用 csrf - httpSecurity.csrf().disable();
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception
...
httpSecurity.csrf().disable();
...
【讨论】:
没有。禁用该令牌不是解决方案。它将打开 CSRF 漏洞。以上是关于当要发送的请求是多部分请求时,Spring CSRF 令牌不起作用的主要内容,如果未能解决你的问题,请参考以下文章
Spring Data Rest:如何使用请求正文发送多部分文件
使用 multer 文件输入的 axios 请求中不存在所需的请求部分“文件”
关于在php中使用curl发送get请求时参数传递问题的解析