CORS(교차 출처 리소스 공유)의 비밀을 파헤치고 웹 애플리케이션에서 안전하게 교차 도메인 요청을 활성화하는 방법을 배워보세요. 이 종합 가이드는 기본부터 고급 설정까지 모든 것을 다루어, 다른 출처 간의 원활하고 안전한 통신을 보장합니다.
CORS 완전 정복: 교차 출처 리소스 공유에 대한 종합 가이드
오늘날 서로 연결된 웹 환경에서 애플리케이션은 종종 다른 출처의 리소스에 접근해야 합니다. 바로 이럴 때 교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)가 사용됩니다. CORS는 웹 브라우저가 한 출처(도메인, 프로토콜, 포트)에서 다른 출처로의 요청을 처리하는 방식을 제어하는 중요한 보안 메커니즘입니다. 안전하고 기능적인 웹 애플리케이션을 구축하기 위해 모든 웹 개발자는 CORS를 필수적으로 이해해야 합니다.
동일 출처 정책(Same-Origin Policy)이란 무엇인가?
CORS를 살펴보기 전에, 동일 출처 정책(Same-Origin Policy, SOP)을 이해하는 것이 중요합니다. SOP는 웹 브라우저에 구현된 기본적인 보안 메커니즘입니다. 이 정책의 목적은 한 웹사이트의 악성 스크립트가 다른 웹사이트의 민감한 데이터에 접근하는 것을 방지하는 것입니다. 출처는 프로토콜(예: HTTP 또는 HTTPS), 도메인(예: example.com), 포트 번호(예: 80 또는 443)의 조합으로 정의됩니다. 두 URL이 동일한 프로토콜, 도메인, 포트를 공유하면 동일한 출처를 가진 것으로 간주됩니다.
예시:
http://example.com/app1
와http://example.com/app2
- 동일 출처 (프로토콜, 도메인, 포트가 동일)https://example.com/app1
와http://example.com/app1
- 다른 출처 (프로토콜이 다름)http://example.com:8080/app1
와http://example.com/app1
- 다른 출처 (포트가 다름)http://sub.example.com/app1
와http://example.com/app1
- 다른 출처 (서브도메인이 다름 – 다른 도메인으로 간주)
SOP는 CORS와 같은 특정 조치가 허용하지 않는 한, 스크립트가 다른 출처의 리소스에 접근하는 것을 제한합니다.
CORS가 필요한 이유는 무엇인가?
동일 출처 정책은 보안에 필수적이지만, 때로는 제약이 될 수 있습니다. 많은 현대 웹 애플리케이션은 API나 콘텐츠 전송 네트워크(CDN)와 같은 다른 서버에서 데이터를 가져오는 것에 의존합니다. CORS는 보안을 유지하면서 SOP를 완화하고 합법적인 교차 출처 요청을 허용하는 통제된 방법을 제공합니다.
http://example.com
에 호스팅된 웹 애플리케이션이 http://api.example.net
에 호스팅된 API 서버에서 데이터를 가져와야 하는 시나리오를 생각해 보십시오. CORS가 없다면 브라우저는 SOP 때문에 이 요청을 차단할 것입니다. CORS는 API 서버가 어떤 출처가 자신의 리소스에 접근할 수 있는지 명시적으로 지정할 수 있게 하여 웹 애플리케이션이 올바르게 작동하도록 합니다.
CORS의 작동 방식: 기본
CORS는 클라이언트(브라우저)와 서버 간에 교환되는 일련의 HTTP 헤더를 통해 작동합니다. 서버는 이 헤더를 사용하여 요청된 리소스에 접근이 허용되는지 여부를 브라우저에 알립니다. 관련된 핵심 HTTP 헤더는 Access-Control-Allow-Origin
입니다.
시나리오 1: 단순 요청(Simple Request)
"단순 요청"은 특정 기준(예: Content-Type
헤더가 application/x-www-form-urlencoded
, multipart/form-data
, 또는 text/plain
중 하나)을 충족하는 GET, HEAD, POST 요청입니다. 이 경우, 브라우저는 요청을 서버로 직접 보내고, 서버는 Access-Control-Allow-Origin
헤더와 함께 응답합니다.
클라이언트 요청 (http://example.com에서):
GET /data HTTP/1.1
Host: api.example.net
Origin: http://example.com
서버 응답 (http://api.example.net에서):
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://example.com
Content-Type: application/json
{
"data": "서버에서 온 데이터"
}
이 예에서 서버는 Access-Control-Allow-Origin: http://example.com
으로 응답하여 http://example.com
에서의 요청이 허용됨을 나타냅니다. 만약 요청의 출처가 Access-Control-Allow-Origin
헤더의 값과 일치하지 않거나 헤더가 없는 경우, 브라우저는 응답을 차단하고 클라이언트 측 스크립트가 데이터에 접근하는 것을 막습니다.
시나리오 2: 프리플라이트 요청(Preflight Request) (복잡한 요청의 경우)
PUT, DELETE와 같은 HTTP 메서드를 사용하거나 사용자 지정 헤더가 있는 더 복잡한 요청의 경우, 브라우저는 HTTP OPTIONS 메서드를 사용하여 "프리플라이트" 요청을 수행합니다. 이 프리플라이트 요청은 실제 요청을 보내기 전에 서버에 허가를 요청합니다. 서버는 허용되는 메서드, 헤더, 출처를 지정하는 헤더로 응답합니다.
클라이언트 프리플라이트 요청 (http://example.com에서):
OPTIONS /data HTTP/1.1
Host: api.example.net
Origin: http://example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
서버 응답 (http://api.example.net에서):
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://example.com
Access-Control-Allow-Methods: GET, PUT, DELETE
Access-Control-Allow-Headers: X-Custom-Header, Content-Type
Access-Control-Max-Age: 3600
헤더 설명:
Access-Control-Allow-Origin: http://example.com
-http://example.com
에서의 요청이 허용됨을 나타냅니다.Access-Control-Allow-Methods: GET, PUT, DELETE
- 교차 출처 요청에 허용되는 HTTP 메서드를 지정합니다.Access-Control-Allow-Headers: X-Custom-Header, Content-Type
- 실제 요청에서 허용되는 사용자 지정 헤더를 나열합니다.Access-Control-Max-Age: 3600
- 프리플라이트 응답이 브라우저에 의해 캐시될 수 있는 시간(초)을 지정합니다. 이는 프리플라이트 요청의 수를 줄이는 데 도움이 됩니다.
서버의 프리플라이트 응답이 요청이 허용됨을 나타내면, 브라우저는 실제 요청을 진행합니다. 그렇지 않으면 브라우저는 요청을 차단합니다.
클라이언트 실제 요청 (http://example.com에서):
PUT /data HTTP/1.1
Host: api.example.net
Origin: http://example.com
X-Custom-Header: some-value
Content-Type: application/json
{
"data": "업데이트할 데이터"
}
서버 응답 (http://api.example.net에서):
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://example.com
Content-Type: application/json
{
"status": "데이터가 성공적으로 업데이트되었습니다"
}
주요 CORS 헤더
다음은 이해해야 할 주요 CORS 헤더에 대한 설명입니다:
Access-Control-Allow-Origin
: 이 헤더는 가장 기본적인 헤더입니다. 리소스에 접근할 수 있는 출처를 지정합니다. 가능한 값은 다음과 같습니다:- 특정 출처 (예:
http://example.com
). *
(와일드카드): 이 값은 모든 출처의 요청을 허용합니다. 민감한 데이터가 관련된 경우 보안을 위협할 수 있으므로 주의해서 사용해야 합니다. 일반적으로 프로덕션 환경에서는 피해야 합니다.
- 특정 출처 (예:
Access-Control-Allow-Methods
: 이 헤더는 교차 출처 요청에 허용되는 HTTP 메서드(예: GET, POST, PUT, DELETE)를 지정합니다. 프리플라이트 응답에 사용됩니다.Access-Control-Allow-Headers
: 이 헤더는 교차 출처 요청에서 허용되는 사용자 지정 헤더를 나열합니다. 이 또한 프리플라이트 응답에 사용됩니다.Access-Control-Allow-Credentials
: 이 헤더는 서버가 교차 출처 요청에 자격 증명(예: 쿠키, 인증 헤더)을 포함하는 것을 허용하는지 여부를 나타냅니다. 자격 증명을 보내야 하는 경우true
로 설정해야 합니다. 클라이언트 측에서도 XMLHttpRequest 객체에withCredentials = true
를 설정해야 합니다.Access-Control-Expose-Headers
: 기본적으로 브라우저는 클라이언트 측 스크립트에 제한된 응답 헤더(예:Cache-Control
,Content-Language
,Content-Type
,Expires
,Last-Modified
,Pragma
)만 노출합니다. 다른 헤더를 노출하려면Access-Control-Expose-Headers
헤더에 해당 헤더들을 나열해야 합니다.Access-Control-Max-Age
: 이 헤더는 브라우저가 프리플라이트 요청을 캐시할 수 있는 최대 시간(초)을 지정합니다. 값이 길수록 프리플라이트 요청 수가 줄어들어 성능이 향상됩니다.
다양한 서버 측 언어에서의 CORS
CORS를 구현하는 것은 일반적으로 서버 측 애플리케이션이 적절한 CORS 헤더를 보내도록 구성하는 것을 포함합니다. 다음은 다양한 언어 및 프레임워크에서 이를 수행하는 방법에 대한 예입니다:
Node.js와 Express
cors
미들웨어 패키지를 사용할 수 있습니다:
const express = require('express');
const cors = require('cors');
const app = express();
// 모든 출처에 대해 CORS 허용 (프로덕션 환경에서는 주의해서 사용)
app.use(cors());
// 또는 특정 출처에 대해 CORS 설정
// app.use(cors({
// origin: 'http://example.com'
// }));
app.get('/data', (req, res) => {
res.json({ message: '모든 출처에 대해 CORS가 활성화되었습니다!' });
});
app.listen(3000, () => {
console.log('서버가 3000번 포트에서 실행 중입니다');
});
Python과 Flask
Flask-CORS
확장을 사용할 수 있습니다:
from flask import Flask
from flask_cors import CORS
app = Flask(__name__)
CORS(app)
# 또는 특정 출처에 대해 CORS 설정
# CORS(app, resources={r"/api/*": {"origins": "http://example.com"}})
@app.route("/data")
def hello():
return {"message": "모든 출처에 대해 CORS가 활성화되었습니다!"}
if __name__ == '__main__':
app.run(debug=True)
Java와 Spring Boot
애노테이션이나 구성 클래스를 사용하여 Spring Boot 애플리케이션에서 CORS를 구성할 수 있습니다:
애노테이션 사용:
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@CrossOrigin(origins = "http://example.com") // http://example.com에서의 요청 허용
public class DataController {
@GetMapping("/data")
public String getData() {
return "http://example.com에 대해 CORS가 활성화되었습니다!";
}
}
구성 사용:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/data")
.allowedOrigins("http://example.com") // http://example.com에서의 요청 허용
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*");
}
}
PHP
"http://example.com에 대해 CORS가 활성화되었습니다!");
echo json_encode($data);
?>
CORS와 보안 고려 사항
CORS가 교차 출처 요청을 가능하게 하지만, 안전하게 구현하는 것이 중요합니다. 다음은 몇 가지 중요한 고려 사항입니다:
- 프로덕션 환경에서
Access-Control-Allow-Origin
에*
사용을 피하세요: 이는 모든 출처의 요청을 허용하여 보안 위험이 될 수 있습니다. 대신, 리소스에 접근이 허용된 출처를 명시적으로 지정하세요. - 서버 측에서
Origin
헤더를 검증하세요: CORS 구성을 처리하는 프레임워크를 사용하더라도, 서버 측에서Origin
헤더를 검증하여 요청이 예상된 출처에서 오는지 확인하는 것이 좋습니다. Access-Control-Allow-Credentials
에 유의하세요: 자격 증명(예: 쿠키, 인증 헤더)을 사용하는 경우, 서버 측에서Access-Control-Allow-Credentials: true
를 설정하고 클라이언트 측에서withCredentials = true
를 설정해야 합니다. 그러나Access-Control-Allow-Credentials
가true
로 설정된 경우Access-Control-Allow-Origin: *
를 사용하는 것은 허용되지 않습니다. 허용된 출처를 명시적으로 지정해야 합니다.Access-Control-Allow-Methods
와Access-Control-Allow-Headers
를 올바르게 구성하세요: 애플리케이션이 올바르게 작동하는 데 필요한 HTTP 메서드와 헤더만 허용하세요. 이는 공격 표면을 줄이는 데 도움이 됩니다.- HTTPS를 사용하세요: 전송 중인 데이터를 보호하기 위해 웹 애플리케이션과 API에 항상 HTTPS를 사용하세요.
CORS 문제 해결
CORS 문제는 디버깅하기 까다로울 수 있습니다. 다음은 몇 가지 일반적인 문제와 해결 방법입니다:
- "No 'Access-Control-Allow-Origin' header is present on the requested resource": 이것은 가장 흔한 CORS 오류입니다. 서버가 응답에
Access-Control-Allow-Origin
헤더를 보내지 않고 있음을 의미합니다. 서버 측 구성을 다시 확인하여 헤더가 올바르게 전송되고 있는지 확인하세요. - "Response to preflight request doesn't pass access control check: It does not have HTTP ok status": 이 오류는 프리플라이트 요청이 실패했음을 나타냅니다. 이는 서버가 OPTIONS 요청을 처리하도록 구성되지 않았거나,
Access-Control-Allow-Methods
또는Access-Control-Allow-Headers
헤더가 올바르게 구성되지 않은 경우 발생할 수 있습니다. - "The value of the 'Access-Control-Allow-Origin' header in the response is not equal to the origin in the request": 이 오류는 요청의 출처가
Access-Control-Allow-Origin
헤더의 값과 일치하지 않음을 의미합니다. 서버가 응답에 올바른 출처를 보내고 있는지 확인하세요. - 브라우저 캐싱: 때때로 브라우저가 CORS 응답을 캐시하여 예기치 않은 동작을 유발할 수 있습니다. 브라우저 캐시를 지우거나 다른 브라우저를 사용하여 문제가 해결되는지 확인해 보세요. 또한
Access-Control-Max-Age
헤더를 사용하여 브라우저가 프리플라이트 응답을 캐시하는 기간을 제어할 수 있습니다.
디버깅 도구:
- 브라우저 개발자 도구: 브라우저의 개발자 도구(보통 F12 키로 접근)를 사용하여 네트워크 요청과 응답을 검사하세요. CORS 관련 헤더와 오류 메시지를 찾아보세요.
- 온라인 CORS 검사기: CORS 구성을 테스트하는 데 도움이 되는 온라인 도구들이 있습니다. 이 도구들은 서버에 요청을 보내고 응답 헤더를 분석하여 잠재적인 문제를 식별합니다.
고급 CORS 시나리오
기본적인 CORS 개념은 비교적 간단하지만, 고려해야 할 몇 가지 고급 시나리오가 있습니다:
- 서브도메인과 CORS: 여러 서브도메인(예:
app1.example.com
,app2.example.com
)의 요청을 허용해야 하는 경우,Access-Control-Allow-Origin
헤더에*.example.com
과 같은 와일드카드를 간단히 사용할 수 없습니다. 대신, 요청의Origin
헤더를 기반으로Access-Control-Allow-Origin
헤더를 동적으로 생성해야 합니다. 보안 취약점을 방지하기 위해 허용된 서브도메인의 화이트리스트에 대해 출처를 검증하는 것을 잊지 마세요. - 여러 출처와 CORS: 여러 특정 출처의 요청을 허용해야 하는 경우,
Access-Control-Allow-Origin
헤더에 여러 출처를 지정할 수 없습니다(예:Access-Control-Allow-Origin: http://example.com, http://another.com
은 유효하지 않음). 대신, 요청의Origin
헤더를 기반으로Access-Control-Allow-Origin
헤더를 동적으로 생성해야 합니다. - CORS와 CDN: API를 제공하기 위해 CDN을 사용하는 경우, CDN이
Origin
헤더를 원본 서버로 전달하고Access-Control-Allow-Origin
헤더를 올바르게 캐시하도록 구성해야 합니다. 특정 지침은 CDN 제공업체의 설명서를 참조하세요.
CORS 모범 사례
안전하고 효율적인 CORS 구현을 보장하려면 다음 모범 사례를 따르세요:
- 최소 권한 원칙: 애플리케이션이 올바르게 작동하는 데 필요한 최소한의 출처, 메서드, 헤더만 허용하세요.
- 정기적인 CORS 구성 검토: 애플리케이션이 발전함에 따라 CORS 구성을 정기적으로 검토하여 여전히 적절하고 안전한지 확인하세요.
- 프레임워크 또는 라이브러리 사용: 내장된 CORS 지원을 제공하는 기존 프레임워크나 라이브러리를 활용하세요. 이는 구현을 단순화하고 오류 위험을 줄일 수 있습니다.
- CORS 위반 모니터링: 잠재적인 CORS 위반을 감지하고 대응하기 위한 모니터링을 구현하세요.
- 최신 정보 유지: 최신 CORS 사양 및 보안 권장 사항에 대한 정보를 계속 업데이트하세요.
결론
CORS는 웹 애플리케이션에서 통제된 교차 출처 요청을 가능하게 하는 중요한 보안 메커니즘입니다. CORS의 작동 방식과 올바른 구성 방법을 이해하는 것은 모든 웹 개발자에게 필수적입니다. 이 종합 가이드에 설명된 지침과 모범 사례를 따르면, 다른 출처의 리소스와 원활하게 상호 작용하는 안전하고 기능적인 웹 애플리케이션을 구축할 수 있습니다.
항상 보안을 우선시하고 지나치게 허용적인 CORS 구성을 피하는 것을 기억하세요. CORS 설정의 보안 영향을 신중하게 고려함으로써 애플리케이션과 데이터를 무단 접근으로부터 보호할 수 있습니다.
이 가이드가 CORS에 대한 궁금증을 해소하는 데 도움이 되었기를 바랍니다. 즐거운 코딩 되세요!