10. AWS EC2 + Spring boot (8) - AWS S3 이미지 파일 업로드

2021. 3. 4. 20:05프로젝트/Salle(살래) 중고거래 웹

업데이트: 210311

웹사이트에 필요한 외부 파일(img, txt)은 로컬 폴더에서 관리하고 있었습니다. 하지만 서버 배포를 AWS EC2를 통해 ubuntu에서 하므로 Amazon S3를 이용하기로 결정했습니다. 주요 결정원인은 AmazonS3 SDK 메서드(putObject, deleteObject)를 이용해 추가/삭제 관리를 편리하게 하고 로컬 메모리를 아낄 수 있기 때문입니다.

S3 설정방법


1. S3 버킷 만들기

AWS 튜토리얼은 공식 사이트를 참고해도 되지만, 이코딩님의 생활코딩을 추천드립니다. AWS 강좌가 잘 정리되어 있습니다.

참고자료:  AWS S3 생활코딩 강좌

 

AWS S3 - 생활코딩

본 수업에서는 AWS의 파일서버 서비스인 S3(Simple Storage Serivce)에 대해서 다룹니다. 

www.opentutorials.org


2. AWS IAM 생성하기

Spring boot에서 AWS S3를 사용하기 위해선 AWS IAM(Identity Access & Management)를 생성해야 합니다. IAM란 S3, RDS, EC2 등 AWS 서비스에 Access가 필요한 경우 부여된 Credential Key를 사용해 권한자만 접근할 수 있도록 보안기능이 됩니다. AWS 동영상으로 튜토리얼이 잘 구성되어 있어 따라하시면 됩니다.

참고자료: AWS IAM 링크 

 

AWS Identity and Access Management(IAM) - Amazon Web Services

AWS Identity and Access Management(IAM)를 시작하십시오. AWS IAM은 AWS 리소스에 대한 액세스 권한을 관리하는 데 도움이 됩니다. IAM을 사용하면 AWS 리소스를 사용할 수 있는 사람(인증)과 이들이 사용할 수

aws.amazon.com


3. Spring boot 설정

AWS S3와 연결하기 위해 Spring boot application.properties에 어떤 설정을 추가해야 하는지, 파일 관리를 위해 어떤 AmazonS3 Java  API를 사용해야 하는지 간략히 소개하겠습니다.

 

방법은 jojoldu님 블로그를 참고하면 좋습니다. 다만 프로그래밍 언어는 변화가 빠르고 몇 년전 작성하신 글이라 S3Upload.Java의 AmazonS3Client 클래스는 Deprecated된 API 라는 것은 참고하셔야 합니다. 그래서 저는 AWS SDK API Doc 문서를 참고해 사용할 객체를 직접 빌드해주었습니다. 혹시 오류가 있다면 제보주시면 감사하겠습니다.

참고자료: jojoldu님 블로그(2.배포 환경에서 사용하기), AWS SDK API Doc

 

AmazonS3 (AWS SDK for Java - 1.11.967)

Completes a multipart upload by assembling previously uploaded parts. You first upload all parts using the uploadPart(UploadPartRequest) method. After successfully uploading all individual parts of an upload, you call this operation to complete the upload.

docs.aws.amazon.com

 

배포용 코드 - application.properties

로컬과 배포용 코드의 차이는 Credential Key들을 처리하는 방식입니다. 위의 jojoldu님 블로그 2. 배포환경에서 사용하기에 자세히 나와있기에 간략하게 넘어가겠습니다. 다행이도 AWS EC2에 IAM 등록해주면 서버에서 S3에 접근할 때 따로 Credential을 지정해줄 필요가 없습니다. cloud.aws.stack.auto는 Spring Cloud가 실행될 때 자동으로 AWS cloudformation을 실행시키지 '않도록' 해주는 설정입니다. (* cloudformation이란 AWS에서 제공하는 리소스 관리 툴입니다.) ...credentials.instanceProfile은 EC2 인스턴스에 등록한 IAM credential을 사용하겠다는 설정입니다.

1
2
3
4
cloud.aws.s3.bucket= sallestorage
cloud.aws.region.static= ap-northeast-2
cloud.aws.stack.auto= false
cloud.aws.credentials.instanceProfile= true
cs

 

로컬용 코드 - AWS application.properties

