파이썬 boto3 AWS S3 가이드: 파일 업로드와 Presigned URL
파이썬 boto3로 AWS S3 파일을 업로드하고 다운로드·직접 업로드용 Presigned URL을 안전하게 발급하는 방법을 정리합니다.

파이썬에서 AWS S3에 파일을 올리려면 Boto3의 upload_file()을 사용하고, 외부 사용자가 제한된 시간 동안 객체를 내려받거나 올리게 하려면 Presigned URL을 발급하면 됩니다. 운영 환경에서는 액세스 키를 코드에 넣지 않고 IAM 역할과 최소 권한을 적용하는 것이 핵심입니다.
핵심 요약
upload_file()은 파일 경로·버킷·객체 키를 받아 관리형 전송을 수행합니다.upload_fileobj()는 바이너리 파일 객체를 전송할 때 사용합니다.- Presigned URL은 버킷을 공개하지 않고 서명한 주체의 권한 범위에서 임시 접근을 제공합니다.
- 발급 자격 증명이 먼저 만료되거나 취소되면 URL도 사용할 수 없습니다.
Boto3 설치와 인증 준비
pip install boto3로 설치한 뒤 boto3.client("s3")를 만듭니다. 로컬에서는 AWS CLI 프로필이나 환경변수를 사용할 수 있지만 EC2·ECS·Lambda에서는 해당 실행 환경의 IAM 역할을 우선 사용하세요. 장기 액세스 키를 소스 저장소, 컨테이너 이미지, 로그에 넣으면 안 됩니다.
import boto3
s3 = boto3.client("s3", region_name="ap-northeast-2")
리전은 실제 버킷과 일치시켜야 합니다. 자격 증명 공급자 체인이 환경에 맞는 키를 찾으므로 코드에 액세스 키와 비밀키를 직접 적을 필요가 없습니다. 개발·운영 계정과 버킷을 분리하고 실행 주체에는 필요한 버킷과 작업만 허용하세요.
upload_file로 파일 업로드하기
from botocore.exceptions import ClientError
def upload(path, bucket, key):
try:
s3.upload_file(
path, bucket, key,
ExtraArgs={"ContentType": "image/webp"}
)
except ClientError as exc:
raise RuntimeError("S3 업로드 실패") from exc
upload("cover.webp", "example-private-bucket", "images/cover.webp")
객체 키는 폴더 경로처럼 보이지만 실제로는 문자열 식별자입니다. 사용자 파일명을 그대로 키로 쓰지 말고 허용 문자·길이·확장자를 검증하고 충돌하지 않는 이름을 생성하세요. AWS 공식 문서에 따르면 upload_file()은 큰 파일을 나눠 병렬 업로드하는 관리형 전송을 지원합니다.
메모리 데이터나 웹 업로드 객체처럼 파일 유사 객체를 보낼 때는 upload_fileobj()를 씁니다. 파일 객체는 텍스트가 아니라 바이너리 모드여야 합니다. ExtraArgs로 콘텐츠 유형과 메타데이터를 지정할 수 있지만 습관적으로 공개 ACL을 부여하지 마세요.
다운로드용 Presigned URL 만들기
url = s3.generate_presigned_url(
ClientMethod="get_object",
Params={
"Bucket": "example-private-bucket",
"Key": "reports/result.pdf",
},
ExpiresIn=300,
)
이 URL은 AWS 자격 증명이 없는 사용자가 제한된 시간 동안 지정 객체에 접근하게 합니다. 300초는 예시이므로 실제 값은 파일 민감도와 사용 흐름에 맞춰 최소화하세요. URL은 가진 사람이 사용할 수 있는 bearer token과 비슷하므로 채팅, 분석 로그, 공개 페이지에 남기지 마세요.
URL의 권한은 발급자보다 강해지지 않습니다. 발급 IAM 주체가 s3:GetObject 권한이 없으면 URL도 동작하지 않습니다. 임시 자격 증명으로 만들었다면 ExpiresIn이 더 길어도 원래 자격 증명이 만료되는 시점에 사용할 수 없게 됩니다.
브라우저 직접 업로드용 Presigned POST
post = s3.generate_presigned_post(
Bucket="example-private-bucket",
Key="uploads/user-123/${filename}",
Fields={"Content-Type": "image/webp"},
Conditions=[
{"Content-Type": "image/webp"},
["content-length-range", 1, 5 * 1024 * 1024],
],
ExpiresIn=300,
)
클라이언트가 S3로 직접 업로드하게 하려면 Presigned POST가 유용합니다. 서버는 반환된 URL과 폼 필드를 전달합니다. 콘텐츠 유형과 크기 조건을 제한하고, 업로드 후 서버에서 실제 파일 형식과 악성 콘텐츠를 검사하세요. 클라이언트가 보낸 MIME 유형만 믿으면 안 됩니다.
키는 사용자별 접두사와 무작위 ID로 분리하세요. 같은 키를 허용하면 기존 객체를 덮어쓸 수 있습니다. 완료 통지만 믿지 말고 head_object나 이벤트 처리로 객체 존재, 크기, 메타데이터를 확인하는 절차가 필요합니다.
IAM 최소 권한과 버킷 보안
| 기능 | 대표 권한 | 권장 범위 |
|---|---|---|
| 업로드 | s3:PutObject |
특정 버킷의 uploads 접두사 |
| 다운로드 | s3:GetObject |
필요한 객체 접두사 |
| 목록 | s3:ListBucket |
필요한 경우에만 |
| 삭제 | s3:DeleteObject |
삭제 기능이 있을 때만 |
애플리케이션에 관리자 권한을 주지 말고 ARN과 접두사를 좁혀야 합니다. S3 퍼블릭 액세스 차단을 기본으로 유지하고 외부 제공은 짧은 Presigned URL이나 CloudFront 같은 통제된 경로를 검토하세요. 서버 측 암호화, TLS, 버전 관리, 수명주기 정책도 데이터 중요도에 맞춰 설정합니다.
Presigned URL도 서버에서 발급 기준이 필요합니다
로그인했다는 이유만으로 임의의 버킷과 키에 대한 URL을 만들어 주면 안 됩니다. 요청한 사용자가 해당 객체의 소유자이거나 접근 권한이 있는지 애플리케이션 데이터베이스에서 먼저 확인하고, 서버가 허용한 접두사와 작업만 서명하세요. 다운로드 파일명이나 응답 헤더를 입력받는 경우에도 줄바꿈과 특수문자를 검증해야 합니다.
URL 발급 이벤트에는 토큰 원문 대신 사용자 ID, 객체 키, 허용 작업, 만료 시각, 요청 식별자를 감사 기록으로 남기는 편이 좋습니다. 비정상적으로 많은 발급이나 반복 실패를 탐지하고 필요하면 IAM 자격 증명 취소, 버킷 정책 변경, 객체 이동으로 접근을 차단할 수 있어야 합니다.
오류 처리와 파일 검증
ClientError의 응답 코드로 권한 거부, 버킷 없음, 잘못된 요청을 구분하되 자격 증명이나 URL이 로그에 들어가지 않도록 하세요. 네트워크 오류는 지수 백오프로 제한적으로 재시도하고, 동일 키 덮어쓰기가 문제라면 요청 ID와 데이터베이스 상태로 중복 실행을 제어합니다.
업로드 성공 응답만으로 사용자 파일이 안전하다고 판단하면 안 됩니다. 허용 확장자, MIME 유형, 파일 시그니처, 크기, 이미지 디코딩 가능 여부를 확인하세요. 다운로드 동작에 필요한 ContentType과 ContentDisposition도 명시하고 브라우저에서 시험합니다.
대용량 업로드에서 확인할 점
네트워크가 불안정한 환경에서는 전송 설정과 재시도 동작을 실제 크기의 파일로 시험하세요. 멀티파트 업로드가 중단됐을 때 남은 조각을 정리하는 수명주기 규칙, 업로드 진행률, 요청 타임아웃도 준비해야 합니다. 같은 객체 키에 여러 사용자가 동시에 쓰지 못하도록 애플리케이션 수준의 소유권 검증도 필요합니다.
실수 방지 체크리스트
- 버킷과 클라이언트 리전을 일치시킵니다.
- 장기 액세스 키를 코드에 넣지 않습니다.
- IAM 정책을 버킷·접두사·작업 단위로 최소화합니다.
- 퍼블릭 ACL을 기본값처럼 사용하지 않습니다.
- 사용자 파일명을 객체 키로 그대로 사용하지 않습니다.
- Presigned URL 만료를 필요한 만큼만 설정합니다.
- URL 원문을 로그에 남기지 않습니다.
- 직접 업로드에는 크기와 콘텐츠 조건을 둡니다.
- 업로드 후 실제 파일 형식을 검사합니다.
- 실패 재시도와 중복 덮어쓰기를 테스트합니다.
자주 묻는 질문
1. upload_file과 put_object 중 무엇을 쓰나요?
일반 파일과 큰 파일에는 관리형 전송을 제공하는 upload_file()이 편리합니다.
2. upload_fileobj는 언제 쓰나요?
디스크 경로가 아닌 바이너리 파일 객체나 메모리 스트림을 업로드할 때 씁니다.
3. Presigned URL이면 버킷을 공개해야 하나요?
아닙니다. 비공개 버킷을 유지한 채 제한된 접근을 제공할 수 있습니다.
4. 만료 뒤 다운로드는 어떻게 되나요?
AWS는 요청 시점의 만료를 검사합니다. 만료 뒤 새 연결이나 재시도는 실패할 수 있습니다.
5. URL을 가진 사람은 누구나 쓰나요?
조건과 만료 범위에서 사용할 수 있으므로 URL을 비밀처럼 취급해야 합니다.
6. ExpiresIn만 길게 하면 오래 쓰나요?
임시 자격 증명이 먼저 만료되거나 권한이 취소되면 URL도 더 일찍 무효화됩니다.
7. Content-Type이면 이미지가 보장되나요?
아닙니다. 헤더는 조작될 수 있어 업로드 후 실제 파일을 검사해야 합니다.
8. S3 폴더를 먼저 만들어야 하나요?
아닙니다. 키의 슬래시가 콘솔에서 폴더처럼 보일 뿐입니다.
9. 자격 증명은 어디에 저장하나요?
AWS에서는 IAM 역할을 우선하고 로컬에서는 보호된 프로필이나 비밀 관리 방식을 사용하세요.
10. 업로드 진행률을 표시할 수 있나요?
공식 문서의 Callback으로 전송된 바이트를 받아 계산할 수 있습니다.
공식 출처와 확인일
- AWS Boto3 Uploading files — 관리형 업로드와 ExtraArgs·Callback 확인
- AWS Boto3 Presigned URLs — URL 생성 방법 확인
- Amazon S3 Presigned URL 가이드 — 권한과 만료 확인
실제 확인일: 2026년 9월 10일. AWS 기능과 보안 정책은 변경될 수 있으므로 배포 전 현재 공식 문서를 다시 확인하세요.