AWS

[AWS] AWS S3 - Spring 파일 업로드

chaego 2024. 4. 11. 12:36

AWS S3를 이용해 파일업로드 기능을 구현해보자 ❕

 

 

지난 글에서 S3 버킷을 생성하고 권한을 설정했다. 이번에는 파일 업로드하는 기능을 코드로 구현해보려고 한다.

 

AWS S3 - 개념 및 버킷 설정

AWS에서 제공하는 서비스인 S3를 이용해보자❕ 1. AWS S3(Simple Storage Service) S3는 AWS에서 제공하는 클라우드 기반의 객체 스토리지 서비스이다. 이미지, 동영상 및 여러 형태의 파일을 저장하고 관리

chchaego.tistory.com

출처 : https://aws.amazon.com/ko/s3/

 

 

 

1. 구현


구현 환경

  • Spring
  • Maven

 

1-1. 의존성 추가 및 application.properties 설정

pom.xml

<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-aws -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-aws</artifactId>
    <version>2.2.5.RELEASE</version>
</dependency>

 

application.properties

# == AWS ACCESS SETTING ==
cloud.aws.credentials.access-key={ACCESS_KEY}
cloud.aws.credentials.secret-key={SECRET_KEY}

#== AWS S3 Bucket Setting ==
cloud.aws.s3.bucket={BUCKET_NAME}
cloud.aws.region.static={REGION}
cloud.aws.stack.auto=false
  • IAM을 생성해 발급받은 Access Key와 Secret Key를 채워 넣으면 된다.
  • spring-cloud-starter-aws는 기본적으로 서비스가 *CloudFormation 스택 내에서 실행된다고 가정하기 때문에, 설정한 CloudFormation이 없으면 해당 내용을 사용하지 않도록 `cloud.aws.stack.auto=false`로 설정해줘야 한다.

 

 

* CloudFormation?

AWS에서 제공하는 대표적인 IaC 기반의 서비스. CloudFormation을 사용하면 AWS의 EC2, Lambda 등과 같은 리소스를 수동으로 생성할 필요 없이, 템플릿(코드)으로 구성하고 스택을 생성하여 인프라를 구성할 수 있다.

 

 

1-2. FileUploaderApi 컨트롤러 생성

@RestController
@RequestMapping("/api/v1/upload")
public class FileUploaderApi {

    @Autowired
    private FileUploaderService fileUploaderService;

    @PostMapping("/cloud")
    public ResponseEntity<?> uploadFilesToCloud(@RequestParam("file") MultipartFile file) {
        Message message = new Message();

        try{
            message.setData(fileUploaderService.uploadFileToCloud(file, "uploads"));
            message.setStatus(HttpStatus.OK);
            message.setMessage("success");
        } catch(Exception e) {
            message.setStatus(HttpStatus.BAD_REQUEST);
            message.setMessage("error");
        }

        return new ResponseEntity<>(message, message.getStatus());
    }
}
  • /api/v1/upload/cloud로 요청이 들어오면 파라미터 file로 넘어오는 MultipartFile를 받아 서비스단에 넘긴다.
  • uploadFileToCloud()의 두번째 인자 “uploads”는 S3에서 저장되는 디렉토리를 명시한 것이다.

 

 

1-3. FileUploaderService 서비스 생성

@Service
@RequiredArgsConstructor
public class FileUploaderService {
    private final AmazonS3Client s3Client;

    @Value("${cloud.aws.s3.bucket}")
    public String bucket;

        // fileName(Key)은 중복되지 않도록 랜덤한 값을 추가
    public String uploadFileToCloud(MultipartFile file, String dir) throws IOException {
        String fileName = dir + "/"
                + UUID.randomUUID().toString().replaceAll("-", "")
                + file.getOriginalFilename();

        return putS3(file, fileName);
    }

