관리 메뉴

피터의 개발이야기

[AWS]spring s3 파일 다운로드, 업로드, 삭제, 이름변경 방법 본문

AWS

[AWS]spring s3 파일 다운로드, 업로드, 삭제, 이름변경 방법

기록하는 백앤드개발자 2021. 1. 31. 08:00
반응형

 

S3의 파일 업로드, 다운로드, 삭제, 이름변경 방법에 대해서 정리하였습니다.

 

 

 

gradle

// aws s3
implementation 'com.amazonaws:aws-java-sdk-s3:1.11.475'
implementation 'com.amazonaws:aws-java-sdk-secretsmanager:1.11.339'
implementation 'com.amazonaws:aws-encryption-sdk-java:1.6.0'

 

application.properties

# aws s3
aws.s3.accessKey=accessKey
aws.s3.secretKey=secretKey
aws.s3.region=ap-northeast-2
aws.s3.bucket=test/test

 

AWSConfiguration.java

package com.peterica.swagger.config;

import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
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 AWSConfiguration {

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

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

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

    @Bean
    public AmazonS3 setS3Client() {
        AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);

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

AWS를 사용하기 위해서는 인증작업을 거쳐야 합니다. AWS를 사용할 수 있는 AmazonS3Client를 빈으로 생성합니다.

 

 

S3 파일 업로드

    /**
     * aws s3 로 파일 업로드
     *
     * @param file
     * @return
     */
    public String uploadToAWS(MultipartFile file) {
        String key = UUID.randomUUID() + "_" + file.getOriginalFilename();
        try {

            ObjectMetadata metadata = new ObjectMetadata();
            metadata.setContentType(file.getContentType());
            PutObjectRequest request = new PutObjectRequest(bucketName, key, file.getInputStream(), metadata);
            request.withCannedAcl(CannedAccessControlList.AuthenticatedRead); // 접근권한 체크
            PutObjectResult result = s3Client.putObject(request);
            return key;
        } catch (AmazonServiceException e) {
            // The call was transmitted successfully, but Amazon S3 couldn't process
            // it, so it returned an error response.
            log.error("uploadToAWS AmazonServiceException filePath={}, yyyymm={}, error={}", e.getMessage());
        } catch (SdkClientException e) {
            // Amazon S3 couldn't be contacted for a response, or the client
            // couldn't parse the response from Amazon S3.
            log.error("uploadToAWS SdkClientException filePath={}, error={}", e.getMessage());
        } catch (Exception e) {
            // Amazon S3 couldn't be contacted for a response, or the client
            // couldn't parse the response from Amazon S3.
            log.error("uploadToAWS SdkClientException filePath={}, error={}", e.getMessage());
        }

        return "";
    }

S3에 저장 시 파일이름은 UUID와 파일명을 합하여 저장합니다.

 첫번째 이유는 파일명이 중복으로 발생할 수 있다는 것입니다.
 두번째 이유는 보안적인 이유인데, 단지 개발용 S3에만 국한되는 이유입니다. S3가 외부에 모두 오픈된 상태이기 때문에 UUID가 없을 경우 파일 이름을 통해 쉽게 S3에 저장된 파일에 쉽게 접근할 수 있기 때문입니다. 버켓의 경로는 prefix로 정해져 있기 때문에 무분별한 접근을 막을 수 있도록 UUID를 적용하는 것이 좋습니다.

 

파일업로드 테스트

 

기존 파일 명에 UUID를 생성하여 파일이 생성되는 것을 확인하였습니다.

 

 

파일 다운로드

/**
 * file 다운로드
 *
 * @param fileKey  파일 key 로 해당 버킷에서 파일 찾아서 들고옴
 * @param downloadFileName 다운로드 파일명
 * @param request
 * @param response
 * @return
 */
public boolean download(String fileKey, String downloadFileName, HttpServletRequest request, HttpServletResponse response) {
    if (fileKey == null) {
        return false;
    }
    S3Object fullObject = null;
    try {
        fullObject = s3Client.getObject(bucketName, fileKey);
        if (fullObject == null) {
            return false;
        }
    } catch (AmazonS3Exception e) {
        throw new BadRequestException("다운로드 파일이 존재하지 않습니다.");
    }

    OutputStream os = null;
    FileInputStream fis = null;
    boolean success = false;
    try {
        S3ObjectInputStream objectInputStream = fullObject.getObjectContent();
        byte[] bytes = IOUtils.toByteArray(objectInputStream);

        String fileName = null;
        if(downloadFileName != null) {
            //fileName= URLEncoder.encode(downloadFileName, "UTF-8").replaceAll("\\+", "%20");
            fileName=  getEncodedFilename(request, downloadFileName);
        } else {
            fileName=  getEncodedFilename(request, fileKey); // URLEncoder.encode(fileKey, "UTF-8").replaceAll("\\+", "%20");
        }

        response.setContentType("application/octet-stream;charset=UTF-8");
        response.setHeader("Content-Transfer-Encoding", "binary");
        response.setHeader( "Content-Disposition", "attachment; filename=\"" + fileName + "\";" );
        response.setHeader("Content-Length", String.valueOf(fullObject.getObjectMetadata().getContentLength()));
        response.setHeader("Set-Cookie", "fileDownload=true; path=/");
        FileCopyUtils.copy(bytes, response.getOutputStream());
        success = true;
    } catch (IOException e) {
        log.debug(e.getMessage(), e);
    } finally {
        try {
            if (fis != null) {
                fis.close();
            }
        } catch (IOException e) {
            log.debug(e.getMessage(), e);
        }
        try {
            if (os != null) {
                os.close();
            }
        } catch (IOException e) {
            log.debug(e.getMessage(), e);
        }
    }
    return success;
}

/**
 * 파일명이 한글인 경우 URL encode이 필요함.
 * @param request
 * @param displayFileName
 * @return
 * @throws UnsupportedEncodingException
 */
private String getEncodedFilename(HttpServletRequest request, String displayFileName) throws UnsupportedEncodingException {
    String header = request.getHeader("User-Agent");

    String encodedFilename = null;
    if (header.indexOf("MSIE") > -1) {
        encodedFilename = URLEncoder.encode(displayFileName, "UTF-8").replaceAll("\\+", "%20");
    } else if (header.indexOf("Trident") > -1) {
        encodedFilename = URLEncoder.encode(displayFileName, "UTF-8").replaceAll("\\+", "%20");
    } else if (header.indexOf("Chrome") > -1) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < displayFileName.length(); i++) {
            char c = displayFileName.charAt(i);
            if (c > '~') {
                sb.append(URLEncoder.encode("" + c, "UTF-8"));
            } else {
                sb.append(c);
            }
        }
        encodedFilename = sb.toString();
    } else if (header.indexOf("Opera") > -1) {
        encodedFilename = "\"" + new String(displayFileName.getBytes("UTF-8"), "8859_1") + "\"";
    } else if (header.indexOf("Safari") > -1) {
        encodedFilename = URLDecoder.decode("\"" + new String(displayFileName.getBytes("UTF-8"), "8859_1") + "\"", "UTF-8");
    } else {
        encodedFilename = URLDecoder.decode("\"" + new String(displayFileName.getBytes("UTF-8"), "8859_1") + "\"", "UTF-8");
    }
    return encodedFilename;

}

