본문 바로가기
IT이야기/AWS

[AWS] S3 StatusCode 301에러 해결

by JI_NOH 2024. 10. 22.

 

S3에 업로드 테스트를 하는데 자꾸

(Service: Amazon S3; Status Code: 301; Error Code: PermanentRedirect; Request ID:)
AWS Error Code: PermanentRedirect
 

이런 에러가 나는 것임.

 

좀 찾아보니까 region설정, endpoint에러 등등 이유가 있다고 하는데

아무리봐도 region도 문제가 없고

- AmazonS3ClientBuiler로 셋팅할 때 application.yml에 설정해둔 값으로 잘 들어감

s3 정책도 잘 주어져있고

#버킷 정책 : S3 -> 버킷 -> 권한탭 [버킷정책]
{
    "Version": "2012-10-17",
    "Id": "Policy1696948386695",
    "Statement": [
        {
            "Sid": "Stmt1696948368464",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:*",
            "Resource": "arn:aws:s3:::project-shopping1"
        }
    ]
}
 

위 정책을 해석하자면 모두에게 허가되어있으며 PUT, DELETE, POST Action이 다 허가된다.

정도로 이해하면 되겠다. 좀 더 궁금하다면 AWS S3 버킷정책을 보도록 하자.

일부 아이디에만 권한을 주겠다면 그건 S3 인스턴스 생성 때 부터 설정을 좀 해주거나 수정해야됨. 이건 상관없으니 쓰루하고

 

이곳저곳을 뒤지다가 발견한 한 글 덕에 어????? 하고 시도했다가 뭔가 해결의 실마리를 찾았다.

 

 

변경전 S3설정

    @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;

    public AmazonS3 amazonS3Client() {
        BasicAWSCredentials basicAWSCredentials = new BasicAWSCredentials(accessKey, secretKey);
        return AmazonS3ClientBuilder
                .standard()
                .withCredentials(new AWSStaticCredentialsProvider(basicAWSCredentials))
                .withRegion(region)
                .build();
    }
 

application.yml에 설정한 aws설정값을 가지고와 설정하는 파일이다.

 

한 파일 안에 몰아서 관리할 수도 있는데 일단 나는 config파일을 따로 빼서 관리했고 여기서 주의깊에 봐야할 곳은 pulbic AmazonS3 amazonS3Client() 이 부분이다.

@Service
public class S3ImgUploaderService {

    @Value("${cloud.aws.s3.bucket}")
    private String bucket;
    private final AmazonS3 amazonS3;     #변경 전


    public List<ItemImgDTO> upload(String fileType, List<MultipartFile> multipartFiles) throws IOException {
        List<ItemImgDTO> s3files = new ArrayList<>();

        String uploadFilePath = fileType + "/" + getFolderName();

        for (MultipartFile multipartFile : multipartFiles) {
            String oriFileName = multipartFile.getOriginalFilename();
            String uploadFileName = getUuidFileName(oriFileName);
            String uploadFileUrl = "";

            ObjectMetadata objectMetadata = new ObjectMetadata();
            objectMetadata.setContentLength(multipartFile.getSize());
            objectMetadata.setContentType(multipartFile.getContentType());

            try (InputStream inputStream = multipartFile.getInputStream()) {
                // ex) 구분/년/월/일/파일.확장자
                String keyName = uploadFilePath + "/" + uploadFileName;

                // S3에 폴더 및 파일 업로드  # 변경 전
                amazonS3.putObject(
                        new PutObjectRequest(bucket, keyName, inputStream, objectMetadata));

                // S3에 업로드한 폴더 및 파일 URL  # 변경 전
                uploadFileUrl = amazonS3.getUrl(bucket, keyName).toString();
            } catch (IOException e) {
                e.printStackTrace();
                log.error("Filed upload failed", e);
            }

            s3files.add(
                    ItemImgDTO.builder()
                            .oriImgName(oriFileName)
                            .uploadImgName(uploadFileName)
                            .uploadImgPath(uploadFilePath)
                            .uploadImgUrl(uploadFileUrl)
                            .build());
        }
        return s3files;
    }
}
 

그리고 업로드 서비스를 시행하는 파일이다. 이것도 뒤져보면 소스가 다 비슷비슷하다.

여기서도 주의깊에 봐야할 곳은 private final AmazonS3 amazonS3; 와 그를 사용 하는 곳이다.

 

 

