SOP , CORS 가 뭔데 ?

ChoiYongHyeun·2024년 1월 15일
0

HTTP

목록 보기
13/17
post-thumbnail

fetch , async/await 등을 공부하기 위해 기상청API를 이용해 실시간 날씨를 불러와보려고 했다.

// APIKEY 는 발급받아서 선언함
const URL = 
      `https://apihub.kma.go.kr/api/typ01/url/kma_sfctm2.php?tm=202211300900&stn=0&help=1&authKey=${APIKEY}`;

fetch(URL).then(console.log);

그랬더니 이게 웬걸 , CORS error 가 뜨며 GET 요청이 보내지지 않는다.

그런김에 CORS 에 대해 공부해봤다.

해당 블로그를 보고 내용을 이해하고 챗 지피티의 목을 졸라 공부했다.
[ERROR] 공공데이터 API CORS 오류 : 배포 시에도 난 오류...🤯


CORS 는 범인이 아니다.

나는 해당 에러를 만나면 이렇게 생각했다.

아 ~! CORS 때문에 GET 요청 안되네 짜증나네 ~!!
CORSGET 못하게 막고있네 ~!!

그런데 이는 정확한 표현이 아니다.

오히려 CORSCross-Origin Resource Sharing , 교차 출처 리소스 공유 의 약자로, 내가 다른 홈페이지에서 리소스를 얻어올 때 허용해주는 것이다.

CORS 때문이 아니라 CORS 가 없어서 문제가 생긴다.

오히려 나를 막는 것은 SOP (Same Origin Policy)동일 출처 정책 이 막고 있는 것이다.


SOP 가 뭐고 왜 필요할까 ?

SOP , Same Origin Policy 는 말 그대로 동일한 Origin 에서만 리소스의

교환이 가능하도록 하는 정책이다.

동일한 Origin 이란 리소스를 제공하는 리소스 서버와 같은 스킴 , 호스트 , 포트를 의미한다.

http://www.naver.com 이라면 http스킴 , www.naver.com 이 호스트 , 이곳엔 적히지 않았지만 80 이 포트이다.

그럼 그런 정책은 왜 필요할까 ?

기본적으로 이런 정책은 사용자를 불신한다기 보다, 사용자가 이용하는 페이지 를 불신한다.

우리가 리소스 서버에 정보를 요청 할 때 request header , response header 에 우리의 개인 정보가 담겨 있을 수도 있고

브라우저는 우리의 개인 정보를 쿠키에 담아 저장한다.

리소스 서버에 정보를 삽입하기도 , 수정 삭제 하기도 한다.

이런 기능을 막는 것은 리소스 서버가 알아서하고 있겠지만

그런데 우리가 의도치 않게 정보를 훔치거나 악의를 가지고 만든 페이지 에서 해당 리소스 서버와 자원을 교환한다고 해보자

그러면 해당 페이지는 리소스 서버와 나의 정보 교환에 관한 내용을 모두 취득 할 수 있고 우리가 의도하지 않은 요청을 보낼 수도 있다.

이처럼 리소스 서버는 모든 페이지에서 오는 요청을 허용하는 것이 아니라, 리소스 버어와 동일한 페이지들과만 정보를 교환해야 하도록 하는 정책이 SOP이다.

SOP 에 위배되는 요청들의 예시를 살펴보자

예시 설명
다른 스킴 (Scheme) http://example.com에서 로드된 페이지에서 https://example.com의 리소스에 접근하는 시도.
다른 호스트 (Host) http://sub.example.com에서 로드된 페이지에서 http://other.example.com의 리소스에 접근하는 시도.
다른 포트 (Port) http://example.com:8080에서 로드된 페이지에서 http://example.com:9090의 리소스에 접근하는 시도.
다른 프로토콜 (Protocol) http://example.com에서 로드된 페이지에서 ftp://example.com의 리소스에 접근하는 시도.
서브도메인 간의 액세스 http://sub1.example.com에서 로드된 페이지에서 http://sub2.example.com의 리소스에 접근하는 시도.
로컬 파일 시스템 액세스 file:// 프로토콜을 사용하여 로컬 파일 시스템에 접근하는 시도.
다른 프레임 간의 액세스 하나의 프레임에서 로드된 페이지에서 다른 프레임의 리소스에 직접적으로 접근하는 시도.
Cross-Origin XMLHttpRequest 클라이언트 측에서 생성된 JavaScript 코드에서 다른 출처의 서버로 XMLHttpRequest를 사용하여 리소스에 접근하는 시도.

그러니 만약 http://www.example.com:80/img1.jpg 에 접근 할 수 있는 페이지 오로지 http://www.example.com:80 라는 것 뿐이다.

같이 보면 좋을 동영상 : 웹개발 짜증유발자! CORS가 뭔가요?


CORS 는 그럼 뭔데 ?

그런데 생각해보면 우리는 다양한 곳들의 API 를 가지고 와서 서비스를 제공하는 다양한 페이지들을 볼 수 있다.

얘네들은 해당 리소스 서버와 다른 주소인데도 불구하고 말이다.

이는 리소스를 제공하는 리소스 서버에서 CORS , Cross Origin Resource Sharing , 교차 출처 자원 공유를 제공하기 때문이다.

CORS 는 다른 Origin (다른 스킴, 다른 호스트 , 다른 포트) 더라도 정보의 교환이 가능하도록 해주는 기법이다.

리소스 서버는 다른 Origin 을 갖는 웹 페이지와 정보를 교환 할 수 있도록 CORS 헤더 를 설정한다.


실제 서버를 만들고 실험해보기

express 를 이용해서 간단히 실습해보자

CORS 오류 체험해보기

