[React Native] S3 연동 이미지 업로드


🔥 1. 사전 설치

yarn add react-native-url-polyfill
yarn add react-native-image-picker
yarn add @aws-sdk/client-s3
yarn add @aws-sdk/lib-storage
yarn add buffer
yarn add react-native-get-random-values
yarn add react-native-fs

yarn add -D @aws-sdk/types
yarn add @types/react-native-get-random-values
  • **react-native-image-picker : 앨범 접근 및 카메라 허용 ⇒ 이미지 업로드니 당연히 필요**
  • **react-native-url-polyfill : URL 객체를 사용할 때 호환성 관리**
  • **@aws-sdk/client-s3 : S3 버킷을 생성하거나 삭제하고, 버킷 내 객체 관리 및 업로드에 필요**
  • **@aws-sdk/lib-storage : Amazon S3 (Simple Storage Service)와 같은 스토리지 서비스를 통해 파일을 업로드**
  • **buffer : 파일, 이미지, 네트워크 통신 등 RN에서 이진 데이터를 위함**
  • **react-native-get-random-values : 무작위 바이트를 생성 작업**
  • **react-native-fs : 이미지 파일 읽기 쓰기**

🔥 2. IAM 설정

1. 사용자 생성

1

2. 세부 정보 지정

2

3. 권한 설정 (AmazonS3FullAccess)

3

4. 사용자 생성

4

5. 액세스 키 다운로드

  • .csv 파일을 다운로드
  • 다운로드 기회는 한번

5

🔥 3. 버킷 생성

1. 사용자 생성

6

2. 리전 선택

7

3. 객체 소유권

8

  • AWS S3 버킷에서 객체(파일)의 소유권은 업로드를 수행한 IAM 사용자(계정)에게 자동으로 할당된다. 객체의 소유권은 기본적으로 업로드한 IAM 사용자에게 속함
  • ACL(Access Control List)은 AWS S3 버킷의 객체에 대한 액세스 권한을 관리하는 기능 ⇒ 업로드된 객체에 대한 공개 읽기 액세스를 허용
  • 업로드를 수행한 IAM 사용자에게 자동으로 할당

4. 퍼블릭 엑세스 설정

9

  • S3에 접근할 수 있는 URL을 입력하여 이미지가 정상적으로 업로드
  • 실무에선 모든 퍼블릭 액세스 차단

10

  • 버킷에 저장되는 객체를 자동으로 암호화해 주는 설정

5. 암호화 설정

11

6. 버킷 만든 후 권한 설정

12

13

7. 버킷 권한 설정

{

		{
		"Version": "2012-10-17",
		"Id": "Policy1686890931481",
		"Statement": [
		{
		"Effect": "Allow",
		"Principal": "",
		"Action": [
		"s3:GetObject",
		"s3:PutObject"
		],
		"Resource": "arn:aws:s3:::your-storage"
		}
	]
}
  1. 정책 타입 선택
  2. 버킷 정책이 적용될 대상 (전체: *)
  3. 버킷이 수행할 액션 (GetObject, PutObject 선택)
  4. 버킷에 어떤 리소스에 적용할지 (버킷이름/*)

8. 이미지 업로드 테스트

14

15

16

17

🔥 3. 실제 코드 반영

1. 이미지 등록 후 S3에 업로드

import {
  ImageLibraryOptions,
  ImagePickerResponse,
  launchImageLibrary,
} from "react-native-image-picker";

const chooseImage = async () => {
  const options: ImageLibraryOptions = {
    mediaType: "photo", // 추가: 원하는 미디어 타입('photo', 'video' 등)을 지정합니다.
  };

  launchImageLibrary(options, async (response: ImagePickerResponse) => {
    // 이미지 라이브러리로부터의 응답 처리
    if (response.didCancel) {
      console.log("사용자가 이미지 선택을 취소했습니다.");
    } else if (response.errorCode === "camera_unavailable") {
      console.log("카메라 사용이 불가능 합니다");
    } else if (response.errorCode === "permission") {
      console.log("카메라 권한 설정이 잘못되었습니다");
    } else {
      if (response.assets && response.assets.length > 0) {
        const { uri, fileName, type: fileType } = response.assets[0];

        try {
          const uploaded = await uploadToS3(uri, fileName, fileType);
          if (uploaded) {
            console.log("S3 이미지 업로드 완료:", uploaded);
            setCertificateImage(uploaded);
          }
          // 업로드 성공 후 추가 작업을 수행할 수 있습니다.
        } catch (error) {
          console.error("S3 이미지 업로드 실패:", error);
        }
      } else {
        // 선택된 이미지가 없는 경우의 처리
      }
    }
  });
};

2. 이미지 등록 후 S3에 업로드

const chooseImage = () => {
  const options: ImageLibraryOptions = {
    mediaType: "photo", // 추가: 원하는 미디어 타입('photo', 'video' 등)을 지정.
  };

  launchImageLibrary(options, (response: ImagePickerResponse) => {
    // 이미지 라이브러리로부터의 응답 처리
    if (response.didCancel) {
      console.log("사용자가 이미지 선택을 취소했습니다.");
    } else if (response.errorCode === "camera_unavailable") {
      console.log("카메라 사용이 불가능 합니다");
    } else if (response.errorCode === "permission") {
      console.log("카메라 권한 설정이 잘못되었습니다");
    } else {
      console.log(response);
      const selectedImageUri = response.assets && response.assets[0]?.uri;

      if (selectedImageUri) {
        setSelectedImage(selectedImageUri);
        setProfile(selectedImageUri.uri);
      } else {
        // 선택된 이미지가 없는 경우의 처리
      }
    }
  });
};

3. 업로드 후 URL 반환

import { S3Client } from "@aws-sdk/client-s3";
import "react-native-url-polyfill/auto";
import "react-native-get-random-values";
import { Upload } from "@aws-sdk/lib-storage";
import { Buffer } from "buffer";
import RNFS from "react-native-fs";
import { AwsCredentialIdentity } from "@aws-sdk/types";
import Config from "react-native-config";

const options = {
  keyPrefix: "uploads/",
  bucket: Config.BUCKET,
  region: Config.REGION,
  successActionStatus: 201,
};

let credentials: AwsCredentialIdentity = {
  accessKeyId: Config.ACCESSKEYID || "",
  secretAccessKey: Config.SECRETACCESSKEY || "",
};

const client = new S3Client({
  region: options.region,
  credentials: credentials,
});

export async function uploadToS3(uri: any, name: any, type: any) {
  try {
    const file = {
      uri: uri,
      name: name,
      type: type,
    };

    const fileData = await RNFS.readFile(uri, "base64");

    const upload = new Upload({
      client: client,
      params: {
        Bucket: Config.BUCKET,
        Key: "uploads/" + file.name,
        Body: Buffer.from(fileData, "base64"),
        ContentType: "image/*",
      },
    });
    const response = await upload.done();
    console.log("response?", response);
    if (response && response.Location) {
      return response.Location.replace(
        `${Config.BUCKET}.s3.ap-northeast-2.amazonaws.com`,
        `s3.ap-northeast-2.amazonaws.com/${Config.BUCKET}`
      );
    }
  } catch (error) {
    console.log("에러>", error);
    return false;
  }
}