繁体   English   中英

Struts2中的文件上传以及Spring CSRF令牌

[英]File upload in Struts2 along with the Spring CSRF token

我用,

  • Spring Framework 4.0.0发布(GA)
  • Spring Security 3.2.0发布(GA)
  • 支撑杆2.3.16

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

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

这是一个多部分请求,其中CSRF令牌对于Spring安全性不可用,除非已正确配置MultipartFilterMultipartResolver以便Spring处理多部分请求。

web.xml MultipartFilter配置如下。

<?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>
        <url-pattern>/*</url-pattern>
    </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>

并且在applicationContext.xmlMultipartResolver如下注册。

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

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

现在,Spring安全人员会收到CSRF令牌,但这样做会在Struts中引起另一个问题。

现在,在Struts动作类中,已上传的文件为null ,如下所示。

@Namespace("/admin_side")
@ResultPath("/WEB-INF/content")
@ParentPackage(value="struts-default")
public final class CategoryAction extends ActionSupport implements Serializable, ValidationAware, ModelDriven<Category>
{
    private File fileUpload;
    private String fileUploadContentType;
    private String fileUploadFileName;
    private static final long serialVersionUID = 1L;

    //Getters and setters.

    //Necessary validators as required.
    @Action(value = "AddCategory",
        results = {
            @Result(name=ActionSupport.SUCCESS, type="redirectAction", params={"namespace", "/admin_side", "actionName", "Category"}),
            @Result(name = ActionSupport.INPUT, location = "Category.jsp")},
        interceptorRefs={
            @InterceptorRef(value="defaultStack", "validation.validateAnnotatedMethodOnly", "true"})
        })
    public String insert(){
        //fileUpload, fileUploadContentType and fileUploadFileName are null here after the form is submitted.
        return ActionSupport.SUCCESS;
    }

    @Action(value = "Category",
            results = {
                @Result(name=ActionSupport.SUCCESS, location="Category.jsp"),
                @Result(name = ActionSupport.INPUT, location = "Category.jsp")},
            interceptorRefs={
                @InterceptorRef(value="defaultStack", params={ "validation.validateAnnotatedMethodOnly", "true", "validation.excludeMethods", "load"})})
    public String load() throws Exception{
        //This method is just required to return an initial view on page load.
        return ActionSupport.SUCCESS;
    }
}

这是因为,以我的猜测, 多部分请求已经处理和春天因此食用,更不可用Struts作为multipart请求,因此,在Struts Action类文件对象为null

有办法解决这种情况吗? 否则,我现在只剩下将令牌作为查询字符串参数附加到URL的唯一选项,强烈建议不要这样做。

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

长话短说:如果使Spring处理多部分请求,如何在Struts动作类中获取文件? 另一方面,如果使Spring处理多部分请求,则它将取消安全令牌。 如何克服这种情况?

似乎最好的选择是创建一个委托给Spring的MultipartRequest的自定义MultiPartRequest实现 这是一个示例实现:

sample / SpringMultipartParser.java

package sample;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map.Entry;

import javax.servlet.http.HttpServletRequest;

import org.apache.struts2.dispatcher.multipart.MultiPartRequest;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.util.WebUtils;

import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;

public class SpringMultipartParser implements MultiPartRequest {
    private static final Logger LOG = LoggerFactory.getLogger(MultiPartRequest.class);

    private List<String> errors = new ArrayList<String>();

    private MultiValueMap<String, MultipartFile> multipartMap;

    private MultipartHttpServletRequest multipartRequest;

    private MultiValueMap<String, File> multiFileMap = new LinkedMultiValueMap<String, File>();

    public void parse(HttpServletRequest request, String saveDir)
            throws IOException {
        multipartRequest =
                WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class);

        if(multipartRequest == null) {
            LOG.warn("Unable to MultipartHttpServletRequest");
            errors.add("Unable to MultipartHttpServletRequest");
            return;
        }
        multipartMap = multipartRequest.getMultiFileMap();
        for(Entry<String, List<MultipartFile>> fileEntry : multipartMap.entrySet()) {
            String fieldName = fileEntry.getKey();
            for(MultipartFile file : fileEntry.getValue()) {
                File temp = File.createTempFile("upload", ".dat");
                file.transferTo(temp);
                multiFileMap.add(fieldName, temp);
            }
        }
    }

    public Enumeration<String> getFileParameterNames() {
        return Collections.enumeration(multipartMap.keySet());
    }

    public String[] getContentType(String fieldName) {
        List<MultipartFile> files = multipartMap.get(fieldName);
        if(files == null) {
            return null;
        }
        String[] contentTypes = new String[files.size()];
        int i = 0;
        for(MultipartFile file : files) {
            contentTypes[i++] = file.getContentType();
        }
        return contentTypes;
    }

    public File[] getFile(String fieldName) {
        List<File> files = multiFileMap.get(fieldName);
        return files == null ? null : files.toArray(new File[files.size()]);
    }

    public String[] getFileNames(String fieldName) {
        List<MultipartFile> files = multipartMap.get(fieldName);
        if(files == null) {
            return null;
        }
        String[] fileNames = new String[files.size()];
        int i = 0;
        for(MultipartFile file : files) {
            fileNames[i++] = file.getOriginalFilename();
        }
        return fileNames;
    }

    public String[] getFilesystemName(String fieldName) {
        List<File> files = multiFileMap.get(fieldName);
        if(files == null) {
            return null;
        }
        String[] fileNames = new String[files.size()];
        int i = 0;
        for(File file : files) {
            fileNames[i++] = file.getName();
        }
        return fileNames;
    }

    public String getParameter(String name) {
        return multipartRequest.getParameter(name);
    }

    public Enumeration<String> getParameterNames() {
        return multipartRequest.getParameterNames();
    }

    public String[] getParameterValues(String name) {
        return multipartRequest.getParameterValues(name);
    }

    public List getErrors() {
        return errors;
    }

    public void cleanUp() {
        for(List<File> files : multiFileMap.values()) {
            for(File file : files) {
                file.delete();
            }
        }

        // Spring takes care of the original File objects
    }
}

接下来,您需要确保Struts正在使用它。 您可以在struts.xml文件中执行此操作,如下所示:

struts.xml

<constant name="struts.multipart.parser" value="spring"/>
<bean type="org.apache.struts2.dispatcher.multipart.MultiPartRequest" 
      name="spring" 
      class="sample.SpringMultipartParser"
      scope="default"/>

警告 :通过正确设置bean的范围,确保为每个多部分请求创建一个新的MultipartRequest实例是绝对必要的,否则您将看到竞争条件。

之后,您的Struts操作将像以前一样添加文件信息。 请记住,现在使用filterMultipartResolver而非Struts完成文件(即文件大小)的验证。

使用主题自动包含CSRF令牌

您可能会考虑创建自定义主题,以便可以在表单中自动包含CSRF令牌。 有关如何执行此操作的更多信息,请参见http://struts.apache.org/release/2.3.x/docs/themes-and-templates.html

Github上的完整示例

您可以在github上找到完整的工作示例, 网址https://github.com/rwinch/struts2-upload

根据W3C文档 ,表单编码multipart/formdata打算用于文件上载方案:

内容类型“ multipart / form-data”应用于提交包含文件,非ASCII数据和二进制数据的表单。

MultipartResolver类期望仅文件上传,而不是其他表单字段,这来自javadoc:

/**
 * A strategy interface for multipart file upload resolution in accordance
 * with <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>.
 *
 */