const express = require('express');
// const cors = require('cors');
const PORT = 3000;
const app = express();
let dataBase = [{ id: 1, name: 'lee' }];

app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.listen(PORT, () => {
  console.log('Server Start !');
  console.log(`Server address : http://localhost:${PORT}`);
});

app.get('/', (req, res) => {
  res.send(dataBase);
});

다음과 같이 서버를 만들고 서버에 GET 요청을 보내면 dataBase 에 있는 값이 response 된다.

그럼 다른 포트에 index.html 을 만들고 GET 요청을 보내보자

const address = 'http://localhost:3000';
const $content = document.querySelector('.content');

const fetchUrl = async (url) => {
  try {
    const response = await fetch(url);
    const result = await response.json();
    $content.textContent = result;
  } catch (error) {
    $content.textContent = error;
    $content.style.color = 'red';
  }
};

fetchUrl(address);

데이터를 가져오는게 잘못되었다고 한다.

에러창을 열어보자

리소스 서버인 http://localhost:3000/http://127.0.0.1 (localhost와 같다):5500 에서 정보를 가져오는데 실패했다고 한다.

너의 요청 헤더에 Access-Control-Allow-Origin 가 존재하지 않기 때문이기에, 만약 너가 CORS 정책을 피하고 정보를 가져오고 싶다면 mode : no-cors 를 하라고 한다.

그럼 말을 들어보자

const fetchUrl = async (url) => {
  try {
    // {mode : 'no-cors'} 추가
    const response = await fetch(url, { mode: 'no-cors' });
    const result = await response.json();
    $content.textContent = result;
  } catch (error) {
    $content.textContent = error;
    $content.style.color = 'red';
  }
};

fetchUrl(address);


이번엔 정보를 가져오지 못했다고 한다. 상태 코드는 200인데 말이다.

그 이유는 no-cors 모드 일 때에는 리소스 서버가 제공하는 응답 엔터티에 직접적으로 접근 할 수 없기 때문에 response.json() 을 할 수 없다.

CORS 문제 해결하기

그럼 서버단에서 CORS 할 수 있도록 설정해주자

// CORS 설정
app.use((req, res, next) => {
  const allowUrl = 'http://127.0.0.1:5500';
  // 모든 origin에 대해 허용
  res.header('Access-Control-Allow-Origin', allowUrl);
  // 허용하는 HTTP 메소드
  res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); 
  // 허용하는 헤더
  res.header('Access-Control-Allow-Headers', 'Content-Type'); 
  next();
});

그 다음 요청을 보내보면 ?

const address = 'http://localhost:3000';
const $content = document.querySelector('.content');

const fetchUrl = async (url) => {
  try {
    const response = await fetch(url);
    const result = await response.json();

    $content.textContent += JSON.stringify(result);
  } catch (error) {
    $content.textContent += error;
    $content.style.color = 'red';
  }
};

fetchUrl(address);

야호 ~ 잘 가져온다.

날라온 response header 부분을 보자

Response headers

HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: http://127.0.0.1:5500 // CORS 를 허용할 URL
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type

Access-Control-Allow-Method: GET
Date: Mon, 15 Jan 2024 08:11:50 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 23
ETag: W/"17-A7ZTpzlobz9lqv6MisTgSnaMdsM"

서버단에서 다른 Origin 에서도 정보를 교환 할 수 있도록 헤더를 설정해두니 잘만 가져와진다.

만약 특정 URL 이 아니라 모든 URL 에서 정보 교환을 하고 싶다면 * 로 설정해주면 된다.


프론트 단에서 CORS 해결법

결국 CORS 는 리소스를 제공하는 리소스 서버 단에서 해결해야 한다는 것이다.

그럼 나는 기상청 API를 가져올 때 CORS error 로 인해 정보를 가져오지 못했으니 포기해야 할까 ?

다 해결하는 방법이 있다.

그건 바로 프록시 를 사용해주는 것이다.

키킥 때마침 일주일 전 프록시를 공부했는데 여기서 쓰여서 기분이 좋다.
프록시가 뭔데?

SOP 는 최종 사용자인 웹 페이지와 리소스 서버가 직접적으로 연결되어 있을 때 , 즉 브라우저리소스 서버 간의 정보 교환을 막는 정책이다.

그럼 내가 바로 리소스 서버에 요청을 보내는 것이 아니라

프록시 서버로 요청을 보내고 프록시 서버가 리소스 서버에게 요청을 보내면

리소스 서버는 정보를 프록시 서버에게 전달하기 때문에

브라우저 와 리소스 서버 간의 정보 교환이 아닌 프록시 서버와 리소스 서버 의 정보 교환 , 서버와 서버 간의 정보 교환이기 때문에 CORS 를 우회 할 수 있다.


사실 프록시 서버까지 만들어서 해결해보려고 했는데

내가 맨 처음 받은 API 를 제공하는 곳은 https 프로토콜을 사용했다.

이 문제를 해결하려면 프록시 서버도 https 로 해야 한다고 하는데 이는 비용이 들더라

그래서 다른 사이트에서 동일한 http 프로토콜을 사용하는 API 를 사용하니 CORS 에러가 안떠서 해결됐다 ..

하지만 2~3시간 내내 붙잡고 프록시 서버를 이용한 우회에 대해 생각해봤는데 아직 이해가 잘 안간다.

어째서 로컬 환경일 때는 프록시 서버로 우회해야 하지만 배포 한 이후에는 문제가 없는 것일까 ?

더 공부해봐야겠다.


같이 보면 좋을 사이트
🌐 악명 높은 CORS 개념 & 해결법 - 정리 끝판왕 👏

profile
빨리 가는 유일한 방법은 제대로 가는 것이다

0개의 댓글