CORS (Cross-Origin Resource Sharing) & Solution

soom·2020년 12월 28일
2
post-thumbnail
post-custom-banner

출처(Origin)란 무엇일까?

우리가 흔히 알고있는 URL은 다양한 요소로 구성되어 있다.

https://blog.naver.com/dnvld1/?page=1 라는 URL이 있다고하면 이는 아래와같이 구성되어 있다.

  • https://-> protocol

  • blog.naver.com -> domain

  • /dnvld1 -> path

  • /?page=1 -> query string

출처란 프로토콜을 포함한 도메인을 의미한다.

이 도메인에는 port번호가 포함되어 있으며, port가 다른경우에도 다른 출처로 본다.

SOP (Same Origin Policy)

SOP란 같은 출처 정책으로, 말 그대로 같은 출처에 대한 HTTP 요청만을 허락하는 것으로, 웹 어플리케이션에서의 중요한 보안 모델이다. 보통 자바스크립트에 의한 데이터 접근에 해당하는 정책이며, HTML 태그를 통한 이미지, CSS, Script 요청은 SOP에 의해 제한되지 않는다.

위에서 보안상의 이유로 다른 출처로의 스크립트 HTTP 요청을 제한한다고 했지만. 실제로 웹을 개발하다보면, 다른 출처의 HTTP요청을 해야만 하는경우가 너무나도 많습니다. 따라서 몇가지 예외 조항을 두어 이에 해당하는 리소스 요청은 출처가 다르더라도 허용하기로 결정되었는데 그중 하나가 CORS 정책을 지킨 리소스의 요청은 허락하는 것이다.

접근 제어의 시나리오 예제

CORS가 동작하는 방식으로는 세가지가 있다. 바로 단순요청, 프리플라이트(preflight) 요청, 그리고 인증정보를 포함하는 요청이 그것이다.

  1. 단순요청 (Simple Request)

이 요청은 CORSpreflight를 트리거하지 않고 단순히 요청을 하고, 이 요청은 아래와 같은 조건이 설정되었을때 발생합니다.

  • GET, HEAD, POST에 해당하는 HTTP 메소드

  • 아래와 같은 Request 헤더만 존재하는 경우

    • Accept : 서버에게 자원을 요청한 클라이언트가 이해가능한 컨텐츠 타입이 무엇인지 알려준다.

    • Accept-Language : 어떤 언어를 클라이언트가 이해할 수 있는지와 지역 설정 중 어떤것이 선호되는지 알려준다. ( 자연언어를 의미함, 프로그래밍 언어를 의미하지않음 )

    • Content-Language : 컨텐츠가 어떤 청중을 위한 언어인지를 설명한다. 명시되어 있지 않으면 모든청중이라고 간주한다.

    • Content-Type : 해당 헤더는 아래 값들만 허용된다.

      • application/x-www-form-urlencoded

      • multipart/form-data

      • text/plain

  • 요청에 ReadableStream 객체가 사용되지 않는 경우

단순요청은 클라이언트가 헤더에 Origin에 대한 정보를 담아 요청을 보내면, 서버에서 Access-Control-Allow-Origin 헤더에 접근이 허용될 Origin에 정보를 담아 응답하고, 브라우저가 요청한 Origin과 허용된 Origin의 비교를 통해 요청한 응답을 제공하는 방식이다.

2.프리플라이트(preflight) 요청

프리플라이트 요청이란 단순요청과는 다르게, 먼저 OPTIONS 메소드를 통해 다른 도메인의 리소스로 HTTP 요청을 보내 실제 요청이 전송하기에 안전한지 미리 확인한다. Cross-Site로의 요청은 유저 데이터에 영향을 줄 수도 있기 때문에 이와깉이 미리 전송하는것이다.

프리플라이트 요청을 위해서는 위에 단순 요청이 되기위한 조건에 위배가 되어야하는데, Content-Typeapplication/xml로 설정한다고 가정하면 해당 요청은 프리플라이트 처리된다.

3.인증정보를 포함한(Credentialed) 요청

Credential requestsHTTP CookiesHTTP Authentication 정보를 인식한다. 기본적으로 Cross-site에 대한 XMLHttpRequest 혹은 FetchAPI 요청에서 브라우저는 자격증명에 대한 정보를 보내지 않기 때문에, 특정한 플래그를 설정해주어야한다.

const xhr = new XMLHttpRequest();
xhr.withCredentials = true;

위와 같은 요청을 했을 경우, 서버에서는 Control-Allow-Crendentials 헤더에 true를 담아 응답해야 하고, 그렇지 않을경우 브라우저에서는 해당 응답을 거부한다.

교차 출처 리소스 공유 (CORS)

교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)는 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제입니다. 하지만 다른 origin에서 내 리소스에 함부로 접근하지 못하게 하기 위해 사용된다.

Request Header

요청 헤더 목록에는 다음과 같다.

  • Origin
  • Access-Control-Request-Method
    preflight요청을 할 때 실제 요청에서 어떤 메서드를 사용할 것인지 서버에게 알리기 위해 사용.
  • Access-Control-Request-Headers
    preflight요청을 할 때 실제 요청에서 어떤 header를 사용할 것인지 서버에게 알리기 위해 사용.

Response Header

