본문 바로가기
개발/브라우저와 네트워크

CORS 정리

by 안뇽! 2021. 11. 22.
반응형

CORS(Cross Origin Resource Sharing)

CORS 정책은 우리가 가져오는 리소스들이 안전한지 검사하는 관문이다.

 

웹에는 SOP(Same Origin Policy)와 CORS(Cross Origin Resurce Sharing) 두가지 정책이 있다.

SOP는 Same Origin에서만 리소스를 공유한다 라는 규칙을 가진 정책이다.

 

우리가 Cross Origin으로 리소스를 요청하면 SOP정책을 위반한것이 되고, CORS정책까지 지키지 않으면 아예 다른 Origin의 리소스를 사용할 수 없게 된다.

 

그럼 Cross Origin과 Same Origin은 무엇일까

Cross Origin과 Same Origin

https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy

요악하면 프로토콜, 포트, 호스트중 하나라도 일치하지 않으면 Cross Origin 이라고 한다.

(Internet Explorer는 Origin비교시 포트를 무시한다. => 보안 취약)

 

CORS는 약속된 외부인만 출입을 허용하는 과정이다. 동작과정을 간단하게 살펴보자.


CORS 동작과정

1. 클라이언트에서 HTTP요청의 헤더에 Origin을 담아 전달한다.

다른 Origin의 리소스 요청시 클라이언트는 HTTP요청을 보낸다.

이때 요청헤더의 Origin필드에는 요청을 보내는 Origin을 담아보낸다. 

2. 서버는 응답헤더에 Access-Control-Allow-Origin을 담아 클라이언트로 전달한다.

서버가 응답을 보낼때, 허락하는 Origin을 클라이언트에게 전달한다.

3. 클라이언트에서 자신이 보냈던 요청의 Origin과 서버가 보내준 Access-Control-Allow-Origin을 비교한다.

자신이 보낸 Origin과 서버가 보내준 Access-Control-Allow-Origin을 비교하여 차단할지 말지를 결정한다.

만약 유효하지 않다면 그 응답을 사용하지 않고 버린다.

위의 경우에는 둘다 http://localhost:3000이기 때문에 유효한 경우이다.

 

서버 응답은 CORS정책 위반 여부에 관여하지 않는다.

CORS 정책에 의해 Origin을 비교하는 로직은 브라우저에 구현되어 있다.

그래서 서버에서 정상적인 응답을 하여 상태코드가 200이 나오더라도 브라우저가 응답을 CORS정책 위반이라고 분석하면 그 응답은 사용하지 않는다. 

 

브라우저가 CORS정책 위반을 분석하는 시간은 서버의 응답이 도착한 이후이다.

 

즉, CORS정책을 위반하는 리소스 요청때문에 에러가 발생하더라도 서버 쪽 로그에서는 정상응답을 했다는 로그만 남기때문에, CORS를 정확히 이해해야만 CORS에러를 해결할 수 있는것이다.

 


CORS의 3가지 시나리오

위의 3단계는 CORS의 기본적인 작동방식이고, 실제 CORS가 동작하는 방식은 3가지 시나리오에 따라 변경된다.

 

Preflight Request(예비요청)

브라우저는 요청을 한번에 보내지 않고 예비요청과 본요청으로 나누어 서버에 전달한다.

이 예비요청의 메소드에는 OPTIONS가 사용된다.

예비요청의 역할은 본 요청을 보내기 전에 브라우저 스스로 안전한 요청인지 확인하는 것이다.

 

preflight요청이 먼저 서버에 전달됨을 알 수 있다.

클라이언트는 자신이 보낸 Origin과 서버에서 전달된 Access-Control-Allow-Origin을 비교하여 유효한지 아닌지 판단한다.

여기서는 같은 Origin 이기 때문에 유효하다 판단하고 뒤이어 본요청을 보낸다.

 

Simple Request

서버에 예비요청을 보내지않고 바로 본요청을 날린 후 응답의 Access-Control-Allow-Origin을 보고 CORS 위반 여부를 검사한다.

Simple Request를 사용한다는 것은 예비요청을 생략한다는 뜻인데, 예비요청 생략은 아부때나 할 수 있는것이 아니고 아래 3가지 경우를 만족할때만 가능하다.

  1. 요청의 메소드는 GET, HEAD, POST 중 하나여야 한다.
  2. 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만 허용된다.

