개념

CORS는 Cross-Origin Resource Sharing의 약자로, 서로 다른 출처 간의 리소스 요청을 제어하기 위한 보안 기능이다. 브라우저는 보안상의 이유로 스크립트가 다른 도메인에 있는 리소스에 접근하는 것을 기본적으로 차단한다. 이 기본적인 보안 제한을 ‘동일 출처 정책(Same-Origin Policy)’라고 한다.

CORS는 이러한 동일 출처 정책의 제한을 우회할 수 있도록 서버에서 명시적으로 설정해, 브라우저가 다른 도메인에서 리소스를 요청하는 것을 허용하게 만드는 방법이다.

  • 출처(Origin): 도메인, 프로토콜, 포트를 포함한 것을 의미한다. 예를 들어, http://example.com:3000https://example.com:3000은 서로 다른 출처다.
  • 동일 출처 정책 (Same-Origin Policy): 웹 페이지가 자신과 다른 출처의 리소스를 로드하는 것을 제한하는 브라우저의 보안 정책이다.

 

원인

클라이언트가 웹 서버에 다른 출처의 리소스에 접근하려고 할 때 발생한다. 브라우저가 서버에 요청을 보내고, 서버가 올바른 CORS 헤더를 포함하지 않으면 브라우저에서 요청이 차단된다.

  • 예를 들어, 웹 애플리케이션의 클라이언트가 http://localhost:3000에서 실행 중이고, http://api.example.com으로 API 요청을 보내려고 할 때, 출처가 다르기 때문에 브라우저는 보안 정책에 의해 요청을 차단할 수 있다.

 

동작 방식

CORS 동작 방식은 브라우저와 서버 간의 HTTP 요청 및 응답 헤더를 통해, 서로 다른 출처에서 리소스를 공유하는 과정을 제어하는 것이다.

1. Simple Request(단순 요청)

특정 조건을 만족할 때 preflight 요청을 보내지 않고 바로 요청이 이루어진다. 특정 조건은 아래와 같다.

  • HTTP 메서드는 GET, POST, 또는 HEAD 여야 한다.
  • Content-Type 헤더가 다음과 같은 POST 요청
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain
  • 헤더는 브라우저가 자동으로 추가하는 안전한 헤더여야 하며, 여기에는 다음과 같은 제한이 있다.
    • Accept, Accept-Language, Content-Language, Content-Type (application/x-www-form-urlencoded, multipart/form-data, text/plain 중 하나).

단순 요청에서는 브라우저가 요청을 보낼 때 Origin 헤더를 추가한다. 서버는 응답 시 해당 요청을 허용할지 여부를 결정하고, 다음과 같은 CORS 관련 헤더를 응답에 포함한다.

  • Access-Control-Allow-Origin: 요청을 허용할 출처를 지정( * 또는 특정 도메인).
  • Access-Control-Expose-Headers (선택적): 클라이언트에서 접근 가능한 응답 헤더 목록을 지정.

브라우저는 서버의 응답에 Access-Control-Allow-Origin 헤더가 포함되어 있는지 확인하고, 클라이언트가 해당 출처의 리소스에 접근하는 것을 허용할지 결정한다.

2. Preflight Request(사전 요청)

브라우저가 보안 확인을 위해 실제 요청을 보내기 전에 서버에 미리 허락을 구하는 요청이다. 주로 민감한 요청에 대해 사용되며, 다음과 같은 경우에 발생한다.

  • 요청 메서드가 GET, POST, HEAD가 아닌 다른 메서드인 경우 (PUT, DELETE, OPTIONS 등)
  • 요청 헤더가 안전하지 않은 커스텀 헤더를 포함하는 경우 (예: Authorization, Content-Type이 JSON 등)
  • 요청에 Credentials (쿠키, 인증 토큰 등)이 포함된 경우

프리플라이트 요청은 OPTIONS 메서드를 사용해 서버에 보내진다. 이 요청은 브라우저가 실제 요청을 보내도 안전한지 확인하기 위해 서버에게 허락을 구하는 과정이다. 프리플라이트 요청에서는 다음과 같은 헤더가 사용된다.

Request Header(브라우저 → 서버)

  • Origin: 요청을 보낸 출처.
  • Access-Control-Request-Method: 실제 요청에서 사용하려는 HTTP 메서드.
  • Access-Control-Request-Headers (선택적): 실제 요청에서 사용될 추가 헤더 목록.

서버는 이러한 프리플라이트 요청에 대해 다음과 같은 응답을 보내야 한다.

Response Header(서버 → 브라우저)

  • Access-Control-Allow-Origin: 허용된 출처(* 또는 특정 도메인)
  • Access-Control-Allow-Methods: 클라이언트가 사용할 수 있는 HTTP 메서드 목록 (GET, POST, PUT 등).
  • Access-Control-Allow-Headers: 허용된 헤더 목록 (예: Authorization, Content-Type).
  • Access-Control-Max-Age (선택적): 클라이언트가 프리플라이트 응답을 캐시할 시간 (초 단위).
  • Access-Control-Allow-Credentials: 클라이언트가 쿠키를 전송할 수 있도록 허용

