我用,
其中,我使用内置安全令牌来防范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>
这是一个多部分请求,其中除了MultipartFilter
和MultipartResolver
以及MultipartFilter
已正确配置以便Spring处理多部分请求之外,CSR安全性无法使用CSRF令牌。
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>
<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>
注册如下。
null
现在,Spring安全性收到了CSRF令牌,但这样做会导致Struts出现另一个问题。
现在,Struts动作类中的上传文件为@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;
}
}
,如下所示。
null
这是因为我的猜测,多部分请求已经被Spring处理和使用,因此Struts不能将其作为多部分请求使用,因此,Struts动作类中的文件对象是<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>
。
有办法解决这种情况吗?否则,我现在唯一的选择是将令牌作为查询字符串参数附加到URL,这是非常不鼓励的,根本不推荐。
{{1}}
长话短说:如果Spring处理多个请求,如何在Struts动作类中获取文件?另一方面,如果Spring 不处理多部分请求,那么它就会使安全令牌成为可能。如何克服这种情况?
答案 0 :(得分:9)
似乎最好的办法是创建一个委托给Spring的MultipartRequest的自定义MultiPartRequest implementation。以下是一个示例实现:
<强>样品/ 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上找到完整的工作示例答案 1 :(得分:4)
表单编码multipart/formdata
旨在用于文件上传方案,这是根据W3C documentation:
内容类型&#34; multipart / form-data&#34;应该用于提交 包含文件,非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,无法执行此操作,请参阅此answer。要么使POST成为ajax请求并使用一些Javascript添加标头,要么将CSRF令牌作为URL参数发送,如上所述。
如果CSRF令牌经常被重新生成,理想情况下应该在请求之间重新生成,那么将其作为请求参数发送则不是问题,可能是可以接受的。
在服务器端,您需要配置CSRF解决方案以从标头中读取令牌,这通常是使用CSRF解决方案预见的。
答案 2 :(得分:1)
乍一看,您的配置对我来说是正确的。因此,我认为这个问题可能会在某处出现一些错误配置。
我遇到了类似Spring Spring MVC而不是Struts的问题,我可以在Spring Security团队的帮助下解决这个问题。有关详细信息,请参阅this answer。
您也可以将自己的设置与可用的工作样本on Github进行比较。我在Tomcat 7,JBoss AS 7,Jetty和Weblogic上测试了这个。
如果这些不起作用,如果您可以使用配置演示单个控制器,单页面应用程序来演示问题并将其上传到某个位置,将会很有帮助。
答案 3 :(得分:1)
我不是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;
}
如果此请求是多部分,请通过表单输入名称获取file:
if (multipart != null)
{
MultipartFile mf = multipart.getFile("forminputname");
// do your stuff
}