Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 

README.md

01. 브라우저 보안 모델

브라우저는 서로 다른 출처(origin)의 코드가 같은 공간에서 실행되는 환경이다. 어떻게 격리할 것인가?

핵심 개념

1. Same-Origin Policy (SOP, 동일 출처 정책)

브라우저 보안의 가장 기본적인 원칙이다.

Origin(출처) = 프로토콜 + 호스트 + 포트

https://opndoctor.com:443  ← 이게 하나의 origin
URL A URL B 같은 origin? 이유
https://opndoctor.com https://opndoctor.com/map O 경로만 다름
https://opndoctor.com http://opndoctor.com X 프로토콜 다름
https://opndoctor.com https://api.opndoctor.com X 호스트 다름
https://opndoctor.com https://opndoctor.com:8080 X 포트 다름

SOP가 막는 것:

  • A 출처의 JavaScript가 B 출처의 DOM에 접근
  • A 출처의 JavaScript가 B 출처의 쿠키/스토리지에 접근
  • A 출처의 JavaScript가 B 출처의 API 응답을 읽기 (CORS 없이)

SOP가 막지 않는 것:

  • <img>, <script>, <link> 태그로 다른 출처의 리소스 로드 (읽기는 불가)
  • <iframe>으로 다른 출처 페이지 표시 (내부 DOM 접근은 불가)
  • <form>으로 다른 출처에 데이터 전송

2. CORS (Cross-Origin Resource Sharing)

SOP의 제한을 서버가 명시적으로 허용하는 메커니즘이다.

[브라우저] GET https://api.opndoctor.com/users
           Origin: https://opndoctor.com

[서버 응답]
           Access-Control-Allow-Origin: https://opndoctor.com  ← "이 출처는 허용"

Preflight 요청 (OPTIONS): 단순하지 않은 요청(PUT, DELETE, 커스텀 헤더 등)은 브라우저가 먼저 OPTIONS 요청을 보내 서버에 "이거 해도 돼?"라고 물어본다.

1. [브라우저] OPTIONS /users  (preflight)
2. [서버]    Access-Control-Allow-Methods: GET, POST, PUT
3. [브라우저] PUT /users       (실제 요청)

3. 쿠키의 보안 속성

Set-Cookie: token=abc123;
  Secure;       ← HTTPS에서만 전송
  HttpOnly;     ← JavaScript에서 접근 불가 (XSS 방어)
  SameSite=Lax; ← cross-site 요청 시 쿠키 전송 제한 (CSRF 방어)
  Domain=.opndoctor.com;  ← 서브도메인 포함 전송
  Path=/;
속성 방어 대상 설명
Secure 네트워크 도청 HTTPS에서만 쿠키 전송
HttpOnly XSS document.cookie로 접근 불가
SameSite CSRF cross-site 요청 시 쿠키 포함 여부 제어

4. 웹 스토리지 격리

스토리지 격리 단위 용량 JS 접근
Cookie origin + path 4KB document.cookie (HttpOnly 제외)
localStorage origin 5~10MB window.localStorage
sessionStorage origin + 탭 5~10MB window.sessionStorage
IndexedDB origin 수백 MB~ window.indexedDB

핵심: 모든 웹 스토리지는 origin 단위로 격리된다. https://a.com의 localStorage는 https://b.com에서 절대 접근 불가.

5. Safari ITP (Intelligent Tracking Prevention)

Apple Safari에만 있는 추가적인 보안/프라이버시 정책이다. SOP 위에 한 층 더 얹은 것.

ITP가 하는 일:

  • Third-party 쿠키 전면 차단
  • Third-party iframe의 스토리지를 파티셔닝 (부모 사이트별로 격리)
  • 스토리지를 에페머럴 처리 (세션 종료 시 삭제)

Third-party란?

[부모 페이지: https://opndoctor.com]
  └── [iframe: https://next.opndoctor.com]  ← 호스트가 다르면 third-party

ITP의 스토리지 차단 방식:

스토리지 ITP 차단 방식 결과
Cookie 전면 차단 아예 전송 안 됨
localStorage 파티셔닝 + 에페머럴 값이 격리되고 세션 후 삭제
IndexedDB 이벤트 미발화 open() 호출 후 응답 없음 → hang

IndexedDB가 hang하는 이유:

indexedDB.open('mydb')
  → IDBOpenDBRequest 객체 반환
  → onsuccess / onerror 이벤트를 기다림
  → Safari ITP: 이벤트를 dispatch하지 않음 (침묵 차단)
  → Promise가 pending 상태로 영원히 대기

Safari가 에러를 던지지 않는 이유: 차단 사실 자체를 third-party에 알려주지 않기 위해서. 에러를 던지면 트래커가 우회를 시도할 수 있으므로, "침묵하는 실패(silent failure)"를 설계 원칙으로 채택.

6. iframe과 보안

iframe은 다른 페이지를 현재 페이지 안에 삽입하는 HTML 요소다.

<!-- opndoctor.com 페이지 안에 -->
<iframe src="https://next.opndoctor.com/signup"></iframe>

iframe의 보안 제약:

  • 부모 ↔ iframe 간 DOM 접근: SOP에 의해 차단 (다른 origin이면)
  • 통신 방법: window.postMessage() — 명시적으로 메시지를 보내야 함
  • iframe 내 스토리지: origin 기준이지만, Safari ITP는 추가 제한

postMessage 동작:

// iframe → 부모
window.parent.postMessage({ type: 'auth', token: 'abc' }, 'https://opndoctor.com');

// 부모에서 수신
window.addEventListener('message', (event) => {
  if (event.origin !== 'https://next.opndoctor.com') return; // 출처 검증 필수!
  console.log(event.data);
});

실무 연결: 오픈닥터 트러블슈팅

오픈닥터의 구조:

Flutter-web (https://opndoctor.com) — 부모
  └── Next.js (https://next.opndoctor.com) — iframe (third-party)
  1. Next.js에서 회원가입 완료 → postMessage로 Flutter에 토큰 전달
  2. Flutter가 FlutterSecureStorage로 토큰 저장 시도
  3. FlutterSecureStorage v1.2.1 → 내부적으로 IndexedDB 사용
  4. Safari ITP → iframe 내 IndexedDB 침묵 차단 → hang
  5. 해결: IndexedDB를 우회하고 인메모리(AuthManager().opn_token)로 전환

참고 자료