当要发送的请求是多部分请求时,Spring CSRF令牌不起作用

我用,

  • Spring Framework 4.0.0发布(GA)
  • Spring Security 3.2.0 RELEASE(GA)
  • Struts 2.3.16

其中,我使用内置的安全令牌来防范CSRF攻击。

Struts的forms如下所示。

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

这工作正常,除非请求是multipart在这种情况下,请求以状态代码403结束。

HTTP状态403 – 在请求参数“_csrf”或头部“X-CSRF-TOKEN”上find无效的CSRF令牌“null”。

键入状态报告

消息在请求参数“_csrf”或头部“X-CSRF-TOKEN”上find无效的CSRF令牌'null'。

说明禁止访问指定的资源。

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的实现可以在我之前的这个问题中find。


在Spring Security之前放置MultipartFilter也没有帮助。

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> 

只有当令牌作为查询string参数追加时,它才起作用,然而这是不鼓励的。

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

如果您使用@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的configuration

需要在启用CSRF的SecurityConfig之前注册multipart / form-datafilter(MultipartFilter)。 你可以这样做:

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, "/*"); } } 

在这种情况下,除非MultipartFilterMultipartResolverconfiguration正确,以便Spring可以处理多部分请求,否则这是一个多部分请求,其中CSRF令牌不可用于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> 

MultipartFilter<filter-mapping>需要更改为

 <url-pattern>/*</url-pattern> 

这是文档中的一个错误 。

这将工作得很好,万一它是单独的Spring MVC。

但是如果它是Spring和Struts(2)的集成,那么在关联的Struts动作类中会引发另一个问题。 上传文件的信息在关联的Struts动作类中将为null

要解决这个特定的问题,请参阅这个答案来自定义多部分请求 。

我解决了这个问题:

  • 使用vanilla javascript发送多部分文件,就像在Mozilla的指南中一样
  • 在HTML标头中添加_csrf标记,在元标记中,就像在Spring指南中一样, 使用Ajax发送CSRF标记
  • 而不是使用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); 

你可以禁用csrf – httpSecurity.csrf()。disable();

  @Configuration public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity httpSecurity) throws Exception { ... httpSecurity.csrf().disable(); ... } }