简体   繁体   English

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

[英]Spring CSRF token does not work, when the request to be sent is a multipart request

I use,我用,

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

In which, I use an in-built security token to guard against CSRF attacks.其中,我使用内置的安全令牌来防范 CSRF 攻击。

The Struts form looks like the following. 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>

The generated HTML code is as follows.生成的 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>

This works fine, unless the request is multipart in which case, the request ends with the status code 403.这工作正常,除非请求是多部分的,在这种情况下,请求以状态代码 403 结束。

HTTP Status 403 - Invalid CSRF Token 'null' was found on the request parameter '_csrf' or header 'X-CSRF-TOKEN'. HTTP 状态 403 - 在请求参数“_csrf”或标头“X-CSRF-TOKEN”上发现无效的 CSRF 令牌“null”。

type Status report类型状态报告

message Invalid CSRF Token 'null' was found on the request parameter '_csrf' or header 'X-CSRF-TOKEN'.在请求参数“_csrf”或标头“X-CSRF-TOKEN”上发现消息无效的 CSRF 令牌“null”。

description Access to the specified resource has been forbidden. description已禁止访问指定的资源。

The spring-security.xml file is as follows. 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>

So, where to look for this token, when a request is multipart?那么,当请求是多部分的时,在哪里寻找这个令牌? (This should not be related to Struts at all.) (这根本不应该与 Struts 相关。)

The implementation of UserDetailsService can be found in this earlier question of mine, if needed.实施UserDetailsService可以发现这个矿的早期问题,如果需要的话。


Placing MultipartFilter before Spring Security did not help either. 在 Spring Security 之前放置MultipartFilter也没有帮助。

The web.xml file looks like the following. 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>

It only works, when the token is appended as a query-string parameter as follows which is however, discouraged.它仅在令牌作为查询字符串参数附加如下时才有效,但不鼓励这样做。

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

If you are using @annotations, and the jsp view like this:如果您使用@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>

this may help:这可能有帮助:

AppConfig.java : 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;
}
...

The SecurityConfig.java extends WebSecurityConfigurerAdapter and is the configuration for SpringSecurity SecurityConfig.java 扩展了 WebSecurityConfigurerAdapter 并且是 SpringSecurity 的配置

The multipart/form-data filter (MultipartFilter) needs to be registered before the SecurityConfig that enables the CSRF. multipart/form-data过滤器(MultipartFilter)需要在启用CSRF的SecurityConfig之前注册。 You can do it with this:你可以这样做:

SecurityInitializer.java: 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, "/*");

}
}

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.在这种情况下,由于它是一个多部分请求,其中 CSRF 令牌不可用于 Spring 安全性,除非正确配置MultipartFilterMultipartResolver以便 Spring 可以处理多部分请求。

MulipartResolver in the applicationContext.xml file has to be registered as follows applicationContext.xml文件中的MulipartResolver必须注册如下

<bean id="filterMultipartResolver" 
      class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> 

    <property name="maxUploadSize" value="-1" />
</bean> 

The attribute value -1 of maxUploadSize puts no limit on the uploaded file size. maxUploadSize的属性值-1对上传的文件大小没有限制。 This value may vary depending upon the requirements.该值可能因要求而异。 In case of multiple files, the file size is the size of all uploaded files.如果有多个文件,文件大小为所有上传文件的大小。


Also,还,

<servlet-name>/*</servlet-name> 

of <filter-mapping> of MultipartFilter needs to be changed to MultipartFilter<filter-mapping>需要改为

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

This is a bug in the documentation .这是文档中错误

This will work just fine, in case, it is Spring MVC alone.这将工作得很好,以防万一它是单独的 Spring MVC。

but if it is an integration of Spring and Struts(2), it incurs another problem in the associated Struts action class.但如果它是 Spring 和 Struts(2) 的集成,则会在关联的 Struts 操作类中引发另一个问题。 The information of the uploaded file will be null in the associated Struts action class(es).上传文件的信息在关联的 Struts 操作类中将为null

To solve this particular issue, see this answer to customize a multipart request .要解决此特定问题,请参阅答案以自定义多部分请求

I solved this problem by:我通过以下方式解决了这个问题:

  • sending the multi-part file using vanilla javascript, like in Mozilla's guide使用 vanilla javascript 发送多部分文件,就像在Mozilla 的指南中一样
  • adding the _csrf token in the HTML header, in meta tags, like in the Spring guideline for sending the CSRF token with Ajax在 HTML 标头中添加 _csrf 令牌,在元标记中,就像在 Spring 指南中使用 Ajax 发送 CSRF 令牌一样
  • instead of using jquery, adding it directly to the XHR object而不是使用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);

In case of Spring Boot + Security + CSRF + Multipart, multipart files get binding to neither ModelAttribure nor RequestParam (MultipartFile file)在 Spring Boot + Security + CSRF + Multipart 的情况下,multipart 文件不会绑定到 ModelAttribure 和 RequestParam(MultipartFile 文件)

Below Code Worked fine for me.下面的代码对我来说很好用。

1.MvcConfiguration.java 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. application.properties 2. application.properties

security.filter-order=2

You can disable csrf - httpSecurity.csrf().disable();您可以禁用 csrf - httpSecurity.csrf().disable();

 @Configuration
    public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        ...
        httpSecurity.csrf().disable();
        ...
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM