컴퓨터 기초 지식

CORS(Cross-Origin Resource Sharing)

Ella Seon 2023. 8. 4. 16:45

0. Origin (출처)

Protocol + Host + Port 3가지가 같으면 동일 출처(Origin)라고 한다. 프로토콜의 HTTP는 80번, HTTPS는 443번 포트를 사용하는데, 80번과 443번 포트는 생략이 가능하다.

https://beomy.github.io/tech/browser/cors/

URL 구조 더 보고 싶으면 더보기 누르기

더보기
  • Protocol(Scheme) : http, https
  • Host : 사이트 도메인
  • Port : 포트 번호
  • Path : 사이트 내부 경로
  • Query string : 요청의 key와 value값
  • Fragment : 해시 태크

*동일 출처 예시

http://example.com:80
http://example.com
HTTP 기본 Port인 80번이 생략되어있으므로 동일 출처입니다
http://example.com/app1/index.html
http://example.com/app2/index.html
Protocol, Host, Port(생략)이 같으며, Path부터 다르므로 동일 출처입니다

 

* 다른 출처 예시

http://example.com/app1
https://example.com/app2
Protocol이 다릅니다
http://example.com
http://www.example.com
http://myapp.example.com
Host가 다릅니다
http://example.com
http://example.com:8080
80, 8080으로 포트가 다릅니다

 

다른 출처 요청일 경우, CORS 정책에 준수하여 요청해야만 정상적으로 응답을 받습니다.


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

- 동일 출처 정책 : 동일한 출처에서 리소스를 공유할 수 있다. 다른 출처의 리소스 접근을 금지한다. 

어떤 출처에서 불러온 문서나 스크립트가 다른 출처에서 가져온 리소스와 상호작용하는 것을 제한하는 보안방식. 

즉, 동일 출처 정책은 웹 브라우저 보안을 위해 프로토콜, 호스트, 포트가 동일한 서버로만 ajax 요청을 주고 받을 수 있도록 한 정책이다. 하지만, 실제로 웹페이지는 상당히 자주 다른 출처의 리소스를 사용한다.

 

⭕예를 들어,  www.example.com에서 로딩된 자바스크립트는 동일 출처 정책에 의해 다음의 동작들을 수행할 수 있다.

- www.example.com 내의 모든 리소스를 자유롭게 읽고 수정할 수 있습니다.
- 페이지의 DOM을 변경하거나 XHR/Fetch 요청을 통해 같은 출처의 리소스를 가져올 수 있다.
- www.example.com에 대한 쿠키를 설정하거나 읽을 수 있다.

❌반면에,  www.example.com에서 로딩된 자바스크립트는 동일 출처 정책에 의해 다음의 동작들을 수행할 수 없다.
- www.example2.com의 페이지를 읽거나 수정하려고 시도할 수 없다.
- www.example2.com에 대한 요청을 통해 가져온 리소스를 읽거나 수정하려고 시도할 수 없다.
- www.example2.com에 대한 쿠키를 설정하거나 읽을 수 없다.

Postman으로 API를 테스트하거나, 다른 서버에서 API를 호출할 때는 멀쩡히 잘 동작하다가 브라우저에서 API를 호출할 때만 CORS policy 오류가 발생하는것은 바로 위 정책 때문이다. 출처를 비교하는 로직은 서버에 구현된 스펙이 아닌 브라우저에 구현된 스펙이다. 서버는 리소스 요청에 의한 응답은 말끔히 해준다(Postman 으로 테스트하면 잘되었다시피) 하지만, 브라우저가 이 응답을 분석해서 동일 출처가 아니면, 에러를 뿜는것이다. 그래서 브라우저에는 에러가 뜨지만, 정작 서버 쪽에는 정상적으로 응답을 했다고 하기 때문에 난항을 겪는 것이다. 즉, 응답 데이터는 멀쩡하지만 브라우저 단에서 받을 수 없도록 차단한것이다. 

🔸동일 출처 요청 vs 다른 출처 요청 그림

