spring boot로 서비스를 구축할 때 반드시 구축해야 할 것이 정적 파일 업로더이다.
이번 포스팅에는 AWS S3와 연동하여 이미지를 업로드 해볼 것이다.
AWS S3 bucket 생성
[AWS -> S3 -> 버킷 만들기]로 들어와 새로운 버킷을 만든다.
버킷을 만들 때, 권한을 수정한다. 권한을 수정하지 않고 파일을 업로드하면, 퍼블릭 액세스가 차단된 상태이기 때문에 아래의 403 에러가 발생한다.
Access Denied (Service: Amazon S3; Status Code: 403; Error Code: AccessDenied; Request ID: ~~~ ; S3 Extended Request ID: ~~~)
따라서 예제에서는 퍼블릭 엑세스 차단 해제를 하고, 버킷 정책을 설정하는 방법을 사용할 것이다.
버킷을 생성한 뒤, [권한 -> 버킷 정책]으로 들어가서 정책을 설정한다.
{
"Version": "2012-10-17",
"Id": "Policy1580913852302",
"Statement": [
{
"Sid": "Stmt1580913849359",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::[버킷 이름]/*"
}
]
}
AWS IAM 사용자 생성
생성한 S3에 접근하기 위해서는 권한이 필요하다. IAM 사용자로 등록하여 접근을 할 것이다.
권한 설정에서 s3를 검색하여 AmazonS3FullAccess를 선택한다.
사용자를 생성하면 Access key와 Secret key를 부여받는다. 이것을 이제 메모장에 복사해 놓고 나중에 사용할 것이다.
build.gradle
의존성을 주입 받기 위해 build.gradle에 아래의 코드를 추가한다.
repositories {
mavenCentral()
maven { url 'https://repo.spring.io/libs-milestone'}
}
dependencies {
...
implementation 'org.springframework.cloud:spring-cloud-starter-aws'
...
}
dependencyManagement {
imports {
mavenBom 'org.springframework.cloud:spring-cloud-aws:2.2.1.RELEASE'
}
}
S3Upload.java
S3에 정적 파일을 올리는 기능을 하는 S3Upload.java를 생성한다.
package com.leveloper.chacha.springbootchacha.aws;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import com.amazonaws.services.s3.model.PutObjectRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Optional;
@Slf4j
@RequiredArgsConstructor
@Component
public class S3Uploader {
private final AmazonS3Client amazonS3Client;
@Value("${cloud.aws.s3.bucket}")
private String bucket;
public String upload(MultipartFile multipartFile, String dirName) throws IOException {
File uploadFile = convert(multipartFile)
.orElseThrow(() -> new IllegalArgumentException("MultipartFile -> File로 전환이 실패했습니다."));
return upload(uploadFile, dirName);
}
private String upload(File uploadFile, String dirName) {
String fileName = dirName + "/" + uploadFile.getName();
String uploadImageUrl = putS3(uploadFile, fileName);
removeNewFile(uploadFile);
return uploadImageUrl;
}
private String putS3(File uploadFile, String fileName) {
amazonS3Client.putObject(new PutObjectRequest(bucket, fileName, uploadFile).withCannedAcl(CannedAccessControlList.PublicRead));
return amazonS3Client.getUrl(bucket, fileName).toString();
}
private void removeNewFile(File targetFile) {
if (targetFile.delete()) {
log.info("파일이 삭제되었습니다.");
} else {
log.info("파일이 삭제되지 못했습니다.");
}
}
private Optional<File> convert(MultipartFile file) throws IOException {
File convertFile = new File(file.getOriginalFilename());
if(convertFile.createNewFile()) {
try (FileOutputStream fos = new FileOutputStream(convertFile)) {
fos.write(file.getBytes());
}
return Optional.of(convertFile);
}
return Optional.empty();
}
}
S3Controller.java
S3Upload를 테스트할 Controller를 생성한다.
package com.leveloper.chacha.springbootchacha.aws;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
@RequiredArgsConstructor
@Controller
public class S3Controller {
private final S3Uploader s3Uploader;
@GetMapping("/test")
public String index() {
return "test";
}
@PostMapping("/upload")
@ResponseBody
public String upload(@RequestParam("data") MultipartFile multipartFile) throws IOException {
return s3Uploader.upload(multipartFile, "static");
}
}
test.html
<!DOCTYPE HTML>
<html>
<head>
<title> SpringBoot & AWS S3</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<script
src="https://code.jquery.com/jquery-3.3.1.js"
integrity="sha256-2Kok7MbOyxpgUVvAk/HJ2jigOSYS2auK4Pfzbm7uH60="
crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
</head>
<body>
<h1>
S3 이미지 업로더
</h1>
<div class="col-md-12">
<div class="col-md-2">
<form>
<div class="form-group">
<label for="img">파일 업로드</label>
<input type="file" id="img">
</div>
<button type="button" class="btn btn-primary" id="btn-save">저장</button>
</form>
</div>
<div class="col-md-10">
<p><strong>결과 이미지입니다.</strong></p>
<img src="" id="result-image">
</div>
</div>
<script>
$('#btn-save').on('click', uploadImage);
function uploadImage() {
var file = $('#img')[0].files[0];
var formData = new FormData();
formData.append('data', file);
$.ajax({
type: 'POST',
url: '/upload',
data: formData,
processData: false,
contentType: false
}).done(function (data) {
$('#result-image').attr("src", data);
}).fail(function (error) {
alert(error);
})
}
</script>
</body>
</html>
테스트
파일을 선택하여 [저장] 버튼을 누르면 정상적으로 업로드가 된다.
S3에 들어가서 정상적으로 업로드된 것을 확인할 수 있다.
참고
'Spring Boot' 카테고리의 다른 글
[스프링] 배포용 서버에 데이터베이스 Schema 및 Data 초기 설정하기 (3) | 2020.02.01 |
---|---|
[스프링] 프록시 기반 AOP(Aspect Oriented Programming) 정리 (0) | 2020.01.25 |
[스프링] IoC(Inversion of Control), DI(Dependency Injection), Spring Container, Bean 정리 (0) | 2020.01.25 |
[스프링] 오픈 api 사용해서 데이터 가져오기 (네이버 영화 검색 api) (7) | 2020.01.14 |
[스프링] Spring 웹 계층 (2) | 2020.01.03 |