변경 후 S3설정

    @Bean
    public AmazonS3Client amazonS3Client() {
        BasicAWSCredentials basicAWSCredentials = new BasicAWSCredentials(accessKey, secretKey);
        return (AmazonS3Client)AmazonS3ClientBuilder
                .standard()
                .withCredentials(new AWSStaticCredentialsProvider(basicAWSCredentials))
                .withRegion(region)
                .build();
    }
 
@Service
public class S3ItemImgUploaderService {
    @Value("${cloud.aws.s3.bucket}")
    private String bucket;

    private final AmazonS3Client amazonS3Client;


    public List<ItemImgDTO> upload(String fileType, List<MultipartFile> multipartFiles) throws IOException {
        List<ItemImgDTO> s3files = new ArrayList<>();

        String uploadFilePath = fileType + "/" + getFolderName();

        for (MultipartFile multipartFile : multipartFiles) {
            String oriFileName = multipartFile.getOriginalFilename();
            String uploadFileName = getUuidFileName(oriFileName);
            String uploadFileUrl = "";

            ObjectMetadata objectMetadata = new ObjectMetadata();
            objectMetadata.setContentLength(multipartFile.getSize());
            objectMetadata.setContentType(multipartFile.getContentType());

            try (InputStream inputStream = multipartFile.getInputStream()) {
                // ex) 구분/년/월/일/파일.확장자
                String keyName = uploadFilePath + "/" + uploadFileName;

                // S3에 폴더 및 파일 업로드
                amazonS3Client.putObject(
                        new PutObjectRequest(bucket, keyName, inputStream, objectMetadata));

                // S3에 업로드한 폴더 및 파일 URL
                uploadFileUrl = amazonS3Client.getUrl(bucket, keyName).toString();
            } catch (IOException e) {
                e.printStackTrace();
                log.error("Filed upload failed", e);
            }

            s3files.add(
                    ItemImgDTO.builder()
                            .oriImgName(oriFileName)
                            .uploadImgName(uploadFileName)
                            .uploadImgPath(uploadFilePath)
                            .uploadImgUrl(uploadFileUrl)
                            .build());
        }
        return s3files;
    }
 

차이점이 보이나?

AmazonS3 -> AmazonS3Client로 바꾸자마자 잘 됨. 와 이게 뭐냐??

일단 이렇게하면 해결은 되는데

진짜!!! 최종 답안은 이게 아니니 진짜가 궁금하다면 제일 마지막으로 스크롤 고고

 

 

아니 그래서 두개의 차이가 뭔지 좀 찾아봤는데

오히려 AmazonS3Client가 deprecated됐다는 말도 있고 AmazonS3를 써야 됐다는 사람들도 있고...

실제로도 AWS제공 github소스코드를 보면 AmazonS3를 쓰고 있어서 더 미스테리,,

AWS 개발자가이드에도 AmazonS3를 쓰고 있다. 이러면 더 이상해지는데..?

 

그래서 뭔가 찜찜하다. 조금만 더 테스트를 해보자!

 

 

 

테스트1

테스트1-1. S3Config 파일에서 AmazonS3 return

 

결국 S3와 연결 설정하는 가장 중요한 이 부분을 가지고

AmazonS3로 리턴했을 경우, AmazonS3Client로 리턴했을 경우를 각각 테스트해봤는데 놀라운 사실이 발견되었다.

    public AmazonS3 amazonS3Client() {
        BasicAWSCredentials basicAWSCredentials = new BasicAWSCredentials(accessKey, secretKey);
        return AmazonS3ClientBuilder
                .standard()
                .withCredentials(new AWSStaticCredentialsProvider(basicAWSCredentials))
                .withRegion(region)
                .build();
    }
 

특이사항이라고 하면 AmazonS3가 Interface였다는 점.

그리고 여기 설정까지만 해도 withRegion(region)에는 ap-northeast-2로 잘 설정되어있었음!!

 

근데 이제 upload하는 곳에서 amazonS3를 가져와서 썼더니 ??? us-west-2는 웬말이여?

AmazonS3

 

테스트1-2. S3Config 파일에서 AmazonS3Client return

그래서 다시 AmazonS3Client로 바꿔서 리턴해주고

