본문 바로가기

Back-End/Spring

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

728x90

AWS EC2 서버를 사용하면서 이미지 처리를 위해 AWS 에서 제공하는 S3 를 사용하려고 합니다. S3 (Simple, Storage, Service) 는 AWS 에서 제공하는 인터넷 스토리지 서비스입니다.

그럼 S3 버킷을 만들어보겠습니다.

 

1.  S3 버킷 만들기

1) AWS 에 접속하여 S3 를 검색한 후, 아래와 같이 버킷 만들기 를 클릭합니다.

버킷 만들기 클릭

 

2) 아래 화면에서 버킷 이름과 리전을 입력합니다.

버킷 이름, 리전 입력

 

3) 객체 소유권은 그대로 두고, 퍼블릭 액세스 를 아래와 같이 설정해줍니다. '모든 퍼블릭 액세스 차단' 을 해제한 다음, ' 새 퍼블릭 버킷 또는 액세스 지점 정책을 통해 부여된 버킷 및 객체에 대한 퍼블릭 액세스 차단' 을 선택해줍니다.

퍼블릭 액세스 설정

 

4) 버킷 버전 관리와 기본 암호화를 비활성화 로 선택해준 후, 버킷 만들기 를 클릭해줍니다.

버킷 버전 관리, 기본 암호화 설정 후 버킷 만들기 클릭

 

5) 버킷을 생성하면, 리스트에서 확인이 가능합니다.

리스트에서 생성한 버킷 확인

 

2.  AWS 버킷 퍼블릭 설정

위에서 확인할 수 있듯이, '객체를 퍼블릭으로 설정할 수 있음' 이라고 나옵니다. 버킷의 정책을 수정해서 모든 사람이 액세스할 수 있도록 수정합니다.

1) 버킷 상세 페이지에서 아래와 같이 권한 탭으로 이동한 후, 버킷 정책의 편집 버튼을 클릭합니다.

권한 > 버킷 정책

 

2) 버킷 정책 편집에서 버킷 ARN 값을 복사하고 정책 생성기 버튼을 누릅니다.

정책 생성기

 

3) 아래와 같이 설정해줍니다. Action 에는 GetObject, PutObject, DeleteObject 이렇게 3개를 체크하고 ARN 에는 복사해둔 ARN 값을 입력합니다. 이는 버킷을 조회, 생성, 삭제를 위한 것입니다.

정책 생성기 설정

 

4) Add Statement 를 클릭한 후, Generate Policy 를 클릭하면, JSON 으로 형성된 버킷 정책이 나옵니다. 이를 복사해줍니다.

5) 복사해둔 버킷 정책을 아래와 같이 버킷 정책 편집에 붙여넣고 변경 사항을 저장해줍니다.

버킷 정책 편집

 

3.  IAM 계정 발급 및 accessKey 생성

AWS 에서 S3 버킷 접근 권한을 가진 IAM 계정을 생성하고, 해당 계정에 대해 accessKey 와 secretKey 를 발급받아보겠습니다.

1) 아래와 같이 IAM 을 검색한 후, 사용자 추가 를 클릭합니다.

IAM 검색 후, 사용자 추가 클릭

 

2) 사용자 이름을 입력한 후, 다음 버튼을 클릭합니다.

사용자 이름 설정

 

3) 아래와 같이 직접 정책 연결 을 선택해준 후, 권한 정책 에서 IAMFullAccessAmazonS3FullAccess 정책을 선택해줍니다.

권한 정책 설정

 

4) 다음 버튼을 클릭하면 권한 요약 에서 선택한 권한 정책들을 확인할 수 있습니다. 사용자 생성 버튼을 클릭하여 IAM 계정을 생성합니다.

권한 요약

 

5) 이제, accessKey 를 발급받아보겠습니다. 방금 생성한 IAM 계정에 들어가면 아래와 같이 액세스 키가 활성화되지 않은 것을 확인할 수 있습니다.

스프링부트 애플리케이션에서 IAM 계정에 기반한 액세스 키를 활용하여 S3 에 파일을 읽고 쓰는 작업을 진행하는데, 액세스 키가 활성화되어 있지 않으면 S3 에 대한 접근 권한이 없기 때문에 파일 업로드가 제대로 진행이 안됩니다. 따라서 액세스 키를 활성화해주어야 합니다.

액세스 키 활성화되어 있지 않음

 

6) 보안 자격 증명 탭을 클릭하여 액세스 키를 생성합니다. 액세스 키 만들기 버튼을 클릭하여 

액세스 키 생성

 

7) 아래와 같이 체크한 후, 다음 버튼을 클릭하여 액세스 키를 생성합니다.

액세스 키 설정

 

8) 액세스 키를 생성하면 다음과 같이 비밀 액세스 키도 발급이 되는데, 비밀 액세스 키는 발급받은 후로 다시 조회가 불가하기 때문에 csv 파일로 다운을 받아놓는 것이 좋습니다.