요청하는 클라이언트와 요청받는 서버가 같은 출처에 있으면 동일 출처, 서로 다른 서버에 있으면 다른 출처 요청

 

 

 

왼쪽의 핸드폰의 URL은 domain-a.com 

오른쪽 서버의 URL은 domain-a.com과 domain-b.com 2가지 이다.

domain-a.com 유저가 domain-a.com 서버에 요청하면 동일 정책이기 때문에 아무런 문제가 없지만,
domain-a.com 유저가 domain-b.com 서버에 요청하면 호스트(Host_가 다르기 때문에 다른 출처 요청을 한다.)

도메인 이외에, 같은 프로젝트 내에 정의된 css 파일 요청은 동일 출처 요청이고, font같은 경우에는 다른 외부 사이트에서 실시간으로 import를 통해 가져온다면 다른 출처 요청이다. 이처럼, 같은 출처가 아닌 외부에 자원을 요청하는 경우가 있는지 잘 확인해보아야한다. 

 

기본적으로 동일 출처 요청만 자유롭게 요청이 가능하며 이를 동일 출처 정책(Same-Origin Policy) 이라고 한다.

하지만 기준을 완화하여 다른 출처 요청도 할 수 있도록 기준을 만든 체제가 다른 출처 정책(Cross-Origin Policy)

 

🔸다른 출처 요청의 위험성

악의적인 마음을 품은 해커가 자신의 웹사이트를 구축해놓고, 이 웹사이트를 가리키는 링크를 담은 메일을 사용자에게 보내는 것이다. 그리고 이 사용자는 A라는 웹사이트에 로그인이 되어 있어서 브라우저 단에 인증 정보가 존재한다고 해보자. 만약 그 사용자가 실수로 해당 링크를 클릭하여 해커의 웹사이트에 접속하면, 해커가 심어둔 JavaScript 코드가 실행되어 자기도 모르게 A 웹사이트로 개인 정보를 조회하는 API 요청을 보낼 것이다. 이 사용자의 브라우저 단에는 인증 정보가 존재하기 때문에, 이것이 해당 요청에 함께 실어서 전송되면 서버는 인증된 요청이라 생각하여 개인 정보를 응답해줄 것이다. 그러고 나면 그 개인 정보를 해커가 빼돌릴 수 있게 된다. (이것이 바로 CSRF 공격이다.)

 

그런데 만약 다른 Origin으로의 요청을 막는 정책, 즉 SOP가 존재한다면 이러한 문제를 어느 정도 예방할 수 있다. 해커가 구축한 웹사이트와 A 웹사이트는 당연히 Origin이 다르기 때문에, 해커의 웹사이트에서 A 웹사이트로 API 요청을 보낼 수 없기 때문이다. 따라서 SOP는 브라우저의 아주 기본적인 보안 정책으로서 기능한다.

 

🔸동일 출처 정책이 필요한 이유? (위 설명의 부가설명)

동일 출처 정책을 지키면 외부 리소스를 가져오지 못해 불편하지만, 동일 출처 정책은 XSS나 CSRF 등의 보안 취약점을 노린 공격을 방어할 수 있다. 하지만 현실적으로는 외부 리소스를 참고하는 것은 필요하기 때문에 외부 리소스를 가져올 수 있는 방법이 존재해야 한다. 외부 리소스를 사용하기 위한 SOP의 예외 조항이 CORS이다.

 


2. 다른 출처 요청 정책 (Cross Origin Resource Sharing)

- CORS : 출처가 다른 자원들을 공유한다는 뜻 , 한 출처에 있는 자원에서 다른 출처에 있는 자원에 접근하도록 하는 개념

🔸CORS 의 동작원리

