-
[Network] SOP 와 CORS PolicyNetwork Basis 2021. 12. 1. 14:57
CSRF 와 XSS 같은 방법을 사용하여, 어플리케이션에서 사용자의 정보를 탈취하는 것을 막기 위한 정책들
브라우저 단에서 적용되는 정책이다.
서버간의 통신에는 적용되지 않는다.
# SOP(Same-Origin Policy)
- '같은 출처에서만 리소스를 공유할 수 있다' 라는 개념의 정책
# CORS(Cross-Origin Resource Sharing) Policy
- 다른 출처로 리소스를 요청할때 지켜야하는 SOP가 허용하는 예외 조항
# 같은 출처와 다른 출처의 판단
ex) https://www.naver.com:8080
Scheme : https://
Host : www.naver.com
Port : :8080
- 기본적으로 Scheme, Host, Port 가 동일해야 같은 출처 (RFC 6454)
- 브라우저에 따라 Scheme, Host 만 비교하는 브라우저도 있다. (Intenet Explorer...)
- 브라우저에 따라 비교 로직이 다르다.
- 일단 서버에서 Response를 받지만, 정책위반(CORS,SOP)시 브라우저에서 Response를 파기함
- 즉, http://localhost:3000 과 http://localhost:8080 는 포트번호가 다르므로 다른 출처가 되고, 3000 포트에서 8080 포트로 리소스를 요청한다면 다른출처로 요청한다고 판단하여 CORS 정책을 실시하게 된다.
- Origin = Scheme + Host + Port
# CORS 의 기본적인 동작방식
- 기본적으로 클라이언트에서 다른 출처로 리소스를 요청할 때, 브라우저는 request 헤더의 'Origin' 이라는 필드에 본인 origin 을 담아 보낸다.
- 서버가 Response 를 보낼 때, Response 헤더에 'Access-Control-Allow-Origin' 필드에 서버가 어디까지 허용하는지 허용된 URL을 담아 보낸다.
- Response를 받은 브라우저는 'Origin' 과 'Access-Control-Allow-Origin'을 비교 후, 유효한 응답인지 결정한다.
# CORS 의 기본정책을 지키는 코드 예시
// Client (React App) //서버에 Request (to fetch list of user) //React App 은 http://localhost:3030 으로 가정 axios.get("http://localhost:8080/api/user") .then((res)=>console.log(res)).catch().finally(); /* 요청을 보낼때 {"Origin":"http://localhost:3030"} 을 자동으로 헤더에 실어 같이 보낸다. */
// Serever (Spring App) @GetMapping("/api/user") public Res<User> getUsers(){ /* ...get List of User from DB */ HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.set("Access-Control-Allow-Origin","*"); // "*" 은 모든 곳에서의 요청을 허용하겠다는 의미 httpHeaders.set("Access-Control-Allow-Methods","GET"); // 허용하는 HTTP 메소드 httpHeaders.set("Access-Control-Allow-Headers","Content-Type, Authorization, Content-Length, X-Requested-With"); return res; } /* 응답을 보낼때 {"Access-Control-Allow-Origin":"허용범위"} 를 꼭 헤더에 실어 같이 보내야 CORS 정책을 위반하지 않는다. */ /* The CORS spec 은 all-or-nothing 이므로 {"Access-Control-Allow-Origin" : "*://*.mysite.*"} 같은 표현은 먹히지 않는다. 정규식으로 Origin 도메인이 허용범위에 있는지 확인 후 요청온 Origin 도메인을 넣어주어야 한다. */
# 서버로 실어보낸 헤더 내용
브라우저가 아닌 Talent API 를 통해 요청을 보냈을 때는 Origin 헤더가 요청에 담기지 않는다.
위의 코드에서 보았듯이,
'Access-Control-Allow-Origin' 필드뿐 아니라
CORS 위한 다른 필드들이 있음을 짐작할 수 있다.
# CORS 심층분석 (3가지 시나리오)
Scenario 1. (PreFlight Request)
Scenario2, 3 이 아닌 모든 상황(Default)일때, 브라우저는 요청을 한번에 보내지 않고 예비요청과 본요청으로 나누어서 서버로 전송한다.
예비요청(PreFlight)은 HTTP 메소드의 OPTIONS 메소드를 사용한다.
예비요청의 역할은 본 요청을 보내기 전에 브라우저 스스로 이 요청이 안전한지 확인하는 것이다.
서버는 예비요청에 대한 응답으로, 자신이 어떤것들을 허용하고 금지하는지에 대한 정보를 헤더에 담아 보내준다.
* 매번 PreFlight를 날리는 것은 비효율적이기에, 서버 설정을 통해 PreFlight 의 결과의 캐시를 일정 기간 동안 저장 시킬 수 있다.
* 서버측에서 Access-Control-Max-Age 헤더에 값을 담아 캐시의 지속시간을 설정한다.
Scenario 2. (Simple Request)
예비요청없이 본요청을 바로 때려박는다.
다음 조건을 모두 충족해야 simple request 가 실행된다.
Scenario 3. (Credentialed Request)
다른 출처 간 통신에서 좀 더 보안을 강화하고 싶을 때 사용하는 방법
request 헤더에 'crendeitals' : [OPTION] 을 담아 보낸다.
사용가능한 OPTION 은 다음 3가지 이다.
1. 'same-origin' (default option)
- 같은 출처 간 요청에만 인증정보를 담을 수 있다
2. 'include'
- 모든 요청에 인증정보를 담을 수 있다.
3. 'omit'
- 모든 요청에 인증정보를 담지 않는다.
# 코드 예시 (Backend 쪽은 추후, 업데이트 하겠음)
// Client (React App) //서버에 Request (to fetch list of user) axios.get("http://localhost:8080/api/user",{withCredentials:true}) .then((res)=>console.log(res)).catch().finally();
# Reference
* CORS에 대해 매우 잘 정리 해주신 글
* CORS (WEB MDN)
* CORS Spec is All or Nothing
728x90'Network Basis' 카테고리의 다른 글
[Network] JWT - 왜 refresh 토큰이 필요하지? (refresh 토큰이 탈취된다면?) (12) 2021.12.10 [Network] 쿠키와 세션, 그리고 토큰에 대하여 (0) 2021.12.01 [Network] DNS란? (0) 2021.11.03 [Network] 윈도우에서 nginx 가 실행되지 않아요! & 윈도우에서 사용 중 포트 종료시키기 (0) 2021.04.12 [Network] TCP 프로토콜의 통신절차(TCP 패킷사이즈, TCP Flags, 3-way handshake, 4-way handshake) (0) 2021.03.03