본문 바로가기
Development/Web

[Web] 크로스 도메인 문제 원인부터 해결책까지 총정리

by 은스타 2022. 9. 23.
반응형
크로스 도메인(Cross Domain) 문제 원인부터 해결책까지 총정리

크로스 도메인(Cross Domain) 완벽 가이드: 문제 원인부터 해결책까지

개요

안녕하세요! 웹 개발을 하다 보면 자주 마주치게 되는 오류 메시지가 있습니다. "Access to XMLHttpRequest has been blocked by CORS policy" - 이 빨간색 오류 메시지를 본 적이 있다면, 여러분은 이미 크로스 도메인 문제를 경험한 것입니다.

이 글에서는 크로스 도메인이 무엇인지부터 해결 방법까지 초보자도 쉽게 이해할 수 있도록 설명해 드리겠습니다. 프론트엔드와 백엔드 분리 환경, API 통합, 마이크로서비스 아키텍처 등 실제 개발 환경에서 발생하는 다양한 크로스 도메인 이슈와 그 해결책을 상세히 다룹니다.
목차
1. 크로스 도메인이란?
2. 동일 출처 정책(Same-Origin Policy)
3. CORS(Cross-Origin Resource Sharing)란?
4. 크로스 도메인 문제 해결 방법
5. 자주 묻는 질문(FAQ)

#1. 크로스 도메인이란?
크로스 도메인(Cross Domain)은 웹 애플리케이션에서 현재 접속한 도메인과 다른 도메인으로 데이터를 요청할 때 발생하는 보안 이슈를 말합니다. 쉽게 말해, A라는 웹사이트에서 B라는 웹사이트의 데이터를 가져오려고 할 때 브라우저가 이를 제한하는 것입니다.
💡 크로스 도메인 예시
https://mywebsite.com에서 https://api.othersite.com의 데이터를 요청하는 경우
http://localhost:3000에서 https://api.example.com으로 API 호출을 시도하는 경우

이런 상황에서 브라우저는 기본적으로 '동일 출처 정책(Same-Origin Policy)'이라는 보안 정책에 따라 이러한 요청을 차단합니다.
1) 크로스 도메인 이슈가 발생하는 주요 상황
크로스 도메인 이슈는 다음과 같은 상황에서 주로 발생합니다:

프론트엔드와 백엔드의 분리: React, Vue, Angular 등의 프론트엔드 프레임워크와 별도의 백엔드 API 서버를 사용할 때
예: React 앱은 https://app.example.com에서 실행되고, API 서버는 https://api.example.com에 있는 경우

API 통합: 다양한 외부 API(소셜 미디어, 결제, 지도 등)를 웹 애플리케이션에 통합할 때
예: 자사 웹사이트에서 유튜브 API, 페이스북 API 등을 호출하는 경우

마이크로서비스 아키텍처: 여러 서비스가 서로 다른 도메인에서 실행될 때
예: 인증 서비스, 상품 서비스, 결제 서비스가 각각 다른 도메인에 있는 경우

로컬 개발 환경: 로컬 개발 서버에서 실제 API 서버로 요청을 보낼 때
예: http://localhost:3000에서 실제 API 서버로 요청을 보내는 경우

#2. 동일 출처 정책(Same-Origin Policy)
동일 출처 정책(Same-Origin Policy)은 웹 브라우저가 실행 중인 웹 애플리케이션이 다른 출처(도메인)의 리소스와 상호작용하는 것을 제한하는 중요한 보안 메커니즘입니다. 이 정책은 잠재적으로 악의적인 웹사이트가 사용자의 개인 데이터에 접근하는 것을 방지합니다.
1) 출처(Origin)의 정의
출처는 다음 세 가지 요소의 조합으로 결정됩니다:

프로토콜: http, https 등
호스트(도메인): example.com, subdomain.example.com 등
포트: 80, 443, 3000 등

예를 들어, https://example.com:443의 출처는 이 세 가지 요소의 조합입니다.
. . . . .
2) 같은 출처 vs 다른 출처 예시
URL A URL B 같은 출처? 이유
https://example.com/page1 https://example.com/page2 ✅ 예 프로토콜, 호스트, 포트(443 기본값) 동일
https://example.com http://example.com ❌ 아니오 프로토콜 다름 (https vs http)
https://example.com https://sub.example.com ❌ 아니오 호스트 다름
https://example.com https://example.com:8080 ❌ 아니오 포트 다름 (443 vs 8080)

#3. CORS(Cross-Origin Resource Sharing)란?
CORS(Cross-Origin Resource Sharing, 교차 출처 리소스 공유)는 동일 출처 정책의 제한을 완화하여, 다른 출처에서도 자원을 안전하게 공유할 수 있게 해주는 메커니즘입니다. 브라우저와 서버 간의 통신을 통해 어떤 도메인에서 접근할 수 있는지 제어합니다.
1) CORS의 작동 방식
(1) 단순 요청(Simple Request)
특정 조건을 만족하는 요청은 바로 전송됩니다:

