프로젝트/크루트

spring boot + aws s3

발전생 2022. 3. 9. 19:23

s3 버킷을 생성하는 방법은 이 블로그에 친절하게 나와 있어서 도움이 많이 됐다. 

https://devlog-wjdrbs96.tistory.com/323

 

[Spring] Spring Boot AWS S3 사진 업로드 하는 법

Spring Boot S3 File Upload 하는 법 이번 글에서는 Spring Boot 로 AWS S3 로 File Upload 하는 법에 대해서 정리해보겠습니다. 먼저 AWS S3 Bucket 생성을 하겠습니다. AWS S3 Bucket 생성 그리고 권한 탭을 들..

devlog-wjdrbs96.tistory.com

 

그리고 전 배민 개발팀장님의 블로그에서도 해당 주제의 포스팅을 찾을 수 있었다.

https://jojoldu.tistory.com/300

 

SpringBoot & AWS S3 연동하기

안녕하세요? 이번 시간엔 SpringBoot & AWS S3 연동하기 예제를 진행해보려고 합니다. 모든 코드는 Github에 있기 때문에 함께 보시면 더 이해하기 쉬우실 것 같습니다. (공부한 내용을 정리하는 Github와

jojoldu.tistory.com

 

이 글에서는 IAM을 통해 ec2와 s3를 연결하는 방법을 알려준다. 이 방법을 사용하면 중요한 보안 항목인 s3의 access Key와 secretKey를 설정 파일(.yml)이나 환경변수에 추가할 필요가 없다.  한결 편해진다.

 

그리고 위 두 글을 보면 파일 업로드 시에 로컬에 한 번 저장해놨다가 s3에 올리고 로컬에 있는 걸 삭제한다. 하지만 굳이 그럴 필요가 없어 보였고 원하는 정보를 외국 사이트에서 찾을 수 있었다.

 

https://www.techgeeknext.com/cloud/aws/amazon-s3-springboot-upload-file-in-s3-bucket

 

TechGeekNext - Next Generation Tech, Reviews and Tips

Adblocker detected! Please consider whitelist or disable this site. We've detected that you are using AdBlock Plus or some other adblocking software which is preventing the page from fully loading. To keep the site operating, we need funding, and practical

www.techgeeknext.com

여기에서 결정적인 해결의 실마리를 찾을 수 있었다.

 

 

 

이 세 글들을 종합해서 만든 내 코드는 아래와 같다.

 

일단 build.gradle의 dependency에 아래 내용을 추가해서 설치하게 한다.

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

 

src/main/resources에 aws.yml을 만들어준다. .gitignore에 해당 파일을 추가하여 key가 github에 올라가지 않게 한다.

주석 처리해 놓은 credentials.instanceProfile을 true로 하면 ec2에서 iam을 보고 s3의 key를 가져와 사용한다.  아직 ec2에는 올리지 않았으므로 일단 주석 처리해놨다.

# aws
cloud:
  aws:
    credentials:
      accessKey: AKIASNN7CKCU4QB22XHT
      secretKey: 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
    s3:
      bucket: cruit
    region:
      static: ap-northeast-2
    stack:
      auto: false
#    credentials:
#      instanceProfile: true

 

s3 관련 설정을 참조하여 s3 client 객체를 만들어 빈으로 등록한다. 싱글톤을 떠올리면 이해가 될 것이다.

@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 basicAWSCredentials = new BasicAWSCredentials(accessKey, secretKey);
        return (AmazonS3Client) AmazonS3ClientBuilder.standard().withRegion(region)
                .withCredentials(new AWSStaticCredentialsProvider(basicAWSCredentials)).build();
    }
}

 

중요한 upload() 로직이다. s3에 파일을 업로드하는 함수이다. 프론트엔드 쪽에서 formData에 file을 담아 전달해야 한다.  매개변수로 어느 디렉토리에 넣을 지를 dirName에 받는다. 그 디렉토리에 uuid 값 + 파일 이름으로 s3에 저장한다.

uuid를 사용한 이유는 이름이 같은 파일이 입력 값으로 들어올 수 있기 때문이다. 확인해보면 s3에서 디렉토리가 만들어진 것을 볼 수 있다. 물론 파일도 해당 이름으로 잘 들어간다. 최종적으로는 s3에 저장된 파일(이미지)에 접근할 수 있는 url을 리턴해준다. 

@RequiredArgsConstructor
@Service
public class S3UploaderService {
    private final AmazonS3Client amazonS3Client;

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

    public String upload(MultipartFile file, String dirName) throws IOException{
        String storedFilePath = dirName + "/" + UUID.randomUUID() + file.getOriginalFilename();
        ObjectMetadata metadata = new ObjectMetadata();
        metadata.setContentLength(file.getSize());
        amazonS3Client.putObject(bucket, storedFilePath, file.getInputStream(), metadata);
        return amazonS3Client.getUrl(bucket, storedFilePath).toString();
    }
}

 

이런 식으로 api controller에서 사용하면 된다. 필자의 경우 유저의 프로필 이미지를 바꿀 때 사용했기 때문에 만들어놓은 api 처리 함수에서 upload 함수를 호출하는 방식으로 사용했다.

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1")
public class FileApiController {
    private final S3UploaderService s3UploaderService;

    @PostMapping("/files/upload")
    public ResponseWrapper uploadFile(@RequestPart("file") MultipartFile file) throws IOException {
        s3UploaderService.upload(file, "upload");
        return new ResponseWrapper(new SimpleMessageBody("파일 업로드 성공"));
    }
}

 

아래가 응용한 방식이다.

@PatchMapping("/me/profile")
    public ResponseWrapper setMyProfile(@RequestPart("file") MultipartFile file, @CurrentUser SessionUser sessionUser) throws IOException {
        //....

        // s3에 업로드
        String fileUrl = s3UploaderService.upload(file, "profiles");

        userService.setProfile(sessionUser.getId(), fileUrl);
        return new ResponseWrapper(new SetMyProfileResponse(fileUrl));
    }

MultiPartFile을 받을 때 @RequestPart, @RequestParam 모두 사용 가능하다. 인텔리제이에서 해당 애노테이션에 마우스를 대보면 설명이 보이는데 거기에 둘의 차이가 적혀 있다. 보고서 편한 걸 사용하면 된다. 

 

여러 yml을 설정 파일로 읽어와야 하기 때문에 메인 애플리케이션 또한 변경이 불가피하다.

@SpringBootApplication
public class CruitApplication {

	public static final String APPLICATION_LOCATIONS = "spring.config.location="
			+ "classpath:application.yml,"
			+ "classpath:aws.yml";

	public static void main(String[] args) {
		new SpringApplicationBuilder(CruitApplication.class)
				.properties(APPLICATION_LOCATIONS)
				.run(args);
	}

}