[SpringBoot] AWS S3로 이미지 업로드

민정·2022년 12월 22일
5

AWS

목록 보기
2/11
post-thumbnail

✅ 프리티어 S3 기준


✨ S3 Bucket 생성

버킷 이름은 전역에서 고유해야하고, 공백 또는 대문자를 포함할 수 없다.

  • 버킷 이름 작성
  • AWS 리전 : 서울

✔ 버킷 생성 완료


✨ IAM 사용자 권한 추가

보안 자격 증명

오른쪽 상단 계정 - 보안 자격 증명

사용자 추가

  • 사용자 = 하나의 접근 계정
  • 애플리케이션에서 버킷에 접근을 위해 필요한 계정
  • 추가된 사용자로부터 발급된 Access KeySecret Key를 활용해 Spring Boot Application에서 버킷에 접근 가능

왼쪽 탭 사용자 - 사용자 추가

사용자 추가 과정

사용자명은 자유

기존 정책 직접 연결 - 정책 필터 : S3 검색 - AmazonS3FullAccess 선택

태그 추가는 추가 설정 없이 넘어감

작성 제대로 했는지 검토!

사용자 추가 성공적으로 완료

  • 엑세스 키 ID, 비밀 엑세스 키
    나중에 spring boot application에서 사용할 값이라서 따로 잘 저장해두자!

✨ Spring Boot와 S3 연결

Gradle Dependency 추가

dependencies {
	// AWS S3
	implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
}

프로퍼티 작성

application.yml에 작성!

  • bucket 이름은 S3에서 생성했던 bucket명과 동일하게 적어야하는 것 같다.
  • 다르게 적었더니 no such bucket이라고 버킷을 찾지 못한다.
cloud:
  aws:
    s3:
      bucket: carrotmarketclone # {버킷명}
    credentials:
      access-key: {발급 받은 Access key}
      secret-key: {발급 받은 Secret key}
    region:
      static: ap-northeast-2
      auto: false
    stack:
      auto: false

S3Config 작성

@Valueapplication.yml에 작성한 값 읽어온다.
amazonS3Client 객체 생성해서 Bean으로 주입

@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 AmazonS3 amazonS3Client() {
        AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);

        return AmazonS3ClientBuilder
                .standard()
                .withCredentials(new AWSStaticCredentialsProvider(credentials))
                .withRegion(region)
                .build();
    }
}

S3Upload

@Slf4j
@RequiredArgsConstructor
@Service
public class S3Uploader {
    private final AmazonS3Client amazonS3Client;

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

    // MultipartFile을 전달받아 File로 전환한 후 S3에 업로드
    public String upload(MultipartFile multipartFile, String dirName) throws IOException { // dirName의 디렉토리가 S3 Bucket 내부에 생성됨

        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);  // convert()함수로 인해서 로컬에 생성된 File 삭제 (MultipartFile -> File 전환 하며 로컬에 파일 생성됨)

        return uploadImageUrl;      // 업로드된 파일의 S3 URL 주소 반환
    }

    private String putS3(File uploadFile, String fileName) {
        amazonS3Client.putObject(
                new PutObjectRequest(bucket, fileName, uploadFile)
                        .withCannedAcl(CannedAccessControlList.PublicRead)	// 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();
    }
}

Controller

@RestController
@RequestMapping("/boards")
@RequiredArgsConstructor
public class BoardController {

    private final BoardService boardService;
    private final FileService fileService;
    private final S3Uploader s3Uploader;

    /**
     * 생성
     */
    @PostMapping("")
    public BaseResponse<PostBoardRes> create(
            @RequestPart(value = "postBoardReq") PostBoardReq postBoardReq,
            @RequestPart(value = "boardImg", required = false) MultipartFile multipartFile){

        String fileName = "";
        if(multipartFile != null){ // 파일 업로드한 경우에만
            
            try{// 파일 업로드
                fileName = s3Uploader.upload(multipartFile, "images"); // S3 버킷의 images 디렉토리 안에 저장됨
                System.out.println("fileName = " + fileName);
            }catch (IOException e){
                return new BaseResponse<>(FAIL_FILE_CHANGE);
            }
        }
}

🚨 에러 1) java.net.SocketException: Network is unreachable

aws sdk 에러

build.gradle에 spring-cloud-starter-aws 의존성 주입시, 로컬 환경은 aws 환경이 아니라서 발생하는 에러

해결

com.amazonaws.sdk.disableEc2Metadata 설정

@SpringBootApplication class에 설정

@SpringBootApplication
public class CarrotMarketCloneApplication {
	static {
		System.setProperty("com.amazonaws.sdk.disableEc2Metadata", "true");
	}
	public static void main(String[] args) {
		SpringApplication.run(CarrotMarketCloneApplication.class, args);
	}

}

다만, 아래와 같은 Warning은 발생
Unable to retrieve the requested metadata. EC2 Instance Metadata Service is disabled

메세지 거슬릴 경우
application.yml에 로깅 수준 Error로 변경하면 Warning 안뜬다고 한다!

logging:
  level:
    com:
      amazonaws:
        util:
          EC2MetadataUtils: ERROR

🚨 에러2) The specified bucket does not exist

해결 : application.yml 버킷명 S3와 동일하게 작성

application.yml에서 버킷명을 S3에서 생성한 버킷명과 동일하게 적어주었다.


🚨 에러3) multipart -> file 변환이 안됨

해결 : 버킷 정책 편집

버킷 선택

권한 탭

버킷 정책 - 편집

버킷 ARN 복사한 뒤, 정책 생성기 클릭

Actions

  • DeleteObject
  • GetObject, GetObjectAcl, GetObjectVersion
  • PutObject, PutObjectAcl
  • RestoreObject

생성된 정책 복사

다시 버킷 정책 - 편집

복사해뒀던 정책 붙여넣기 - 변경 사항 저장


😄 성공

포스트맨

body : form-data 형식
body에 이미지 첨부하고 요청 보냈더니 요청 성공 메세지를 받았다!

S3에 사진 저장 확인

images 폴더에 첨부했던 snowBear2.jpg가 들어있는 것을 확인!


참고

[AWS] Spring Boot에서 AWS S3와 연계한 파일 업로드처리

[SpringBoot] AWS S3로 이미지 업로드하기

AWS S3 이미지 업로드 Spring으로 사용해보기

[Spring err] com.amazonaws.SdkClientException: Failed to connect to service endpoint 에러 -(aws 의존성 주입시 에러)

[AWS] 📚 S3 개념 정리 & 버킷 생성 · 권한(Policy / ACL) 설정하기

0개의 댓글