因此,这就是为什么无法将CSRF添加为表单字段的原因,保护文件上传请求免受CSRF攻击的通常方法是在HTTP请求标头(而不是POST正文)中发送CSRF令牌。 为此,您需要将其设为ajax POST。

对于普通的POST,无法执行此操作,请参见此答案 您可以将POST设为ajax请求,然后使用一些Javascript添加标头,或者将CSRF令牌作为URL参数发送。

如果CSRF令牌被频繁地重新生成(理想情况下应该在请求之间),那么将其作为请求参数发送就不会有问题,并且可以接受。

在服务器端,您需要配置CSRF解决方案以从标头读取令牌,这通常是所使用的CSRF解决方案所预见的。

乍一看,您的配置对我来说是正确的。 因此,我认为该问题可能是某处的一些错误配置。

我用Spring MVC而不是Struts遇到了类似的问题,我可以在Spring Security团队的帮助下解决该问题。 有关详细信息,请参见此答案

您也可以将自己的设置与Github上提供的工作样本进行比较。 我已经在Tomcat 7,JBoss AS 7,Jetty和Weblogic上进行了测试。

如果这些方法不起作用,则可以用配置来创建一个控制器,单个页面应用程序来演示问题并将其上载到某个地方,这将对您有所帮助。

我不是一个Struts的用户,但我认为你可以使用Spring的事实MultipartFilter包装在一个请求MultipartHttpServletRequest

首先获得HttpServletRequest ,在Struts中,我认为您可以执行以下操作:

ServletRequest request = ServletActionContext.getRequest();

然后从中获取MultipartRequest ,并在必要时包装包装器:

MultipartRequest multipart = null;
while (multipart == null)
{
    if (request instanceof MultipartRequest)
        multipart = (MultipartRequest)request;
    else if (request instanceof ServletRequestWrapper)
        request = ((ServletRequestWrapper)request).getRequest();
    else
        break;                
}

如果此请求是多部分请求,请通过表单输入名称获取文件

if (multipart != null)
{
    MultipartFile mf = multipart.getFile("forminputname");
    // do your stuff
}

暂无
暂无

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

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