서버가 프리플라이트 요청에 대해 성공적으로 응답하면, 브라우저는 실제 요청을 서버로 전송하게 된다!



CORS 요청 예시

브라우저가 CORS 요청을 보내고, 서버가 응답하는 예시다.

1. Simple Request 예시

  • 클라이언트가 GET 요청을 보내는 경우
    • 여기서 브라우저는 응답에 포함된 Access-Control-Allow-Origin 헤더를 확인하여 클라이언트에서 이 데이터를 사용할 수 있게 한다.
// 요청(client -> Server)
GET /api/data HTTP/1.1
Host: api.example.com
Origin: http://localhost:3000

// 응답(Server -> Client)
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://localhost:3000
Content-Type: application/json

{
  "message": "CORS 요청 성공"
}

2. Preflight Request 예시

// Preflight 요청(client -> Server)
OPTIONS /api/data HTTP/1.1
Host: api.example.com
Origin: http://localhost:3000
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: Authorization

// Preflight 응답(Server -> Client)
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Authorization
Access-Control-Max-Age: 86400
  • Access-Control-Allow-Origin: 해당 요청을 허용할 출처를 명시 (http://localhost:3000).
  • Access-Control-Allow-Methods: 허용 가능한 메서드(GET, POST, PUT, DELETE).
  • Access-Control-Allow-Headers: Authorization 헤더가 허용됨을 명시.
  • Access-Control-Max-Age: 86400초 동안 프리플라이트 응답을 캐시함.

Preflight 요청에 대한 성공적인 응답을 받은 후, 클라이언트는 실제 요청을 보낸다.

// 실제 요청(client -> Server)
DELETE /api/data HTTP/1.1
Host: api.example.com
Origin: http://localhost:3000
Authorization: Bearer token123

// 실제 응답(Server -> Client)
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://localhost:3000
Content-Type: application/json

{
  "message": "리소스가 삭제되었습니다"
}

 

CORS 관련 주요 HTTP 헤더 요약

  1. Access-Control-Allow-Origin: 허용할 출처를 지정 (은 모든 출처를 허용).
  2. Access-Control-Allow-Methods: 클라이언트가 사용할 수 있는 HTTP 메서드를 지정.
  3. Access-Control-Allow-Headers: 클라이언트가 사용할 수 있는 추가 헤더를 지정.
  4. Access-Control-Allow-Credentials: 클라이언트가 쿠키나 인증 정보를 서버로 전송할 수 있도록 허용.
  5. Access-Control-Max-Age: 프리플라이트 요청의 응답을 캐시할 수 있는 시간을 지정 (초 단위).
  6. Access-Control-Expose-Headers: 클라이언트에서 접근 가능한 응답 헤더를 지정.

 

해결 방법

1.서버 측에서 CORS 설정

  • 서버가 올바른 CORS 헤더를 포함하도록 설정하는 것이 가장 일반적인 해결 방법이다.
  • 예를 들어, Node.js/Express 서버에서는 다음과 같이 설정할 수 있다.
const express = require('express');
const cors = require('cors');
const app = express();

app.use(cors()); // 모든 출처에 대해 CORS 허용
// 또는 특정 출처만 허용하고 싶다면:
// app.use(cors({ origin: 'http://localhost:3000' }));

app.get('/api/data', (req, res) => {
  res.json({ message: 'CORS 설정 성공' });
});

app.listen(5000, () => {
  console.log('Server is running on port 5000');
});

2.프록시 서버 사용

  • 클라이언트와 서버 간에 프록시 서버를 두어 요청을 중계함으로써 동일 출처 정책을 회피하는 방법이다.
  • 개발 환겨에서 주로 사용되며, 예를 들어 React에서는 package.json 에 proxy 설정을 추가해 사용할 수 있다.
{
  "proxy": "http://api.example.com"
}

3.브라우저 확장 프로그램 사용(개발 환경)

  • Chrome 등 브라우저에서 제공하는 CORS 해제 확장 프로그램을 사용해 에러를 일시적으로 해결할 수 있다.
  • 하지만, 이 방법은 개발 환경에서만 사용하는 것이 좋다. 보안상 이유로 운영 환경에서는 권장되지 않는다!

4.서버 설정 변경

  • Apache 또는 Nginx 같은 웹 서버를 사용하는 경우, 서버 설정 파일에서 CORS 헤더를 추가해야 한다.
  • Nginx의 경우
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';

Access-Control-Allow-Credentials 사용 시 주의

  • 클라이언트가 쿠키 또는 인증 정보를 서버로 보내기 위해서는 Access-Control-Allow-Credentials: true를 설정하고, Access-Control-Allow-Origin* 대신 특정 출처를 명시해야 한다.


정리

  • CORS는 브라우저 보안 정책으로 인해 발생하는 문제로, 클라이언트가 다른 출처의 리소스에 접근하려고 할 때 발생한다.
  • 서버에서 CORS 헤더를 적절히 설정함으로써 이를 해결할 수 있다.
  • 프록시 서버 사용이나 브라우저 확장 프로그램 같은 해결책이 있지만, 이는 개발 환경에서 주로 사용되며, 운영 환경에서는 서버에서 CORS 정책을 설정하는 것이 가장 바람직한 방법이다.
반응형

+ Recent posts