항해99 5주차 WIL

백종석·2022년 6월 13일
0

[항해99] WIL

목록 보기
4/5
post-thumbnail

CORS(Cross Origin Resource Sharing)


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

🚨 Access to fetch at ‘https://api.lubycon.com/me’Visit Website from origin ‘http://localhost:3000’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.

'https://api.lubycon.com/me'에서 오리진 'https://localhost:3000'으로 가져올 수 있는 엑세스가 CORS 정책에 의해 차단되었습니다. 요청된 리소스에 'Access-Control-Allow-Origin' 헤더가 없습니다. 불투명한 응답이 필요에 적합한 경우, 요청 모드를 '노코어'로 설정하여 CORS가 비활성화된 리소스를 가져오십시오.

  • 이는 HTTP 요청에 대해서 어떤 요청을 하느냐에 따라 각기 다른 특징을 가지고 있기 때문이다.
    • HTML → 기본적으로 Cross-Origin 정책을 따름
      • link 태그에서 다른 origin의 css 등의 리소스에 접근하는 것이 가능
      • img 태그등에서 다른 리소스에 접근하는 것이 가능
    • XMLHttpRequest, Fetch API 등 script 태그 내 → 기본적으로 Same-Origin 정책을 따름
      • 자바스크립트는 서로 다른 도메인에 대한 요청을 보안상 제한한다.(브라우저 기본 설정은 하나의 서버 연결만 허용)
      • 이 정책을 Same-Origin-Policy라고 한다.


출처(Origin) 이란?


  • 다른 출처간의 리소스 공유에 대해서 알아보기 전에 출처가 무엇을 의미하는지 이해할 필요가 있다.

  • 서버의 위치를 의미하는 https://google.comVisit Website 과 같은 URL은 하나의 문자열 같지만 다음과 같이 구성되어 있다.

  • 이때 출처는 Protolcal과 Host 그리고 Port까지 모두 합친 것을 의미한다.

  • 자바스크립트로 Location 객체가 가지고 있는 origin 프로퍼티에 접근하여 현재 출처를 알아낼 수도 있다.

    console.log(location.origin);	//	"https://www.naver.com"



Cross Origin / Same Origin


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

SOP(Same-Origin Policy) <동일 출처 정책>

  • SOP는 "같은 출처에서만 리소스를 공유할 수 있다."라는 규칙을 가진 정책이다.

Same Origin 기준 (출처 비교)

  • 두개의 출처를 비교하는 방법은 URL의 구성요소 중 Protocal, Host, Port 이 세가지가 동일한지 확인하면된다.
  • 즉 같은 프로토콜, 호스트, 포트를 사용한다면 다른 요소는 다르더라도 같은 출처로 인정된다. 반대로 리소스가 자신의 출처와 다를 경우 브라우저는 교차출처 요청을 실행한다.
  • 출처를 비교하는 로직은 서버에 구현된 스펙이 아닌브라우저에 구현된 스펙이다.
  • 만약 CORS정책을 위합ㄴ하는 요청에 서버가 정상적으로 응답을 하더라도 브라우저가 이 응답을 분석해서 CORS정책에 위반되면 그 응답은 처리하지 않게 된다.
  • 요약하면 프로토콜, 포트, 호스트 중 하나라도 일치하지 않으면 Cross Origin 이라고 한다.

TIP
internet Explorer는 Origin 비교시 포트를 무시한다. → 보안 취약

Info
실무 예를 들어보자.

보통 프론트엔드 개발자가 React와 같은 라이브러리를 사용해서 개발하는 경우 백엔드 서버와 별도의 프론트 서버가 존재한다.
프론트 개발을 할때, 로컬의 백엔드 서버에 연동하거나 개발 서버에 연결해서 API 연동을 하는데

프론트 서버의 URL이 http://localhost:3000이고,
백엔드 서버가 http://localhost:8080에 띄워져 있다고 하면

이때 프론트 서버와 백엔드 서버는 다른 출처 (Origin)으로써 Same-Origin Policy 정책을 어긋나기 떄문에, 서버로부터 응답이 넘어올 때 브라우저에서 CORS Policy 오류를 발생시킨다.


CORS (Cross-Origin Resource Sharing) <교차&다른 출처 리소스 공유>

  • 교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)는,
  • 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제다.
  • 즉, 위에서 시뻘겋게 CORS 어쩌구 에러는 CORS를 허용해서 아무런 탈 없이 다른 출처 리소스 공유를 해달라는 권고 사항 같은 것이라 볼 수 있다.
  • 웹 애플리케이션은 리소스가 자신의 출처(도메인, 프로토콜, 포트)와 다를 때 교차 출처 HTTP 요청을 실행하게 된다.

Tip
"교차 출처"는 "다른 출처"라고 표현하는게 좀 더 쉽게 이해할 수 있다.

