CORS: Cross-Origin Resource Sharing
웹 보안 정책: 동일 출처 정책(Same-Origin Policy, SOP)
다른 출처(origin)에서 실행되는 스크립트를 방지한다.
하지만 현실적으로 브라우저의 도메인과 백엔드 서버의 origin이 다른 경우는 흔하다. 이를 해결하기 위해 CORS(Cross-Origin Resource Sharing)가 도입되었다.
CORS
CORS의 역할
동일 출처 정책을 우회하여 허용된 외부 도메인에서 API 요청을 가능하게 함.
안전한 방식으로 웹 애플리케이션이 타 도메인 리소스에 접근할 수 있도록 허용.
REST API 및 클라이언트-서버 구조에서 필수적인 보안 기능.
사실 가장 중요한 점은, CORS 요청 제한은 브라우저에서 실행된다는 점이다. 오늘날의 프론트 개발 영역이 서버사이드와 클라이언트가 혼합된 상황이므로, CORS 정책을 그저 서버 개발자들이 해주어야 할 일로 떠넘기기엔 부족한 것 같다. (점차 서버의 코드를 더 많이 만질 일이 생기지 않을까도 생각이 들기에.)
CORS 설정은 서버에서 CORS 관련 헤더를 적절히 설정하여 특정 출처에서의 요청을 허용한다.
CORS의 동작 방식
Preflight 요청과 단순 요청
CORS 요청은 크게 단순 요청(Simple Request)과 사전 요청(Preflight Request)으로 나뉜다.
1. 단순 요청 (Simple Request)
GET
,HEAD
,POST
메서드만 사용 가능.Content-Type
은application/x-www-form-urlencoded
,multipart/form-data
,text/plain
만 허용.추가적인 CORS 검증 없이 즉시 서버에 요청을 보냄.
GET /data HTTP/1.1
Host: api.example.com
Origin: https://example.com
서버가 CORS를 허용한다면, 응답 헤더에 다음과 같이 포함된다.
Access-Control-Allow-Origin: https://example.com
2. Preflight 요청 (OPTIONS 요청)
PUT
,DELETE
,PATCH
등의 메서드 사용.Authorization
,Content-Type: application/json
같은 커스텀 헤더 포함 시.먼저
OPTIONS
메서드로 서버에 해당 요청을 허용하는지 확인한 후 본 요청을 보냄.
OPTIONS /data HTTP/1.1
Host: api.example.com
Origin: https://example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type
서버가 CORS를 허용하면 다음과 같이 응답한다.
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: PUT, DELETE, PATCH
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: 86400
CORS 헤더 설정
CORS는 서버가 응답 헤더를 설정함으로써 적용된다. 주요 헤더는 다음과 같다.
Access-Control-Allow-Origin: https://example.com | "*" 와일드 카드
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 86400l
Access-Control-Allow-Origin
허용할 출처(origin)를 지정한다. 모든 출처에서 요청을 허용하려면
*
를 사용한다.하지만,
*
를 사용할 경우Authorization
헤더와Credentials
요청을 허용할 수 없다.
Access-Control-Allow-Methods
: 허용할 HTTP 메서드를 지정한다.Access-Control-Allow-Headers
: 클라이언트가 사용할 수 있는 요청 헤더를 지정한다.Access-Control-Allow-Credentials
: 쿠키 및 인증 정보를 포함한 요청을 허용할지 결정한다.허용하려면
true
로 설정해야 한다.주의할 점은,
Access-Control-Allow-Origin
에*
을 설정하면Credentials
가 활성화되지 않는다.
Access-Control-Max-Age
: Preflight 요청을 캐싱하여 불필요한 OPTIONS 요청을 줄인다.
CORS 정책 적용 방법
서버
요청을 받고 응답을 보낸다. 주의해야할 점은, CORS에서 요청 헤더를 검증하는 것이다.
서버에서 CORS에 영향을 줄 수 있는 부분은
Preflight부터 요청 헤더에서 허용된 Origin, Method, Credentials 헤더의 적절한 설정
첫 요청을 통과하고 난 후, 서버에서 응답을 보낼 때 적절한 CORS 헤더의 설정이다.
최근에 했던 프로젝트에서 클라이언트로 보내는 응답 객체에 적절한 헤더 설정을 하지 않아 헤맸던 경험이 있다.
// 서버: 요청을 받고 CORS 검증
const authHeader = request.headers.get("Authorization");
const isAuthorized = authHeader === `Bearer ${env.WORKER_API_KEY}`;
const corsHeaders = {
"Access-Control-Allow-Origin": "null",
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
// true로 해주어야 authorization을 받을 수 있다.
"Access-Control-Allow-Credentials": "true",
};
// authorization을 통해 요청 권한 없음을 돌려 준다
if(!isAuthorized) return
// NOTE: CORS의 Preflight 요청 처리
if (request.method === "OPTIONS") {
return new Response(null, {
status: 204,
headers: {
...corsHeaders
},
});
}
// NOTE: 적절한 CORS 관련 헤더를 포함하여 브라우저로 응답 반환
return new Response(JSON.stringify({
success: true,
headers: {
...corsHeaders,
}
})....
클라이언트
요청을 보내고 응답을 받는다. 실행 환경이 브라우저든 서버이든, 요청을 보내는 쪽은 클라이언트다. 그러나 CORS는 브라우저에서 적용되므로, 클라이언트 측에서도 신경 써야 할 설정이 있다.
단순한 요청이라면 클라이언트에서 추가적인 조치를 할 필요가 없을 수 있지만, Credential, Cookie (+HTTP-Only Cookie), Authorization과 같은 보안 관련 헤더가 포함될 경우 CORS 설정이 필요하다. 특히, 서버의 CORS 정책과 클라이언트의 요청 옵션이 일치하지 않으면 요청이 차단될 수 있어 올바르게 설정해야 한다.
// 클라이언트: 브라우저에서 실행하는 요청 함수의 적절한 헤더에 CORS, 보안 설정 포함
async function fetchData() {
try {
const response = await fetch("https://api.example.com/data", {
method: "POST", // 허용된 요청 방식 지정
credentials: "include", // 쿠키 및 인증 정보를 포함하여 요청
headers: {
"Content-Type": "application/json",
// 요청 본문의 데이터 유형을 JSON으로 지정
// NOTE: 인증 토큰은 CORS 뿐만 아니라 보안에서도 사용된다
"Authorization": "Bearer YOUR_ACCESS_TOKEN",
// 보안 토큰을 추가하여 인증
},
// ...
});
// 서버는 올바른 CORS 헤더를 포함하여 클라이언트로 전달.
// 이 함수가 브라우저 실행될 때 적절하지 않은 헤더는 에러가 발생할 수 있다.
const data = await response.json();
console.log("Response Data:", data);
} catch (error) {
console.error("Error fetching data:", error);
}
}
credentials 요청 헤더와 CORS
클라이언트가 보안 관련 정보를 포함하여 요청하려면 fetch
의 credentials
옵션을 적절히 설정해야 한다. 이 옵션은 요청에 쿠키, HTTP 인증 정보, Authorization 헤더를 포함할지 여부를 결정한다.
옵션 값 | 설명 |
---|---|
| 인증 정보를 절대 포함하지 않음 |
| 같은 출처 요청일 경우에만 인증 정보를 포함. (기본값). |
| 모든 요청에 대해 인증 정보를 포함 (서버가 |
1. CORS 요청 시 주의할 점
credentials: "include"
를 사용하려면 서버에서Access-Control-Allow-Credentials: true
헤더를 설정해야 한다.credentials: "same-origin"
은 동일 출처 요청에만 쿠키 및 인증 정보를 포함하며, CORS 요청에는 적용되지 않는다.CORS 응답에서
Access-Control-Allow-Origin: "*"
을 설정하면credentials: "include"
를 사용할 수 없다.특정 도메인을 지정해야 한다:
Access-Control-Allow-Origin: https://your-client.com
서버사이드의 api를 개발하면서 Preflight CORS 설정을 자주 잊었던 것 같다. 응답과 마찬가지로, 첫 단계에서 처리해줘야 한다.
2. 브라우저에서 CORS 요청 차단
CORS를 무시하려면, 브라우저에서 CORS를 비활성화할 수 있다.
chrome.exe --disable-web-security --user-data-dir="C:\chrome_dev"
사실 이건 권장되지 않는다. 서버에서 CORS 설정을 수정하는 것이 바람직하다. 프론트엔드 개발자가 서버 CORS 정책을 직접 설정을 하는 경우는 MSW, Mock 서버의 설정이 대부분일 것이다. 극단적인 브라우저 글로벌 설정 변경보다, 위에서 살펴본 CORS 헤더를 Mock 서버에 적절히 추가하는 것이 알맞다.