요즘 진행하고 있는 프로젝트에서, S3에 파일을 업로드하는 간단한 API를 만들었는데 이게 JsonParseException이 발생하면서 제대로 동작하지 않았다. 에러 내용과 함께 디버그 과정을 기록하려고 한다.
form-data 형식으로 file을 받아서 S3 업로드를 하는 기능을 만들었다. 소스 코드는 다음과 같다.
S3Controller.java
@Slf4j
@Controller
@RequestMapping("/s3")
@RequiredArgsConstructor
@Api("S3 파일 업로드")
public class S3UploadController {
private final AmazonS3Client amazonS3Client;
@Value("${cloud.aws.s3.bucket}")
private String bucket;
@Value("${cloud.aws.region.static}")
private String region;
@PostMapping("/upload")
@ApiOperation(value = "S3에 파일을 업로드한다")
@ResponseBody
public ResponseVO<List<FileDTO>> uploadFile(@RequestParam("file") MultipartFile file) {
try {
String fileName=file.getOriginalFilename();
String fileUrl= "https://" + bucket + ".s3." + region + ".amazonaws.com/" + fileName;
ObjectMetadata metadata= new ObjectMetadata();
metadata.setContentType(file.getContentType());
metadata.setContentLength(file.getSize());
amazonS3Client.putObject(bucket, fileName, file.getInputStream(), metadata);
return new ResponseVO<>(ResponseCode.C,"저장 완료", null);
} catch (IOException e) {
e.printStackTrace();
return new ResponseVO<>(ResponseCode.C,"IOException",null);
}
}
}
이렇게 코딩을 한 후 Postman에서 테스트를 해보면 500 에러가 떴다.
그리고 에러 로그는 이렇게 떴다.
(생략)
.
.
.
ERROR 2023-06-20 22:44:51[http-nio-8080-exec-8] org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/].[dispatcherServlet][log:175] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception
com.fasterxml.jackson.core.JsonParseException: Unexpected character ('-' (code 45)) in numeric value: expected digit (0-9) to follow minus sign, for valid numeric value
at [Source: (StringReader); line: 1, column: 3]
at com.fasterxml.jackson.core.JsonParser._constructError(JsonParser.java:2391)
at com.fasterxml.jackson.core.base.ParserMinimalBase._reportError(ParserMinimalBase.java:735)
at com.fasterxml.jackson.core.base.ParserMinimalBase.reportUnexpectedNumberChar(ParserMinimalBase.java:557)
at com.fasterxml.jackson.core.json.ReaderBasedJsonParser._handleInvalidNumberStart(ReaderBasedJsonParser.java:1718)
at com.fasterxml.jackson.core.json.ReaderBasedJsonParser._parseNegNumber(ReaderBasedJsonParser.java:1467)
at com.fasterxml.jackson.core.json.ReaderBasedJsonParser.nextToken(ReaderBasedJsonParser.java:784)
at com.fasterxml.jackson.databind.ObjectMapper._initForReading(ObjectMapper.java:4762)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4668)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3630)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3598)
at com.whale.partyplace.filter.RequestWrapper.readJSONStringFromBody(RequestWrapper.java:102)
at com.whale.partyplace.filter.RequestWrapper.<init>(RequestWrapper.java:36)
at com.whale.partyplace.filter.XSSFilter.doFilterInternal(XSSFilter.java:30)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
.
.
.
(생략)
com.fasterxml.jackson.core.JsonParseException: Unexpected character ('-' (code 45)) in numeric value: expected digit (0-9) to follow minus sign, for valid numeric value
at [Source: (StringReader); line: 1, column: 3]
나는 MultipartFile을 요청값으로 보내고 있는데 JsonParse 예외가 발생한다. 뭔가 잘못됐다.
실무로 일하고 계신 선배님의 도움을 받아 디버깅을 해 본 결과 요청 객체가 controller에 들어오지도 않는다는 걸 알았다. controller에 들어오기 전에 어딘가에서 처리되고, 그 처리하는 과정에서 json Parse 문제가 발생한다는 것이었다.
계속해서 디버깅을 해 본 결과 문제는 에러로그에도 찍혀있듯이 XSSFiler였다.
public class XSSFilter extends OncePerRequestFilter {
...
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
final HttpServletRequest requestWrapper = request.getRequestURI().startsWith("/file/")?request:new RequestWrapper(request);
final ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
chain.doFilter(requestWrapper, responseWrapper);
}
...
}
XSS 해킹에 대한 보안을 위해 모든 요청과 응답은 이 XSSFilter 클래스의 requestWrapper와 responseWrapper를 거쳐서 처리되는데,
request uri에 /file/이 포함되어 있으면 이 Filter를 거치지 않게 된다.
해당 XSSFilter 클래스를 내가 작성한 게 아니라서 이 부분을 모르고 있었다... 그래서 이 filter를 거치지 않게 하기 위해 uri에 /file/ 이라는 경로를 추가해주었다.
S3에 정상적으로 업로드 되는 것을 확인했다!
결론 : json으로 보내지 않는데 jsonParse 에러가 뜬다면, XSSFilter에 걸린 건지 확인해봐라.