SOP(Same-Origin Policy)는 웹 브라우저의 보안 메커니즘으로, 한 도메인(Origin)에서 실행되는 스크립트가 다른 도메인의 리소스에 접근하는 것을 제한한다.
Origin은 프로토콜(예: HTTP, HTTPS), 도메인(예: example.com), 포트(예: 80, 443)의 조합으로 정의된다.
예를 들어,
https://example.com:443
과http://example.com:80
은 서로 다른 Origin이다.
SOP의 목적은 다음과 같다:
데이터 엔지니어링에서는 SOP로 인해 프론트엔드(예: React 앱)에서 백엔드 API(예: REST API)로의 요청이 차단될 수 있다.
예를 들어,
https://frontend.example.com
에서https://api.example.com
으로 데이터를 요청하면 SOP에 의해 차단된다.
CORS는 SOP를 완화하여 안전한 크로스-오리진 요청을 허용하는 메커니즘이다.
서버가 특정 HTTP 헤더를 응답에 포함시켜 브라우저가 크로스-오리진 요청을 허용할지 결정한다.
API 호출, 데이터 스트리밍, 클라우드 서비스와의 통신에서 CORS가 필수적이다.
Access-Control-Allow-Origin
헤더로 응답한다.OPTIONS
요청을 보내 서버의 허용 여부를 확인한다.https://frontend.example.com
또는 *
). 와일드카드(*
)는 공개 API에 적합하지만, 인증이 필요한 경우 특정 Origin을 지정해야 한다.true
또는 생략). 와일드카드(*
)와 함께 사용할 수 없다.Authorization, Content-Type
).GET, POST, OPTIONS
).86400
).Content-Length
).Nginx는 웹 서버이자 리버스 프록시로, CORS 헤더를 설정하거나 프록시를 통해 SOP/CORS 문제를 해결할 수 있다. 데이터 엔지니어링에서는 Nginx를 프록시 서버로 사용하여 프론트엔드와 백엔드 간의 통신을 중개하거나, CORS 헤더를 추가하여 크로스-오리진 요청을 허용한다.
다음은 Nginx에서 CORS를 활성화하는 기본 설정 예제이다. 이 설정은 https://frontend.example.com
에서 오는 요청을 허용하며, 인증 정보와 커스텀 헤더를 지원한다.
server {
listen 80;
server_name api.example.com;
location / {
# 프리플라이트 요청 처리
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' 'https://frontend.example.com' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, X-Requested-With' always;
add_header 'Access-Control-Max-Age' 86400;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
# 일반 요청 처리
add_header 'Access-Control-Allow-Origin' 'https://frontend.example.com' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, X-Requested-With' always;
proxy_pass http://localhost:3000; # 백엔드 서버로 프록시
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
if ($request_method = 'OPTIONS')
: 프리플라이트 요청(OPTIONS
)을 처리하며, 필요한 CORS 헤더를 추가하고 204 상태 코드를 반환한다.always
: 비정상 응답(예: 4xx, 5xx)에서도 헤더가 추가되도록 한다.Access-Control-Allow-Credentials: true
: 인증 정보(쿠키, Authorization 헤더)를 포함한 요청을 허용한다.Access-Control-Max-Age: 86400
: 프리플라이트 요청 결과를 24시간 동안 캐싱한다.proxy_pass
: 백엔드 서버(예: Node.js)로 요청을 전달한다.CORS 설정 대신 Nginx를 리버스 프록시로 사용하여 프론트엔드와 백엔드를 동일한 도메인으로 제공하면 SOP 문제를 완전히 우회할 수 있다. 예를 들어, 프론트엔드(https://example.com
)와 백엔드(https://example.com/api
)를 동일한 도메인 아래로 통합한다.
server {
listen 80;
server_name example.com;
# 프론트엔드 정적 파일 제공
location / {
root /var/www/frontend;
try_files $uri /index.html; # SPA(Single Page Application) 지원
}
# 백엔드 API로 프록시
location /api {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
/
)은 정적 파일(예: React 빌드 파일)을 제공한다./api
)은 로컬 백엔드 서버(포트 3000)로 전달된다.example.com
)을 사용하므로 SOP 제한이 적용되지 않는다.여러 도메인에서 API 요청을 허용하려면 Origin 헤더를 동적으로 처리한다.
map $http_origin $cors_header {
default "";
"~^https?://(frontend\.example\.com|app\.example\.com)$" $http_origin;
}
server {
listen 80;
server_name api.example.com;
location / {
add_header 'Access-Control-Allow-Origin' $cors_header always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type' always;
proxy_pass http://localhost:3000;
}
}
map
지시어를 사용하여 허용할 Origin을 정의한다.$http_origin
이 허용 목록에 없으면 빈 문자열로 설정되어 요청이 차단된다.Access-Control-Allow-Origin: *
는 공개 API에만 적합하다. 인증 정보가 포함된 요청에서는 특정 Origin을 지정해야 한다.데이터 엔지니어링에서 TCP 소켓을 통해 데이터를 전송하고, Nginx로 CORS를 처리하는 간단한 예제를 제공한다.
import socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('localhost', 3000))
server_socket.listen(1)
print("서버가 시작되었습니다...")
conn, addr = server_socket.accept()
print(f"연결된 클라이언트: {addr}")
data = conn.recv(1024).decode()
print(f"수신된 메시지: {data}")
conn.send("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n서버 응답".encode())
conn.close()
server_socket.close()
server {
listen 80;
server_name api.example.com;
location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' 'https://frontend.example.com' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type' always;
add_header 'Access-Control-Max-Age' 86400;
return 204;
}
add_header 'Access-Control-Allow-Origin' 'https://frontend.example.com' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
proxy_pass http://localhost:3000;
}
}
SOP와 CORS는 웹 애플리케이션의 보안과 데이터 접근을 관리하는 핵심 메커니즘이다. 데이터 엔지니어링에서는 API 호출, 데이터 스트리밍, 클라우드 통신에서 CORS 설정이 필수적이다. Nginx를 사용하면 CORS 헤더를 추가하거나 프록시를 통해 SOP를 우회할 수 있다. 올바른 설정을 통해 보안과 효율성을 모두 확보할 수 있으며, 데이터 파이프라인의 안정성을 높일 수 있다.