2번 조건에는 Authorization 헤더조차 포함되지 않고, 대부분의 HTTP API는 text/xml이나 application/json 타입을 갖고 있기 때문에 사실 위 3가지 조건들을 만족시키는 경우는 희박하다.

 

Credentialed Request

인증된 요청을 사용하는 방법이다. 기존 예비요청에서 보안을 더 강화하고 싶을 때 사용한다.

기본적으로 브라우저가 제공하는 비동기 리소스 요청 API인 XMLHttpRequest 객체나 fetch API는 별도의 옵션 없이 브라우저의 쿠키 정보나 인증과 관련된 헤더를 함부로 요청에 담지 않는다.

이때 요청에 인증과 관련된 정보를 담을 수 있게 해주는 옵션이 바로 credentials 옵션이다.

 

https://evan-moon.github.io/2020/05/21/about-cors/

 

 

credentials : include 사용시 주의해야할 점이다.

  1. Access-Control-Allow-Origin에는 *를 사용할 수 없으며, 명시적인 URL이어야한다.
  2. 응답 헤더에는 반드시 Access-Control-Allow-Credentials: true가 존재해야한다.

fetch를 사용하지 않고 axios를 사용할 땐 withCredentials:true 를 사용하여 쿠키를 전송할 수 있다.


다른 도메인간 쿠키 전송, credentials

fetch에서는 credential 옵션이 있지만 axios에서는 withCredentials 옵션을 사용한다.

 

백엔드와 프론트엔드의 도메인 주소가 다른 경우가 많다. localhost인 경우에도 port가 다르면 다른 주소, 즉 Cross Origin으로 판단한다.

이들간에는 쿠키 전송이 되질 않는다. 그렇다면 다른 도메인일때 어떻게 쿠키를 전송할 수 있을까??

 

1. front에서 ajax요청을 보낼 때 withCredentials를 설정해준다.

const result = await axios
      .get(`${process.env.REACT_APP_API_URL}/user/info`, {
        headers: {
          "Content-Type": "application/json",
        },
		withCredentials: true,

      })

2. back에서 CORS 설정을 해준다.

응답헤더로 아래 항목을 설정해준다.

Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin : 정확한 도메인 명시, *는 사용하지 말자

 

express를 사용한다면 최상단 index.js에서 다음과 같이 cors모듈로 쉽게 설정할 수 있다.

const cors = require("cors");

..(생략)..

app.use(
  cors({
    origin: ["https://localhost:3000", "http://localhost:3000", "https://aneun-dongne.com", "http://aneun-dongne.com"],
    methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"],
    credentials: true,
  })
);

 

이는 아래 이어질 CORS문제 해결방법과 겹치는 내용이 많다. 


 

CORS 해결방법

Access-Control-Allow-Origin 바르게 세팅하기

가장 정석적이고 확실하고 근본적인 해결책이다.

이때 *을 사용하면 모든 Origin에서 오는 요청을 허용한다는 의미이므로 당장은 편할 수 있겠지만, 바꿔서 생각하면 정체도 모르는 이상한 출처에서 오는 요청까지 모두 허용하기 때문에 보안은 더 허술해진다.

그러니 가급적이면 귀찮더라도 Access-Control-Allow-Origin: https://wnsdufdi.tistory.com 와 같이 출처를 명시해주도록 하자.

 

이 헤더는 Nginx나 Apache와 같은 서버 엔진의 설정에서 추가할 수도 있지만, 아무래도 복잡한 세팅을 하기는 불편하기 때문에 소스 코드 내에서 응답 미들웨어 등을 사용하여 세팅하는 것을 추천한다.

 

다음은 express사용시 최상단 루트인 index.js에서 작성한 cors 허용 origin,method 이다.

origin에 적힌 4개의 origin과 methods에 적힌 6개의 method에서만 통신이 허용된다

(options는 안적어도 잘 작동되던데 우선 적었다. )

const cors = require("cors");

..(생략)..

app.use(
  cors({
    origin: ["https://localhost:3000", "http://localhost:3000", "https://aneun-dongne.com", "http://aneun-dongne.com"],
    methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"],
    credentials: true,
  })
);

만약 method항목의 delete를 삭제하고 홈페이지에서 댓글 삭제를 해보자.