응답 헤더들은 다음과 같다.

  • Access-Control-Allow-Origin
    브라우저가 해당 origin이 자원에 접근할 수 있도록 허용합니다. 혹은 *credentials이 없는 요청에 한해서 모든 origin에서 접근이 가능하도록 허용.
  • Access-Control-Expose-Headers
    브라우저가 액세스할 수있는 서버 화이트리스트 헤더를 허용.
  • Access-Control-Max-Age
    얼마나 오랫동안 preflight요청이 캐싱 될 수 있는지를 나타낸다.
  • Access-Control-Allow-Credentials
    • Credentialstrue 일 때 요청에 대한 응답이 노출될 수 있는지를 나타낸다.
    • preflight요청에 대한 응답의 일부로 사용되는 경우 실제 자격 증명을 사용하여 실제 요청을 수행 할 수 있는지를 나타낸다.
    • 간단한 GET 요청은 preflight되지 않으므로 자격 증명이 있는 리소스를 요청하면 헤더가 리소스와 함께 반환되지 않으면 브라우저에서 응답을 무시하고 웹 콘텐츠로 반환하지 않는다.
  • Access-Control-Allow-Methods
    preflight요청에 대한 대한 응답으로 허용되는 메서드들을 나타낸다.
  • Access-Control-Allow-Headers
    preflight요청에 대한 대한 응답으로 실제 요청 시 사용할 수 있는 HTTP 헤더를 나타낸다.

Why CORS needed?

브라우저는 보안상의 이유로 스크립트에서 시작된 교차 출처에 대한 HTTP 요청을 제한한다.

만약 내가 서비스하고 있지 않은 사이트에서 세션을 요청해서 세션을 획득할 수 있다면 해당 사이트는 악의적으로 내 세션을 탈취하거나 다른 행동을 할 수 있다. 그래서 브라우저에서는 이러한 요청을 막는다. 피싱사이트가 대표적인 공격 사례인데 이러한 것을 막고 내가 허용한 origin들만 요청할 수 있도록 하기 위해 필요하다.

CORS Error

CORS 에러는 브라우저에서 서로 다른 도메인/포트의 서버로 요청이 갈때 브라우저에서 발생한다.

웹개발을 하다보면 무조건 마주칠 수 밖에 없는 에러중 하나가 CORS 에러다. CORSCross-Origin Resource Sharing 의 줄임말인데, 해석해보면 교차 출처 자원이 공유 정도가 되겠다. 브라우저간의 데이터를 주고받는 과정에서, 도메인 이름이 서로 다른 사이트간에 api요청을 할 때, 공유를 설정하지 않았다면 CORS Error가 발생한다.

CORS Solution

브라우저에서 도메인 이름이 서로 다른 사이트를 내가 소유하고 있다면 설정을 통해 CORS error를 해결할 수 있다. 데이터를 주고 받는 api요청을 할 때, 요청을 주는 쪽의 request 헤더와 요청을 받는 쪽의 response 헤더에 특정 값을 설정하면 된다.

서버쪽 해결 (response)

브라우저에서 도메인/포트가 다른 백엔드 서버로 요청이 갔을때, 보안을 위해 자체적으로 요청이 차단된다. CORS는 브라우저에서 발생하는 것이기 때문에 프론트서버-백엔드서버간 요청은 포트가 다르더라도 에러가 나지 않는다. 때문에 이점을 이용해 CORS를 해결할 수 있고, 간편하게 미들웨어를 설치하여 해결하기도 한다.

  • 미들웨어 설치 & 설정 (프론트엔드/백엔드 둘다 해당)
  • 프록시방식 사용 : 브라우저에서 프론트서버로 요청 > 프론트서버에서 백엔드서버로 요청.
    프론트에서 요청 headerAccess-Control-Allow-Origin:'도메인:포트 or *(모든도메인)' 옵션 사용

server.js에서 미들웨어 cors를 추가하여주면, 자동으로 모든 요청의 headerAccess-Control-Allow-Origin옵션이 추가되어 진다.

npm i cors

app.use(cors({
  origin : '*', // 모든 요청 허용
  origin : true, // 들어오는 요청 도메인/포트가 자동으로 origin에 삽입된다.
  origin : 'www.domain.com', // 실 서비스에서는 실제 서비스 도메인을 넣어. 해당 도메인 요청만 허용한다.
 credentials : false, // 개발단계에서는 false, 실 서비스에서는 true
})

클라이언트쪽 해결 (request)

리소스 요청하는 서버 사이에 프록시 서버를 하나 거쳐서 요청,응답을 주고 받기.

프록시 서버 : 브라우저와 서버를 통신하는 과정 중간에서 정보교환을 도와주는 중간 서버 ( 브라우저 - 프록시 서버 - 서버 )
프록시 서버는 헤더를 추가하거나 요청을 허용/거부하는 역할을 중간에서 해줘서 Access-Control-Allow-Origin : *의 헤더를 담아 응답해준다

아무래도 중간단계가 있기 때문에 속도가 느려지는 단점이 있다.

const url_sample = "https://cors-anywhere.herokuapp.com/https://A.com")

다음의 글을 참고하였습니다.

profile
yeeaasss rules!!!!
post-custom-banner

0개의 댓글