Spring Boot로 AWS S3 이미지 업로드하기

버드뉴·2024년 3월 4일
0

프로젝트 협업

목록 보기
1/7

작업 환경
IDE: IntelliJ
Spring Boot: 3.2.3
Java: 20


프론트엔드 요청으로 다수의 이미지 파일JSON 데이터POST 요청으로 받아 데이터베이스에 저장하는 기능을 구현하습니다.

클라우드 스토리지는 AWS S3를 활용하였고, 해당 기능을 구현하기 위해 다음과 같은 단계로 구축 하였습니다.

1. AWS S3 버킷 생성하기
이미지를 저장하는 클라우드 스토리지를 AWS S3를 활용하여 구축하였습니다. S3의 장점으로는 신속한 데이터 액세스, 상시 가용성보안을 보장 받을 수 있습니다.

2. AWS IAM에서 버킷 액세스를 위한 자격 증명 생성

2. Spring Boot를 활용하여 S3 버킷에 이미지 업로드하기


Http 요청 살펴보기

Postman으로 이미지 파일과 JSON 데이터를 전송 할 때 Http 요청이 어떻게 전송되는지 살펴보겠습니다.


POST /upload/example HTTP/1.1
Host: example-host.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Length: 485

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="images"; filename="example1.jpeg"
Content-Type: image/jpeg
(data)

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="images"; filename="example2.jpeg"
Content-Type: image/jpeg
(data)

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="json"
{
	"JSON": "data"
}
------WebKitFormBoundary7MA4YWxkTrZu0gW--

Http 요청에 이미지 파일JSON 데이터가 있으면 multipart/form-data 형식으로 전송됩니다.

이 형식은 주로 파일이나 데이터를 서버로 전송할 때 사용되는데, multipart/form-data는 여러 부분으로 구성되며, 각 부분은 다른 종류의 데이터를 담을 수 있습니다.

예를 들어 Text Field, File, JSON 등이 이에 해당됩니다.

각 부분은 boundary라는 구분자로 분리되는데, 이 boundaryHeader에서 정의됩니다.

boundary 값은 요청을 만들 때 생성되며, 일반적으로 랜덤하거나 특정 알고리즘을 통해 생성됩니다.


Content-Disposition은 데이터가 어떤 형태로 전달되는지 설명합니다.

위 예시에서는 form-data라는 값과 함께, field의 name으로 images를 사용합니다. filename은 업로드하는 파일의 이름을 명시합니다.

Content-Type은 전송되는 데이터의 타입을 명시합니다. 위 예시에서는 jpeg 이미지를 업로드했기 때문에 image/jpeg로 명시되었으며, 업로드되는 파일에 따라 Type이 바뀌게 됩니다.


파일이 여러개 있을 경우나 JSON이 있는 경우와 같이, 각 파트를 구분하기위에 boundary로 분리됩니다.

이렇게 각 데이터는 별도의 파트로 구분되어 전송되며, 서버 측에서는 이를 분리하여 각각 처리할 수 있습니다.


AWS S3 버킷 생성하기

