当前位置: 首页 > 知识库问答 >
问题:

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

任小云
2023-03-14

我使用,

  • 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 security不可用,除非正确配置了多部分过滤器和多部分解析器,以便Spring处理多部分请求。

web中的MultipartFilter。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>
        <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>

而在application ationContext.xml中,MultipartResolver注册如下。

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

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

CSRF令牌现在由Spring security接收,但这样做会在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;
    }
}

这是因为据我猜测,多部分请求已经由Spring处理和使用,因此Struts无法将其作为多部分请求使用,因此Struts操作类中的file对象为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处理mulipart请求,如何在Struts操作类中获取文件?另一方面,如果Spring没有处理一个多部分请求,那么它就会调用安全令牌。如何克服这种情况

共有3个答案

笪煌
2023-03-14

乍一看,你的配置在我看来是正确的。因此,我认为问题可能是某个地方的一些微小的配置错误。

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

您还可以将设置与Github上可用的工作示例进行比较。我已经在Tomcat 7、JBoss AS 7、Jetty和Weblogic上对此进行了测试。

如果这些都不起作用,那么如果您可以使用您的配置创建一个单控制器、单页应用程序来演示问题并将其上载到某处,这将非常有用。

汪跃
2023-03-14

表单编码multipart/formdata用于文件上传场景,这是根据W3C留档:

内容类型“多部分/表单数据”应用于提交包含文件、非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设为ajax请求并使用一些Javascript添加标头,要么如您所述将CSRF令牌作为URL参数发送。

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

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

屠振濂
2023-03-14

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

样本/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文件中执行此操作,如下所示:

支柱。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创建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

 类似资料:
  • 主要内容:创建视图文件:,创建action类:,配置文件:,错误消息:Struts 2框架提供了内置支持处理文件上传使用基于HTML表单的文件上传。上传一个文件时,它通常会被存储在一个临时目录中,他们应该由Action类进行处理或移动到一个永久的目录,以确保数据不丢失。 请注意,服务器有一个安全策略可能会禁止写到目录以外的临时目录和属于web应用的目录。 在Struts中的文件上传是通过预先定义的拦截文件上传拦截器这是可通过org.apache.struts2.in

  • 主要内容:1. 动作类,2. 结果页面,3. struts.xml,4. 示例,参考在Struts2, <s:file> 标签用于创建一个HTML文件上传组件,允许用户从本地磁盘选择文件,并将其上传到服务器。在本教程中,您将创建与文件上传组件JSP页面,设置最大大小和允许上传文件的内容类型,并显示上传文件的详细信息。 这里创建一个Web工程:strut2uploadfile,来演示在多个复选框如何设置的默认值,整个项目的结构如下图所示: 1. 动作类 Action类的文件上传,声

  • 我是一个使用struts2文件上传和我的动作类包含3私人文件与getter和setters 我有一些疑问要澄清 > 每当我使用myFileVariableName“FileName”(如果文件变量是myFile,那么文件名变量是MyFileFileFileName,如果文件是xxx,那么文件名是xxxFileName),我就会得到输出,如果我对此格式(即myFileVariableName“Fil

  • 本文向大家介绍C#实现文件上传以及多文件上传功能,包括了C#实现文件上传以及多文件上传功能的使用技巧和注意事项,需要的朋友参考一下 一、前端搭建 1、前端用到js:uploadify(下载地址:http://www.uploadify.com/download/)、layer (下载地址:http://layer.layui.com/),下载之后把它们放在你的项目里 列如 2、根据你的需要在你项目

  • 本文向大家介绍struts2实现多文件上传,包括了struts2实现多文件上传的使用技巧和注意事项,需要的朋友参考一下 本文实例为大家分享了struts2实现多文件上传的具体代码,供大家参考,具体内容如下 首先搭建好struts2的开发环境,导入struts2需要的最少jar包 新建upload.jsp页面,注意一定要把表单的enctype设置成multipart/form-data 新建一个Up

  • 问题内容: 如何使用Ajax在Struts 2中上传文件 问题答案: 下载Struts2 jQuery插件 ,并像通常使用Struts2一样进行操作。