같은 HTTP 요청이라도 브라우저에서 보내면 여러 보안 규칙이 강제되고, 서버에서 보내면 아무 제한이 없다. 브라우저 보안 정책은 신뢰할 수 없는 코드로부터 사용자를 지키려고 있기 때문이다.
| 브라우저 → 서버 | 서버 → 서버 | |
|---|---|---|
| 실행되는 코드 | 아무 웹사이트의 JS(JavaScript) | 개발자가 배포한 코드 |
| SOP(Same-Origin Policy)·CORS(Cross-Origin Resource Sharing) | 적용 | 없음 |
| 쿠키 | Domain·Path·SameSite 규칙대로 자동 첨부 | 아무 쿠키나 헤더에 직접 넣음 |
HttpOnly 쿠키 | JS로 못 읽음 | 요청에서 그냥 읽음 |
| 보안 책임 | 브라우저가 강제 | 개발자 |
브라우저는 PC방 컴퓨터와 같다. 누구나 와서 코드(웹사이트)를 실행하고, 사용자의 로그인 상태가 들어 있으니 규칙이 필요하다. 서버는 권한 있는 사람만 코드를 올리는 서버실이라 그런 규칙이 필요 없고, 있으면 외부 API 호출 자체가 불가능하다. curl이나 Postman에서 CORS 에러가 나지 않는 이유도 같다. 동일 출처 정책과 CORS는 오직 브라우저 안에서만 존재한다.
SSR(Server-Side Rendering)·BFF(Backend for Frontend)에서 쿠키 넘기기
Next.js 같은 서버가 브라우저와 백엔드 API 사이에 낀 구조(BFF)에서는 두 구간의 규칙이 다르다.
- 브라우저 → Next.js 서버: 브라우저 규칙 적용, 쿠키 자동 첨부
- Next.js 서버 → API: 서버 간 요청. 받은 쿠키를 직접 헤더에 넣어 전달해야 한다
- API가
Set-Cookie를 주면 Next.js가 브라우저로 다시 전달하고, 브라우저가 Domain 규칙대로 저장한다
이 구조에서는 내부 API 주소가 브라우저에 드러나지 않는다는 장점이 있다. 대신 보안을 개발자가 챙겨야 한다. 특히 외부 입력으로 요청 주소를 정하면 서버 측 요청 위조(Server-Side Request Forgery, SSRF)가 된다.
// 위험: 외부 입력으로 정한 주소에 모든 쿠키를 보냄 (SSRF + 쿠키 유출)
fetch(req.query.url, { headers: { cookie: allCookies } })
// 안전: 고정된 내부 주소에 필요한 쿠키만
fetch(process.env.INTERNAL_API_URL, { headers: { cookie: `sid=${sid}` } })관련: HTTP 쿠키, 서버 함수, 서버 컴포넌트에서의 데이터 요청, 서버 간 호출 방식 → 스프링에서 외부 API 호출