当要发送的请求是多部分请求时,Spring CSRF 令牌不起作用 [英] Spring CSRF token does not work, when the request to be sent is a multipart request

查看:19
本文介绍了当要发送的请求是多部分请求时,Spring CSRF 令牌不起作用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我用过,

  • Spring Framework 4.0.0 正式版(GA)
  • Spring Security 3.2.0 发布(GA)
  • Struts 2.3.16

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

In which, I use an in-built security token to guard against CSRF attacks.

Struts 表单如下所示.

The Struts form looks like the following.

<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代码如下.

The generated HTML code is as follows.

<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 结束.

This works fine, unless the request is multipart in which case, the request ends with the status code 403.

HTTP 状态 403 - 在请求中发现无效的 CSRF 令牌null"参数_csrf"或标头X-CSRF-TOKEN".

HTTP Status 403 - Invalid CSRF Token 'null' was found on the request parameter '_csrf' or header 'X-CSRF-TOKEN'.

类型状态报告

message 在请求参数中发现无效的 CSRF Token 'null''_csrf' 或标题 'X-CSRF-TOKEN'.

message Invalid CSRF Token 'null' was found on the request parameter '_csrf' or header '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 相关.)

So, where to look for this token, when a request is multipart? (This should not be related to Struts at all.)

UserDetailsS​​ervice 的实现可以在 this 我之前的问题中找到,如果需要的话.

The implementation of UserDetailsService can be found in this earlier question of mine, if needed.

放置MultipartFilter Spring Security 之前的 也没有帮助.

Placing MultipartFilter before Spring Security did not help either.

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>

推荐答案

在这种情况下,由于它是一个多部分请求,其中 CSRF 令牌对 Spring 安全是不可用的,除非 MultipartFilter>MultipartResolver 已正确配置,以便 Spring 可以处理多部分请求.

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.

MulipartResolver 必须按如下方式注册

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

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

maxUploadSize的属性值-1对上传文件大小没有限制.该值可能会因要求而异.如果是多个文件,则文件大小为所有上传文件的大小.

The attribute value -1 of maxUploadSize puts no limit on the uploaded file size. This value may vary depending upon the requirements. In case of multiple files, the file size is the size of all uploaded files.

还有,

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

of of MultipartFilter需要改成

of <filter-mapping> of MultipartFilter needs to be changed to

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

这是 bug文档.

这可以正常工作,以防万一它是单独的 Spring MVC.

This will work just fine, in case, it is Spring MVC alone.

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

but if it is an integration of Spring and Struts(2), it incurs another problem in the associated Struts action class. The information of the uploaded file will be null in the associated Struts action class(es).

要解决此特定问题,请参阅 this 答案以自定义 多部分请求.

To solve this particular issue, see this answer to customize a multipart request.

这篇关于当要发送的请求是多部分请求时,Spring CSRF 令牌不起作用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