CORS(Cross-Origin Resource Sharing)에 대해서

조경석·2023년 4월 9일
0

CORS

CORS(Cross-Origin Resource Sharing)는 웹 페이지가 원래 웹 페이지를 제공한 도메인이 아닌 다른 도메인에 요청하는 것을 방지하기 위해 웹 브라우저에서 구현하는 보안 기능이다.
악성 스크립트가 액세스 권한이 없어야 하는 다른 도메인의 리소스에 액세스하는 것을 방지하기 위한 기능으로, 이를 통해 사용자의 데이터를 훔치거나 사용자를 대신하여 무단 작업을 실행하려는 악성 웹 페이지로부터 사용자를 보호한다.

일반적으로 'CORS 문제'라고 하는 에러의 대부분은 웹 애플리케이션이 웹 페이지를 제공한 출처와 다른 출처(도메인, 프로토콜 또는 포트)의 리소스에 액세스하려고 할 때 발생할 수 있으며, 이는 웹페이지가 기본적으로 동일 출처 정책(SOP : Same-Origin Policy)을 따르기 때문이다.

예를 들어, 웹 페이지에서 외부 데이터를 사용하기 위해 다른 도메인에서 호스팅되는 API 서버에 AJAX 요청을 해야 하는 경우가 대표적이다.
브라우저가 다른 도메인을 대상으로 하는 요청을 감지하면 서버에서 명시적으로 허용하지 않는 한 기본적으로 요청 수준에서 차단한다.

CORS preflight 요청

위 문단의 내용이 기초적인 설명이고, 실제로 시도해보면 요청에 소요되는 시간보다 훨씬 빠른 시간 내에 CORS 에러가 발생한다.
불필요한 요청 지연, 네트워크 소요를 최소화하기 위해 브라우저는 실제 요청을 완료한 이후에 그 응답을 기반으로 CORS 에러를 표기하지는 않는다.

CORS 프리플라이트 요청이란 실제 요청을 전송(flight)하기 전(pre)에 브라우저에서 사전 검사를 먼저 진행하는 CORS 요청의 한 유형으로,
1. GET, POST 또는 HEAD가 아닌 HTTP 메서드를 사용하거나,
2. CORS에서 허용하는 요청 헤더에 포함되지 않는 사용자 지정 헤더를 사용하거나,
3. Content-Type이 application/x-www-form-urlencoded, multipart/form-data 또는 text/plain이 아닌 요청을 전송할 때 적용된다.
브라우저가 위와 같이 프리플라이트 요청이 필요한 CORS 요청을 할 때 사전 검사를 위해 OPTIONS 요청을 먼저 전송하며, 서버는 허용된 HTTP 메서드 및 헤더를 포함한 적절한 CORS 헤더로 응답하고, 브라우저는 이 정보를 통해 실제 요청을 전송할지, 에러를 표기할지를 결정한다.

해결 방법

서버 헤더 설정

가장 기본적인 해결 방법은 서버 측에서 CORS 헤더를 구현하는 것이다. 이 헤더를 통해 서버는 리소스에 접근할 수 있는 도메인을 지정할 수 있으며, 외부 웹 페이지에서 CORS 헤더가 활성화된 서버에 요청을 하면 서버는 응답에 해당 헤더를 포함시켜 브라우저에 요청이 허용되는지 여부를 전달한다.

서버 헤드 옵션

Access-Control-Allow-Origin
Access-Control-Allow-Methods
Access-Control-Allow-Headers
서버의 리소스에 접근할 수 있는 도메인, HTTP 메서드, 헤더를 지정한다.
콤마로 구분한 여러 개의 값이나, 와일드카드 *를 사용하여 모든 도메인, HTTP 메서드, 헤더이 서버의 리소스에 액세스할 수 있도록 허용할 수 있다.

Access-Control-Allow-Credentials
쿠키와 같은 자격 증명을 CORS 요청에 포함할 수 있는지 여부를 지정하며, boolean 형태의 값으로 지정된다.

Access-Control-Max-Age
브라우저에서 CORS 프리플라이트 요청 결과를 캐시할 수 있는 최대 시간을 지정하며, 초 단위 숫자 값으로 지정된다.
해당 헤더가 지정되어 있는 경우, 브라우저는 모든 프리플라이트 요청이 필요한 CORS에 매번 OPTION 확인을 할 필요가 없도록 CORS 프리플라이트 요청의 결과를 지정된 시간동안 캐시할 수 있다.

Access-Control-Expose-Headers
요청한 브라우저에 표기되는 응답 헤더를 지정한다.
기본적으로 CORS 안전 목록 응답 헤더만 노출되지만 이 헤더를 사용해 특정 헤더나 사용자 정의 헤더를 노출할 수 있다.
콤마로 구분된 여러 개의 헤더 이름을 지정할 수 있으며, 와일드카드 *를 사용하여 모든 헤더를 표기하도록 허용할 수 있다.

웹 서버별 CORS 헤더 설정

Apache
.htaccess 파일에 헤더 옵션을 추가한다.

Header set 헤더_옵션 "값"
예 : Header set Access-Control-Allow-Headers "Content-Type"

Nginx
Nginx configuration 파일에 헤더 옵션을 추가한다.

add_header '헤더_옵션' '값';
예 : add_header 'Access-Control-Allow-Headers' 'Content-Type';

Node.js
미들웨어 패키지인 cors를 통해 헤더 옵션을 설정한다.
패키지를 설치한 후, server.js 파일에 해당 패키지를 추가하고 옵션을 설정한다.
JS 객체로 설정하기 때문에 옵션 이름을 조금 다르게 사용한다.

const express = require('express');
const cors = require('cors');
const app = express();

app.use(cors({
  // 옵션 약어: '값'
  // 예
  origin: 'http://example.com'
}));

다른 방법

프록시
클라이언트 측 코드와 동일한 도메인에 요청을 중개할 프록시 서버를 추가하는 방법이다.
서버에서 CORS 헤더를 설정할 수 없는 경우에 적용할 수 있으며, 외부에서는 프록시 서버로 요청을 전송해야 하고, 프록시 서버에서는 받은 요청을 그대로 전달하기 위한 추가적인 처리가 필요하다.

JSONP(JSON with Padding)
서버 측에서 콜백 함수로 감싸진 JSON 데이터로 응답하도록 처리하는 방법이다.
AJAX 요청 대신 대상 도메인의 콜백 함수 이름을 매개변수로 포함한 URL을 참조하는 스크립트를 작성하고, 이 URL이 호출되는 경우 해당 콜백 함수로 감싸진 응답을 전달하여 클라이언트 측에서 응답을 처리하도록 한다.
서버에서 CORS 헤더도, 프록시 서버도 사용할 수 없는 경우에 적용 가능하지만, HTTP GET 요청에 대해서만 작동하며 보안이 보장되지는 않는다.

마무리

사실상 프론트엔드보다는 백엔드 서버 설정에 가까운 내용이지만, 상당히 자주 신경쓰이는 내용이라 단순히 "로컬이 아닌 웹 서버를 사용하면 해결된다"보다는 상세하게 정리해보았다.
프리플라이트 OPTION 요청에 대한 내용부터 웹 서버 설정이 아닌 다른 방법이 있다는 사실은 처음 알게 되어 예상 외로 새로 학습한 내용이 많았다.

0개의 댓글