Signed URL 만료 계산기
유효기간·시계 오차를 고려한 signed URL 만료 시각을 계산합니다.
CloudFront, S3, Cloudflare 같은 CDN과 오브젝트 스토리지는 임시 접근을 위해 만료 시각이 포함된 signed URL(서명 URL) 또는 presigned URL을 발급합니다. 이 계산기는 시작 시각과 유효기간을 입력하면 정확한 만료 시각을 UTC ISO 8601, 로컬 시각, Unix epoch(초)로 한 번에 환산해 보여줍니다.
서버와 클라이언트의 시계가 어긋나면 아직 유효해야 할 URL이 만료로 거부되거나 반대로 너무 오래 살아 있을 수 있습니다. 그래서 시계 오차(clock skew) 허용값을 함께 입력하면 “실질적으로 안전하게 보장되는 만료 시각”까지 계산해 줍니다. 모든 계산은 브라우저 안에서만 이뤄지며 어떤 값도 외부로 전송되지 않습니다.
signed URL 만료는 어떻게 계산되나
만료 시각은 단순합니다. 만료 = 시작 시각 + 유효기간. 대부분의 서명 방식은 만료를 절대 시각(Unix epoch 초)으로 URL에 박아 넣습니다. 예를 들어 CloudFront는 정책 문서의 DateLessThan 값, S3 presigned URL은 발급 시각 기준 X-Amz-Expires(초)로 만료를 표현합니다.
- 시작 시각: 보통 URL을 서명하는 “지금”. 미래 시각으로 미리 발급할 수도 있습니다.
- 유효기간: 초·분·시·일 단위로 입력. S3 presigned URL은 최대 7일(604800초)로 제한됩니다.
- 만료 시각: 이후로는 게이트웨이가
403으로 거부합니다.
시계 오차(clock skew)를 반드시 고려하라
서명을 검증하는 쪽과 URL을 만든 쪽의 시계가 완벽히 같을 수는 없습니다. 검증 서버 시계가 조금 빠르면, 발급한 입장에서는 아직 유효해도 검증 측에서는 이미 만료로 보일 수 있습니다. 그래서 “실질 만료”는 다음과 같이 보수적으로 봐야 합니다.
- 실질 만료 = 만료 − 시계오차 허용값. 이 시각 전에 요청이 도착하도록 설계하세요.
- NTP로 시간을 동기화해도 수 초의 드리프트는 흔합니다. 1~5초 정도의 여유를 두는 것이 안전합니다.
- 반대로 시작 시각을 미래로 잡는 서명(NotBefore)이라면, 검증 측 시계가 느릴 때 “아직 시작 안 됨”으로 거부될 수 있으니 같은 논리로 여유를 둡니다.
운영 팁
- 만료는 가능한 짧게. 유출돼도 위험 창이 작아집니다.
- 긴 다운로드/업로드는 전체 전송 시간보다 유효기간이 충분히 길어야 합니다(연결 시작 시점에 만료가 검증되는지, 전송 중에도 검증되는지는 서비스마다 다름).
- 로그·DB에는 항상 UTC epoch로 저장하고, 사람에게 보여줄 때만 로컬 시각으로 변환하세요. 저장된 epoch 값을 사람이 읽기 좋은 시각으로 바꿀 때는 로그 타임스탬프 변환기가 편리합니다.
CDN 캐시의 신선도(TTL)와 백그라운드 갱신 정책까지 함께 설계하려면 stale-while-revalidate 설계기를 참고하세요.
제공자별 만료 파라미터·최대 유효기간
서비스마다 만료를 표현하는 쿼리스트링/정책 필드와 허용되는 최대 유효기간이 다릅니다. 만료를 절대 시각(epoch)으로 박는 방식과 발급 시점 기준 상대 초(duration)로 넣는 방식이 섞여 있어 혼동하기 쉽습니다.
| 제공자 / 방식 | 만료 표현 | 의미 | 최대 유효기간 |
|---|---|---|---|
| S3 presigned (SigV4) | X-Amz-Expires | X-Amz-Date(발급 시각) 기준 상대 초 | 7일 (604800초) |
| CloudFront canned policy | Expires | 절대 epoch 초 (DateLessThan) | 제한 없음 (직접 지정) |
| CloudFront custom policy | Policy(base64 JSON) | DateLessThan/DateGreaterThan 절대 epoch | 제한 없음 (직접 지정) |
| Cloudflare signed URL | exp (+ verify) | 절대 epoch 초 | 제한 없음 (직접 지정) |
| GCS signed URL (V4) | X-Goog-Expires | X-Goog-Date 기준 상대 초 | 7일 (604800초) |
| Azure Blob SAS | se (+ st) | ISO 8601 절대 시각 (시작/만료) | 제한 없음 (직접 지정) |
이 계산기는 “시작 + 유효기간”으로 만료를 구한 뒤 epoch와 UTC ISO를 모두 보여주므로, 상대 초 방식(X-Amz-Expires)과 절대 epoch 방식(Expires·exp) 어느 쪽에 넣을 값이든 그대로 복사해 쓸 수 있습니다.
예시로 따라가기
시작 시각 2026-06-27T09:00:00Z(epoch 1782032400)에 유효기간 2시간, 시계 오차 허용값 5초를 입력했다고 합시다. 계산 결과는 다음과 같습니다.
- 만료(UTC):
2026-06-27T11:00:00Z— 시작 epoch 1782032400 + 7200초 =1782039600 - 실질 만료(보수적):
1782039600 − 5 = 1782039595→2026-06-27T10:59:55Z - S3에 넣을 값:
X-Amz-Expires=7200(유효기간 초). CloudFront/Cloudflare라면Expires=1782039600또는exp=1782039600(만료 epoch).
즉 같은 만료를 두고도 제공자에 따라 넣는 숫자가 7200일 수도, 1782039600일 수도 있습니다. 계산기의 “유효기간(초)” 칸은 전자, “만료 epoch” 칸은 후자에 대응합니다.
흔한 실수 / 함정
- 밀리초와 초 혼동: 자바스크립트
Date.now()는 밀리초입니다. 서명 라이브러리 대부분은 초 단위 epoch를 기대하므로 1000으로 나누지 않으면 만료가 약 5만 년 뒤로 잡혀 사실상 무한 유효한 URL이 됩니다. - 상대 초를 절대 epoch 칸에 넣기:
X-Amz-Expires에 만료 epoch(예: 1782039600)을 넣으면 “5만 년짜리 URL”이 되고, 반대로Expires에 7200을 넣으면 1970년으로 해석돼 즉시 만료됩니다. - 로컬 시각을 그대로 epoch로 착각: 화면의 로컬 시각은 보기용일 뿐입니다. 서명에 넣을 값은 항상 UTC epoch 또는 UTC ISO를 쓰세요.