프로젝트 진행 시 이미지를 서버에 저장해야 할 경우가 많다. AWS의 S3를 이용하면 이미지를 저장하고 해당 링크를 DB에 저장하여 효율적으로 관리할 수 있다. 이를 통해 서버 용량을 절약하고, 비용 절감 효과를 얻을 수 있다.
그럼 AWS의 S3를 활용하여 이미지를 저장하고 확인해보자.
1. S3 버킷 생성하기
1.버킷 생성하기
AWS > S3 > 버킷 > 버킷만들기 클릭
또는 밑의 주소로 접속합니다.
https://ap-northeast-2.console.aws.amazon.com/s3/bucket/create?region=ap-northeast-2#
2. 버킷 생성
버킷을 생성하기 위해 이름을 만들어 줍니다.
버킷은 디렉토리/폴더 개념으로 이해하시면 됩니다.
그리고 버킷의 퍼블릭 엑세스 차단을 해제해줍니다.
3. 버킷 정책 설정
S3 > 버킷 > 권한 > 버킷 정책 > 편집
퍼블릭IP에서 데이터를 조회할 수 있도록 정책을 설정해셔야 합니다.
그 후에 정책 설정을 입력해줍니다. 저는 밑과 같이 입력해 주었고, < 버킷명 > 여기서 이 안에 만드신 버킷 이름을 넣어줍니다. ex) "arn:aws:s3:::asdf/*" 또한 주석도 삭제해주셔야 합니다.
{
"Version": "2012-10-17",
"Id": "Policy1464968545158",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow", // 허용
"Principal": "*",
"Action": "s3:GetObject", // 객체 읽기 권한
"Resource": "arn:aws:s3:::<버킷명>/*"
},
{
"Sid": "DenyOtherAccess",
"Effect": "Deny", // 차단
"Principal": "*",
"Action": "s3:PutObject", // 객체 업로드 권한
"NotResource": [
"arn:aws:s3:::<버킷명>/*.jpg",
"arn:aws:s3:::<버킷명>/*.png",
"arn:aws:s3:::<버킷명>/*.jpeg",
"arn:aws:s3:::<버킷명>/*.gif"
] // 해당 확장자를 가지지 않은 객체
}
]
}
2. S3 사용자(I AM 자격증명) 설정
1. 사용자 생성을 설정합니다.
AWS > IAM > 엑세스관리 > 사용자 > 사용자 생성 또는 밑의 링크를 클릭하시면 됩니다.
https://ap-northeast-2.console.aws.amazon.com/access-analyzer/home?region=ap-northeast-2#/
2. 사용자 이름 설정
3. 권한 설정
권한 정책에서 AmazonS3FullAccess를 설정해줍니다.
4. 사용자 생성
3. 엑세스 키 생성
1. AWS > IAM > 사용자 > 사용자 이름(생성된 사용자) > 엑세스 키 만들기를 선택합니다.
2. 엑세스 키모범 사례 및 대안
저는 로컬 코드를 사용했습니다. (아무것이나 선택 가능)
3. 엑세스키, 비밀 엑세스키 확인
사용자가 생성되었으면 엑세스키를 생성할 수 있습니다. 여기서 비밀 엑세스 키는 다시 볼수 없기 때문에 CSV파일로 받아두시는 것을 추천드립니다.
4. Spring 설정
1. build.gradle 의존성 추가
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
2. application.yml 추가
cloud:
aws:
credentials:
accessKey: ${AWS_ACCESS_KEY_ID} # AWS IAM AccessKey 적기
secretKey: ${AWS_SECRET_ACCESS_KEY} # AWS IAM SecretKey 적기
s3:
bucket: 버킷명 # 예) myawsbucket
dir: S3 디렉토리 이름 # 예) /home
region:
static: ap-northeast-2
stack:
auto: false
3. S3Config 설정
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class S3Config {
@Value("${cloud.aws.credentials.access-key}")
private String accessKey;
@Value("${cloud.aws.credentials.secret-key}")
private String secretKey;
@Value("${cloud.aws.region.static}")
private String region;
@Bean
public AmazonS3Client amazonS3Client() {
BasicAWSCredentials awsCreds = new BasicAWSCredentials(accessKey,secretKey);
return (AmazonS3Client) AmazonS3ClientBuilder.standard()
.withRegion(region)
.withCredentials(new AWSStaticCredentialsProvider(awsCreds))
.build();
}
}
4. S3 Service , S3ServiceImpl
S3 Service
package com.wms.global.util.s3;
import org.springframework.web.multipart.MultipartFile;
public interface S3Service {
String uploadFile(MultipartFile multipartFile, String url);
boolean delete(String fileUrl);
}
S3 ServiceImpl
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.wms.global.exception.exception.S3Exception;
import com.wms.global.exception.responseCode.S3ExceptionResponseCode;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.UUID;
@RequiredArgsConstructor
@Service
public class S3ServiceImpl implements S3Service {
private final AmazonS3 amazonS3;
@Value("${cloud.aws.s3.bucket}")
private String bucket;
// 파일 업로드
@Override
public String uploadFile(MultipartFile multipartFile, String url) {
String fileName = url+"/"+ UUID.randomUUID().toString() + "_" + multipartFile.getOriginalFilename();
ObjectMetadata objMeta = new ObjectMetadata();
objMeta.setContentLength(multipartFile.getSize());
try {
amazonS3.putObject(bucket, fileName, multipartFile.getInputStream(), objMeta);
} catch (IOException e) {
throw new S3Exception(S3ExceptionResponseCode.UPLOAD_EXCEPTION,"S3 업로드 중 에러가 발생");
}
return amazonS3.getUrl(bucket, fileName).toString();
}
// 파일 삭제
@Override
public boolean delete(String fileUrl) {
try {
String[] temp = fileUrl.split(".com/");
String fileKey = temp[1];
amazonS3.deleteObject(bucket, fileKey);
return true;
} catch (Exception e) {
return false;
}
}
}
5. controller
밑처럼 사진 파일을 받을 때에는 @RequestPart로 받으시면 됩니다.
@Slf4j
@RequiredArgsConstructor
@RestController
@RequestMapping("/api/v1/users")
public class UserController
private final FeedService feedService;
@PostMapping("/register")
public ResponseEntity<ApiResponse<?>> register(@Validated @RequestPart("signUpRequest") SignUpRequestDTO signUpRequestDTO, @RequestPart(value = "profileImage", required = false) MultipartFile profileImage) {
...
}
}
5. Postman,S3 테스트
localhost:8080/api/v1/users/register 쪽으로 회원가입 할때 필요한 json 타입의 request요청과 multipart/form-data 타입의 사진을 넣어주고 동작시키면 잘 작동이 된다.
이미지가 잘 들어온 것을 확인할 수 있다.
6. Service에서 UUID적용 전 코드(문제점) 사례
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.wms.global.exception.exception.S3Exception;
import com.wms.global.exception.responseCode.S3ExceptionResponseCode;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
@RequiredArgsConstructor
@Service
public class S3ServiceImpl implements S3Service {
private final AmazonS3 amazonS3;
@Value("${cloud.aws.s3.bucket}")
private String bucket;
// 파일 업로드 (UUID 미적용)
@Override
public String uploadFile(MultipartFile multipartFile, String url) {
String fileName = url + "/" + multipartFile.getOriginalFilename();
ObjectMetadata objMeta = new ObjectMetadata();
objMeta.setContentLength(multipartFile.getSize());
try {
amazonS3.putObject(bucket, fileName, multipartFile.getInputStream(), objMeta);
} catch (IOException e) {
throw new S3Exception(S3ExceptionResponseCode.UPLOAD_EXCEPTION, "S3 업로드 중 에러가 발생");
}
return amazonS3.getUrl(bucket, fileName).toString();
}
// 파일 삭제
@Override
public boolean delete(String fileUrl) {
try {
String[] temp = fileUrl.split(".com/");
String fileKey = temp[1];
amazonS3.deleteObject(bucket, fileKey);
return true;
} catch (Exception e) {
return false;
}
}
}
UUID 미적용 시 발생한 문제점
동일한 파일명이 업로드되면 기존 파일이 덮어쓰여 중요한 데이터가 손실될 위험이 있었습니다. 특히, 사용자가 동일한 이름의 파일을 여러 번 업로드할 경우 문제가 빈번히 발생했습니다. 그래서 파일명 중복을 방지하기 위해 꼭 UUID를 통해 생성된 고유 식별자를 파일명에 포함하여 동일한 이름의 파일이라도 중복되지 않게 관리해야 합니다.
'프로젝트 > WMS' 카테고리의 다른 글
[CI/CD] EC2+도커+젠킨스+NIGNX 배포하기 (1) (1) | 2024.11.04 |
---|---|
[JPA] List 조회시 N+1문제 Fetch join으로 성능 개선하기 (0) | 2024.08.08 |
Spirng Security + JWT + OAUTH2 를 활용한 일반로그인, 소셜로그인(Kakao) (2) (0) | 2024.07.03 |
Spirng Security + JWT + OAUTH2 를 활용한 일반로그인, 소셜로그인(Kakao) (1) (1) | 2024.07.02 |
Entity의 생성일과 수정일을 자동으로 관리하자 ( BaseEntity ) (0) | 2024.06.05 |