먼저 AWS(https://aws.amazon.com)에 로그인하고 S3에 접속해줍니다.

버킷 만들기를 클릭해줍니다.

AWS 리전을 선택해주시고 버킷 이름을 입력해줍니다.

객체 소유권은 ACL 비활성화로 제 계정에서만 액세스 권한을 허용해줍니다.

버킷의 모든 퍼블릭 액세스 차단을 풀어줍니다.

아래의 주의 사항을 읽고 체크를 해주면 됩니다.

단 용도에 따라서 차단 범위를 지정하는 것이 보안상 유리합니다.

버킷 버전 관리와 기본 암호화는 default 설정으로 하고 버킷을 만들어줍니다.


AWS IAM에서 버킷 액세스를 위한 자격 증명 생성

AWS Console에서 IAM에 접속해줍니다.

사용자 - 사용자 생성을 클릭해줍니다.

원하는 사용자 이름을 작성하고 다음을 클릭해줍니다.

권한 옵션은 직접 정책 연결을 선택해줍니다.

아래 권한 정책에 S3FullAccess를 넣어줍니다.

검토 및 생성에서 선택한 권한을 확인하고 사용자 생성을 해줍니다.


이제 생성한 사용자로 들어가 줍니다.

Spring Boot로 S3 버킷에 Access 하기 위해서는 액세스 키를 만들어줘야합니다.

액세스 키 만들기를 클릭해줍니다.

사용 사례는 직접 적용되는 옵션은 아니고 대안 사항을 안내해주는 역할을 합니다.

어떤 걸 골라도 상관 없기 때문에 원하시는 항목을 선택하고 다음으로 넘어가줍니다.

설명 태그는 선택 사항이므로 공백으로 넘어가겠습니다.

이제 액세스 키 만들기를 클릭하시면 액세스키가 발급됩니다.

생성된 액세스의 비밀 액세스 키는 한 번만 확인 가능하고 그 이후로는 확인이 불가능합니다.

때문에 꼭❗❗ .csv 파일을 다운로드 하시는 것을 추천드립니다.


Spring Boot로 AWS S3 버킷에 이미지 올리기

이제 Spring Boot로 이미지 업로드를 위한 Serivce 클래스와 Controller 클래스를 작성해줍시다.

의존성

먼저 build.gradle에서 다음 의존성을 추가해야합니다.

implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
implementation 'com.amazonaws:aws-java-sdk-s3'

1. org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE
Spring Cloud AWS는 AWS를 사용하는 Spring 애플리케이션을 쉽게 구성할 수 있도록 지원하는 의존성입니다. 이 의존성을 AWS를 사용하는 데 필요한 기본적인 설정과 자동 구성 기능을 포함합니다.

2. com.amazonaws:aws-java-sdk-s3
AWS S3 서비스와 통신하기 위한 AWS java SDK의 일부입니다. S3 버킷에 파일을 업로드하고 관리하는 데 필요한 API와 클라이언트를 제공합니다.


application.properties, application.yml

Config 클래스에 입력할 정보를 application.properties이나 application.yml에 작성해 줍니다.

accessKey, secretKey와 같은 보안상 민감한 정보들은 코드에 노출될 경우 위험할 수 있으니 반드시 application.properties에 작성하시고, .gitignore로 등록하여 외부에 노출되지 않도록 합시다.

applcation.properties

spring.servlet.multipart.max-file-size=8MB
spring.servlet.multipart.max-request-size=128MB

cloud.aws.credentials.accessKey=example
cloud.aws.credentials.secretKey=example
cloud.aws.s3.bucketName=your-bucket-name
cloud.aws.region.static=ap-northeast-2

application.yml

spring:
  servlet:
    multipart:
      max-file-size: 8MB
      max-request-size: 128MB

cloud:
  aws:
    credentials:
      accessKey: example
      secretKey: example
    s3:
      bucketName: your-bucket-name
    region:
      static: ap-northeast-2

(아래 설명은 application.properties를 기준으로 작성되었습니다.)

1. spring.servlet.multipart.max-file-size=
이 설정은 한 번에 업로드 할 수 있는 파일의 최대 크기를 지정합니다. 저는 한 이미지의 크기를 8MB로 지정하였습니다.

2. spring.servlet.multipart.max-request-size=
이 설정은 Http 요청의 최대 크기를 지정합니다. 요청이 전송 될 때 포함된 모든 파일의 크기를 지정합니다. 저는 최대 15개의 이미지를 업로드 받을 예정이고, 추가적인 데이터를 고려해 128MB를 지정하였습니다.

3. cloud.aws.~
cloud.aws.credentials.accessKey=example
AWS 서비스를 사용하기 위해 필요한 액세스 키를 지정합니다.
cloud.aws.credentials.secretKey=example
AWS 서비스를 사용하기 위해 필요한 비밀 키를 지정합니다.
cloud.aws.s3.bucketName=your-bucket-name
AWS 서비스에 사용 될 특정 S3 버킷의 이름을 지정합니다.
cloud.aws.region.static=ap-northeast-2
AWS 서비스를 사용할 때 지정할 리전을 설정합니다. 저는 ap-northeast-2(서울)로 지정하였습니다.


S3Config 클래스

S3 클라이언트 인스턴스를 생성하고 Spring의 의존성 주입 시스템에 등록합니다.

@Configuration
public class S3Config {

    @Value("${cloud.aws.credentials.accessKey}")
    private String accessKey;

    @Value("${cloud.aws.credentials.secretKey}")
    private String secretKey;

    @Value("${cloud.aws.region.static}")
    private String region;

    @Bean
    public AmazonS3 amazonS3() {
        BasicAWSCredentials awsCreds = new BasicAWSCredentials(accessKey, secretKey);
        return AmazonS3ClientBuilder.standard()
                .withRegion(region)
                .withCredentials(new AWSStaticCredentialsProvider(awsCreds))
                .build();
    }
}

@Configuration 어노테이션은 해당 클래스를 Spring Config Class로 지정합니다. 이 클래스에서 정의된 메서드들은 스프링 컨테이너에 의해 관리되는 Bean을 생성합니다.

@Value 어노테이션은 application.properties에서 해당하는 값을 읽어와 원하는 변수에 주입합니다.
accessKeysecretkey, region에 해당하는 값을 주입해줍시다.

@Bean 어노테이션은 지정된 메소드가 생성하는 객체를 스프링 애플리케이션 컨텍스트Bean으로 등록됩니다. 이렇게 등록된 Bean은 전역에서 사용할 수 있습니다.

BasicAWSCredentials(accessKey, secretKey) 클래스는 accessKey와 secretKey를 사용하여 AWS 서비스에 접근할 수 있는 자격 증명 객체를 생성합니다.
그리고 .withRegion(region)으로 AWS 리전을 설정합니다.

.withCredentials(new AWSStaticCredentialsProvider(awsCreds)) 는 생성한 자격 증명을 클라이언트에 제공하는 역할을 합니다.

.build() 로 AmazonS3 클라이언트 인스턴스를 생성합니다.


S3Service 클래스

이제 Service 클래스로 S3 버킷에 파일 업로드 기능을 구현합니다.

@Service
public class S3Service {

    @Autowired
    private AmazonS3 amazonS3;

    @Value("${cloud.aws.s3.bucketName}")
    private String bucketName;

    public void uploadFile(MultipartFile multipartFile) throws IOException {
        File file = multiPartFileToFile(multipartFile);
        String fileName = System.currentTimeMillis() + "_" + multipartFile.getOriginalFilename();
        amazonS3.putObject(new PutObjectRequest(bucketName, fileName, file));
        file.delete();
    }

    private File multiPartFileToFile(MultipartFile file) throws IOException {
        File convertedFile = new File(file.getOriginalFilename());
        try (FileOutputStream fileOutputStream = new FileOutputStream(convertedFile)) {
            fileOutputStream.write(file.getBytes());
        }
        return convertedFile;
    }
}

먼저 @Autowride 어노테이션으로 Spring이 자동으로 AmazonS3 타입의 Bean을 이 필드에 주입할 수 있게합니다.

Config 클래스와 마찬가지로 @Value 어노테이션으로 application.propertiesbucketName을 주입해 줍니다.

uploadFile 메소드를 만들어줍니다. 이 메소드로 MultipartFile 타입의 파일을 받아 처리하게 됩니다.

System.currentTimeMillis() + "_" + file.getOriginalFilename() 해당 기능은 fileName에 파일명 앞에 업로드 시간을 추가해 전송합니다. 추후에 일정 기간 전에 업로드 된 파일을들 관리하기 위해 추가하였습니다.


UploadController 클래스

이제 Http 요청을 처리하고 파일을 업로드하기 위해 Controller 클래스를 작성해야 합니다.

@RestController
public class InformationController {

    @Autowired
    private S3Service s3Service;

    @PostMapping("/upload")
    public ResponseEntity<String> uploadFile(@RequestParam("images") MultipartFile[] files) {
    	if (files.length > 15) {
            	return ResponseEntity.badRequest().body("Error: Cannot upload more than 15 files at a time.");
        	}
        for (MultipartFile file : files) {
            try {
                s3Service.uploadFile(file);
            } catch (IOException e) {
                return ResponseEntity.internalServerError().body("File upload failed: " + e.getMessage());
            }
        }
        return ResponseEntity.ok("Files uploaded successfully");
    }
}

@PostMapping("/upload") /upload 경로로 POST 요청이 들어오는 경우 해당 메서드가 처리하도록 설정합니다.

@RequestParam("images") MultipartFile[] files 는 images 파라미터로 요청이 올 경우 그 파일들을 배열의 형태로 받습니다.

if문을 추가해 배열의 길이가 15를 넘을 경우 오류 메세지와 함께 400 Bad Request를 반환합니다.

for문을 통해 받은 파일 배열을 반복 처리합니다. 여기서 s3Service.uploadFile(file); 을 통해 위에 작성한 Service 클래스의 메소드를 불러와 업로드를 처리합니다.

return ResponseEntity.internalServerError().body("File upload failed: " + e.getMessage()); 는 파일 업로드 중 예외가 발생하면 서버 오류를 반환하는 역할을 합니다.


Postman을 통해 작동 확인하기

이제 모든 S3에 파일 업로드를 위한 모든 기본적인 구현이 완료되었습니다.

Postman을 통해 정상적으로 파일 업로드가 되는지 확인하겠습니다.

엔드포인트를 /upload로 설정합니다.

Postman에서 multipart/form-data로 Http 요청을 보내기 위해서는 다음과 같이 설정해 주어야합니다.

Body 탭에서 form-data를 선택해줍니다.

저는 @RequestParamimages로 설정하였기 때문에 Key에 images를 넣어줍니다.

그리고 옆에 Text를 File로 바꾸셔야 합니다.

Value에 원하는 파일을 업로드 하시면 됩니다.

이제 Send 버튼을 누르시면 정상적으로 파일이 업로드 된 것을 확인할 수 있습니다.

AWS S3에 접속에 업로드된 파일을 확인해줍니다.

파일 이름에 업로드 시간도 정상적으로 작성되는 것을 확인할 수 있습니다.

profile
야생형 Backend 개발일지

0개의 댓글