본문 바로가기
프로젝트 develop

Presigned URL

by BIGENGINEER 2026. 7. 13.

S3객체는 개인 정보가 포함될 수 있으니 비공개로 두고, 필요한 순간에만 임시 접근 권한을 주는 방식이 더 안전함

그 임시 출입증이 Presigned URL임

 

S3 Presigned URL

- AWS가 서명해서 만들어주는 유효기간이 있는 임시 URL

 

  • S3 비공개 이미지: 잠긴 창고 안의 물건
  • 일반 사용자: 창고 열쇠가 없음
  • Presigned URL: 50분 동안만 사용할 수 있는 임시 출입증

 

https://bucket.s3.amazonaws.com/uploads/image.png
?X-Amz-Algorithm=...
&X-Amz-Credential=...
&X-Amz-Date=...
&X-Amz-Expires=3000
&X-Amz-Signature=...

 

 

 

 

 

 

<흐름>

사용자 이미지 

-> Presinged PUT URL로 업로드

-> AWS S3 원본 이미지 저장

-> Presinged GET URL로 다운로드

-> SPRING BOOT 백엔드

-> SPRING BOOT에서 이미지를 BASE64로 변환해서 cloudinary에 업로드

-> cloudinary에서 공개 https 이미지 url을 spring boot로 반환

-> springboot 백엔드가 image_url로 qwen-vl에 전달

 

 

 

 

Qwen-vl은 이미지 어떻게 읽어?

-> 이미지 url 읽을 수 있음

{
  "type": "image_url",
  "image_url": {
    "url": "https://example.com/problem.png"
  }
}

 

- qwen 서버가 해당 url에 직접 접속해서 이미지를 다운로드 함

즉, 우리 백엔드가 qwen에 url 전달 

-> qwen 서버가 그 url에 http 요청

-> 이미지를 다운로드해서 분석

 

 

 

 

Cloudinary

- 이미지와 동영상을 저장하고 전달하는 서비스

- 이미지를 cloudinary에 업로드하면 아래와 같은 https url을 돌려줌

=> https://res.cloudinary.com/cloud-name/image/upload/qwen_123.png

 

- 해당 주소를 브라우저나 qwen에 전달하면 이미지를 바로 가져갈 수 있음

 

 

S3랑 Cloudinary의 다른 점은?

- S3는 범용적인 비공개 원본 파일 저장

- Cloudinary는 이미지 업로드, 변환, 전달에 특화 

즉, 원본은 S3, QWEN 전달용 복사본은 Cloudinary

 

 

 

 

 

Cloudinary Relay

- relay : 중계

 

<과정>

S3 이미지
   ↓
Spring Boot가 다운로드
   ↓
Cloudinary에 다시 업로드
   ↓
Cloudinary URL을 Qwen에 전달

 

 

즉, cloudinary가 S3 URL을 자동으로 연결한 것이 아니라,

spring boot 백엔드가 중간에서 직접 다운로드하고 다시 업로드한 것 

 => spring boot가 s3 이미지와 cloudinary 사이에서 relay 역할을 수행함

 

 

Base64

- 이미지는 원래 바이너리 데이터임

- Base64는 바이너리 데이터를 일반 문자열로 바꾸는 인코딩 방식

- 이렇게 바꾸면 데이터 크기가 일반적으로 약 33% 증가함

 

 

 

 

< 실제 흐름 >

1. 프론트엔드가 api 호출

GET /s3/presigned?count=2

 

2. 백엔드가 문제 이미지와 학생 풀이 이미지에 대해 반환

{
  "uploadUrls": [
    "Presigned PUT URL 1",
    "Presigned PUT URL 2"
  ],
  "downloadUrls": [
    "Presigned GET URL 1",
    "Presigned GET URL 2"
  ],
  "s3Key": "uploads/uuid.png"
}

 

 

3. 프론트엔드가 PUT URL로 이미지를 S3에 업로드 함

- 이미지 파일이 서버를 거치지 않으므로 백엔드의 업로드 트래픽을 줄일 수 있음 

 

 

4. 프론트엔드는 백엔드의 AI 작업을 시작하는 API에 다운로드 URL과 S3 KEY를 전달함

{
  "downloadUrls": [
    "Presigned GET URL 1",
    "Presigned GET URL 2"
  ],
  "s3Key": "uploads/uuid.png"
}

 

 

 

5. SPRING BOOT가 이미지를 다운로드 

new URL(presignedUrl).openStream()

 

 

6. SPRING BOOT가 CLOUDINARY에 업로드 

이미지 바이트
→ Base64
→ data:image/png;base64,...
→ Cloudinary 업로드

 

 

7. Cloudinary가 url 반환

 

8. url을 qwen에 전달 

 

 

 

근데 왜 S3에서 Cloudinary로 한 번 더 올림? 

- Qwen에 복잡한 서명이 붙은 Presigned URL 대신에 이미지 전달에 특화된 일반 HTTPS URL을 제공하기 위해서임

- 외부 모델과의 호환성을 확보하기 쉬워서 

 

 

공개 HTTPS URL?

- 로그인이나 AWS 권한 없이 인터넷에 접근할 수 있고, HTTLS로 암호화돼서 전송되는 이미지 주소

사용자가 S3에 올림 -> 백엔드가 S3에서 읽음 -> Cloudinary에 복사 -> Cloudinary 주소를 qwen에 줌 

 

 

S3PresignedURL을 Qwen2.5-VL에 직접 전달했을 때 외부 AI모델이 이미지에 안정적으로 접근하지 못하는 문제? 

- 기존 방식

S3 Presigned URL
https://s3.../image.png
?X-Amz-Credential=...
&X-Amz-Expires=...
&X-Amz-Signature=...

          ↓ Qwen에 직접 전달

Qwen 서버가 URL을 다시 요청
          ↓
서명 URL Fetch 호환성 문제
          ↓
이미지 분석 실패

 

 

=> S3PresignedURL을 그대로 qwen에 직접 전달했을 때 S3PresignedURL에 긴 서명 파라미터와 만료 시간이 포함되어 있어서 이것을 qwen이 처리하는 과정에서 호환성 문제가 있었음. 

 

=> 즉, qwen의 서버 측 이미지 fetch 모듈이 서명 파라미터와 만료시간이 포함된 임시 url을 안정적으로 처리하지 못했음 

 

 

 

 

그럼 왜 백엔드 proxy를 안 만들었나?

- 백엔드 proxy 만들면 qwen의 이미지 요청 트래픽을 서버가 계속 받아야하기 때문 

'프로젝트 develop' 카테고리의 다른 글

비동기 처리 구조와 @Async  (0) 2026.07.19
동기 vs 비동기  (0) 2026.07.13
[develop]develop plan  (0) 2026.07.02