URL 인코딩 상세 설명: 퍼센트 인코딩이란
링크를 클릭하거나, 양식을 제출하거나, URL을 입력할 때마다 브라우저는 백그라운드에서 URL 인코딩을 처리합니다. URL에서 보이는 퍼센트 기호와 16진수 숫자로 구성된 신비한 문자열은 무작위가 아닙니다. 이는 인터넷이 안정적으로 작동하도록 하는 정교하게 설계된 메커니즘입니다. URL 인코딩을 이해하는 것은 웹 개발자, API 설계자 및 웹 기술을 다루는 모든 사람에게 필수적입니다. 이 가이드는 URL 인코딩이 무엇인지, 왜 존재하는지, 어떻게 작동하는지, 그리고 알아야 할 보안 영향을 설명합니다.
URL 인코딩이란 무엇인가요?
URL 인코딩(공식 명칭은 퍼센트 인코딩)은 RFC 3986에 정의된 메커니즘으로, 문자를 통합 자원 식별자(URI)에서 안전하게 전송할 수 있는 형식으로 변환합니다. URL 사양은 ASCII 문자의 하위 집합만 URL에 직접 나타날 수 있도록 허용합니다. 이 허용 집합에 없는 모든 문자는 해당 문자의 바이트 값을 나타내는 퍼센트 기호(%)와 두 개의 16진수 숫자로 인코딩해야 합니다.
<는 ASCII 코드가 60이고 16진수로 3C이므로 %3C가 됩니다.
URL 인코딩이 필요한 이유
URL은 특정 문자를 구분자로 사용하는 특정 구문을 가집니다. 물음표 ?는 경로와 쿼리 문자열을 분리합니다. & 기호는 쿼리 매개변수를 분리합니다. 등호 =는 매개변수 이름과 값을 분리합니다. 슬래시 /는 경로 세그먼트를 분리합니다. 이러한 문자가 전송되는 데이터에 나타나면 콘텐츠가 아닌 URL 구조로 잘못 해석됩니다.
사용자가 웹사이트에서 "rock & roll"을 검색할 때 어떤 일이 발생하는지 생각해 보세요. 인코딩하지 않으면 URL은 /search?q=rock & roll이 됩니다. 브라우저는 &를 쿼리 매개변수 구분자로 해석하여 쿼리를 q=rock과 roll이라는 이름의 두 번째 매개변수(값 없음)로 분할합니다. URL 인코딩은 이 문제를 해결합니다: /search?q=rock%20%26%20roll.
퍼센트 인코딩 작동 원리
인코딩 과정은 URI 사양에 정의된 간단한 알고리즘을 따릅니다:
- 비예약 문자 식별: 문자 A-Z, a-z, 0-9, -, ., _, ~는 비예약 문자입니다. 인코딩이 필요 없으며 URL에 직접 나타날 수 있습니다.
- 예약 문자 식별: 문자 :, /, ?, #, [, ], @, !, $, &, ', (, ), *, +, ,, ;, =는 URL 구문을 위해 예약되어 있습니다. 구분자가 아닌 데이터로 사용될 때는 반드시 인코딩해야 합니다.
- 기타 모든 문자 인코딩: 비예약 또는 예약 집합에 속하지 않는 모든 문자는 퍼센트 인코딩해야 합니다. 여기에는 공백, 제어 문자, 비ASCII 문자 및 ASCII 범위를 벗어난 모든 문자가 포함됩니다.
비ASCII 문자 인코딩 과정
비ASCII 문자(예: 악센트 문자, CJK 문자 및 이모지)는 추가 단계가 필요합니다. 먼저 문자를 UTF-8 바이트 시퀀스로 변환합니다. 그런 다음 각 바이트를 개별적으로 퍼센트 인코딩합니다. 이는 단일 문자가 여러 개의 퍼센트 인코딩 트리플렛을 생성할 수 있음을 의미합니다.
예를 들어, 유로 기호(€)의 UTF-8 바이트 시퀀스는 E2 82 AC입니다. URL 인코딩 후 %E2%82%AC가 됩니다. 한자 "中"의 UTF-8 시퀀스는 E4 B8 AD로, %E4%B8%AD가 됩니다. 이모지 😀(웃는 얼굴)은 4바이트 UTF-8 시퀀스를 가지며 %F0%9F%98%80으로 인코딩됩니다.
일반적인 인코딩 문자
아래 표는 가장 일반적인 URL 인코딩 문자를 보여줍니다:
| 문자 | 인코딩 | ASCII 코드 | 원인 |
|---|---|---|---|
| 공백 | %20 | 32 (0x20) | URL에서 허용되지 않음 |
| ! | %21 | 33 (0x21) | 예약됨 (하위 구분자) |
| # | %23 | 35 (0x23) | 예약됨 (프래그먼트 구분자) |
| $ | %24 | 36 (0x24) | 예약됨 (하위 구분자) |
| & | %26 | 38 (0x26) | 예약됨 (쿼리 구분자) |
| ' | %27 | 39 (0x27) | 예약됨 (하위 구분자) |
| ( | %28 | 40 (0x28) | 예약됨 (하위 구분자) |
| ) | %29 | 41 (0x29) | 예약됨 (하위 구분자) |
| + | %2B | 43 (0x2B) | 예약됨 (쿼리 내 공백) |
| , | %2C | 44 (0x2C) | 예약됨 (하위 구분자) |
| / | %2F | 47 (0x2F) | 예약됨 (하위 구분자) |
| : | %3A | 58 (0x3A) | 예약됨 (스킴 구분자) |
| ; | %3B | 59 (0x3B) | 예약됨 (매개변수 구분자) |
| = | %3D | 61 (0x3D) | 예약됨 (값 구분자) |
| ? | %3F | 63 (0x3F) | 예약됨 (쿼리 구분자) |
| @ | %40 | 64 (0x40) | 예약됨 (권한 구분자) |
| [ | %5B | 91 (0x5B) | 예약됨 (IPv6 리터럴) |
| ] | %5D | 93 (0x5D) | 예약됨 (IPv6 리터럴) |
URL 인코딩과 HTML 인코딩
URL 인코딩과 HTML 엔티티 인코딩은 자주 혼동되지만, 용도가 완전히 다르며 서로 다른 컨텍스트에서 작동합니다.
URL 인코딩
URL 인코딩은 문자를 %XX 형식으로 변환하여 URL에서 안전하게 전송합니다. RFC 3986에 의해 규정되며, 경로, 쿼리 문자열 및 프래그먼트와 같은 URL 구성 요소에 적용됩니다.
HTML 인코딩과 디코딩
HTML 인코딩은 문자를 &, <, >와 같은 엔티티 참조로 변환하여 HTML 문서에서 안전하게 렌더링합니다. 브라우저가 콘텐츠를 HTML 마크업으로 해석하는 것을 방지합니다. HTML 인코딩은 HTML 사양에 의해 규정됩니다.
주요 차이점
| 측면 | URL 인코딩 | HTML 인코딩과 디코딩 |
|---|---|---|
| 컨텍스트 | URL과 URI | HTML 문서 |
| 형식 | %XX (퍼센트 기호 + 16진수) | 또는 NNN; |
| 웹 디자이너를 위한 색채 이론 | %3C | < |
| 웹 디자이너를 위한 색채 이론 | %26 | 웹 디자이너를 위한 색채 이론 |
| %22 | " | 목적 |
| 안전한 URL 전송 | HTML 인젝션 방지 | 사양 |
| RFC 3986 | HTML Living Standard | HTML Living Standard |
(HTML 인코딩된 & 기호)로 작성됩니다. 둘 다 유효하지만, URL 인코딩 & 기호가 데이터의 의도된 의미를 보존하므로 더 올바른 방식입니다.
다양한 프로그래밍 언어에서의 URL 인코딩
모든 주요 프로그래밍 언어는 URL 인코딩 및 디코딩을 위한 내장 함수를 제공합니다. 그러나 정확한 동작은 다르며, 오류를 피하기 위해 차이점을 이해하는 것이 중요합니다.
JavaScript
JavaScript는 세 가지 인코딩 함수를 제공하며, 각각 동작이 다릅니다:
// encodeURI - encodes a complete URL
// Preserves: :, /, ?, #, &, =, +, @, ;, ,, !, ~, *, ', (, )
const url = encodeURI("https://example.com/search?q=hello world");
// Result: "https://example.com/search?q=hello%20world"
// encodeURIComponent - encodes a URL component
// Encodes ALL special characters including URL delimiters
const param = encodeURIComponent("price=100&discount=20");
// Result: "price%3D100%26discount%3D20"
// Never use escape() - it is deprecated
// It does not handle Unicode correctly주요 차이점은 encodeURI는 URL 구조 문자를 보존하는 반면, encodeURIComponent는 모든 것을 인코딩한다는 것입니다. 전체 URL이 있을 때는 encodeURI를 사용하고, 개별 매개변수 값을 인코딩할 때는 encodeURIComponent를 사용하세요.
Python
from urllib.parse import quote, quote_plus, urlencode
# quote - standard URL encoding (spaces as %20)
encoded = quote("hello world&more")
# Result: "hello%20world%26more"
# quote_plus - spaces become + instead of %20
encoded_plus = quote_plus("hello world")
# Result: "hello+world"
# urlencode - encodes a dictionary as a query string
params = {"q": "hello world", "lang": "en"}
query = urlencode(params)
# Result: "q=hello+world&lang=en"PHP
// urlencode - spaces become +
$encoded = urlencode("hello world&more");
// Result: "hello+world%26more"
// rawurlencode - RFC 3986 compliant (spaces as %20)
$raw = rawurlencode("hello world&more");
// Result: "hello%20world%26more"공백 문자: %20 vs +
공백 문자에는 두 가지 일반적인 인코딩이 있으며, 각각을 언제 사용해야 하는지 이해하는 것이 중요합니다:
- %20: RFC 3986에서 정의된 표준 퍼센트 인코딩입니다. 이는 URL 경로 구성 요소에서 공백의 올바른 인코딩이며 항상 안전합니다.
- +(더하기 기호): application/x-www-form-urlencoded 미디어 유형에 의해 정의되며, 쿼리 문자열의 양식 데이터를 인코딩하는 데 사용됩니다. 이 인코딩에서 공백은 더하기 기호로 대체되고, 더하기 기호 자체는 %2B로 인코딩됩니다.
이 구분은 +를 공백으로 디코딩하는 것이 application/x-www-form-urlencoded 데이터의 컨텍스트에서만 올바르기 때문에 중요합니다. URL 경로 구성 요소에서 +는 공백이 아닌 리터럴 더하기 기호입니다. 대부분의 서버 측 프레임워크는 쿼리 문자열을 올바르게 처리하지만, 경로를 인코딩하거나 사용자 정의 URL 스킴을 사용할 때 미묘한 오류가 발생할 수 있습니다.
보안 영향
URL 인코딩은 모든 웹 개발자가 이해해야 할 중요한 보안 영향을 미칩니다.
이중 인코딩 공격
이중 인코딩은 데이터가 여러 번 URL 인코딩될 때 발생합니다. 공격자는 %2527을 제출할 수 있으며, 이는 첫 번째 디코딩 시 %27이 되고 두 번째 디코딩 시 작은따옴표 '가 됩니다. 보안 필터가 첫 번째 디코딩만 확인하면 악성 문자가 누락됩니다. 이는 입력 유효성 검사, XSS(크로스 사이트 스크립팅) 필터 및 SQL 인젝션 방어를 우회할 수 있습니다.