① GET, HEAD, POST 메서드 중 하나를 사용
② 허용된 헤더만 사용
③ Content-Type이 application/x-www-form-urlencoded, multipart/form-data, text/plain 중 하나
(2) 예비 요청(Preflight Request)
단순 요청이 아닌 경우, 브라우저는 실제 요청 전에 OPTIONS 메서드를 사용해 서버에 권한을 확인합니다:

① 브라우저가 자동으로 수행함
② 서버가 허용하는 메서드와 헤더를 확인
③ 서버는 적절한 CORS 헤더를 포함한 응답을 반환
. . . . .
2) CORS 관련 주요 HTTP 헤더
서버에서 설정하는 주요 CORS 헤더:
(1) Access-Control-Allow-Origin
어떤 도메인에서 리소스에 접근할 수 있는지 지정합니다.
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Origin: * // 모든 도메인 허용
(2) Access-Control-Allow-Methods
허용되는 HTTP 메서드를 지정합니다.
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
(3) Access-Control-Allow-Headers
허용되는 HTTP 헤더를 지정합니다.
Access-Control-Allow-Headers: Content-Type, Authorization
(4) Access-Control-Allow-Credentials
쿠키 포함 여부를 지정합니다 (true/false).
Access-Control-Allow-Credentials: true
(5) Access-Control-Max-Age
예비 요청 결과를 캐시하는 시간(초)을 지정합니다.
Access-Control-Max-Age: 86400 // 24시간

#4. 크로스 도메인 문제 해결 방법
1) 서버 측 CORS 설정
가장 이상적인 방법은 서버 측에서 적절한 CORS 헤더를 설정하는 것입니다.
(1) Express.js (Node.js) 설정
const express = require('express');
const cors = require('cors');
const app = express();

// 모든 도메인에서의 요청 허용
app.use(cors());

// 또는 특정 도메인만 허용
app.use(cors({
  origin: 'https://yourwebsite.com',
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true
}));

app.listen(3000, () => {
  console.log('Server running on port 3000');
});
(2) Spring Boot (Java) 설정
@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("https://yourwebsite.com")
                .allowedMethods("GET", "POST", "PUT", "DELETE")
                .allowedHeaders("*")
                .allowCredentials(true)
                .maxAge(3600);
    }
}
(3) Django (Python) 설정
# settings.py
INSTALLED_APPS = [
    # ...
    'corsheaders',
    # ...
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    # 다른 미들웨어들...
]

CORS_ALLOWED_ORIGINS = [
    "https://yourwebsite.com",
]

# 또는 모든 도메인 허용
# CORS_ALLOW_ALL_ORIGINS = True
(4) Laravel (PHP) 설정
// config/cors.php
return [
    'paths' => ['api/*'],
    'allowed_methods' => ['*'],
    'allowed_origins' => ['https://yourwebsite.com'],
    'allowed_origins_patterns' => [],
    'allowed_headers' => ['*'],
    'exposed_headers' => [],
    'max_age' => 0,
    'supports_credentials' => true,
];
(5) ASP.NET Core (C#) 설정
public void ConfigureServices(IServiceCollection services)
{
    services.AddCors(options =>
    {
        options.AddPolicy("MyCorsPolicy",
            builder => builder
                .WithOrigins("https://yourwebsite.com")
                .AllowAnyMethod()
                .AllowAnyHeader()
                .AllowCredentials());
    });
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // 다른 미들웨어...
    app.UseCors("MyCorsPolicy");
    // 다른 미들웨어...
}
. . . . .
2) 프록시 서버 사용
프론트엔드와 API 서버 사이에 프록시 서버를 두어 CORS 이슈를 우회할 수 있습니다.
(1) React 개발 환경의 프록시 설정
// package.json
{
  "name": "my-app",
  "version": "0.1.0",
  "proxy": "https://api.example.com"
}
(2) Vue CLI 프록시 설정
// vue.config.js
module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: 'https://api.example.com',
        changeOrigin: true
      }
    }
  }
}
(3) Webpack Dev Server 프록시 설정
module.exports = {
  // ...
  devServer: {
    proxy: {
      '/api': {
        target: 'https://api.example.com',
        changeOrigin: true,
        pathRewrite: { '^/api': '' }
      }
    }
  }
};
. . . . .
3) 로컬 프록시 서버 사용
http-proxy-middleware를 사용한 예시:
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');

const app = express();
app.use('/api', createProxyMiddleware({
  target: 'https://api.example.com',
  changeOrigin: true,
  pathRewrite: {'^/api': ''}
}));

