问题
I'm using Spring Framework 4.1.5, Spring Security 4.0.0.RC2, Spring Webflow 2.4.0.RELEASE and Tomcat 8.0.15.
I followed the example in the webflow documentation, but I can't get the file in my form bean.
The form
<form:form action="${flowExecutionUrl}" method="post" commandName="fileForm" enctype="multipart/form-data">
<form:input type="file" value="" path="multipartFileUpload"/>
<button type="submit" name="_eventId_forward"><spring:message code="signup.forward"/></button>
<sec:csrfInput/>
</form:form>
The form bean
public class FileForm implements Serializable {
private static final long serialVersionUID = 1L;
private transient MultipartFile multipartFileUpload;
public MultipartFile getMultipartFileUpload() {
return multipartFileUpload;
}
public void setMultipartFileUpload(final MultipartFile multipartFileUpload) {
this.multipartFileUpload = multipartFileUpload;
}
}
The flow
<view-state id="companyLogo" view="signup/company-logo" model="fileForm">
<var name="fileForm" class="it.openex.pmcommonw.form.FileForm"/>
<transition on="back" to="chooseProfile" bind="false" validate="false"/>
<transition on="forward" to="companyInfo">
<evaluate expression="userCommonBean.uploadImage(fileForm)"/>
</transition>
</view-state>
The backing object
@Component
public class UserCommonBean {
public static void uploadImage(final FileForm fileForm) throws IOException, ServletException {
fileForm.getMultipartFileUpload(); // always null!!!
}
}
The multipartResolver
@Bean
public CommonsMultipartResolver filterMultipartResolver() {
final CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
multipartResolver.setMaxUploadSize(10 * 1024 * 1024);
multipartResolver.setMaxInMemorySize(1048576);
multipartResolver.setDefaultEncoding("UTF-8");
return multipartResolver;
}
webflow configuration
@Configuration
public class WebFlowConfig extends AbstractFlowConfiguration {
@Autowired
TilesViewResolver viewResolver;
@Bean
public FlowDefinitionRegistry flowRegistry() {
return getFlowDefinitionRegistryBuilder()
.setFlowBuilderServices(flowBuilderServices())
.setBasePath("/WEB-INF/flows/")
.addFlowLocation("signup.xml", UrlMap.SIGNUP_WEBFLOW)
.addFlowLocation("user-edit.xml", UrlMap.PROFILE_EDIT_WEBFLOW)
.build();
}
@Bean
public FlowExecutor flowExecutor() {
return getFlowExecutorBuilder(flowRegistry()).build();
}
@Bean
public FlowHandlerAdapter flowHandlerAdapter() {
final FlowHandlerAdapter flowHandlerAdapter = new FlowHandlerAdapter();
flowHandlerAdapter.setFlowExecutor(flowExecutor());
return flowHandlerAdapter;
}
@Bean
public FlowHandlerMapping flowHandlerMapping() {
final FlowHandlerMapping flowHandlerMapping = new FlowHandlerMapping();
flowHandlerMapping.setFlowRegistry(flowRegistry());
// this has to be less than -1
flowHandlerMapping.setOrder(-2);
return flowHandlerMapping;
}
@Bean
public MvcViewFactoryCreator mvcViewFactoryCreator() {
final MvcViewFactoryCreator mvcViewFactoryCreator = new MvcViewFactoryCreator();
final List<ViewResolver> viewResolvers = Collections.singletonList(viewResolver);
mvcViewFactoryCreator.setViewResolvers(viewResolvers);
return mvcViewFactoryCreator;
}
@Bean
public FlowBuilderServices flowBuilderServices() {
return getFlowBuilderServicesBuilder().setViewFactoryCreator(mvcViewFactoryCreator())
.setValidator(localValidatorFactoryBean()).build();
}
@Bean
public LocalValidatorFactoryBean localValidatorFactoryBean() {
return new LocalValidatorFactoryBean();
}
}
Inside Tomcat's context.xml
I already added allowCasualMultipartParsing="true"
Debugging the application I can see the file data inside the request, and I can get it if I try to post the form to a normal controller.
I tried also to remove Spring Security but it still didn't work inside Spring WebFlow.
In the requestParameters object there are only 3 objects:
- execution
- _eventid_forward
- _csrf
There are some relevant rows in the logs
DEBUG 2015-03-13 18:03:15,053: org.springframework.web.multipart.support.MultipartFilter - Using MultipartResolver 'filterMultipartResolver' for MultipartFilter
DEBUG 2015-03-13 18:03:15,053: org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'filterMultipartResolver'
DEBUG 2015-03-13 18:03:15,053: org.springframework.web.multipart.support.MultipartFilter - Resolving multipart request [/registrazione] with MultipartFilter
DEBUG 2015-03-13 18:03:15,060: org.springframework.web.multipart.commons.CommonsMultipartResolver - Found multipart file [multipartFileUpload] of size 469217 bytes with original filename [PoliziaMunicipale.png], stored in memory
....
DEBUG 2015-03-13 18:03:15,072: org.springframework.binding.mapping.impl.DefaultMapper - Beginning mapping between source [org.springframework.webflow.core.collection.LocalParameterMap] and target [it.openex.pmcommonw.form.FileForm]
DEBUG 2015-03-13 18:03:15,072: org.springframework.binding.mapping.impl.DefaultMapping - Adding mapping result [TargetAccessError@34bc31ea mapping = parameter:'execution' -> execution, code = 'propertyNotFound', error = true, errorCause = org.springframework.binding.expression.PropertyNotFoundException: Property not found, originalValue = 'e1s2', mappedValue = [null]]
DEBUG 2015-03-13 18:03:15,072: org.springframework.binding.mapping.impl.DefaultMapper - Completing mapping between source [org.springframework.webflow.core.collection.LocalParameterMap] and target [it.openex.pmcommonw.form.FileForm]; total mappings = 1; total errors = 1
The multipartFileUpload
property is not binded in the FileForm
bean.
I'm not sure if it's useful, but inside org.springframework.webflow.context.servlet.HttpServletRequestParameterMap
at line 52
if (request instanceof MultipartHttpServletRequest) {
// ... process multipart data
}
it fails the check because the request is an instance of org.springframework.security.web.context.HttpSessionSecurityContextRepository$Servlet3SaveToSessionRequestWrapper
Update 1
I can confirm that multipartRequest.getFile("file") also works.
I can't enable the org.springframework.web.multipart.support.MultipartFilter
filter though.
If it's enabled the multipartRequest is an instance of StandardMultipartHttpServletRequest
containing a Servlet3SecurityContextHolderAwareRequestWrapper
, wrapping a Servlet3SaveToSessionRequestWrapper
, finally containing an unreachable DefaultMultipartHttpServletRequest
with the multipartFile I need, but I can't get it.
Disabling it I'm able to get it because multipartRequest became an instance of DefaultMultipartHttpServletRequest
, but there's no file validation and the maxUploadSize limit of CommonsMultipartResolver
is not respected.
Plus if Tomcat launches an exception because the file is too big for Tomcat's maxPostSize limit, the exception is caught by my CustomAccessDeniedHandler
because its type is org.springframework.security.access.AccessDeniedException
, and the error message is Invalid CSRF Token 'null' was found on the request parameter '_csrf' or header 'X-CSRF-TOKEN'.
.
Looking at the request object I can see the original Tomcat exception org.apache.tomcat.util.http.fileupload.FileUploadBase$SizeLimitExceededException
. It seems like there's nothing to handle it properly, but, as I said, if I enable the MultipartFilter I can't get the file.
回答1:
We ran into the same problems, since we use Spring Security 4.xx in our web application.
The Problem is that a org.springframework.security.web.context.HttpSessionSecurityContextRepository$Servlet3SaveToSessionRequestWrapper
isn't instance of org.springframework.web.multipart.MultipartHttpServletRequest
but it contains one. A cast to won't work and ClassCastException
will occur.
Thats the reason why
if (request instanceof MultipartHttpServletRequest) {
// ... process multipart data
}
never can be true
.
The idea was to create a org.springframework.web.multipart.support.StandardMultipartHttpServletRequest
from the native HttpServletRequest
and it works.
In our WebApp we use Pojo Actions indicated in Spring Webflow documentation Section 6.5.1. Invoking a POJO action.
Our Workaround:
PojoAction.java
public String fileUpload(RequestContext requestContext) {
final ServletExternalContext context = (ServletExternalContext) requestContext.getExternalContext();
final MultipartHttpServletRequest multipartRequest = new StandardMultipartHttpServletRequest((HttpServletRequest)context.getNativeRequest());
final File file = multipartRequest.getFile("file");
fileUploadHandler.processFile(file); //do something with the submitted file
}
In flow.xml we have an action state like this:
<action-state id="upload-action">
<evaluate expression="pojoAction.uploadFile(flowRequestContext)"/>
<transition to="show"/>
</action-state>
In this case the binding to a model is not needed. I hope it helps!
According to Update 1
In web.xml the CSRF-Protection Filter must declared before SpringSecurityFilterChain.
In our application the web.xml looks like this
<filter>
<filter-name>csrfFilter</filter-name>
<filter-class>
org.springframework.web.filter.DelegatingFilterProxy
</filter-class>
<async-supported>true</async-supported>
</filter>
<filter-mapping>
<filter-name>csrfFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>
org.springframework.web.filter.DelegatingFilterProxy
</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
来源:https://stackoverflow.com/questions/29037596/file-upload-using-spring-webflow-2-4-0-parameter-not-binded