CORS A to Z

psi·2025년 3월 6일

CORS (Cross Origin Resources Sharing)
브라우저는 기본적으로 동일 출처 정책(SOP)을 따르며, 다른 출처의 리소스 요청을 제한하는 보안 정책이다.

일반적으로 프로젝트를 진행하면 Front,Back은 서로 다른 출처에서 실행된다.
ex) FE: https://example.com, BE: https://api.example.com
이런 경우 CORS 설정을 해주지 않으면 클라이언트는 서버에 API 요청에 대한 응답을 받을 수 없다.

CORS 요청 종류에 대해 알아보기 전에, CORS 작동 방식에 대해 알아보자.


CORS 정책의 주체는 서버가 아닌, 브라우저이다.
서버는 Access-Control-allow-orign 설정을 통해 특정 출처에 대한 허용을 적용하고, 이를 header 저장하여 요청을 보낸다.
여기서 중요한 점은 해당 설정에 특정 출처가 허용되어 있지않아도 서버는 요청에 대한 응답을 반환한다.
이러한 응답은 브라우저가 받고 header를 확인하여 신뢰할 수 있는 요청인지 판단한다.

단순 요청(Simple Request)

브라우저는 아래 조건을 모두 만족할 때, 단순 요청을 보낸다

  1. HTTP 메서드가 다음 중 하나여야 함:

    GET
    HEAD
    POST
  1. 자동으로 설정되는 헤더 외에 다음 헤더만 설정 가능:

    Accept
    Accept-Language
    Content-Language
    Content-Type (아래 값으로 제한)
    Range (이미지 등의 부분 요청)
  1. Content-Type 헤더는 다음 값만 허용:

    text/plain
    multipart/form-data
    application/x-www-form-urlencoded
  1. XMLHttpRequestUpload 객체에 이벤트 리스너가 등록되어 있지 않아야 함
  2. ReadableStream 객체가 요청에 사용되지 않아야 함

사전 요청(preflight Request)

일반적으로 단순 요청의 조건 중 하나라도 충족하지 않을 경우, 사전요청을 하게된다. 사전 요청을 통해 서버에게 이 요청을 허용하니? 라는 질문을 하고 해당 요청에 대해 서버는 판단 후에 응답을 반환한다.

1. HTTP 메서드가 다음 중 하나인 경우:

PUT
DELETE
PATCH
OPTIONS
기타 비표준 메서드

2. Content-Type 헤더가 다음과 같은 값인 경우:

application/json (가장 흔한 케이스)
application/xml
text/xml
기타 표준이 아닌 MIME 타입
  1. 커스텀 헤더를 포함하는 경우:

    Authorization
    X-Requested-With
    X-API-Key
    기타 사용자 정의 헤더

4. ReadableStream을 요청 페이로드로 사용하는 경우
5. XMLHttpRequestUpload 객체에 이벤트 리스너가 등록된 경우

사전 요청을 하는 이유
앞서 말했듯, CORS 정책의 주체는 '브라우저'다.
예를 들어, 악의적으로 회원정보를 삭제하는 delete 요청을 할 경우
서버는 해당 요청에 대한 처리이후에 응답을 반환하고
브라우저에서 해당 요청의 header를 통해 CORS ERROR를 반환한다 하더라도 이미 서버는 해당 정보를 지운 상태가 된다.
즉, DB에 대한 악의적 요청을 사전 요청을 통해 필터링을 하는것.

자격증명 요청(Credential Request)

자격증명 요청은 보안강화를 위해 몇몇 제약사항을 추가하는 요청이다.
ex). CSRF 공격 대비
클라이언트에서 api 요청에 대해 자격증명 옵션을 추가하게 된다면,
서버측에서는 allowCredentials(true) 옵션을 헤더에 추가해서 자격증명 요청에 대한 처리를 해야한다.
그리고, 모든 옵션에 대한 와일드카드 처리를 할 수 없다.

와일드카드:
출처, 메서드, 헤더 등에 '*' 설정으로 모든 요청을 허용하는 방법

자격증명에 대한 설정을 하고 API 요청을 하게되면, 해당 요청에는 쿠키가 포함되며, 쿠키에는 UUID, 세션ID, JWT 토큰 등이 담기게 된다.

BACK(Springboot) 설정 예시

@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
            .allowedOrigins("https://yourfrontend.com")  // 와일드카드(*) 사용 불가
            .allowedMethods("GET", "POST", "PUT", "DELETE")
            .allowedHeaders("Content-Type", "Authorization")
            .allowCredentials(true)  // 중요: 자격 증명 허용
            .maxAge(3600);
    }
}

FRONT(REACT) 설정 예시

// fetch API 사용 시
fetch('https://api.example.com/user', {
  method: 'GET',
  credentials: 'include',  // 중요: 모든 요청에 쿠키 포함
  headers: {
    'Content-Type': 'application/json'
  }
});

// 또는 동일 출처에만 쿠키 포함
fetch('https://api.example.com/user', {
  credentials: 'same-origin'
});

// axios 사용 시
axios.get('https://api.example.com/user', {
  withCredentials: true  // 쿠키 포함
});

credentials 옵션은 다음 세 가지 값을 가질 수 있습니다:

omit: 자격 증명을 포함하지 않음 (기본값)
same-origin: 동일 출처 요청에만 자격 증명 포함
include: 모든 요청(크로스 오리진 포함)에 자격 증명 포함

profile
사용자 경험을 최우선하며 논리적 문제 해결을 즐기는 개발자

0개의 댓글