개념

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 정책을 설정하는 것이 가장 바람직한 방법이다.
반응형

HTTP의 특징

  • HTTP 프로토콜의 특징이자 약점을 보완하기 위해서 사용한다.

  • HTTP 프로토콜 환경에서 서버는 클라이언트가 누구인지 확인해야 한다. 그 이유는 HTTP 프로토콜이 connectionless, stateless 한 특성이 있기 때문이다.

HTTP(Hypertext Transfer Protocol)는 인터넷상에서 데이터를 주고 받기 위해 서버/클라이언트 모델을 따르는 통신규약을 의미한다. 이 HTTP 프로토콜에는 비연결성(Connectionless)과 비상태성(Stateless)이라는 특징을 가진다. 이는 서버의 자원을 절약하기 위해 모든 사용자의 요청마다 연결과 해제의 과정을 거치기 때문에 연결 상태가 유지되지 않고, 연결 해제 후에 상태 정보가 저장되지 않는다는 것이다.

 

하지만, 이로 인해 사용자를 식별할 수 없어서 같은 사용자가 요청을 여러 번 하더라도 매번 새로운 사용자로 인식하는 단점이 있다. 이렇게 HTTP의 비연결 성과 비상 태성을 보완하여 서버가 클라이언트를 식별하게 해주는 것이 Cookie와 Session이다. 

 

 

Connectionless
  • 클라이언트가 요청을 한 후 응답을 받으면 그 연결을 끊어 버리는 특징

  • HTTP는 먼저 클라이언트가 request를 서버에 보내면, 서버는 클라이언트에게 요청에 맞는 response를 보내고 접속을 끊는 특성이 있다.

Stateless
  • 통신이 끝나면 상태를 유지하지 않는 특징

  • 연결을 끊는 순간 클라이언트와 서버의 통신이 끝나며 상태 정보는 유지하지 않는 특성이 있다.


쿠키 (Cookie)

 

  • 쿠키는 클라이언트(브라우저) 로컬에 저장되는 키와 값이 들어있는 작은 데이터 파일

  • 사용자 인증이 유효한 시간을 명시할 수 있으며, 유효 시간이 정해지면 브라우저가 종료되어도 인증이 유지된다는 특징이 있다.

  • 쿠키는 클라이언트의 상태 정보를 로컬에 저장했다가 참조한다.

  • 클라이언트에 300개까지 쿠키 저장 가능, 하나의 도메인당 20개의 값만 가질 수 있음, 하나의 쿠키값은 4KB까지 저장한다.

  • Response Header에 Set-Cookie 속성을 사용하면 클라이언트에 쿠키를 만들 수 있다.

  • 쿠키는 사용자가 따로 요청하지 않아도 브라우저가 Request시에 Request Header를 넣어서 자동으로 서버에 전송한다.

  • 이름, 값, 만료 날짜/시간(쿠키 저장기간), 경로 정보 등이 들어있다.

  • 서버가 가지고 있는 것이 아니라 사용자에게 저장되기 때문에, 임의로 고치거나 지울 수 있고, 가로채기도 쉬워 보안이 취약하다.

 

쿠키의 구성 요소

  • 이름 : 각각의 쿠키를 구별하는 데 사용되는 이름

  • 값 : 쿠키의 이름과 관련된 값

  • 유효시간 : 쿠키의 유지시간

  • 도메인 : 쿠키를 전송할 도메인

  • 경로 : 쿠키를 전송할 요청 경로

 

쿠키의 동작 프로세스

 

  1. 클라이언트가 페이지를 요청

  2. 서버에서 쿠키를 생성

  3. HTTP 헤더에 쿠키를 포함시켜 응답

  4. 브라우저가 종료되어도 쿠키 만료 기간이 있다면 클라이언트에서 보관하고 있음

  5. 같은 요청을 할 경우 HTTP 헤더에 쿠키를 함께 보냄

  6. 서버에서 쿠키를 읽어 이전 상태 정보를 변경할 필요가 있을 때 쿠키를 업데이트하여 변경된 쿠키를 HTTP 헤더에 포함시켜 응답

 

 


세션 (Session)

 

 

  • 세션은 쿠키를 기반하고 있지만, 사용자 정보 파일을 브라우저에 저장하는 쿠키와 달리 세션은 서버 측에서 관리한다.

  • 서버에서는 클라이언트를 구분하기 위해 세션 ID를 부여하며 웹 브라우저가 서버에 접속해서 브라우저를 종료할 때까지 인증 상태를 유지한다.

  • 접속 시간에 제한을 두어 일정 시간 응답이 없다면 정보가 유지되지 않게 설정이 가능하다.

  • 사용자에 대한 정보를 서버에 두기 때문에 쿠키보다 보안에 좋지만, 사용자가 많아질수록 서버 메모리를 많이 차지하게 된다.

  • 접속자 수가 많은 웹 사이트인 경우 서버에 과부하를 주게 되므로 성능 저하의 요인이 된다.

  • 클라이언트가 Request를 보내면, 해당 서버의 엔진이 클라이언트에게 유일한 ID를 부여하는 데 이것이 세션 ID다.

 

세션 동작 프로세스

 

  1. 클라이언트가 서버에 요청했을 때, 필요에 따라 세션에 클라이언트에 대한 데이터를 저장하고 세션 아이디를 응답을 통해 발급해준다. (브라우저 단에서 관리될 수 있도록 쿠키로 발급하는 게 일반적인 구조)

  2. 클라이언트는 발급받은 세션 아이디를 쿠키로 저장한다. (ex. JSESSIONID)

  3. 클라이언트는 다시 서버에 요청할 때, 세션 아이디를 서버에 전달하여 상태 정보를 서버가 활용할 수 있도록 해준다.

 

  Cookie Session
저장위치 클라이언트 서버
저장형식 text Object
리소스 클라이언트의 리소스 서버의 리소스
용량제한 도메인당 20개, 1쿠키당 4KB 제한 없음
만료시점 쿠키 저장시 설정(설정 없을 시에는 브라우저 종료시 만료) 알 수 없음

 

 

 

 

 

반응형

+ Recent posts