HTTP CORS
2025/03/03
n°68
category : HTTP
☼
2025/03/03
n°68
category : HTTP
웹 보안 정책: 동일 출처 정책(Same-Origin Policy, SOP)
다른 출처(origin)에서 실행되는 스크립트를 방지한다.
하지만 현실적으로 브라우저의 도메인과 백엔드 서버의 origin이 다른 경우는 흔하다. 이를 해결하기 위해 CORS(Cross-Origin Resource Sharing)가 도입되었다.
동일 출처 정책을 우회하여 허용된 외부 도메인에서 API 요청을 가능하게 함.
안전한 방식으로 웹 애플리케이션이 타 도메인 리소스에 접근할 수 있도록 허용.
REST API 및 클라이언트-서버 구조에서 필수적인 보안 기능.
사실 가장 중요한 점은, CORS 요청 제한은 브라우저에서 실행된다는 점이다. 오늘날의 프론트 개발 영역이 서버사이드와 클라이언트가 혼합된 상황이므로, CORS 정책을 그저 서버 개발자들이 해주어야 할 일로 떠넘기기엔 부족한 것 같다. (점차 서버의 코드를 더 많이 만질 일이 생기지 않을까도 생각이 들기에.)
CORS 설정은 서버에서 CORS 관련 헤더를 적절히 설정하여 특정 출처에서의 요청을 허용한다.
CORS 요청은 크게 단순 요청(Simple Request)과 사전 요청(Preflight 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.comPUT, 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: 86400CORS는 서버가 응답 헤더를 설정함으로써 적용된다. 주요 헤더는 다음과 같다.
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에 영향을 줄 수 있는 부분은
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 뿐만 아니라 보안에서도 사용된다
...