Computer Science/Network

CORS란? + SOP, JSONP는 대체 무엇인가

둉이 2022. 1. 24. 18:28

개발을 하다 보면 API 요청을 보낼 때 CORS 에러와 마주칠 수 있다.

 

대충 CORS 정책 때문에 요청이 실패했다는 뜻

 

처음엔 CORS가 뭐지? 에러 이름이구나! 하고 가볍게 넘겨짚기만 하고 따로 깊게 찾아보지는 않았었다.

 

하지만 이런 공부 부채들이 쌓이면 쌓일 수록 난 개발자가 아닌 무지성으로 코딩만 하는 기계가 될 뿐이라는 것을 알게 되고...

 

뒤늦게 쌓아놨던 CORS에 대해 공부를 하면서 알게 된 내용을 정리해 보려고 한다.

 

 


 

 

먼저, CORS에 대해 알아보기 전에 SOP에 대해 알아보자.

 

SOP(=Same-Origin Policy, 동일 출처 정책)란?

같은 origin 내에서만 데이터 통신이 가능한 브라우저의 정책
CSRF(=Cross-Site Request Forgery) 공격을 막기 위한 방법 중 하나

 

CSRF이란, 허용되지 않은 origin에서 사용자가 자신의 의도와는 무관하게 공격자가 의도한 행위를 특정 사이트에 요청하게 하는 공격 방식이다.

 

대표적으로는 피싱 사이트를 이용한 개인정보 탈취 등이 있다.

(CSRF 관련 정리는 다음 포스팅에!)

 

 

같은 origin을 판별하는 조건은 프로토콜 + IP Address(Host Address) + 포트 번호가 모두 동일해야 한다.

같은 origin의 조건

 

예시를 통해 이해해보자.

 

 

http://192.168.8.11:8080/ 과 http://192.168.8.11:8081/ 은 포트 번호가 다르기 때문에 같은 origin이 아니다.

 

하지만 https://192.168.8.11:8080/board 와 https://192.168.8.11:8080/users 는 프로토콜(https)과 IP 주소, 포트 번호가 모두 동일하기 때문에 같은 origin으로 인정한다.

 

구형 브라우저인 IE에서는 same-origin 판별 시 포트 번호를 제외한다.

 

따라서, 프로토콜과 IP 주소만 동일하다면 같은 origin으로 취급한다.

 

 

추가적으로, SOP가 적용되지 않는 경우에 대해 알아보자.

 

1. HTML의 이미지, CSS, 스크립트 태그인 img, link, script, iframe 등에는 적용되지 않는다.

(font의 url도 마찬가지)

SOP가 적용되지 않음!

 

따라서, 이러한 SOP의 허점을 이용한 JSONP 방식을 통해 SOP를 우회할 수 있다.

 

JSONP(JSON with Padding)란?

CORS 정책이 나오기 전, 브라우저의 SOP 정책을 우회하던 방법
HTML의 script 태그가 SOP이 적용되지 않는 점을 이용하여 스크립트 태그에 요청을 보낼 URL을 추가하여 요청을 보내고, 콜백 함수를 이용하여 응답받은 결과를 json 형태로 반환

 

JSONP의 사용 방법은 다음과 같다.

 

1. Vanila Javascript에서의 콜백 함수를 이용한 방법

const getJSONP = () => {
    const script = document.createElement('script');
    script.src = 'https://jsonplaceholder.typicode.com/users?callback=callbackJSON';
    document.querySelector('head').appendChild(script);
}

const callbackJSON = obj => {
    console.log(obj);
};

getJSONP();

 

2. jQuery의 ajax에서의 사용

$.ajax({
	dataType: 'jsonp',
	jsonp: 'callback',
	url: 'https://jsonplaceholder.typicode.com/users',
	type: 'GET',
	data: { id, name },
	success: function(data){
		// write your code
	},
});

 

다만, JSONP는 보안 문제가 존재하므로 현재는 거의 사용되지 않는다.

 

2. 서버 간의 통신에서는 SOP가 적용되지 않는다.

왜냐면... SOP는 브라우저의 스펙이니까...

 

그렇기 때문에 프록시 서버를 사용하여 SOP를 우회할 수도 있다.

 

클라이언트의 요청을 프록시 서버에 위임하여 서버에서 데이터 요청을 전송하고, 서버는 응답 결과를 클라이언트에 전달하는 것이다.

 

 

 

그럼 위에서 동일한 origin이 아니라면 데이터 통신이 불가하다고 설명했는데, 그럼 http://192.168.8.11:8080/ 과 http://192.168.8.11:8081/ 처럼 서로 다른 origin에서는 아예 데이터 통신이 불가능한걸까?

 

 

아니!

 

앞으로 설명할 CORS(Cross-Origin Resource Sharing) 정책을 통해 서로 다른 origin 간에도 데이터 통신이 가능하도록 설정할 수 있다.

 

CORS(Cross-Origin Resource Sharing)란?

서로 다른 origin 간에도 데이터 통신이 가능하도록 하는 정책

 

 

CORS를 적용하는 방법으로는 서버의 응답 헤더 내의 Access-Control-Allow-Origin 속성에 허용할 origin을 추가하면 된다.

 

모든 origin에 대해 CORS를 허용하려면 * 기호를 사용하면 된다.

HttpServletRequest request = (HttpServletRequest) req;
    HttpServletResponse response = (HttpServletResponse) res;
    response.setHeader("Access-Control-Allow-Origin", "https://guiyomi.tistory.com");
    response.setHeader("Access-Control-Allow-Credentials", "true");
    response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
    response.setHeader("Access-Control-Max-Age", "3600");
    response.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With, remember-me");

 

