[英]File upload in Struts2 along with the Spring CSRF token
我用,
在其中,我使用内置的安全令牌来防御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安全性不可用,除非已正确配置MultipartFilter
和MultipartResolver
以便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.xml
, MultipartResolver
如下注册。
<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解决方案所预见的。
我不是一个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.