본문 바로가기

Spring

S3로 파일 업로드

728x90

마지막 프로젝트를 진행중이며 유저가 업로드 하는 이미지 파일을 S3 클라우드에 저장하는 방식을 사용헤보기로 했다.
먼저 AWS S3 Bucket을 생성하고 코드 구현을 했다.

S3 버킷 생성

  • S3에서 버킷 만들기 클릭

1) 버킷 이름과 리전 설정

  • 버킷 이름은 고유값으로 설정

2) 퍼블릭 액세스 설정
※ 외부에 S3을 공개할 경우 모든 퍼블릭 액세스 차단을 체크 해제하고, 공개하지 않는다면 체크를 해주면 된다.

3) 버킷 정책 생성

  • 버킷 생성까지 완료 되었으면 외부에서 접근이 가능하도록 버킷 정책을 설정해줘야 한다.
  • 생성된 버킷명을 클릭해서 들어가면 아래와 같은 탭이 있는데 [권한] 탭을 클릭해서 들어가면
    버킷 정책이 있다. 편집으로 들어가자.

  • 정책 생성기를 클릭하고 설정해야한다.
    (버킷 ARN은 복사를 미리해둔다)

  • 버킷 생성기에 들어가면 아래와 같은 화면이 나올거고 해당 항목들을 채워 나간다.
  • Select Type of Policy - S3 Bucket Policy
  • Effect - Allow
  • Principal - *
  • Action - GetObject, PutObject
  • ARN - arn:aws:s3:::{버킷이름}/*

Add Statement 클릭 -> 아래 Generate Policy를 클릭하여 생성된 정책을 복사 후
S3 정책 편집에 돌아가서 수정해주면 된다.

4) access key 발급

  • S3에 접근하려면 별도의 인증 과정이 필요하다.
  • 액세스 키를 가지고 있는 사람만 자격을 증명할 수 있다.
  • 액세스 키는 AWSAccessKeyId와 AWSSecretKey로 구성된다.
  • 보안 자격 증명(IAM) > 액세스 키 > 새 액세스 키 만들기
  • '키 파일 다운로드' 클릭(반드시 다운받고 따로 보관하자)
  • AccessKeyId와 SecretKey는 외부에 노출되어서는 안된다.

AWS 의존성 추가 및 설정

  • build.gradle
    implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-aws', version: '2.2.1.RELEASE'

 

  • apllication.properties
    ccloud.aws.credentials.accessKey= 사용자 엑세스 키
    cloud.aws.credentials.secretKey= 비밀 엑세스 키 
    cloud.aws.stack.auto=false
    

AWS S3 Service bucket

cloud.aws.s3.bucket=버킷이름 (자신이 설정한 버킷이름)
cloud.aws.region.static=ap-northeast-2 (버킷 지역/서울)

AWS S3 Bucket URL

cloud.aws.s3.bucket.url=https://s3.ap-northeast-2.amazonaws.com/버킷이름


# Config 및 Util 클래스 생성

- S3 Config Class
``` java

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

}
  • S3 Service
  • public class S3Uploader { private final AmazonS3Client amazonS3Client; @Value("${cloud.aws.s3.bucket}") public String bucket; // S3 버킷 이름 // 전달 받은 데이터를 바로 S3에 업로드하는 메소드 // 1. 사전 준비 - 메타데이터와 파일명 생성 // 2. S3에 전달 받은 파일 업로드 // 3. S3에 저장된 파일 이름과 주소 반환 // 파라미터로 multipartFile(업로드하려는 파일)과 dirName(이 파일을 업로드하고 싶은 S3 버킷의 폴더 이름)을 받는다. public HashMap<String, String> uploadImage(MultipartFile multipartFile, String dirName) throws IOException { // 0. 이미지 파일인지 체크 isImage(multipartFile); // 1. 사전 준비 // 1-1 메타데이터 생성 // InputStream을 통해 Byte만 전달되고 해당 파일에 대한 정보가 없기 때문에 파일의 정보를 담은 메타데이터가 필요하다. ObjectMetadata metadata = new ObjectMetadata(); metadata.setContentType(multipartFile.getContentType()); metadata.setContentLength(multipartFile.getSize()); // 1-2 S3에 저장할 파일명 생성 // UUID 사용 이유 : 이름이 같은 파일들이 서로 덮어쓰지 않고 구분될 수 있도록 String fileName = createFileName(multipartFile); String uploadImageName = dirName + "/" + UUID.randomUUID() + fileName; // 2. s3로 업로드 amazonS3Client.putObject(new PutObjectRequest(bucket,uploadImageName, multipartFile.getInputStream(),metadata) .withCannedAcl(CannedAccessControlList.PublicRead)); // S3에 업로드한 이미지의 주소를 받아온다. String uploadImageUrl = amazonS3Client.getUrl(bucket, uploadImageName).toString();
    // 4. S3에 저장된 파일 이름과 주소 반환
    HashMap<String, String> imgInfo = new HashMap<>();
    imgInfo.put("fileName", uploadImageName);
    imgInfo.put("img",uploadImageUrl);
    return imgInfo;
}

// 파일 삭제하기
public void deleteImageFile(String fileName) {
    amazonS3Client.deleteObject(bucket, fileName);
}

// 파일 이름 생성 메소드
private String createFileName(MultipartFile multipartFile) {

    String name = multipartFile.getOriginalFilename();
    String ext = "";

    // 확장자와 파일명 분리
    if (name.contains(".")) {
        int position = name.lastIndexOf(".");
        ext = name.substring(position+1);
        name = name.substring(0,position);
    }

    // 파일 이름의 길이가 길면 100자로 자르기 (디비의 varchar(255) 제한에 걸리지 않으려고)
    if (name.length()>100){
        name = name.substring(0,100);
    }

    // S3에 저장할 파일 이름 생성
    String fileName = !ext.equals("")?name+"."+ext:name;

    return fileName;
}

// 이미지 파일인지 확인하는 메소드
private void isImage(MultipartFile multipartFile) throws IOException {

    // tika를 이용해 파일 MIME 타입 체크
    // 파일명에 .jpg 식으로 붙는 확장자는 없앨 수도 있고 조작도 가능하므로 MIME 타입을 체크하는 것이 좋다.
    Tika tika = new Tika();
    String mimeType = tika.detect(multipartFile.getInputStream());

    // MIME타입이 이미지가 아니면 exception 발생생
    if (!mimeType.startsWith("image/")) {
        throw new CustomException(ErrorCode.NOT_IMAGES);
    }
}

}



- S3 Controller
```java
@RequiredArgsConstructor
@RestController
public class AwsController {

    private final S3Uploader s3Uploader;

    @PostMapping("/images/upload")
    public HashMap<String,String> upload(@RequestParam("images") MultipartFile multipartFile) throws IOException {

        String imgUrl = "";
        String fileName = "";
        HashMap<String,String> imgInfo = s3Uploader.upload(multipartFile, "static");
        imgUrl = imgInfo.get("img");
        fileName = imgInfo.get("fileName");

        HashMap<String,String> imageUrl = new HashMap<>();
        imageUrl.put("url",imgUrl);

        return imageUrl;
    }

    // 사진 삭제 테스트
    @DeleteMapping("/image/delete")
    public String deleteimage(@RequestParam("path") String fileName) {
        s3Uploader.deleteFile(fileName);
        return "삭제완료";
    }
}

'Spring' 카테고리의 다른 글

CI/CD  (1) 2024.04.16
WebSocket_2  (0) 2024.04.15
트랜잭션의 ACID란?  (0) 2024.04.15
트랜잭션(Transaction)  (0) 2024.04.15
SQL vs NoSQL  (0) 2024.04.15