[SpringBoot3 + SpringSecurity ] Content-Type: multipart/form-data 일 경우 VO에 담기지 않을 때 ( + Next.js )

2024. 4. 22. 23:13백엔드/Spring

반응형

Next.js로 프로젝트를 처음 진행하게되었는데 React 로 어느정도 프로젝트를 진행해보았기 때문에 크게 어려움이 없었다.

오히려 Next.js의 편리함에 감탄을 하였다.

 

그러다 파일 업로드를 공통으로 만들고 있었는데 SpringBoot쪽 VO에 넘어가지 않았다.

처음엔 Next.js를 처음 접하면서 뭔가 설정이 빠졌다 생각하여 이것저것 넣어보기도 하였지만 아무리해도 잡히지 않았다.

이건 무조건 SpringBoot 쪽 설정이 추가가 필요하다는 확신이 들어 퇴근 후 짬짬히 열심히 각종 글들을 읽으면서 분석해보았다...

 

먼저 간단하게 서버쪽 설정이 필요한 이유를 알고 가보자.

사용자가 웹을 통해 파일을 업로드하면 브라우저는 해당 파일 데이터를 multipart/form-data 형식으로 인코딩하여 서버로 전송, 서버 측에서는 이러한 형식의 데이터를 올바르게 해석하고 처리할 수 있어야 하기 때문이다.

 

1. MultipartResolve 추가

package com.lunch.global.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.unit.DataSize;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;

@Configuration
public class HttpRequestConfig {
	
    @Value("${file.multipart.maxUploadSize:10485760}")
    private long maxUploadSize;

    @Value("${file.multipart.maxUploadSizePerFile:10485760}")
    private long maxUploadSizePerFile;
	
// SpringBoot3 이하를 사용중이라면 아래처럼 사용하자!    
//   	@Bean
//	public MultipartResolver multipartResolver() {
//	     return new CommonsMultipartResolver();
//	}

	@Bean
	public MultipartResolver multipartResolver() {
	   StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
	   return multipartResolver;
	}

	@Bean
	public MultipartConfigElement multipartConfigElement() {
	   MultipartConfigFactory factory = new MultipartConfigFactory();
	   factory.setMaxRequestSize(DataSize.ofBytes(maxUploadSize));
	   factory.setMaxFileSize(DataSize.ofBytes(maxUploadSizePerFile));
	   return factory.createMultipartConfig();
	}
}

 

 

MultipartResolver의 역할

  • 멀티파트 요청 해석: MultipartResolver는 들어오는 요청이 멀티파트 형식인지를 확인하고 멀티파트 요청을 해석하여 개별 파트(예: 파일, 폼 필드)를 추출한다.
  • 데이터 접근: 멀티파트 요청의 데이터에 쉽게 접근할 수 있도록 한다. 예를 들어, 컨트롤러에서 MultipartFile 인터페이스를 통해 업로드된 파일에 접근할 수 있게 해준다.
  • 임시 파일 관리: 대용량의 파일 업로드 처리 시, 파일 데이터를 메모리에 보관하는 대신 디스크에 임시 파일로 저장할 수 있다. 이를 통해 메모리 사용량을 최적화할 수 있으며, MultipartResolver는 이러한 임시 파일의 생성과 정리를 관리한다.

multipart와 spring을 검색하면 대부분 위의 내용일 정도로 필수 설정이다.

 

위 설정만으로 해결된다면 정말 행운이겠지만 안된다면 다음 내용으로 넘어가야한다.

 

 

 

2. MultipartFilter 등록

package com.master.global.config;

import java.util.Arrays;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.MultipartConfigFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.unit.DataSize;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.support.MultipartFilter;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;

import com.master.global.filter.RequestFilter;

import jakarta.servlet.MultipartConfigElement;


@Configuration
public class HttpRequestConfig {

	...MultipartResolve 설정

	@Bean
	public FilterRegistrationBean<MultipartFilter> multipartFilterRegistrationBean() {
		FilterRegistrationBean<MultipartFilter> registrationBean = new FilterRegistrationBean<>();
		MultipartFilter multipartFilter = new MultipartFilter();
		multipartFilter.setMultipartResolverBeanName("multipartResolver");
		registrationBean.setFilter(multipartFilter);
		registrationBean.setOrder(Integer.MIN_VALUE); // 스프링 시큐리티 필터 체인보다 먼저 실행되도록 우선순위 설정
		return registrationBean;
	}

}

 

일반적으로 Spring Boot를 사용하면 MultipartResolver를 @Bean으로 등록하는 것만으로 충분하지만 

SpringSecurity를 같이 사용했다면 얘기가 달라진다.

 

내가 예상했던 순서는 당연히 SpringSecurity가 multipart 요청을 처리하기 전에, multipart 데이터가 MultipartResolver를 통해 파싱되고 그다음 SpringSecurity가 검증하는 줄 알았다.

 

하지만 아니었다. ㅠㅠ

기본 설정은 모든요청이 springSecurityFilterChain 이 실행되고 그 다음으로 이어진다.

그러다보니 파싱이 되지 않은 데이터는 당연히 springSecurityFilterChain에서 검증이 되지 않기 때문에 모두 걸러지고 VO에는 null 로 보여지는 것이었다.

 

그렇기 때문에 우리가 원하는 대로 진행이 되려면 SpringSecurityFilterChain 이전에 MultipartFilter가 실행되어야 한다.

결국 MultipartFilter의 순서만 수정해주면 해결되는 아주 간단한 일이다.

 

사실 SpringSecurity의 기본적인 구조만 알았어도 간단히 해결될 문제였지만 기본적인걸 까먹은 나로써는 해결될리가 없었다.

아직도 갈길이 먼거 같다..

 

 

 

 

+ 만약 나처럼 Next.js + SpringBoot + SpringSecurity 를 사용하면서 위에 방법을 모두 했지만 안된 경우!!

next.config.js 에 아래와 같이 추가해보자!

/** @type {import('next').NextConfig} */
module.exports = {
	...기본설정,
    api: {
        bodyParser: false,
    }
};

 

Next.js에서는 따로 설정을 해주지 않는다면 기본적으로 모든 요청에 body parser가 적용된다고 한다. 

multipart형식의 데이터를 파싱하는데 문제가 될 수 있기 때문에 body parser를 false로 설정해준다.

 

 

 

 

 

반응형