버킷 : 저장소
객체 : 버킷에 업로드된 파일
S3 버킷 생성하기
퍼블릭 엑세스 차단 해제
그리고 그대로 생성하면 됨
버킷에 정책 ( 권한(Permission)을 정의하는 JSON 문서)추가
여기서 정책 추가
s3 -> gerobject
리소스 추가하기
S3에 파일 업로드 할 수 있도록 IAM에서 액세스 키 발급받기
백엔드 서버가 S3에 접근해서 파일을 업로드할 수 있어야 한다. S3에 접근할 수 있는 권한을 받기 위해 IAM이라는 곳에서 권한을 부여받아야 한다.
사용자 생성
정책 생성
엑세스 키 만들기
발급 완료, 저 두 키는 따로 저장해 놔야 함
스프링부트로 S3에 이미지 저장하기
- users 테이블에 imageUrl필드를 추가하여 저장된 프로필 이미지의 url을 저장하여 user 조회시 함께 조회
- 이미지 제목이 중복이 될 수 있기 때문에 원래 원본파일명 + UUID를 붙여서 저장
- "jpg", "jpeg", "png", "gif"인 파일만 저장할 수 있도록 검증
이미지 저장 시도 중 문제 발생 : AccessControlListNotSupported
com.amazonaws.services.s3.model.AmazonS3Exception: The bucket does not allow ACLs (Service: Amazon S3; Status Code: 400; Error Code: AccessControlListNotSupported;
→ S3 버킷이 ACL(Access Control List)을 지원하지 않아서 발생하는 문제라고 한다
ACL
- 사용자(User) 또는 그룹(Group)에 대해 특정 권한을 부여하는 방식
- 버킷 정책(Bucket Policy) 및 IAM 정책보다 간단한 권한 관리 방식
- 현재는 AWS는 ACL을 비활성화하는 것을 기본값(Default) 으로 변경
이미 위에서 버킷 정책을 적용했으니 ACL을 사용하는 코드를 제거해서 해결하면 된다
기존 코드
PutObjectRequest putObjectRequest =
new PutObjectRequest(bucketName, s3FileName, byteArrayInputStream, metadata)
.withCannedAcl(CannedAccessControlList.PublicRead); //ACL 사용
amazonS3.putObject(putObjectRequest);
수정된 코드
PutObjectRequest putObjectRequest =
new PutObjectRequest(bucketName, s3FileName, byteArrayInputStream, metadata);
amazonS3.putObject(putObjectRequest);
스프링부트 코드
poroperties 파일 수정
aws.access.key=
aws.secret.access.key=
aws.s3.bucket=
aws.region.static=ap-northeast-2
aws.stack.auto-=false
라이브러리 추가
// s3
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
S3Config 생성
@Configuration
public class S3Config {
@Value("${aws.access.key}")
private String accessKey;
@Value("${aws.secret.access.key}")
private String secretKey;
@Value("${aws.region.static}")
private String region;
@Bean
public AmazonS3 amazonS3() {
AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);
return AmazonS3ClientBuilder
.standard()
.withCredentials(new AWSStaticCredentialsProvider(credentials))
.withRegion(region)
.build();
}
}
이미지 업로드 및 삭제 컨트롤러
// 사용자 프로필 이미지 업로드
@PostMapping("/users/image")
public ResponseEntity<UserProfileResponse> uploadImage(@AuthenticationPrincipal AuthUser authUser, @RequestPart MultipartFile image) throws IOException {
UserProfileResponse user = userService.uploadImage(authUser.getId(), image);
return ResponseEntity.ok(user);
}
// 사용자 프로필 이미지 삭제
@DeleteMapping("/users/image")
public void imageDelete(@AuthenticationPrincipal AuthUser authUser, @RequestParam String imageUrl) {
userService.deleteImage(authUser.getId(), imageUrl);
}
이미지 업로드 검증 메서드
// 이미지 업로드 검증
private static void validImage(MultipartFile image) {
// 이미지가 비어있는지 확인
if(image.isEmpty() || Objects.isNull(image.getOriginalFilename())){
throw new InvalidRequestException("이미지 처리중 오류가 발생했습니다.");
}
int lastDotIndex = image.getOriginalFilename().lastIndexOf(".");
if (lastDotIndex == -1) {
throw new InvalidRequestException("이미지 처리중 오류가 발생했습니다.");
}
// 확장자 검증
String extention = image.getOriginalFilename().substring(lastDotIndex + 1).toLowerCase();
List<String> allowedExtentionList = Arrays.asList("jpg", "jpeg", "png", "gif");
if (!allowedExtentionList.contains(extention)) {
throw new InvalidRequestException("이미지 처리중 오류가 발생했습니다.");
}
}
이미지 업로드 서비스
// 이미지 업로드
@Transactional
public UserProfileResponse uploadImage(long userId, MultipartFile image) throws IOException {
User user = userRepository.findById(userId).orElseThrow(() -> new InvalidRequestException("User not found"));
validImage(image);
String originalFilename = image.getOriginalFilename(); //원본 파일 명
String extention = originalFilename.substring(originalFilename.lastIndexOf(".")); //확장자 명
String s3FileName = UUID.randomUUID().toString().substring(0, 10) + originalFilename; //변경된 파일 명
InputStream is = image.getInputStream();
byte[] bytes = IOUtils.toByteArray(is);
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentType("image/" + extention);
metadata.setContentLength(bytes.length);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
try{
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, s3FileName, byteArrayInputStream, metadata);
amazonS3.putObject(putObjectRequest); // put image to S3
}catch (Exception e){
throw new InvalidRequestException("이미지 처리중 오류가 발생했습니다.");
}finally {
byteArrayInputStream.close();
is.close();
}
String imageUrl = amazonS3.getUrl(bucketName, s3FileName).toString();
user.updateImageUrl(imageUrl);
User svaedUser = userRepository.findById(userId).orElseThrow(() -> new InvalidRequestException("User not found"));
return new UserProfileResponse(svaedUser.getId(), svaedUser.getEmail(), svaedUser.getNickName(), svaedUser.getImageUrl());
}
이미지 삭제 서비스
@Transactional
public void deleteImage(Long userId, String imageUrl) {
User user = userRepository.findById(userId).orElseThrow(() -> new InvalidRequestException("User not found"));
try{
URL url = new URL(imageUrl);
String decodingKey = URLDecoder.decode(url.getPath(), "UTF-8");
String key = decodingKey.substring(1); // 맨 앞의 '/' 제거
amazonS3.deleteObject(new DeleteObjectRequest(bucketName, key));
}catch (MalformedURLException | UnsupportedEncodingException e){
throw new InvalidRequestException("이미지 처리중 오류가 발생했습니다.");
}
user.updateImageUrl(null);
}
이미지 저장 및 삭제 테스트
저장테스트
삭제테스트
잘 삭제된다
'AWS' 카테고리의 다른 글
RDS 구축하고 EC2에 연결하기 (0) | 2025.03.19 |
---|---|
Spring 프로젝트 AWS에 배포하기 (0) | 2025.03.19 |
AWS 인스턴스 생성 : Nginx에서 443 포트 열기 (0) | 2025.03.17 |