리타의 저장소

Presigned Url, Signed Url, Signed Cookie 본문

Dev/Backend

Presigned Url, Signed Url, Signed Cookie

ريتا 2026. 3. 2. 21:53

1. Presigned URL (S3 직접 접근)

Presigned URL (미리 서명된 URL) 이란 AWS 자원에 대한 접근 권한을 제공하기 위해서 사용되는,
이름 그대로 사전에(미리) 적절한 권한을 가진 자격증명에 의하여 signed된 URL

 

 

앱에서 이미지가 저장되는 과정

  1. Client에서 이미지를 업로드한다는 요청을 보낸다.
  2. Server에서는 이미지 업로드를 하기위해 DB와 S3에 각각 이미지에 대한 정보를 업로드한다.
  3. DB, S3에 저장이 에러없이 끝나면 프론트에 response 200을 날린다.
  4. 프론트에서는 서버에서 200 응답을 받으면 방금 업로드한 이미지를 표시한다.

 

Presigned URL 방식?

  1. 클라이언트가 서버에 “업로드 URL 주세요”
  2. 서버가 Presigned URL 발급
  3. 클라이언트가 직접 S3에 업로드
// 요청 1
POST /presigned-url
{ "key": "test/file.txt" }

응답:
{
  "url": "https://bucket.s3.amazonaws.com/test/file.txt?
         X-Amz-Date=20260222T100000Z&
         X-Amz-Signature=abc123..."
}

// 5분 후 요청 2 (같은 파일)
POST /presigned-url
{ "key": "test/file.txt" }

응답:
{
  "url": "https://bucket.s3.amazonaws.com/test/file.txt?
         X-Amz-Date=20260222T100500Z&    ← 시간 다름
         X-Amz-Signature=xyz789..."       ← 서명 다름
}

 

S3 객체의 경우, Default 설정은 비공개이며, 소유자만 접근이 가능하다.

Bucket Policy나 ACL(Access Control List, 접근 제어 목록) 과 같은 제한 설정과 관계없이 소유자의 보안 자격 증명을 사용하여 특정 "유효시간" 내에 S3에 PUT, GET이 가능한 URL을 생성하는 것이 Presigned URL 방식이다. 

Presigned URL을 통해 임시적으로 객체에 접근이 가능하다. 이 말인 즉슨, 해당 URL을 아는 모든 사람에게 S3 버킷에 대한 Access 가 가능하다는 소리이다. 때문에, 유효시간 설정이 꼭 필요하다.

 

동작 흐름

1. 클라이언트 → 서버: "파일 업로드하고 싶어요"
2. 서버: AWS 자격증명으로 URL에 서명 (HMAC-SHA256)
3. 서버 → 클라이언트: "이 URL로 1시간 동안 업로드 가능해요"
   예: https://bucket.s3.amazonaws.com/file.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=...
4. 클라이언트 → S3: 서명된 URL로 직접 PUT 요청
5. S3: 서명 검증 후 파일 저장

 

특징

  • 서버 부하 없음 (파일이 서버를 거치지 않음)
  • IAM 자격증명 기반 (AWS Access Key/Secret Key)
  • 단일 파일 업로드/다운로드에 최적

 

TEST

요청

{
  "body": "{\"key\": \"test/sample.txt\", \"method\": \"putObject\", \"expiresSeconds\": 3600}"
}

 

응답

{
  "statusCode": 200,
  "headers": {...},
  "body": "{
      \"url\":\"presignedUrl\",
      \"method\":\"PUT\",
      \"expiresIn\":3600,
      \"key\":\"test/sample.txt\",
      \"bucket\":\"bucket-name\"
  }"
}

 

 


2. Signed URL (CloudFront Signed Url)

CloudFront 배포를 통해 콘텐츠에 접근하는 서명된 URL
RSA 키 페어로 서명하여 더 강력한 보안 제공
URL에 서명 파라미터가 포함된다.

 

Signed URL은 말 그대로 서명된 URL이다.

CloudFront로 배포되는 특정 리소스에 대한 엑세스를 제한하는 URL로, Presigned Url 이 Client에서 직접적으로 S3에 접근하는 방식이었다면, Signed Url 같은경우 이름에서 느껴지듯 Client와 S3 사이에 CloudFront를 거치는 단계가 필요하다고 생각하면 조금 더 이해하기 쉽다. 

접근 기한 제한, 혹은 IP 제한 등에 사용된다.

 

 

동작 흐름

1. 클라이언트 → 서버: "파일 다운로드하고 싶어요"
2. 서버: RSA 비공개 키로 URL에 서명
3. 서버 → 클라이언트: "이 서명된 URL로 접근하세요"
   예: https://d123.cloudfront.net/file.txt?Expires=1234567890&Signature=abc...&Key-Pair-Id=APKA...
4. 클라이언트 → CloudFront: 서명된 URL로 GET 요청
5. CloudFront: 공개 키로 서명 검증 → S3에서 파일 가져옴 → 클라이언트에 전달

 

특징

  • CloudFront 엣지 캐싱 활용 (빠른 전송)
  • RSA 키 페어 기반 (더 강력한 보안)
  • 지역별 제한, 사용자별 개별 접근 제한, IP 제한 등 고급 정책 가능
  • URL에 서명이 노출됨 (로그에 남을 수 있음)
  • 단일 파일 접근에 적합

테스트

signed url을 이용하기 위해서는 일단 lambda 함수 생성이 필요한데 조금 과정이 복잡하다. 이건 나중에 더 자세히 정리해서 올려보겠다..

 