액세스 키 생성

 

 

2.  Spring Boot 로 파일 업로드

S3 버킷 설정을 모두 마친 후, 이제 스프링 부트 프로젝트에서 파일 업로드 기능을 추가해보도록 하겠습니다.

1) build.gradle 에 의존성을 추가해줍니다.

implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'

 

2) application.properties 에서 다음과 같이 작성해줍니다. access-key 와 secret-key 에 각각 값을 입력하고, bucket 이름을 입력해줍니다.

# AWS S3
cloud.aws.credentials.access-key=
cloud.aws.credentials.secret-key=
cloud.aws.s3.bucket=
cloud.aws.region.static=ap-northeast-2
cloud.aws.stack.auto=false

 

3) S3Config 를 다음과 같이 작성해줍니다.

package com.example.capstone.config;

import com.amazonaws.services.s3.AmazonS3;
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 AmazonS3 amazonS3Client() {
        AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);

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

 

4) Controller 와 Service 를 아래와 같이 작성해줍니다.

[ Controller ]

@RestController
@RequiredArgsConstructor
@RequestMapping("/exchange")
@Slf4j
public class ExchangePostController {

    private final UserRepository userRepository;
    private final PostRepository postRepository;
    private final TokenProvider tokenProvider;
    private final PostService postService;
    private ObjectNode responseJson;
    
    @PostMapping(value = "/post", consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE})
    public ResponseEntity<?> createPost(@Valid @RequestPart ExchangePostDTO exchangePostDTO,
                                        @RequestPart(value = "images", required = false) List<MultipartFile> imageFiles,
                                        BindingResult bindingResult,
                                        HttpServletRequest request) {
        responseJson = JsonNodeFactory.instance.objectNode();

        // 필수정보 체크
        if (bindingResult.hasErrors()) {
            responseJson.put("message", "필수 정보가 없습니다.");

            return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                    .contentType(MediaType.APPLICATION_JSON)
                    .body(responseJson);
        }

        try {
            // 토큰 값 추출
            String token = request.getHeader("Authorization");
            token = token.replaceAll("Bearer ", "");

            // 토큰 검증
            if (!tokenProvider.validateToken(token)) {
                return TokenResponse.handleUnauthorizedRequest("유효하지 않은 토큰입니다.");
            }

            // token 을 통해 UserEntity 조회
            Optional<UserEntity> loggedInUserEntity = TokenResponse.getLoggedInUser(tokenProvider, token, userRepository);
            UserEntity userEntity = null;

            if (loggedInUserEntity.isPresent()) {
                userEntity = loggedInUserEntity.get();

                // 이미지가 없는 경우
                if (imageFiles == null) {
                    postService.save(exchangePostDTO, null, userEntity); // 가져온 userEntity 를 해당 포스트 컬럼에 추가
                } else {
                    postService.save(exchangePostDTO, imageFiles, userEntity);
                }

                responseJson.put("message", "재능거래 게시물이 성공적으로 등록되었습니다.");

                return ResponseEntity.status(HttpStatus.CREATED)
                        .contentType(MediaType.APPLICATION_JSON)
                        .body(responseJson);
           }
        } catch (Exception e) {
            return ServerErrorResponse.handleServerError("서버에 예기치 않은 오류가 발생했습니다." + e);
        }
    }
}

 

[ Service ]

>> 'images/' 경로를 붙였기 때문에 S3 에 이미지를 업로드하게 되면, images 폴더가 생기게 되고 그 안에 해당 이미지들이 저장됩니다.

private List<String> saveImages(List<MultipartFile> imageFiles) throws IOException {

    List<String> imageUrls = new ArrayList<>();

    for (MultipartFile imageFile: imageFiles) {
        // 메타데이터 설정
        ObjectMetadata objectMetadata = new ObjectMetadata();
        objectMetadata.setContentType(imageFile.getContentType());
        objectMetadata.setContentLength(imageFile.getSize());

        // S3 bucket 디렉토리명 설정
        String fileName = imageFile.getOriginalFilename();
        String uploadFileName = UUID.randomUUID() + "_" + fileName; // S3 에 저장할 파일명
        log.info("uploadFileName: {}", uploadFileName);
        amazonS3Client.putObject(bucket, "images/" + uploadFileName, imageFile.getInputStream(), objectMetadata); // S3 에 파일 업로드
        String imageUrl = amazonS3Client.getUrl(bucket, "images/" + uploadFileName).toString();
        log.info("image 업로드 {}: ", imageUrl);

        imageUrls.add(imageUrl);
    }
    return imageUrls;
}

 

5) 아래와 같이 AWS S3 에 이미지가 잘 올라오는 것을 확인할 수 있습니다.

S3 버킷에 'images' 폴더 생성됨
images 폴더에 이미지 저장

 

728x90