        // s3에 파일 업로드
    private String putS3(MultipartFile file, String fileName) {
        ObjectMetadata objMeta = new ObjectMetadata();
        objMeta.setContentType(file.getContentType());
        objMeta.setContentLength(file.getSize());

        try {
            s3Client.putObject(new PutObjectRequest(bucket, fileName, file.getInputStream(), objMeta)
                    .withCannedAcl(CannedAccessControlList.PublicRead));   // 오브젝트 권한 설정
        } catch (IOException e) {
            throw new FileUploadException("S3 파일 업로드 실패");
        }
        // 업로드된 파일의 S3 URL 주소를 반환
        return s3Client.getUrl(bucket, fileName).toString();
    }
}
  • spring-cloud-starter-aws가 S3 관련 Bean을 자동 생성해주기 때문에, 설정 코드 없이도 AmazonS3Client를 DI 받은 후 관련된 기능들을 사용할 수 있다. (application.properties에 미리 값을 설정해줘야 한다)
  • 버킷 내에서 동일한 키 값이 업로드되면 덮어 씌워버리기 때문에 fileName에 랜덤한 값을 추가해줬다. (버전 관리를 설정하면 동일한 키 값 사용 가능)
  • S3에 파일을 업로드하는 객체 `PutObjectRequest`를 만들어 AmazonS3Client의 `putObject()`를 통해 저장한다.
  • PutObjectRequest는 다음을 참고해서 생성할 수 있다. 나는 세번째 생성자를 통해 `PutObjectRequest`를 만들었다. (AbstractPutObjectRequest는 AmazonWebServiceRequest의 자식 클래스)
    • `CannedAccessControlList.PublicRead` : 외부에 공개할 이미지 이므로, 해당 파일에 public read 권한을 설정했다.

 

 

1-4. index.html 생성

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Spring - AWS S3</title>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
    <!-- 업로드 form -->
    <form id="img-uploader-form" enctype="multipart/form-data" method="post">
        <input id="img-uploader" type="file" name="uploadfile" accept="image/*" onchange="checkFileType(this);" />
        <input type="button" value="확인" onclick="uploadFile();" />
    </form>

    <!-- 결과 이미지 -->
    <div>
        <img src="" id="uploaded-img" style="width: 150px; height: 150px">
    </div>

    <script>
        // 업로드 파일 확장자 체크
        function checkFileType(obj) {
            var fileKind = obj.value.lastIndexOf('.');
            var type = obj.value.substring(fileKind+1, obj.length);
            var fileType = type.toLowerCase();
            var allowType = ['jpg', 'gif', ,'png','jpeg','bmp','tif'];


            if(allowType.indexOf(fileType) == -1){
                alert('Only image file can be uploaded.');
                obj.value = '';
            }
        }

        function uploadFile() {
            var formData = new FormData();

            var file = $("#img-uploader")[0].files[0];
            var urlPath = "/api/v1/upload/cloud";
            formData.append("file", file);

            $.ajax({
                type: "POST",
                url: urlPath,
                data: formData,
                enctype: 'multipart/form-data',
                processData: false,
                contentType: false,
                cache: false,
                success: function(res) {
                    $('#uploaded-img').attr("src", res.data)
                },
                error: function(e) {
                    console.log("File Upload Error : ", e);
                }
            });
        }
    </script>
</body>
</html>
  • 업로드가 성공된다면 응답값으로 조회된 이미지 경로를 설정한다. $('#uploaded-img').attr("src", res.data)

 

 

1-5. 결과 확인

 

  • /api/v1/upload/cloud에 업로드 요청을 하면
  • 정상적으로 S3 버킷 ‘chaego-bucket’의 /uploads에 저장되고, 이미지 결과가 표시된다.

 

 

 

2.  마무리

구현 과정은 매우 간단하다. 실무에서는 파일 사이즈 제한, 다중 파일 업로드 처리 및 S3를 안전하게 사용하기 위해 설정해야 할 기능이 많을 것이다. 찾아보니, S3에 Cloudfront를 연결해 S3를 퍼블릭으로 공개하지 않고 Cloudfront를 통해서 S3에 접근할 수 있도록 하는 방법이 있다고 한다. 이런 기능들을 잘 활용하는 것이 보다 더 안전하게 서비스를 운영할 수 있을 것이다.

 

출처 : https://techblog.woowahan.com/6217

 

 

 

 

 

참고자료 😃

https://jojoldu.tistory.com/300

https://techblog.woowahan.com/11392/