CORS

1. CORS

 

Cross-Origin-Resource-Sharing의 줄임말로, 교차 출처 리소스 공유라는 뜻

 

  • 브라우저에서 요청 시 (Origin)이 다르면 발생하는 브라우저 정책
  • CORS는 허가되지 않은 외부 요청을 막고, 악의적으로 정보가 탈취되는 것을 막아줌

 

 

CORS의 기본 개념과 동작 방식(부제: Preflight 요청이란?)

우선 Preflight request를 이해하기 위해선 CORS에 대해서 알아야한다. CORS란 Cross-Origin Resource Sharing 의 줄임말로, 직역하면 교차 출처 리소스 공유라는 뜻이다. CORS는 “다른 출처”에 리소스를 요청할

velog.io

 

CORS(Cross-Origin Resource Sharing, 크로스 오리진 리소스 공유)는 웹 브라우저에서 서로 다른 도메인, 프로토콜, 또는 포트에서 자원을 공유할 수 있도록 허용하는 보안 기능입니다. 기본적으로 웹 브라우저는 동일 출처 정책(Same-Origin Policy, SOP)을 따르며, 이 정책은 보안상의 이유로 한 도메인에서 로드된 웹 페이지가 다른 도메인의 리소스에 접근하지 못하게 제한합니다. -chatGPT-

CORS가 필요한 이유 : 웹 애플리케이션이 API 서버와 통신할 때, 특히 이 서버가 다른 도메인에 위치한 경우, 브라우저는 보안상의 이유로 이러한 요청을 차단합니다. 하지만 일부 경우에는 서로 다른 출처에서 리소스를 요청해야 할 때가 있으며, 이를 허용하기 위해 CORS가 사용됩니다. -chatGPT-

당장 브라우저의 개발자 도구만 열어도 DOM이 어떻게 작성되어 있는지, 어떤 서버와 통신하는지, 리소스의 출처는 어디인지와 같은 각종 정보들을 아무런 제재 없이 열람할 수 있지 않은가?

최근에는 자바스크립트 소스 코드를 난독화해서 읽기 어렵다고 하지만, 난독화는 어디까지나 난독화일 뿐이지 암호화가 아니다. 그리고 아무리 난독화되어 있다고 해도 사람이 바로 이해할 수 없는 정도도 아닌 데다가, 소스 코드를 직접 볼 수 있다는 것 자체가 보안적으로 상당히 취약한 부분이다.

이런 상황 속에서 다른 출처의 애플리케이션이 서로 통신하는 것에 대해 아무런 제약도 존재하지 않는다면, 악의를 가진 사용자가 소스 코드를 쓱 구경한 후 CSRF(Cross-Site Request Forgery)나 XSS(Cross-Site Scripting)와 같은 방법을 사용하여 여러분의 애플리케이션에서 코드가 실행된 것처럼 꾸며서 사용자의 정보(토큰이나 쿠키 등)를 탈취하기가 너무나도 쉬워진다

출처 : https://evan-moon.github.io/2020/05/21/about-cors/#access-control-allow-origin-%EC%84%B8%ED%8C%85%ED%95%98%EA%B8%B0

 

1.1 cross-origin

 

같은 출처와 다른 출처의 구분을 판단하는 법

 

  • 프로토콜 : http / https 두 프로토콜은 다름
  • 도메인 (Host) : localhost / example.com 두 개는 다름
  • Port : 8080 / 3000은 다름

 

cross-origin은 다음 중 한 가지라도 다른 경우를 말함. 이 세 개 중에 하나라도 다르면 CORS가 발생

 

즉, 위 3가지만 동일하면 같은 출처라고 판단

 

2. CORS 검사 방식

 

출처를 비교하는 로직이 서버에 구현된 스펙이 아니라 브라우저에 구현되어 있는 스펙

 

CORS 정책을 위반하는 리소스 요청을 하더라도 해당 서버가 같은 출처에서 보낸 요청만 받겠다는 로직을 가지고 있는 경우가 아니라면 서버는 정상적으로 응답을 하고, 이후 브라우저가 이 응답을 분석해서 CORS 정책 위반이라고 판단되면 그 응답을 사용하지 않고 그냥 버림

 

