当要发送的请求是多部分请求时,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, "/*"); } }
在这种情况下,除非MultipartFilter
和MultipartResolver
configuration正确,以便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(); ... } }