wooing

Spring boot에서 카카오클라우드 Object Storage사용하기 본문

Server/Spring

Spring boot에서 카카오클라우드 Object Storage사용하기

우잉_ 2024. 5. 20. 12:25

카카오클라우드의 오브젝트 스토리지는 AWS의 S3와 동일한 역할을 한다. 그리고 AWS S3 API와 호환이 가능하다. 그러므로 우리 프로젝트에서 오브젝트 스토리지를 사용하기위해 AWS S3 API를 사용하려 한다.

0. 사전작업

오브젝트 스토리지를 사용하기 전 아래 스텝의 사전작업이 필요하다.

엑세스 키 발급 → API 인증 토큰 발급 → S3 API를 위한 Credential 발급

https://docs.kakaocloud.com/start/console-guide/create-access-key

https://docs.kakaocloud.com/start/api-preparation

https://docs.kakaocloud.com/tutorial/storage/object-storage-s3-api#type-3-java-sdk-%EC%98%88%EC%A0%9C

위의 링크를 순서대로 따라하면 최종적으로 아래와 같은 결과를 얻을 수 있다. (비밀 정보이니 유출되면 안됨)

 {"credential": {"user_id": "~~~", "tenant_id": "~~~", "access": "~~~", "secret": "~~~", "trust_id": null, "links": {"self": "http://iam.kakaocloud.com/v3/users/~~~/credentials/OS-EC2/~~~"}}}

공식문서에 따르면 API 인증 토큰이 12시간후에 만료된다고 되어있는데, 이로인해 API Credential도 만료되는지는 나중에 확인해봐야 할 것 같다. 만약 만료된다면 백엔드 서버에서 스케줄러나 다른 방식을 통해 일정 시간마다 생성하도록 해야할 것 같다.

1. S3Client Bean 생성

Config객체를 생성하여 S3Client bean을 생성하도록 하였다. 코드는 아래와 같다. 이를 구현하기 위해 기술문서를 참고했지만 여러 문제가 있었고 해결방법은 아래의 트러블 슈팅에 있다.

@Configuration
public class ObjectStorageConfig {
    @Value("${cloud.kakao.object-storage.endpoint}")
    private String endpoint;
    @Value("${cloud.kakao.object-storage.access-key}")
    private String accessKey;
    @Value("${cloud.kakao.object-storage.secret-key}")
    private String secretKey;
    @Value("${cloud.kakao.object-storage.region}")
    private String region;

    @Bean
    public S3Client s3Client() {
        return S3Client.builder()
            .region(Region.of(region))
            .credentialsProvider(StaticCredentialsProvider.create(
                AwsBasicCredentials.create(accessKey, secretKey)))
            .endpointOverride(URI.create(endpoint))
            .forcePathStyle(true)
            .build();
    }
}

2. 서비스 구현

우리는 오브젝트 스토리지를 클라이언트가 업로드한 이미지(MultipartFile)를 저장하는 용도로 사용할 것이다. S3Client는 Path객체를 통해 버킷에 업로드하도록 되어있다. 이에 맞춰 MultipartFile타입을 Path타입으로 변경하는 로직이 필요하다.

비어있는 TempFile을 생성하여 Path에 저장하고, multipartFile타입의 file을 inputStream을 통해 복사하는 방식으로 구현하였다.

Path path = Files.createTempFile(null,null);
Files.copy(file.getInputStream(),path, StandardCopyOption.REPLACE_EXISTING);

이를 s3Client.putObject()로 업로드하면 된다. 업로드할때 첫번째 인자에는 PutObjectRequest타입 두번째 인자에는 Path타입이 들어가야한다. PutObjectRequest는 버킷이름과 키값이 필요하다. 버킷이름은 말 그대로 파일을 저장할 버킷의 이름이고 키값은 이미지를 식별할 파일명이다(키값 그대로 버킷에 저장된다.).

아래의 코드는 파일을 업로드하는 코드 전문이다.

// objectKey : 업로드할 파일명
    // filePath : 업로드할 파일이 위치한 로컬 경로
    public List<String> uploadFile(List<MultipartFile> multipartFiles)  {
        List<String> fileList = new ArrayList<>();

        // forEach 구문을 통해 multipartFile로 넘어온 파일들 하나씩 fileList에 추가
        multipartFiles.forEach(file -> {
            try {
                Path path = Files.createTempFile(null,null);
                Files.copy(file.getInputStream(),path, StandardCopyOption.REPLACE_EXISTING);

                String fileName = createFileName(file.getOriginalFilename());

                PutObjectRequest putObjectRequest = PutObjectRequest.builder()
                    .bucket(bucketName)
                    .key(fileName)
                    .build();

                s3Client.putObject(putObjectRequest, path);

                fileList.add(endpoint + "/v1/" + projectID + "/" + bucketName + "/" + fileName);

                Files.delete(path);
            } catch (IOException e) {
                throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR,
                    "파일 업로드에 실패했습니다.");
            }

        });
        return fileList;
    }

그 외의 기능들은 공식문서를 참고하여 그대로 사용하였다.

트러블슈팅

Cannot resolve method “forcePathStyle” 문제

공식문서의 S3 API Java SDK 예제에서 S3Client를 선언하는 코드에서 forecPathStyle메소드를 찾을 수 없는 문제이다. 공식문서에서 제공한 의존성을 사용하였지만 발생한 문제이다.

아래의 버전정보를 사용하면 해결할 수 있었다.

	implementation platform('software.amazon.awssdk:bom:2.23.7')
	implementation 'software.amazon.awssdk:s3'
	implementation 'ch.qos.logback:logback-classic:1.4.12' // try-catch문 Exception

Unable to load region from any of the providers… 문제

기술문서의 예제 코드에서 S3Client Bean을 생성하는 코드는 아래와 같다.

final S3Client client = S3Client.builder()
    .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(accessKey, secretAccessKey)))
    .endpointOverride(URI.create(s3Endpoint))
    .forcePathStyle(true)
    .build();

그러나 해당 문제는 S3Client를 생성할때 리전 정보가 제공되지 않아 발생한 문제로 보였다. S3Client를 생성할때 region을 입력하여 해결할 수 있었다. Region객체에는 이미 정의된 AWS 리전들이 있었다. 그러나 우리는 카카오클라우드를 사용하기때문에 적절한 리전을 찾을 수 없었고 .of 메소드를 통해 수동으로 지정할 수 있어 이를 사용하여 해결하였다.

    @Bean
    public S3Client s3Client() {
        return S3Client.builder()
            .region(Region.of("kr-central-2"))
            .credentialsProvider(StaticCredentialsProvider.create(
                AwsBasicCredentials.create(accessKey, secretKey)))
            .endpointOverride(URI.create(endpoint))
            .forcePathStyle(true)
            .build();
    }

 

해당 문제들을 카카오클라우드에 문의하여 공식문서를 수정하였습니다.

https://wooing1084.tistory.com/23

 

카카오클라우드 튜토리얼 수정 요청하기

최근 카카오클라우드의 Object storage를 튜토리얼을 따라 프로젝트에 도입하다가 Cannot resolve method “forcePathStyle” 문제를 직면했다. 이를 해결한 트러블 슈팅을 기록했었는데, 이를 방치하면 나처

wooing1084.tistory.com

 


References

https://docs.kakaocloud.com/tutorial/storage/object-storage-s3-api#type-3-java-sdk-%EC%98%88%EC%A0%9C

'Server > Spring' 카테고리의 다른 글

Spring boot에서 RabbitMQ를 사용하는 방법 (STOMP, AMQP)  (0) 2025.03.05