💡 응답의 파기 여부는 브라우저가 결정, 브라우저를 이용하지 않고 서버 간 통신을 할 때는 이 정책이 적용되지 않음

 

3. CORS 동작 방식

 

기본 흐름

  1. 브라우저는 요청 헤더에 Origin이라는 필드에 요청을 보내는 출처를 함께 보냄
  2. Origin: `https://…`
  3. 서버는 이 요청에 대한 응답을 할 때 응답 헤더의 Access-Control-Allow-Origin 라는 값에 해당 리소스를 접근하는 것이 허용된 출처를 포함함
  4. 응답을 받은 브라우저는 출처를 비교하여, 응답이 유효한 응답인지 아닌지를 결정

 

기본 흐름은 위와 같지만, CORS가 동작하는 방식은 한 가지가 아니라 세 가지의 시나리오에 따라 변경됨

 

3.1 Simple Request

 

일반적인 요청에 대해서는 CORS 정책 검사를 하지 않음. 일반적인 요청은 다음과 같은 사항임

  1. 요청의 메서드는 GET, HEAD, POST 중 하나여야 함
  2. Request Header에는 다음 속성만 허용 Accept, Accept-Language, Content-Language, Content-Type, DPR, Downlink, Save-Data, Viewport-Width, Width
  3. 만약 Content-Type를 사용하는 경우에는 application/x-www-form-urlencoded, multipart/form-data, text/plain만 허용
  4. 요청에 사용된 XMLHttpRequestUpload 객체에는 이벤트 리스너가 등록되어 있지 않고, 이들은 XMLHttpRequest.upload 프로퍼티를 사용하여 접근
  5. 요청에 ReadableStream 객체가 사용되지 않음

 

하지만 이들은 일반적인 방법으로 웹 애플리케이션 아키텍처를 설계하게 되면 거의 충족시키기 어려운 조건

 

대부분의 HTTP API는 text/xml이나 application/json 콘텐츠 타입을 가지도록 설계되기 때문

💡 application/json은 Simple Request에 해당되지 않음

 

사용자 인증에 사용되는 Authorization 헤더조차 위의 조건에는 포함되지 않음

 

💡 Athorization 헤더를 사용하면 Simple Request에 해당되지 않음. 이외에 다른 커스텀 헤더, 권한과 관련된 헤더가 있으면 Simeple Request에 해당되지 않음

 

3.2 Preflight Request

 

일반적으로 웹 애플리케이션을 개발할 때 가장 마주치는 시나리오

 

이 시나리오에 해당하는 상황일 때 브라우저는 요청을 한 번에 보내지 않고 예비 요청과 본 요청으로 나누어서 서버로 전송

 

simple Request와 전반적인 로직 자체는 같고, 예비 요청의 존재 유무만 다름.

 

Simple Request와 같은 요청이 아닌 경우 브라우저는 접근할 리소스를 가지고 있는 서버에 preflight Request (예비 요청)을 보냄

 

예비 요청에는 HTTP 메서드 중 OPTIONS 메서드가 사용. 예비 요청의 역할은 본 요청을 보내기 전에 브라우저 소스로 이 요청을 보내는 것이 안전한지 확인하는 것

 

OPTIONS 요청을 받은 서버는 Response Header에 서버가 허용할 옵션을 설정하여 브라우저에게 전달

 

응답 헤더에 Access-Control-Allow-Origin 항목을 추가하여 허용할 도메인을 지정할 수 있는데, 설정하게 되면 개발자 도구에서 아래와 같이 확인할 수 있음

	Access-Control-Allow-Origin: https://example.com

 

브라우저는 서버가 보낸 Response 정보를 이용하여 허용되지 않은 요청인 경우 405 Method Not Allowed 에러를 발생시키고, 실제 페이지의 요청(본 요청)은 서버로 전송하지 않고, 반대로 허용된 요청인 경우 본 요청을 보냄

 