app.listen(3000);
. . . . .
4) 서버리스 함수를 프록시로 사용
AWS Lambda, Netlify Functions, Vercel Serverless Functions 등을 사용하여 프록시를 구현할 수 있습니다.
// Netlify Function 예시
exports.handler = async function(event, context) {
  try {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();

    return {
      statusCode: 200,
      headers: {
        "Access-Control-Allow-Origin": "*"
      },
      body: JSON.stringify(data)
    };
  } catch (error) {
    return {
      statusCode: 500,
      body: JSON.stringify({ error: 'Failed fetching data' })
    };
  }
};
. . . . .
5) 개발 환경에서의 임시 해결 방법
⚠️ 주의: 다음 방법들은 테스트 목적으로만 사용하세요. 실제 서비스에는 사용하지 마세요.
(1) 브라우저 보안 비활성화 모드
Chrome 실행 시 보안 옵션을 비활성화할 수 있습니다:
chrome.exe --disable-web-security --user-data-dir="C:/ChromeDev"
(2) 브라우저 CORS 확장 프로그램
① Chrome: CORS Unblock
② Firefox: CORS Everywhere

#5. 자주 묻는 질문(FAQ)
1) Q: CORS 오류는 보안 취약점인가요?
A: CORS 오류 자체는 보안 취약점이 아니라 브라우저의 보안 메커니즘이 정상적으로 작동하고 있다는 신호입니다. 오히려 이 제한을 우회하는 것이 보안 위험을 초래할 수 있습니다.
. . . . .
2) Q: 모든 API에 CORS 설정이 필요한가요?
A: 다른 도메인에서 브라우저를 통해 접근해야 하는 API라면 CORS 설정이 필요합니다. 서버-서버 간 통신이나 같은 도메인 내 통신에는 CORS 설정이 필요 없습니다.
. . . . .
3) Q: Access-Control-Allow-Origin을 '*'로 설정해도 안전한가요?
A: 공개 API나 모든 사용자에게 개방된 리소스의 경우 '*'를 사용할 수 있지만, 인증이 필요한 API의 경우 특정 도메인만 허용하는 것이 보안상 더 안전합니다.
. . . . .
4) Q: 왜 서버 간 통신에서는 CORS 문제가 발생하지 않나요?
A: CORS는 브라우저에서 시행하는 보안 정책입니다. 서버 간 직접 통신에서는 브라우저가 개입하지 않기 때문에 CORS 제한이 적용되지 않습니다.
. . . . .
5) Q: CORS 설정이 서버 성능에 영향을 주나요?
A: CORS 헤더 설정 자체는 서버 성능에 미미한 영향만 줍니다. 다만, 예비 요청(OPTIONS)이 추가되므로 약간의 오버헤드가 발생할 수 있습니다. Access-Control-Max-Age를 설정하여 예비 요청을 캐시하면 이를 최소화할 수 있습니다.
. . . . .
6) Q: 클라이언트에서만 CORS 문제를 해결할 수 있나요?
A: 완전히 해결하기는 어렵지만, 프록시 서버나 브라우저 확장 프로그램을 사용하여 우회할 수 있습니다. 가장 올바른 방법은 서버 측에서 적절한 CORS 설정을 하는 것입니다.
. . . . .
7) Q: 자주 발생하는 CORS 에러 메시지와 해결 방법은?
"Access to fetch at 'https://api.example.com' from origin 'http://localhost:3000' has been blocked by CORS policy"
원인: 서버에서 요청한 도메인을 허용하지 않음
해결: 서버 측에서 올바른 CORS 헤더를 설정합니다.

"No 'Access-Control-Allow-Origin' header is present on the requested resource"
원인: 서버 응답에 CORS 헤더가 없음
해결: 서버 응답에 필요한 CORS 헤더가 포함되어 있는지 확인합니다.

"The 'Access-Control-Allow-Origin' header contains the invalid value"
원인: CORS 헤더 값이 잘못됨
해결: 적절한 값(예: '*' 또는 특정 도메인)을 설정합니다.

"Request header field Content-Type is not allowed by Access-Control-Allow-Headers"
원인: 허용되지 않은 헤더를 사용함
해결: 서버에서 Access-Control-Allow-Headers에 필요한 헤더를 추가합니다.

"Credential is not supported if the CORS header 'Access-Control-Allow-Origin' is '*'"
원인: 쿠키를 사용하면서 출처를 '*'로 설정함
해결: 특정 도메인을 명시적으로 설정하고 Access-Control-Allow-Credentials: true와 함께 사용합니다.

마무리
크로스 도메인 이슈는 웹 개발에서 피할 수 없는 부분이지만, 이제 여러분은 이 문제가 무엇인지, 그리고 어떻게 해결할 수 있는지 이해하셨을 것입니다. 상황에 맞는 적절한 해결책을 선택하여 사용하세요:

API 서버를 직접 제어할 수 있는 경우: 서버 측에서 적절한 CORS 헤더 설정
외부 API를 사용하는 경우: 프록시 서버 또는 서버리스 함수 활용
개발 환경에서만 필요한 경우: 개발 서버의 프록시 설정 활용

웹 보안과 편의성 사이의 균형을 찾는 것이 중요합니다. CORS는 웹 보안을 위한 중요한 메커니즘이지만, 적절한 설정을 통해 크로스 도메인 통신을 안전하게 구현할 수 있습니다.

이 가이드가 크로스 도메인 문제를 해결하는 데 도움이 되었기를 바랍니다.
긴 글 읽어주셔서 감사합니다.

끝.
반응형