    @Bean
    public AmazonS3Client amazonS3Client() {
        BasicAWSCredentials basicAWSCredentials = new BasicAWSCredentials(accessKey, secretKey);
        return (AmazonS3Client)AmazonS3ClientBuilder
                .standard()
                .withCredentials(new AWSStaticCredentialsProvider(basicAWSCredentials))
                .withRegion(region)
                .build();
    }
 

upload서비스 로직에서도 amazonS3Client를 가져와 사용했더니 이건 정상적으로 ap-northeast-2로 뜸.

 

혹시나해서 AmazonS3Client를 봤더니 AmazonS3를 상속받고 있는데...? 모지..??

이정도면 두개는 같은 라이브러리라고봐도 사실 무방한데..

 

 

흠.. 흠.. 암튼 region이 다르니까 301이 떴었다는 것은 이로 증명이 되긴했는데 왜 저런지는 이해가 여전히 잘 안된다.

 
 
AmazonS3Client

 

 

 

 

 

테스트2

 

그러다가 문득 AmazonS3 에 대해 @Bean 선언을 해서 주입을 했는데

DI가 제대로 되지 않은 것이 아닐까 하여 다시 테스트를 했다. 객체 번호(?) 도 함께 확인을 해봤는데

 

테스트2-1. S3Config 파일에서 AmazonS3 return

 

 

 

최초셋팅값고 실제 업로드에 사용되는 amazonS3의 번호가 달라짐!!

빈 선언까지는 제대로 됐는데 upload파일에서 AmazonS3의 의존성 주입이 제대로 안된게 맞는듯?

 

 

테스트2-2. S3Config 파일에서 AmazonS3Client return

혹시 모르니 더블체크로 AmazonS3Client로 돌려봤다.

 
사진 삭제

사진 설명을 입력하세요.

 
사진 삭제

사진 설명을 입력하세요.

즉, 같은 객체니까 이건 문제없이 실행이 됐던 것이다!!!

이걸 어떻게 해결하냐.. 혹시몰라

@Autowired

private final AmazonS3 amazonS3;

도 해줬는데 왜 안되는 것일까나...

 

 

 

 

테스트3 이자 최종 답안

두번째 테스트에 이르러서 암만생각해도 뭔가 빈주입에서 문제가 생긴 것 같다. 라는 유추에 도달

뭘 더 해야할지 몰라 고민하다가 흠..? 혹시..? 하면서 Config파일의 빈 선언부를 다시 고쳐보았다.

 

테스트3-1. S3Config 파일에서 메소드이름 amazonS3()로 변경

    @Bean
    public AmazonS3 amazonS3() {
        BasicAWSCredentials basicAWSCredentials = new BasicAWSCredentials(accessKey, secretKey);

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

빈 주입하는 친구 이름때문인가..? 싶어서

public AmazonS3 amazonS3()로 이름을 바꾸고 테스트를 했더니

@Service
public class S3ItemImgUploaderService {

    private final AmazonS3 amazonS3;
 

갑자기 여기에 정상적으로 주입이 됐음.............. 아니 뭐지?? 빈 주입하는거에 저 함수 이름이 중요해..?

그냥 리턴 객체를 빈 등록하는거 아니었어????? 하고서 부랴부랴 찾아보고나서야 이유도 알아냈다.

 

@Bean 대해서 좀 알아봤더니 날림으로 공부한 티가 나네 나 ..

 

1. 빈의 이름은 기본적으로 메서드 이름이 됨

2. 메서드의 리턴 객체가 스프링 빈 객체임을 선엄함

3. @Configuration설정된 클래스의 메서드에서 사용 가능

4. @Component 어노테이션을 이용해 @Configuration 없이도 빈객체 생성 가능

 

1번때문에 안된거였어~~ amazonS3Client()로 호출하니까 amazonS3Client이 빈 객체가 되면서 amazonS3Client로 final 선언했을 때만 잘가져오지 미친놈들아 ㅠㅠㅠ

 

에효 그래도 덕분에 몇 시간 삽질했지만 좋은 것 알아간다.

 

 

 

추가로 S3 파일 업로드 테스트하다가 난 에런데 최대 이미지 사이즈가 제한이 있나보다

고화질로 찍어둔 사진가지고 테스트하다가 받은 오류값임

 

첨부파일 사이즈 오버오류로 추정. 이거는 다음에 해결방법을 찾아보겠다.. 오늘은 이만..

2023-10-17 00:37:54.129  WARN 67959 --- [nio-8080-exec-4] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [org.springframework.web.multipart.MaxUploadSizeExceededException: Maximum upload size exceeded; nested exception is java.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.impl.FileSizeLimitExceededException: The field files exceeds its maximum permitted size of 1048576 bytes.]