백엔드 프레임워크에서 제공하는 cors 미들웨어가 있는 경우에는 일일이 응답 헤더에 CORS 관련 속성을 추가하지 않아도 손쉽게 처리가 가능하다.

 

아래는 express의 cors 미들웨어 적용 예제 코드이다.

import cors from 'cors';
import express from 'express';

const app = express();

const LOCALHOST = process.env.LOCALHOST as string;
const PRODUCTION = process.env.PRODUCTION as string;
const DOMAIN_NAME = process.env.DOMAIN_NAME as string;

app.use(cors({ credentials: true, origin: [LOCALHOST, PRODUCTION, DOMAIN_NAME] }));

 

 

이제 CORS가 어떻게 동작하는 지 알아보자!

 

CORS의 동작 방식은 총 3가지 형태로 나뉜다.

 

Simple Request

단순 요청

 

아래 조건을 만족하는 요청의 경우에는 Preflight Request를 보내지 않고 바로 본요청을 보낸다.

 

1. GET, POST, HEAD 중 하나의 HTTP 메소드로 요청한 경우

 

2. POST인 경우에는 Content-type이 다음과 같을 때

  • application/x-www-form-urlencoded
  • multipart/form-data
  • text/plain

 

Preflight Request

앞에서 설명한 Simple Request의 조건에 맞지 않는 요청들은 실제 요청을 보내기 전, 브라우저에서 예비 요청(Preflight Request)을 먼저 보내게 된다.

preflight 요청 이후 본요청까지의 과정

 

과정은 다음과 같다.

 

1. OPTIONS 메소드를 사용하여 서버에 예비 요청을 보냄

 

2. 예비 요청의 응답 헤더 내의 CORS 관련 속성(Access-Control-*)의 값을 가져와서 본 요청의 조건이 이와 맞는지 체크 

 

- 응답의 결과가, CORS 정보가 본 요청과 맞지 않는 경우에는 CORS 에러를 발생시킴

- 허용된 요청인 경우에는 본 요청을 전송

 

Credential  Request

요청에 쿠키를 포함하여 전송하고 싶다면 응답 헤더에 Access-Control-Allow-Credentials 값이 true여야 함

 

cross origin 간의 통신에서 인증 정보나 쿠키를 함께 전달하기 위해서는 서버 응답 헤더의 Access-Control-Allow-Credentials 값을 true로 설정 + 클라이언트에서 요청 시에 credentials 속성(axios에서는 withCredentials)을 include로 지정하여 요청을 보내야 한다.

export const getUserInfo = async () => {
  const res = await fetch(`${BACKEND_URI}/check-login`, {
  	method: 'GET',
    headers: { 'Content-Type': 'application/json' },
    credentials: 'include',
  });
  return res.json();
};

 

참고로 credentials 속성에 지정 가능한 값은 다음과 같다.

 

  • omit
    • 모든 origin에 인증 정보 담지 않음
  • same-origin(기본값)
    • same origin에만 인증 정보 담아서 전송
  • include
    • 모든 origin에 인증 정보 담아서 전송

 

 

그리고 주의할 점!

 

Access-Control-Allow-Credentials: true인 경우에는 Access-Control-Allow-Origin의 값으로 *을 사용하는 것은 불가하다.

(명확한 origin을 작성해야 함)

 

즉, 하나의 안전한 cross origin 간의 통신에서만 쿠키를 전송하겠다는 의미이다.

 

별도의 credential 관련 설정을 하지 않은 경우에는 기본적으로 Non-Credential 요청으로 처리된다.

 

 

cf) same-site

쿠키의 옵션을 공부하다가 알게 된 sameSite에 대해 알아보자.

 

same-origin과 비슷한 용어인 듯 하지만 다른 same-site는 same-origin의 조건보다는 관대하다.

 

같은 site의 조건

 

즉, 서브 도메인(예제의 map)이 다르더라도 최상위 도메인 + 그 다음 도메인이 같은 경우에는 same-site라고 간주한다.

 

그럼 프로토콜이 same-site 판별에 영향을 줄까?

 

정답은 No인 듯 하다.

 

블로그를 찾아봐도 설명이 애매하게 되어 있어서 MDN 홈페이지를 찾아봤는데, 프로토콜은 영향을 주지 않는다고 기재되어 있다.

 

 

same-site 옵션에 지정 가능한 값으로는 다음과 같다.

 

  • none
    • 모든 도메인 간 쿠키 전송 가능
    • 기본적으로 csrf 방어가 되지 않으므로 https를 필수로 사용해야 함
      • secure 속성 활성화
  • lax(기본값)
    • strict + a 태그, location.href, get 요청일 경우에는 예외
    • form 태그로 post 전송할 경우에는 cross-site일 경우에 기본적으로 쿠키 전송이 됨
  • strict
    • 같은 도메인에서만 쿠키 전송 가능

 

참고 자료

 

CORS와 Jsonp에 대해서

Cross Origin Resource Sharing and Json with Padding

simsimjae.medium.com

 

교차 출처 리소스 공유 (CORS) - HTTP | MDN

교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)는 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라

developer.mozilla.org

 

 

Site - MDN Web Docs Glossary: Definitions of Web-related terms | MDN

The site of a piece of web content is determined by the registrable domain of the host within the origin. This is computed by consulting a Public Suffix List to find the portion of the host which is counted as the public suffix (e.g. com, org or co.uk).

developer.mozilla.org