1. 웹 어플리케이션에서 다른 출처의 리소스를 요청하는 경우 HTTP 프로토콜을 사용해서 요청을 보낸다.
2. 이 과정에서 브라우저는 요청 헤더 속 Origin 이라는 필드에 출처를 함께 담아보낸다. (ex : Origin: https:
//mywebsite.com:8080)
3. Origin 헤더를 통해 서버는 어디서 오는 요청인지 판단할 수 있다. 그리고, 서버에선 요청에 대한 응답을 전달한다.
4. 이 때 response header에 Access-Control-Allow-Origin 이란 값을 보낸다.

1) 특정 출처에만 접근을 허용할 경우
요청을 보낸 출처가 http://mywebsite.com일 경우, 서버는 이 출처만 접근을 허용하도록 응답할 수 있다.
Access-Control-Allow-Origin: http://mywebsite.com
2) 모든 출처에 접근을 허용할 경우
이 경우, 서버는 * 값을 사용하여 모든 출처에 대한 접근을 허용하도록 응답한다.
Access-Control-Allow-Origin: *
3) 서버로직에 따라 동적으로 결정할 경우
서버는 Origin 요청 헤더의 값을 읽어, 허용된 출처 목록과 비교한 후, 매칭되는 경우 해당 출처를 ACAO 헤더 값으로 사용할 수 있다. 예를 들어, 요청의 Origin이 http://allowedwebsite.com이며, 서버에 이 출처가 허용된 출처 목록에 포함되어 있을 경우:
Access-Control-Allow-Origin: http://allowedwebsite.com
서버는 이렇게 응답 헤더에 Access-Control-Allow-Origin 값을 포함시켜 브라우저에 전달하고, 브라우저는 이 값을 기반으로 해당 웹페이지에서의 리소스 접근을 허용할지를 결정합니다.

5. 응답을 받은 브라우저에선 보낸 요청의 Origin과 서버의 response header 속 Access-Control-Allow-Origin을 비교해 본 다음 유효한 응답인지 아닌지를 결정한다.

 

내 프로젝트에서 네트워크탭을 확인해본 결과 요청헤더의 Origin 과 응답헤더의 Access-Control-Allow-Origin:* 을 통해 CORS에러가 나지 않는 것을 볼 수 있었다. 

🔸요청의 3가지 방식

1) 단순요청(Simple Request)

- 서버에게 바로 본 요청을 전송하는 방식으로 요청에 대한 응답으로 서버가 헤더에**Access-Control-Allow-Origin** 를 확인하여 브라우저가 CORS 동작을 수행을 검토한다.

 

https://youhavetosleep.dev/cors/

위 그림처럼 Simple request은 브라우저에서 서버로 API요청을 보내고, 서버에선 Access-Control-Allow-Origin 헤더를 포함한 응답을 브라우저에 보낸다. 브라우저는 **Access-Control-Allow-Origin** 헤더를 확인해서 CORS 동작을 수행할지 판단한다.

🔸 Simple request 조건
- 요청 메소드는 GET, HEAD, POST 중 하나만 가능하다.
- User Agent가 자동으로 설정한 헤더를 제외하면,  Accept, Width, Accept-Language, Content-Language, Downlink, Content-Type, DPR, Save-Data, Viewport-Width 외의 다른 헤더를 사용하면 안된다.
- Content-Type를 사용하는 경우엔 text/plain, multipart/form-data, application/x-www-form-urlencoded만 허용된다.

위와 같은 조건을 만족하는 단순 요청은 안전한 요청으로 취급되어, (뒤에서 설명할) 프리플라이트 요청이 필요 없이 단 한 번의 요청만을 전송한다. 즉, 위에서 언급한 CORS의 기본적인 동작 원리를 그대로 따른다. 다른 Origin으로 요청을 보낼 때 Origin 헤더에 자신의 Origin을 설정하고, 서버로부터 응답을 받으면 응답의 Access-Control-Allow-Origin 헤더에 설정된 Origin의 목록에 요청의 Origin 헤더 값이 포함되는지 검사하는 것이다. 

2) 프리플라이트 요청(Preflighted Request)

- Simple request 와 달리 options 메서드로 서버에 사전 검증 후 요청을 보내는 방법이다.