S3의 파일을 접근하기 위해서는 URL로 접근하게 됩니다. 하지만 파일명으로 영어가 아닌 특수 기호나 이모티 문자열을 넣는 경우가 발생하기도 합니다. 실제로 그런 일이 있었고 replaceAll에 관한 글을 여기에 쓰기도 하였습니다. 빈칸 혹은 문자가 파일명으로 들어갈 경우 URL로 접근을 할 수 없는 경우가 발생하기도 합니다. 그것을 방지하기 위해서 url encode로직을 추가해야합니다.

 

 

파일 다운로드 테스트

 

 

파일 이름 변경

/**
 * 파일 이름변경
 * @param sourceKey
 * @param destinationKey
 */
public void rename(String sourceKey, String destinationKey){
    s3Client.copyObject(
            bucketName,
            sourceKey,
            bucketName,
            destinationKey
    );
    s3Client.deleteObject(bucketName, sourceKey);
}

이미 생성한 것은 변경할 수가 없습니다. 다만 새로운 이름으로 복사를 하고 기존 것을 지우는 방법으로 파일의 이름을 변경합니다.

 

파일 이름 변경 테스트

파일의 이름이 UUID가 빠진 test.png로 변경이 되었습니다. 

 

 

파일삭제

/**
 * file 삭제
 * @param fileKey
 */
public void delete(String fileKey) {
    s3Client.deleteObject(bucketName, fileKey);
}

 

파일삭제 테스트

버켓에서 파일이 사라져 있습니다.

 

 

소스는 여기에 있습니다.

반응형
Comments