Back-End/Flask

[Flask] 플라스크와 AWS S3 연동하고 이미지 업로드하기 (boto3)

현기 2023. 5. 28. 17:50

AWS S3Simple Storage Service의 준말입니다.

파일, 이미지, 동영상 등 다양한 유형의 데이터를 저장할 수 있습니다.

 

Flask와 S3를 연동하는 방법은 2가지가 있습니다.

✔ Flask의 확장 프로그램 Flask-S3 사용

✔ AWS에서 제공하는 Python SDK인 Boto3 사용

 

Flask-S3는 간단하게 활용할 수 있고, Boto3는 더 많은 유연성을 제공하지만

조금 더 복잡한 설정과 사용법을 가지고 있다고 합니다.

 

각각의 장단점이 있지만 저는 AWS에서 제공하는

Boto3를 사용해서 이미지를 업로드하는 API를 만들어 보겠습니다. 😀

 


📝S3 연동 및 파일 업로드 구현

 

⦁ 라이브러리 설치

pip install boto3

#파일이름 보안 라이브러리 - 해킹공격 방지
pip install werkzeug

필요한 라이브러리를 설치합니다. 가상 환경에 설치하는 것을 권장드립니다.

 

⦁ S3 연결

import boto3
from botocore.client import Config

s3 = boto3.client(
    's3',
    aws_access_key_id=app.config['S3_ACCESS_KEY'],
    aws_secret_access_key=app.config['S3_SECRET_KEY'],
    config=Config(signature_version='s3v4')
)

엑세스 키와 시크릿 키를 입력해야 합니다. app.config[]를 거치지 않고, 직접 입력해도 되지만

키가 노출되면 안되기 때문에 환경 변수를 사용하는 것을 추천드립니다.

 

👍 참고하세용 https://hyunki99.tistory.com/98

 

⦁ S3 파일 업로드

# request도 import가 필요합니다 !
from flask import Flask, request

#파일이름 보안 라이브러리
from werkzeug.utils import secure_filename

#이미지 업로드
@app.route('/imgupload', methods=['POST'])
def upload_file():
    file = request.files['file']
    if file:
        filename = secure_filename(file.filename)
        s3.upload_fileobj(file, app.config['S3_BUCKET_NAME'], filename)
        return 'File uploaded successfully', 200
    
    return 'No file selected', 404

파일 데이터는 FormData로 전송받아야 합니다. 요청에 file이라는 key로 전송된 파일을 가져옵니다. if문을 통해 파일이 정상적으로 전송되었는지 확인합니다.

 

secure_filename()는 werkzeug에서 제공하는 함수입니다.

업로드된 파일의 이름을 보안 상 안전한 파일 이름으로 변환합니다. 악의적인 파일 이름으로 인한 보안 문제를 방지할 수 있습니다.

 

이후 성공적으로 완료되었다면 메세지와 함께 200 상태 코드를 반환합니다.

 


📝테스트 하기

 

⦁ test.html

<form enctype="multipart/form-data" method="POST" action="http://127.0.0.1:5000/imgupload">
    <input type="file" name="file">
    <input type="submit" value="Submit">
</form>

새로운 html파일을 하나 생성합니다. action에 들어가는 url은 서버 주소에 맞게 변경해야 합니다.

 

성공적으로 S3에 이미지가 업로드된 모습을 확인할 수 있습니다. 😎

 


📝에러 해결 과정

 

❗ 권한 에러 발생

botocore.exceptions.ClientError: An error occurred (AccessDenied) when calling the PutObject operation: Access Denied

 

내용에서 바로 알 수 있듯이, S3 버킷에 PutObject 권한이 없어서 발생한 에러입니다.

저는 버킷을 생성할 때 아무런 권한도 부여하지 않았기 때문에 해당 에러가 발생했습니다.

 

⦁ 버킷 권한 부여

버킷 → 권한 →  버킷 정책 탭에 접속합니다.

 

 

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::your-bucket-name/*"
    }
  ]
}

 

버킷 정책에 다음과 같은 json을 추가해주면 에러가 해결되고 파일을 업로드할 수 있게 됩니다.

 

권한과 정책에 대한 더 자세한 정보는 공식문서를 참고하세요 !

 

https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-policy-language-overview.html

 

Policies and Permissions in Amazon S3 - Amazon Simple Storage Service

Thanks for letting us know this page needs work. We're sorry we let you down. If you've got a moment, please tell us how we can make the documentation better.

docs.aws.amazon.com

 



📝파일이 덮어쓰기 되는 문제 해결

 

 

S3에 파일을 저장할 때, 파일 이름이 동일하다면 기존 파일을 덮어쓰는 문제가 발생합니다.

따라서 이름을 고유하게 만드는 방법 중 하나인 UUID을 사용했습니다.

 

🤔 UUID 란?

UUID(Universally Unique IDentifier)는 네트워크상에서 고유성을 보장하는 ID를 만들기 위한 표준 규약이다. UUID는 다음과 같이 32개의 16진수로 구성되며 5개의 그룹으로 표시되고 각 그룹은 붙임표(-)로 구분한다.

280a8a4d-a27f-4d01-b031-2a003cc4c039 적어도 서기 3400년까지는 같은 UUID가 생성될 수 없다고 한다.
이러한 이유로 UUID를 데이터베이스의 프라이머리 키(primary key)로 종종 사용한다.

 

🤔 수정한 코드

#이미지 업로드
@app.route('/imgupload', methods=['POST'])
def upload_file():
    file = request.files['file']
    
    if file:
        folder = 'img/'  # 업로드할 폴더 이름
        filename = secure_filename(file.filename)
        unique_filename = str(uuid.uuid4()) + os.path.splitext(filename)[1]  # UUID를 사용하여 파일 이름 고유성 보장
        key = os.path.join(folder, unique_filename)  # 경로 생성
        
        s3.upload_fileobj(file, app.config['S3_BUCKET_NAME'], key)
        return 'File uploaded successfully', 200
    
    return 'No file selected', 404

+ S3의 특정 폴더에 업로드하는 코드도 추가 되어었습니다.

 

😎 결과

똑같은 파일이지만 고유하게 식별되는 파일 이름으로 저장된 모습입니다 !

 

 


 


참고 문헌 : https://wikidocs.net/131351