- 대부분의 API 요청은 그냥 예비요청(prefelight)으로 이루어진다 라고 이해하면된다. 왜냐하면 대부분 HTTP API 요청은 text/xml 이나 application/json 으로 통신하기 때문에 Content-Type 이 위반되기 때문이다. 

- Simple reques의 조건에 벗어나는(= 안전하지 않은) 요청의 경우, 서버에 실제 요청을 보내기 전에 예비 요청에 해당하는 프리플라이트 요청(Preflight Request)을 먼저 보내서 실제 요청이 전송하기에 안전한지 확인한다. 만약 안전한 요청이라고 확인이 된다면, 그때서야 실제 요청을 서버에게 보낸다. 따라서 총 두 번의 요청을 전송한다.

https://youhavetosleep.dev/cors/

 

브라우저에서 options 메서드를 통해 사전 요청을 보내고 서버에선 어떤 것을 허용, 금지할지의 정보를 헤더에 담아 보내주게 되는데 이러한 방식은 안정성을 조금 더 보장받을 수 있다.

 

구체적으로 설명을 해보자면 아래와 같은 절차로 진행된다. 

  • 메소드로 OPTIONS를 사용한다.
  • Origin 헤더에 자신의 Origin을 설정한다.
  • Access-Control-Request-Method 헤더에 실제 요청에 사용할 메소드를 설정한다.
  • Access-Control-Request-Headers 헤더에 실제 요청에 사용할 헤더들을 설정한다.

서버는 이러한 프리플라이트 요청에 대해 다음과 같은 특징을 가진 응답을 제공해야 한다.

  • Access-Control-Allow-Origin 헤더에 허용되는 Origin들의 목록 혹은 와일드카드(*)를 설정한다.
  • Access-Control-Allow-Methods 헤더에 허용되는 메소드들의 목록 혹은 와일드카드(*)를 설정한다. (예: GET,PUT,POST)
  • Access-Control-Allow-Headers 헤더에 허용되는 헤더들의 목록 혹은 와일드카드(*)를 설정한다.
  • Access-Control-Max-Age 헤더에 해당 프리플라이트 요청이 브라우저에 캐시 될 수 있는 시간을 초 단위로 설정한다.

이러한 응답을 받고 나면 브라우저는 이 응답의 정보를 자신이 전송한 요청의 정보와 비교하여 실제 요청의 안전성을 검사한다. 만약 이 안전성 검사에 통과하게 된다면, 그때서야 실제 요청을 서버에게 보낸다. 단, 이때는 Access-Control-Request-XXX 형태의 헤더는 보내지 않는다.

 

예를 들어, Content-Type 헤더의 값이 application/json이고 사용자 정의 헤더로 Custom-Header를 사용하는 POST 요청을 서버에게 보내려 한다고 해보자. 그러면 이는 단순 요청의 조건에 벗어나기 때문에 프리플라이트 요청이 필요하다. 따라서 총 두 번의 요청이 서버에게 전송되며, 이를 그림으로 나타내면 다음과 같다.

 

https://it-eldorado.tistory.com/163

3) 인증정보 요청(Credential Request)

위에서 소개한 두 유형의 요청은 인증 정보가 없는 경우였다. 여기서 말하는 인증 정보(Credential)란 쿠키(Cookie) 혹은 Authorization 헤더에 설정하는 토큰 값 등을 일컫는다. 만약 이러한 인증 정보를 함께 보내야 하는 요청(Credentialed Request)이라면, 별도로 따라줘야 하는 CORS 정책이 존재한다. 이에 대해 한 번 알아보자.

 

우선, 쿠키 등의 인증 정보를 보내기 위해서는 클라이언트 단에서 요청 시 별도의 설정이 필요하다. 이는 Ajax 요청을 위해 어떠한 도구를 사용하느냐에 따라 달라진다. 만약 XMLHttpRequest, jQuery의 ajax, 또는 axios를 사용한다면 withCredentials 옵션을 true로 설정해줘야 한다.