Preflight Request 방식은 많은 리소스를 잡아먹는다. 그렇기 때문에 서버에서 "Access-Control-Max-Age" 헤더 정보를 통해서 Preflight Request를 캐싱함으로써 그 효율을 높일 수 있다.
출처 : [Web] CORS 동작 방식과 해결 방법

preflight request 조건에 해당되는 경우지만, preflight request를 보내지 않는 경우도 존재

우선 브라우저를 쓰지 않으면 보내지 않는데, origin이 다른지 판단하는 것은 브라우저 스펙이기 때문에 Postman과 같은 기능을 사용하면 json 형태든, POST로 보내든 CORS 문제가 발생하지 않게 됨

 

3.2 Credential Request

 

헤더에 인증과 관련된 정보(쿠키, 토큰 등)를 담아서 보내는 Credential Request (인증된 요청)을 사용하는 방법

 

CORS의 기본적인 방식이라기보다는 다른 출처 간 통신에서 좀 더 보안을 강화하고 싶을 때 사용하는 방법

 

💡 예를 들어 서버로 쿠키를 함께 전송해야 하는 경우가 있는데, 요청에 쿠키가 담기게 되면 Credentialed Request 허용이 되어 있어야 함

 

인증과 관련된 정보를 담을 수 있게 해주는 옵션 'credentials'를 줘야 하는데, 이때 서버 쪽에서 응답 헤더에 Access-Control-Allow-Credentials: true를 보내주지 않는다면 브라우저에서 응답을 받는 것을 거부하게 됨

 

이 옵션에는 총 3가지의 값을 사용할 수 있으며, 각 값들이 가지는 의미는 다음과 같음

 

same-origin (기본값) 같은 출처 간 요청에만 인증 정보를 담을 수 있다
include 모든 요청에 인증 정보를 담을 수 있다
omit 모든 요청에 인증 정보를 담지 않는다

 

요청에 인증 정보가 담겨있는 상태에서 다른 출처의 리소스를 요청하게 되면 브라우저는 CORS 정책 위반 여부를 검사하는 룰에 다음 두 가지를 추가하게 됨

  1. Access-Control-Allow-Origin에는 * (와일드카드)를 사용할 수 없으며, 명시적인 URL이어야 함. (https://foo.com과 같이 구체적인 origin을 지정.)
  2. 응답 헤더에는 반드시 Access-Control-Allow-Credentials: true가 존재해야 함

 

4. CORS 에러 해결

 

4.1 Access-Control-Allow-Origin 세팅

 

CORS 정책 위반으로 인한 문제를 해결하는 가장 대표적인 방법은 서버에서 Access-Control-Allow-Origin 헤더에 알맞은 값을 세팅해 주는 것

 

이때 와일드카드인 *을 사용하여 이 헤더를 세팅하게 되면 모든 출처에서 오는 요청까지 모두 받겠다는 것으로 보안적으로 심각한 이슈가 발생할 수도 있음

 

그러니 가급적이면 Access-Control-Allow-Origin: https://evan.github.io와 같이 출처를 명시해 주는 것이 보안적으로 안전

 

API 서버들은 SOP나 CORS에 관계없이 접근이 가능해야 하고, 사용자의 쿠키 등 중요한 데이터를 보관하지 않는다. 이런 경우에는 Access-Control-Allow-Origin: * 와 같은 헤더를 적용하여 클라이언트 사이드에서 낮은 수준의 보안을 유지해도 괜찮다. 일반 서버들은 자신의 사이트를 이용하는 사용자의 정보가 중요하기 때문에 Access-Control-Allow-Origin 헤더를 자신의 사이트로 한정하는 등, 엄격한 기준을 적용하는 보안이 필요하다.

 

참고

 

CORS란 무엇인가?

CORS가 무엇인지 알기 전에, 이 CORS가 등장하게 된 배경을 먼저 알아보자.SOP는 2011년 RFC 6454에서 등장한 보안 정책으로 "같은 출처에서만 리소스를 공유할 수 있다"라는 규칙을 가진 정책이다.그러

velog.io

 

'Dev > Spring Security' 카테고리의 다른 글

JWT와 AccessToken, RefreshToken  (0) 2025.02.27
CSRF  (0) 2025.02.11