RequestMapping params Multipart 문제

SpringBoot 2.0.0 기반으로 개발에서 접한 문제 기록.

상황:

“xxx/yyy” 라는 엔드포인트로 쿼리스프링에 따라 다르게 동작하는 요건으로
RequestMapping(values=”xxx/yyy”, params=”type=A”) 와 같이 params 속성을 지정.

기대결과:

xxx/yyy?type=xxx 와 xxx/yyy?type=yyy 가 다른 컨트롤러 메서드로 동작

현상:

RequestMapping(values=”xxx/yyy”, params=”type=A”) 해당 RequestMapping에서는 정상적으로 분기되어 동작되는 것을 확인.

하지만 엉뚱한 다른 엔트포인트에서 MultipartFile을 읽을수 없는 현상이 발생

원인:

SpringSecurity 의 CorsFilter 에서 Origin Header 존재 시

CorsConfiguration corsConfiguration =
this.configSource.getCorsConfiguration(request); 호출
-> 내부에서 RequestMappingInfo를 조회.

등록되어진 RequestMapping 중 params 속성이 지정된게 있는 경우
ParamsRequestCondition 에 의해 Request의 FormData를 읽는 과정이 있음.
그리고 유실되어진다.

해결:

MultipartFilter를 등록해서 , SpringSecurity — securityFilterChain의 CorsFilter보다 이전에 동작하게 하면 된다.

SpringSecurity 설정시
http.addFilterBefore(multipartFilter, CorsFilter.class);
MultipartFilter를 CorsFilter 보다 먼저 동작하게 등록하여 formData 유지

또는
SecurityFilterAutoConfiguration 에서 확인한.
SecurityProperties.getFilter().getOrder() // default -100
이하로 filter order를 설정하면 된다.
또는 spring.security.filter.order = xxx 으로 application prop설정을 해도된다.

또는

https://www.docs4dev.com/docs/en/spring-security/5.1.2.RELEASE/reference/web-app-security.html

public class SecurityApplicationInitializer extends AbstractSecurityWebApplicationInitializer {

@Override
protected void beforeSpringSecurityFilterChain(ServletContext servletContext) {
insertFilters(servletContext, new MultipartFilter());
}
}

문제 확인시 보게된 stacktrace

InputBuffer.read(byte[], int, int) line: 345CoyoteInputStream.read(byte[], int, int) line: 183FileUploadBase$FileItemIteratorImpl$1(FilterInputStream).read(byte[], int, int) line: 133FileUploadBase$FileItemIteratorImpl$1(LimitedInputStream).read(byte[], int, int) line: 132MultipartStream$ItemInputStream.makeAvailable() line: 977MultipartStream$ItemInputStream.read(byte[], int, int) line: 881MultipartStream$ItemInputStream(InputStream).read(byte[]) line: 101Streams.copy(InputStream, OutputStream, boolean, byte[]) line: 98Streams.copy(InputStream, OutputStream, boolean) line: 68MultipartStream.readBodyData(OutputStream) line: 572MultipartStream.discardBodyData() line: 596MultipartStream.skipPreamble() line: 614FileUploadBase$FileItemIteratorImpl.findNextItem() line: 865FileUploadBase$FileItemIteratorImpl.<init>(FileUploadBase, RequestContext) line: 845ServletFileUpload(FileUploadBase).getItemIterator(RequestContext) line: 256ServletFileUpload(FileUploadBase).parseRequest(RequestContext) line: 280Request.parseParts(boolean) line: 2869Request.parseParameters() line: 3216Request.getParameter(String) line: 1137RequestFacade.getParameter(String) line: 381StrictHttpFirewall$1(ServletRequestWrapper).getParameter(String) line: 153HandlerMappingIntrospector$RequestAttributeChangeIgnoringWrapper(ServletRequestWrapper).getParameter(String) line: 153ParamsRequestCondition$ParamExpression.matchValue(HttpServletRequest) line: 153ParamsRequestCondition$ParamExpression(AbstractNameValueExpression<T>).match(HttpServletRequest) line: 76ParamsRequestCondition.getMatchingCondition(HttpServletRequest) line: 102RequestMappingInfo.getMatchingCondition(HttpServletRequest) line: 219RequestMappingHandlerMapping(RequestMappingInfoHandlerMapping).getMatchingMapping(RequestMappingInfo, HttpServletRequest) line: 94RequestMappingHandlerMapping(RequestMappingInfoHandlerMapping).getMatchingMapping(Object, HttpServletRequest) line: 58RequestMappingHandlerMapping(AbstractHandlerMethodMapping<T>).addMatchingMappings(Collection<T>, List<AbstractHandlerMethodMapping<Matc>>, HttpServletRequest) line: 383RequestMappingHandlerMapping(AbstractHandlerMethodMapping<T>).lookupHandlerMethod(String, HttpServletRequest) line: 351RequestMappingHandlerMapping(AbstractHandlerMethodMapping<T>).getHandlerInternal(HttpServletRequest) line: 317RequestMappingHandlerMapping(AbstractHandlerMethodMapping<T>).getHandlerInternal(HttpServletRequest) line: 62RequestMappingHandlerMapping(AbstractHandlerMapping).getHandler(HttpServletRequest) line: 351HandlerMappingIntrospector.getCorsConfiguration(HttpServletRequest) line: 146CorsFilter.doFilterInternal(HttpServletRequest, HttpServletResponse, FilterChain) line: 87CorsFilter(OncePerRequestFilter).doFilter(ServletRequest, ServletResponse, FilterChain) line: 107FilterChainProxy$VirtualFilterChain.doFilter(ServletRequest, ServletResponse) line: 334HeaderWriterFilter.doFilterInternal(HttpServletRequest, HttpServletResponse, FilterChain) line: 66

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store