이 사이트의 실습은 자바스크립트를 켜야 움직입니다. 아래는 같은 내용을 글로 적은 것입니다.
기초개념 › 보안
출처는 무엇이고, 브라우저는 왜 다른 출처를 가르는가
출처(origin)는 주소(URL)의 앞부분 세 가지, 스킴(https나 http), 호스트(사이트 이름), 포트(번호)를 합친 것입니다. 셋 가운데 하나라도 다르면 다른 출처이고, 뒤쪽의 경로와 쿼리는 출처에 들어가지 않습니다. 브라우저는 페이지 안에서 도는 스크립트가 다른 출처에 보낸 요청의 응답을 읽지 못하게 막습니다. 이 규칙이 동일 출처 정책입니다. 이 실습은 코드 칸에 열려 있는 페이지의 주소와, 그 페이지의 스크립트가 요청하는 주소를 적어 두고, 요청하는 주소를 한 부분씩 바꿔 가며 브라우저가 출처를 같다고 보는지, 요청이 서버에 도착하는지, 스크립트가 응답을 읽는지를 봅니다. 코드 칸은 프로그램이 아니라 주소 두 개를 적은 것입니다. 서버가 읽기를 허락하는 방법(CORS)은 이 실습에서 다루지 않습니다.
일부만 실행으로 확인 · 주소 다섯 가지의 출처 계산은 Node.js 24.18.0의 URL로, 응답을 읽는지와 요청이 서버에 도착했는지는 이 PC의 Chrome(창 없이 실행)과 127.0.0.1 시험 서버로 포트·호스트·스킴 차이를 만들어 확인함. 화면의 shop.example 주소 자체와 브라우저의 모든 판단 규칙은 실행하지 않고 문서로 확인함
# 지금 열려 있는 페이지 https://shop.example/cart # 그 페이지의 스크립트가 요청하는 주소 https://shop.example/api/items
코드 칸은 실행한 프로그램이 아니라 열려 있는 페이지의 주소와 스크립트가 요청하는 주소를 적은 것이고, shop.example은 예약된 예시 도메인이라 실제 서버가 없습니다. 그래서 두 층으로 확인했습니다. 첫째, 화면의 주소 여섯 개(페이지 하나와 요청 주소 다섯 개)의 출처를 Node.js 24.18.0의 URL 표준 구현(new URL(...).origin)으로 계산해, 같은 출처인지와 스킴·호스트·포트 가운데 어느 것이 다른지를 모형과 대조했습니다. 같은 계산에서 https://shop.example:443/x의 출처가 포트를 적지 않은 주소와 같은 https://shop.example로 나오는 것(기본 포트는 생략), 경로와 쿼리가 다른 주소의 출처가 같은 것도 확인했습니다. 둘째, 브라우저가 응답을 스크립트에 넘기는지는 이 PC의 Chrome 155(HeadlessChrome)를 창 없이 실행(--headless=new --dump-dom)해 확인했습니다. 127.0.0.1에 CORS 헤더를 달지 않는 시험 서버를 띄우고(포트는 빈 포트), 서버가 보낸 페이지의 스크립트가 fetch로 아래 주소를 요청해 그 결과를 페이지에 적게 했습니다. 같은 출처는 같은 서버의 /api/items, 경로와 쿼리만 다른 것은 같은 서버의 /other?x=1, 호스트가 다른 것은 같은 컴퓨터 같은 포트에 localhost라는 이름으로(페이지는 127.0.0.1), 포트가 다른 것은 다른 포트의 서버, 스킴이 다른 것은 같은 포트에서 http와 https를 나눠 받는 서버에 https로(자체 서명 인증서) 요청했습니다. 다섯 조합 모두 요청은 서버에 도착했고, 같은 출처 둘은 스크립트가 상태 200과 본문을 읽었고, 나머지 셋은 스크립트가 TypeError(Failed to fetch)를 받아 읽지 못했습니다. 이 실험의 주소는 화면의 주소와 같은 차이를 만든 것이지 화면의 주소 자체는 아닙니다. 스킴 실험은 http 페이지에서 https 주소를 요청하는 방향이라 화면의 예(https 페이지에서 http 주소)와 반대이므로, 화면과 같은 방향(https 페이지에서 http 주소)도 따로 실행해 스크립트가 읽지 못하고 요청은 서버에 도착하는 것을 확인했습니다. 화면의 스킴 예는 스킴과 함께 포트도(http 80, https 443) 다르지만 Chrome 실험은 스킴만 다르게 만들었습니다. 같은 출처에 보낸 요청에는 Origin 헤더가 없었고 다른 출처로 보낸 요청에는 Origin 헤더가 있었는데, 화면은 이를 다루지 않습니다. 서버를 꺼 둔 포트에 요청해도 스크립트가 받는 오류는 막힌 경우와 같은 TypeError(Failed to fetch)였고, 다른 출처 서버가 응답에 Access-Control-Allow-Origin으로 페이지의 출처를 적어 주면 스크립트가 본문을 읽었습니다. 이것들은 이 Chrome 버전에서 관찰한 동작이고, 다른 브라우저와 다른 버전은 실행하지 않았습니다. 문서로 확인한 것은 RFC 6454의 3.2절·4절·5절·6절(출처는 스킴·호스트·포트의 세 값이고, 포트가 없으면 스킴의 기본 포트를 쓰며, 세 값이 모두 같을 때만 같은 출처이고, 적을 때 기본 포트는 생략), WHATWG URL 표준의 4.2절·4.4절·4.7절(http와 https의 기본 포트 80과 443, 기본 포트는 URL에서 null로 저장, http·https URL의 출처는 스킴·호스트·포트로 이루어진 값), MDN의 Same-origin policy(경로만 다른 주소는 같은 출처, 다른 출처로의 읽기는 대체로 막힘, 읽기를 허락하는 방법이 CORS, 악성 페이지의 스크립트가 로그인해 둔 웹메일의 내용을 읽어 가지 못하게 하려는 목적), MDN의 Using the Fetch API(다른 출처 서버가 허락 헤더를 보내지 않으면 브라우저가 응답을 호출한 쪽에 공유하지 않음)입니다. 브라우저가 서버의 응답을 받은 뒤에 허락 표시를 확인한다는 순서와 "로그인해 둔 다른 사이트의 내용을 지킨다"는 목적은 문서의 설명을 따른 것이고, 쿠키가 실제로 함께 가는지는 실행하지 않았습니다. 화면에서 줄인 것은 페이지를 받는 과정, 요청과 응답의 헤더, 응답의 본문 해석입니다. 요청은 특별한 헤더가 없는 평범한 GET 하나만 다뤘습니다. 요청을 보내기 전에 서버에 먼저 묻는 경우(사전 요청), 쿠키를 함께 보내는 요청, 서버가 읽기를 허락하는 방법(CORS)은 "세부 동작 실습: CORS"에서, 출처를 노린 공격은 "세부 동작 실습: 웹 공격 세 가지"에서, 다른 사이트의 페이지를 프레임에 끼워 넣는 일은 "세부 동작 실습: 클릭재킹과 오픈 리다이렉트"에서 다룹니다. 이미지나 스크립트 파일을 다른 출처에서 불러 오는 것(임베딩)은 보통 허용된다는 것과 나머지 출처 규칙(쿠키, 저장소 등)도 이 실습에서 다루지 않았습니다.
줄을 그은 곳은 처음 코드에서 지운 것이고, 색을 칠한 곳은 새로 쓴 것입니다.