// axios 라이브러리
axios.post('https://example.com:1234/users/login', { 
    profile: { username: username, password: password } 
}, { 
	withCredentials: true // 클라이언트와 서버가 통신할때 쿠키와 같은 인증 정보 값을 공유하겠다는 설정
})

반면, fetch API를 사용한다면 credentials 옵션을 include로 설정해줘야 한다. 이러한 별도의 설정을 해주지 않으면 쿠키 등의 인증 정보는 절대로 자동으로 서버에게 전송되지 않는다.

// fetch 메서드
fetch("https://example.com:1234/users/login", {
	method: "POST",
	credentials: "include", // 클라이언트와 서버가 통신할때 쿠키와 같은 인증 정보 값을 공유하겠다는 설정
    body: JSON.stringify({
        userId: 1,
    }),
})

 

위와 같은 설정을 통해 인증 정보를 요청에 포함시켰다면, 이 요청은 이제 인증 정보를 포함한 요청이 된다. 그리고 서버는 이러한 요청에 대해 일반적인 CORS 요청과는 다르게 대응해줘야 한다. 응답의 Access-Control-Allow-Origin 헤더가 와일드카드(*)가 아닌 분명한 Origin으로 설정되어야 하고, Access-Control-Allow-Credentials 헤더는 true로 설정되어야 한다. 그렇지 않으면 브라우저에 의해 응답이 거부된다. 이를 그림으로 나타내면 다음과 같다.

 

https://it-eldorado.tistory.com/163


3. CORS 에 대처하는 방법과 우회하는 방법(아직 미완성)

- 외부 서버로 ajax 요청이 안될 경우 아래와 같은 단계로 처리를 생각해 볼 수 있습니다

1. 개발자가 테스트 혹은 개발단계에서 쉽게 요청하기: 웹 브라우저 실행 옵션이나 플러그인을 통한 동일 출처 정책 회피

2. CORS구현이 안되어 있는 서버로 ajax요청을 해야하지만 서버 쪽 컨트롤이 불가능할 경우: jsonp방식으로 요청

3. Ajax요청을 해야 하는 다른 도메인의 서버를 클라이언트와 같이 개발하거나 서버 개발 쪽 수정 요청이 가능한 경우: 서버에서 CORS 요청이 허용되도록 구현

 


참고자료

https://escapefromcoding.tistory.com/724

 

CORS란 무엇인가?

개요 웹 프로그래밍에서 프런트와 백엔드 작업을 하면, 한번씩 발생하는 문제가 CORS 문제입니다. 현재 하는 업무가 이런 경우는 없었지만, 개인 프로젝트 시 발생했던 문제를 기억하며 해당 문

escapefromcoding.tistory.com

https://inpa.tistory.com/entry/WEB-%F0%9F%93%9A-CORS-%F0%9F%92%AF-%EC%A0%95%EB%A6%AC-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95-%F0%9F%91%8F#%EC%B6%9C%EC%B2%98origin_%EB%9E%80? 

 

🌐 악명 높은 CORS 개념 & 해결법 - 정리 끝판왕 👏

악명 높은 CORS 에러 메세지 웹 개발을 하다보면 반드시 마주치는 멍멍 같은 에러가 바로 CORS 이다. 웹 개발의 신입 신고식이라고 할 정도로, CORS는 누구나 한 번 정도는 겪게 된다고 해도 과언이

inpa.tistory.com

https://it-eldorado.tistory.com/163

 

[Web] CORS (Cross Origin Resource Sharing) 이해하기

이번 포스팅에서 다룰 내용은 바로 CORS(Cross Origin Resource Sharing)이다. 웹 개발자라면 한 번쯤은 CORS와 관련하여 콘솔에 뜨는 빨간 글씨의 에러 때문에 짜증 났던 적이 있을 것이다. 하지만 CORS 정책

it-eldorado.tistory.com

 

'컴퓨터 기초 지식' 카테고리의 다른 글

SSR과 CSR의 차이점  (0) 2023.04.26