요청

curl -X POST https://example.execute-api.ap-northeast-2.amazonaws.com/default/signed-url-generator \
  -H "Content-Type: application/json" \
  -d '{"key": "test/sample.txt", "expiresSeconds": 3600}'

 

응답

{
  "statusCode": 200,
  "headers": {
    "Content-Type": "application/json",
    "Access-Control-Allow-Origin": "*"
  },
  "body": "{
    \"url\":\"https://d3xxxxxxx.cloudfront.net/test/sample.txt?Policy=1708590000adsfZCLVKJADIFExample&Signature=abc123...&Key-Pair-Id=KXXXXXXXX\",
    \"expiresIn\":3600,
    \"key\":\"test/sample.txt\"
  }"
}

 

URL 구조

  • Expires (canned policy) || Policy (custom policy)
  • Signature
  • Key-Pair-Id

이렇게 응답에 있는 Signed URL로 CloudFront를 통해 S3 버킷의 파일에 접근 가능하다.

 


3. Signed Cookie (CloudFront Signed Cookie)

URL 대신 쿠키에 서명 정보를 담아 인증
여러 파일에 대한 접근을 하나의 쿠키로 처리
URL이 깔끔하고 서명이 노출되지 않음

 

Signed Cookie는 서명된 쿠키, 그러니까 쿠키에 서명을 담는다. Signed Url은, 각각의 리소스에 대해 URL을 개별적으로 생성해야한다. 또, URL이 길어지고, 보안적인 이슈 또한 무시할 수 없다. 그래서 여러 리소스에 접근 가능하도록 만든 것이 Signed Cookie라고 생각하면 된다. 

 

동작흐름

1. 클라이언트 → 서버: "private/* 경로의 파일들에 접근하고 싶어요"
2. 서버: RSA 비공개 키로 쿠키에 서명
3. 서버 → 클라이언트: Set-Cookie 헤더로 3개의 쿠키 전달 (무조건 3개임)
   - CloudFront-Policy: 접근 정책 (경로, 만료시간)
   - CloudFront-Signature: RSA 서명
   - CloudFront-Key-Pair-Id: 키 페어 ID
4. 클라이언트 → CloudFront: 쿠키와 함께 여러 파일 요청
   GET /private/file1.txt (Cookie: CloudFront-Policy=...; CloudFront-Signature=...)
   GET /private/file2.txt (Cookie: CloudFront-Policy=...; CloudFront-Signature=...)
5. CloudFront: 쿠키의 서명 검증 → 파일 전달

만약 signed url을 사용한다면 모든 파일 마다, url이 생성될테지만,

Signed Cookie 방식은 파일마다 서명하지 않고 쿠키에 권한을 담아버림

 

정리

Client → 로그인

Server → Signed Cookie 발급

Client → /premium/video.mp4 요청

CloudFront → 쿠키 검증 → 통과

 

특징

  • URL이 깔끔함 (서명 정보가 쿠키에 있음)
  • 여러 파일 접근에 하나의 쿠키 사용
  • 경로 패턴 지원
    예)
    videos/*
    private/*
  • 로그에 서명이 남지 않음
  • 쿠키 관리 필요 (브라우저 환경에 적합)
  • 스트리밍 서비스, 프리미엄 콘텐츠에 적합

 

Signed url / Signed Cookie를 구분하여 사용하는 이유를 좀 더 자세히 살펴 보자면,,,

전달 방식 & 적용 범위의 차이

 

  • Signed URL (CloudFront)
    요청 1개(특정 URL)에 서명 붙여서 접근 허용
    → 링크 하나 공유, 이메일/메신저로 전달, “이 파일 딱 하나” 열어주기 좋음.

  • Signed Cookie (CloudFront)
    브라우저가 쿠키를 들고 있는 동안, 정책에 맞는 경로들에 자동 적용
    → 사용자가 같은 사이트에서 이미지/영상/세그먼트 등 여러 리소스를 연속으로 가져오는 서비스 UX에 좋음.
    → 로그인된 사용자에게 콘텐츠 사이트 전체 접근 허용
  • 링크 == 권한 → Signed Url
  • 세션처럼 일정 시간동안 여러 리소스 라면 Signed Cookie

정리

애초에 Signed Url의 만료 시간을 무한정 늘리는 것의 한계(url 자체가 권한이기 때매 보안/성능 등의 이슈가 있음 - ‘URL = 인증’ 모델 자체의 한계를 해결하기 위해) 를 극복하기 위해 나온 개념이 Signed Cookie라고 보면 된다.

 

특히,

페이지 1개 =
이미지 20개
JS 5개
CSS 3개
영상 세그먼트 300개

이런 상황에서 Signed Url이라면, 모든 리소스 마다 URL 서명이 필요하다.

img1 → signed url
img2 → signed url
img3 → signed url
...
segment_001.ts → signed url
segment_002.ts → signed url

그럼 위와 같은 상황이 벌어지는데,

이러면, 구조적으로 복잡해지고, UX적으로 안좋다.

게다가, CDN 캐싱과 충돌이슈도 있다.

Signed URL은 아래처럼 쿼리스트링을 포함한다.

file.jpg?sig=A
file.jpg?sig=B

그렇다면 CDN 입장에선 :

서로 다른 URL = 서로 다른 객체

  • 캐시 효율 떨어짐
  • CDN hit rate 감소
  • 비용 증가

고로 성능/비용 이슈도 발생하게 된다.