Confidential한 데이터는 절대 오픈소스로 공유돼선 안됩니다. 실수로 한번 git commit push 했다가 AWS에서 이메일과 전화까지 받았습니다.ㅎㅎ... 다행히 바로 Delete했던터라 AWS 계정 비번 변경과 AWS IAM AccessKey 재발급 후 해결됐습니다.

따라서 AWS Credential 정보용  properties를 하나 더 파일을 만드셔야 합니다. 그리고 Git Commit 시 gitignore에 추가해주셔야 합니다.  --> 참고자료 jojoldu님 글에 잘 설명돼 있습니다.

1
2
3
4
5
6
7
8
cloud:
  aws:
    credentials:
      accessKey: ****
      secretKey: ****
    s3:
      bucket: sallestorage
 
cs

4. AWS S3 파일 업로드

설정은 끝났으니 이제 파일을 업로드 해볼 차례입니다. 먼저 AmazonS3Config 클래스를 만들어줍니다. 애플리케이션 S3 접근은 모두 이 클래스에서 처리할 겁니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.example.salle.application;
 
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
 
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
 
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import com.amazonaws.services.s3.model.DeleteObjectRequest;
import com.amazonaws.services.s3.model.PutObjectRequest;
 
import lombok.RequiredArgsConstructor;
 
@Service
@RequiredArgsConstructor    
public class AmazonS3Service {
    
    private final AmazonS3 amazonS3;
    
    @Value("${cloud.aws.s3.bucket}")
    private String bucket;
    
    
    public String searchIcon() {
        String fileName = "static/img/searchicon.png";
        return amazonS3.getUrl(bucket, fileName).toString();
    }
    
    
    public String uploadImg(String bucket, String fileName, MultipartFile multiFile) throws IOException {
        File uploadFile = convert(multiFile);
        amazonS3.putObject(new PutObjectRequest(bucket, fileName, uploadFile).withCannedAcl(CannedAccessControlList.PublicRead));
        return "uploadS3 success";
    }
    
    
    public File convert(MultipartFile multiFile) throws IOException {
        File uploadFile = new File(multiFile.getOriginalFilename());
        FileOutputStream fos = new FileOutputStream(uploadFile);
        fos.write(multiFile.getBytes());
        fos.close();
        return uploadFile;
    }
 
 
    public void deleteFile(String bucket, String deleteFile) {
        amazonS3.deleteObject(new DeleteObjectRequest(bucket, deleteFile));
    }
 
}
 
cs

 

@Service : @Component와 유사해 Spring Framework가 @Service를 붙인 클래스에 Bean을 부여해 애플리케이션 내에서 인스턴스로 사용할 수 있게 해줍니다. 추가적으로 DB접근이나 복잡한 기능들을 처리하는 service 특징을 지닌 클래스에 붙이는 Annotation 입니다.

 

@RequiredArgsConstructor : Lombok 라이브러리의 Annotation 입니다. 초기화(uninitialized) 되지 않은 final 필드나 @NonNull로 설정한 필드는 하나의 parameter 당 하나의 생성자가 @RequiredArgsConstructor에 의해 자동 생성됩니다. 

 

getUrl() : AWS S3 파일 URL을 가져오는 메서드 입니다(다운로드 X). 중고거래 플랫폼에선 S3에 업로드한 이미지 파일을 가져와야 할 경우가 많이 발생합니다. 상품 사진을 보고 살지 말지를 결정하기 때문이죠. 성공적으로 가져오기 위해서 반드시 파일에 퍼블릭 엑세스(읽기) 권한이 있는지 확인해야 합니다. AWS S3 Console에서 가능합니다.

 

putObject(String bucket, String Key, File file) : AWS SDK가 제공하는 S3 API 입니다. 파일을 업로드 할 때에 사용해주는데요, 길어서 잘 보이지 않지만 뒤에 .withCannedAcl() 메서드는 추후 URL로 이미지 파일을 HTML에 추가할 때 접근이 가능하도록 PublicRead로 설정해주는 기능을 합니다. new PutObjectRequest() 객체를 써 준것도 바로 이런 이유 때문입니다.

 

convert 메서드는 제가 MultipartFile로 된 이미지 파일을 S3에 업로드 할 수 있는 File 형태로 convert 해준 것 인데요. 원본 파일 Name으로 새로운 File 객체를 만든 다음 FileOutputStream을 사용해 원본 파일의 정보들을 byte 형태로 새로운 File에 write() 해주면 됩니다.   

 

deleteObject() : putObject와 방식은 같으며, 업로드 되어있는 파일을 삭제할 수 있는 메서드입니다.