CORS 기본 동작과정

  1. 클라이언트에서 HTTP요청의 헤더에 Origin을 담아 전달한다.
    • 다른 Origin의 리소스 요청시 클라이언트는 HTTP요청을 보낸다.
    • 이때 요청헤더의 Origin필드에는 요청을 보내는 Origin을 담아보낸다.
  1. 서버는 응답헤더에 Access-Control-Allow-Origin을 담아 클라이언트로 전달한다.
    • 서버가 응답을 보낼때, 허락하는 Origin을 클라이언트에게 전달한다.
  1. 클라이언트에서, 자신이 보냈던 요청의 Origin과 서버가 보내준 Access-Control-Allow-Origin을 비교한다.
    • 자신이 보낸 Origin과 서버가 보내준 Access-Control-Allow-Origin을 비교하여 차단할지 말지를 결정한다.
    • 만약 유효하지 않다면 그 응답을 사용하지 않고 버린다.
      (위의 경우에는 둘다 http://localhost:3000이기 때문에 유효한 경우이다.)

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

CORS 정책에 의해 Origin을 비교하는 로직은 브라우저에 구현되어 있다.
그래서 서버에서 정상적인 응답을 하여 상태코드가 200이 나오더라도, 브라우저가 응답을 CORS정책 위반이라고 분석하면 그 응답은 사용하지 않는다.

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


CORS의 3가지 시나리오

  • 실제 CORS가 동작하는 방식은 3가지 시나리오에 따라 변경된다.

예비요청(Preflight Request)

  • 브라우저는 요청을 한번에 보내지 않고, 예비요청과 본요청으로 나누어 서버에 전달한다.
  • 이때 브라우저가 예비요청을 보내는 것을 Preflight라고 부르며, 이 예비요청의 메소드에는 OPTIONS가 사용된다.
  • 예비요청의 역할은 본 요청을 보내기 전에 브라우저 스스로 안전한 요청인지 확인하는 것이다.
  1. 자바스크립트의 fetch API를 통해 브라우저에게 리소스를 받아오려고 한다.
  2. 브라우저는 서버로 예비요청을 먼저 보낸다.
  3. 서버는 이 예비요청에 대한 응답으로 어떤 것을 허용하고 어떤것을 금지하고 있는지에 대한 정보를 담아서 브라우저로 다시 보내준다.
  4. 이후 브라우저는 보낸 요청과 서버가 응답해준 정책을 비교하여 해당 요청이 안전한지 확인하고 본 요청을 보내게 된다.
  5. 이후 서버가 본 요청에 대한 응답을 하면 최종적으로 이 응답 데이터를 자바스크립트로 넘겨준다.

Tip
예비 요청은 보통 PUT DELETE 같은 요청을 보낼떄 이용된다.
PUT이나 DELETE는 서버의 데이터를 변경해 버리는 요청이기 때문에, 코드가 돌아가게 하는 요청을 보내기전에 예비 요청을 보내서 우선 인증부터하고 본 요청을 받아 서버에서 코드가 돌아가게 하는 원리이다.
이 다음에 배울 단순 요청은 GET POST같은 서버 자체 데이터가 변경될 일이 적은 요청에서 작동된다고 보면 된다.
하지만 POST 같은 경우 서버에 데이터를 변경할수 있는 것이기 때문에, 따라서 개발자는 SOP만 믿을 것이 아니라 따로 서버 프로그래밍을 해야한다.

단순 요청(Simple Request)

  • 단순요청은 예비 요청(Prefilght)을 보내지 않고 바로 서버에 본 요청을 한 후,
  • 서버가 이에 대한 응답의 헤더에 Access-Control-Allow-Origin과 같은 값을 보내주면 브라우저가 CORS정책 위반여부를 검사하는 방식이다.

  • 즉, Simple Request를 사용한다는 것은 예비요청을 생략한다는 뜻인데,
  • 예비요청 생략은 아부때나 할 수 있는것이 아니고 아래 3가지 경우를 만족할때만 가능하다.
  1. 요청의 메소드는 GET, HEAD, POST 중 하나여야 한다.
  2. 유저 에이전트가 자동으로 설정한 헤더외에,
    수동으로 설정할 수 있는 헤더는 Fetch 명세에서 "CORS-safelisted request-header"로 정의한 헤더만 사용할 수 있다.
    1. Accept
    2. Accept-Language
    3. Content-Language
    4. Content-Type
    5. DPR
    6. Downlink
    7. Save-Data
    8. Viewport-Width
    9. Width
  3. Content-Type 을 사용하는 경우에는 다음의 값들만 허용된다.
    1. application/x-www-form-urlencoded
    2. multipart/form-data
    3. text/plain

Tip
위 조건과 같이 다소 까다로운 조건들이 많기 때문에 위 조건을 모두 만족시키는 상황을 만드는것은 쉽지 않다.

인증된 요청(Credentialed Request)

  • 기존 예비요청에서 보안을 더 강화하고 싶을 때 사용한다.
  • 기본적으로 브라우저가 제공하는 비동기 리소스 요청 API인 XMLHttpRequest 객체나 fetchAPI는 별도의 옵션 없이 브라우저의 쿠키 정보나 인증과 관련된 헤더를 함부로 요청에 담지 않는다.
  • 따라서, 요청에 인증과 관련된 정보를 담을 수 있게 해주는 옵션이 있는데 따로 credentials 옵션이다.
  • 이 옵션은 총 3가지 값을 사용할 수 있다.
fetch('http://localhost:3001/cors', {
  method: 'PUT',
  credentials: 'include' // credentials 옵션
})
.then(function(response) {
  ... 코드
})
.catch(function(error) {
  ... 코드
})
  • 만약 same-origin 이나 include와 같은 옵션을 사용하여 리소스 요청에 인증 정보가 포함된다면,
    브라우저는 다른 출처의 리소스를 요청할 때 Access-Control-Allow-Origin만 확인하는 것이 아니라 다른 조건을 추가로 검사한다.
  • 요청에 인증정보가 담겨있는 상태에서 다른 출처의 리소스를 요청하게 되면,
    브라우저는 CORS정책 위반 여부를 검사하는 룰에 다음 두가지를 추가하게 된다.
  1. Access-Control-Allow-Origin 에는 모든 요청을 허용하는 * 을 사용할 수 없으며, 명시적인 URL이어야 한다
  2. 응답 헤더에는 반드시 Access-Control-Allow-Credentials: true가 존재해야 한다

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


참고

[WEB] 📚 CORS 개념 💯 완벽 정리 & 해결 방법 👏

profile
항해중인 우당탕탕 코린이

0개의 댓글