methods: ["GET", "POST", "PUT", "OPTIONS", "PATCH"],

CORS 정책에 의해 block되었다는 메시지가 뜨게 된다.


Spring, Express, Django와 같이 이름있는 백엔드 프레임워크의 경우에는 모두 CORS 관련 설정을 위한 세팅이나 미들웨어 라이브러리를 제공하고 있으니 세팅 자체가 어렵지는 않을 것이다.

 

 

 

참고 : https://evan-moon.github.io/2020/05/21/about-cors/

https://www.zerocho.com/category/NodeJS/post/5e9bf5b18dcb9c001f36b275

https://developer.mozilla.org/ko/docs/Web/API/Request/credentials

 

 


1/1 면접준비

 

CORS가 뭐에요??

 

Cross-Origin 부터 말씀을 드리면 Cross-Origin은 프로토콜,포트,호스트 중 하나라도 서버의 도메인과 일치하지 않는 도메인을 이야기합니다.

CORS는 Same Origin과 서버에서 허용한 Cross-Origin,method가 아닌 모든 연결을 제한하는 방식입니다. 사용자가 가져오는 리소스가 안전한지 알 수 없기 때문입니다.

 

CORS의 기본적인 작동 방식은 브라우저가 요청 헤더에 Origin을 담아서 서버에 보내주고 서버는 Access control allow origin 헤더에 허용하는 origin을 담아 브라우저에 보내줍니다. 브라우저는 자신이 보낸 요청헤더의 origin과 서버에서 허용하는 cross origin을 비교하여 응답이 유효한지 아닌지 결정합니다.

 

CORS의 작동 시나리오는 크게 3종류가 있습니다.

첫번째 preflighted 요청은 브라우저가 options메소드를 사용하는 예비요청을 보내면 서버는 허용하는 cross origin,methods를 응답에 첨부하여 보내줍니다. 클라이언트는 자신이 보낸 요청의 origin과 허용하는 cross-origin을 비교하여 본요청을 보낼지 말지 결정하는 방식입니다. 

두번째 simple request 는 예비요청을 생략하고 본요청만 보내는 방식인데, 특정 조건이 만족하는 경우에만 가능합니다. 이 조건들이 까다롭기 때문에 일반적으로는 충족하기 어려운 조건들이라 생각합니다. 저는 아직 이런 경우를 경험해보지 못했습니다.

세번째 credential 요청은 기존 Cross origin 통신에서 보안을 더욱 강화하고 싶을때 사용하는 방식인데, 기본적으로 XMLHttpRequest나 fetchAPI는 별도의 옵션 없이 브라우저에 쿠키 같은 인증정보를 함부로 요청에 담지 않습니다. 이때 credentials 속성에 omit을 할당하면 절대 인증정보를 전송하지 않고, include를 할당하면 cross origin 여부에 상관없이 인증정보를 전송, 기본값인 same-origin인 경우 same-origin인 경우에만 인증정보를 전송합니다.

 

CORS 정책에 의해 Origin을 비교하는 로직은 브라우저에 구현되어 있습니다.

그래서 서버에서 정상적인 응답을 하여 상태코드가 200이 나오더라도 브라우저가 응답을 CORS정책 위반이라고 분석하면 그 응답은 사용하지 않습니다.

 

즉, CORS정책을 위반하는 리소스 요청때문에 에러가 발생하더라도 서버 쪽 로그에서는 정상응답을 했다는 로그만 남기때문에, CORS를 정확히 이해해야만 CORS에러를 해결할 수 있습니다.

 

CORS에러를 해결하는 방법은 서버의 access-control-allow-origin헤더, access-control-allow-method헤더에 특정 origin,메소드를 명시하는 방법입니다. express에서는 최상 index.js에 cors미들웨어를 사용하여 명시하는 방식이 있습니다.

 

 

반응형

'개발 > 브라우저와 네트워크' 카테고리의 다른 글

TLS 프로토콜 작동방식  (0) 2022.01.04
HTTP와 HTTPS  (0) 2021.12.22
토큰  (0) 2021.11.02
Accept 헤더 (콘텐츠 협상 헤더) 그리고 Content-type과의 차이  (0) 2021.10.28
HTTP 자주 쓰이는 헤더들  (0) 2021.10.28