CORS 이해와 예시(postman 요청도 막아보자)

CORS란?

CORS(Cross-Origin Resource Sharing)는 웹 브라우저가 동일 출처 정책(Same-Origin Policy)을 우회하여, 한 출처(origin)에서 실행 중인 웹 애플리케이션이 다른 출처의 자원에 접근할 수 있도록 하는 메커니즘입니다. CORS는 추가 HTTP 헤더를 사용하여 브라우저와 서버 간의 특정 교차 출처 요청을 안전하게 허용합니다.

왜 CORS가 필요한가?

기본적으로 웹 브라우저는 보안상의 이유로, 한 출처에서 로드된 웹 페이지가 다른 출처의 리소스에 임의로 접근하는 것을 방지합니다. (악성 웹 페이지에 접속할 경우 해당 현재 내가 로그인해 놓은 웹 페이지에 악성 요청을 보낼 수 있기 때문!)이를 동일 출처 정책(SOP: same origin policy)이라고 합니다. 그러나 현대 웹 애플리케이션에서는 다양한 출처의 자원(예: API 엔드포인트, CDN 등)에 접근할 필요가 있습니다. 이때 CORS가 사용됩니다.

CORS의 작동 원리

CORS는 서버가 특정 출처의 요청을 허용하도록 명시적으로 허용하는 HTTP 헤더를 포함하여 교차 출처 요청을 처리합니다. CORS 요청은 다음과 같이 두 가지 유형으로 나뉩니다:

  1. 단순 요청(Simple Request): 특정 조건을 만족하는 HTTP 요청으로, 브라우저는 자동으로 요청을 전송합니다.
  2. 프리플라이트 요청(Preflight Request): 서버에 실제 요청을 보내기 전에, 브라우저가 OPTIONS 메서드를 사용하여 서버가 실제 요청을 허용하는지 확인합니다.

주요 CORS 헤더

서버에서 설정하는 헤더

  1. Access-Control-Allow-Origin: 요청을 허용할 출처를 지정합니다.
    Access-Control-Allow-Origin: https://example.com
    

    또는 모든 출처를 허용하려면:

    Access-Control-Allow-Origin: *
    
  2. Access-Control-Allow-Methods: 허용할 HTTP 메서드를 지정합니다.
    Access-Control-Allow-Methods: GET, POST, PUT, DELETE
    
  3. Access-Control-Allow-Headers: 허용할 HTTP 헤더를 지정합니다.
    Access-Control-Allow-Headers: Content-Type, Authorization
    
  4. Access-Control-Allow-Credentials: 자격 증명(쿠키, 인증 헤더 등)을 포함한 요청을 허용합니다.
    Access-Control-Allow-Credentials: true
    
  5. Access-Control-Expose-Headers: 클라이언트가 접근할 수 있는 응답 헤더를 지정합니다.
    Access-Control-Expose-Headers: Content-Length, X-Kuma-Revision
    
  6. Access-Control-Max-Age: 프리플라이트 요청의 결과를 캐시할 시간(초)을 지정합니다.
    Access-Control-Max-Age: 600
    

CORS 설정 예시 (Express.js)

Express.js에서 CORS를 설정하는 예시는 다음과 같습니다:

1. 기본 설정

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

app.use(cors());

app.get('/data', (req, res) => {
  res.json({ message: 'Hello, world!' });
});

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

2. 특정 출처 허용

const corsOptions = {
  origin: 'https://example.com',
};

app.use(cors(corsOptions));

3. 다양한 옵션 설정

const corsOptions = {
  origin: 'https://example.com',
  methods: 'GET,POST,PUT,DELETE',
  allowedHeaders: 'Content-Type,Authorization',
  credentials: true,
  maxAge: 600,
};

app.use(cors(corsOptions));

위 과정을 통해 특정 origin으로부터의 요청만 허용할 수 있다.

postman 요청 막기

이때 궁금한 점이 생겼다. 만약 cors 설정을 한다면 postman 요청도 막힐 것인가?

테스트 결과 postman의 요청은 막히지 않았다. 그 이유는 Postman은 브라우저와는 별개의 애플리케이션으로, 브라우저의 동일 출처 정책(SOP)에 영향을 받지 않기 때문이라고 한다.

나의 경우 특정한 웹 사이트에 로그인한 후, 해당 웹 사이트의 기능을 활용해야만 api를 호출할 수 있도록 만들고 싶었기 때문에 postman을 이용한 호출은 아래와 같이 막아버렸다.

function isAdminRequest(req, res, next) {
    const adminPageURL = '허용할 웹 사이트 url';

    if (!req.headers.referer || req.headers.referer !== adminPageURL) {
        console.log(new Date().toLocaleString(), '[허용되지 않은 접근 방식] req.headers.referer : ', req.headers.referer);
        return res.status(403).send('허용되지 않은 접근 방식입니다');
    }
    next();
}
app.use(isAdminRequest);

// 아래에 app.use( api 경로 및 디렉토리 설정 )

해당 미들웨어를 통해 request header 옵션 중 referer 값이 허용된 웹 사이트 url로 설정되어 있지 않으면 403에러를 띄우도록 했다.

※ 추가로 발견된 문제점 위 방법을 통해 보안이 강화되었다고 생각했지만 문제가 발생함. Postman에서도 headers에 key, value 값으로 referer, 관리자 웹 페이지 주소만 넣어주면 어떤 사용자도 쉽게 api 호출이 가능하다. 즉, postman에서 간단하게 referer를 조작할 수 있었다.

해당 api를 이용하기 위해서는 특정한 토큰값을 요구하거나 ip 주소 자체에 제한을 두는 방식으로 해당 문제를 해결해야 할 것 같다.

results matching ""

    No results matching ""