웹 애플리케이션에서 AJAX 요청 시 발생하는 CORS(Cross-Origin Resource Sharing) 오류와 해결 방법을 알아보겠습니다.
Access to XMLHttpRequest at 'http://api.example.com' from origin 'http://localhost:3000' has been blocked by CORS policy
No 'Access-Control-Allow-Origin' header is present on the requested resource
<?php
// 기본 CORS 헤더 설정
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With");
// OPTIONS 요청 처리 (Preflight)
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit();
}
// API 로직
echo json_encode(['message' => 'CORS 설정 완료']);
?>
<?php
class CorsHandler {
private $allowedOrigins = [
'http://localhost:3000',
'https://myapp.com',
'https://www.myapp.com'
];
private $allowedMethods = ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'];
private $allowedHeaders = ['Content-Type', 'Authorization', 'X-Requested-With'];
public function handleCors() {
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
// Origin 검증
if (in_array($origin, $this->allowedOrigins)) {
header("Access-Control-Allow-Origin: $origin");
}
header("Access-Control-Allow-Methods: " . implode(', ', $this->allowedMethods));
header("Access-Control-Allow-Headers: " . implode(', ', $this->allowedHeaders));
header("Access-Control-Allow-Credentials: true");
header("Access-Control-Max-Age: 3600"); // 1시간 캐시
// Preflight 요청 처리
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit();
}
}
}
// 사용법
$cors = new CorsHandler();
$cors->handleCors();
?>
<?php
class CorsMiddleware {
public static function handle($request, $next) {
// CORS 헤더 설정
self::setCorsHeaders();
// OPTIONS 요청은 여기서 종료
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
return;
}
// 다음 미들웨어 또는 컨트롤러 실행
return $next($request);
}
private static function setCorsHeaders() {
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
$allowedOrigins = self::getAllowedOrigins();
if (in_array($origin, $allowedOrigins) || self::isLocalDevelopment()) {
header("Access-Control-Allow-Origin: $origin");
}
header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With, X-CSRF-Token");
header("Access-Control-Allow-Credentials: true");
}
private static function getAllowedOrigins() {
return [
'https://myapp.com',
'https://www.myapp.com',
'https://admin.myapp.com'
];
}
private static function isLocalDevelopment() {
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
return strpos($origin, 'localhost') !== false ||
strpos($origin, '127.0.0.1') !== false;
}
}
?>
<?php
require_once 'middleware/cors_middleware.php';
class ApiRouter {
public function __construct() {
// CORS 미들웨어 적용
CorsMiddleware::handle($_REQUEST, function($request) {
$this->route();
});
}
private function route() {
$method = $_SERVER['REQUEST_METHOD'];
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
switch ($path) {
case '/api/users':
if ($method === 'GET') {
$this->getUsers();
} elseif ($method === 'POST') {
$this->createUser();
}
break;
case '/api/login':
if ($method === 'POST') {
$this->login();
}
break;
default:
http_response_code(404);
echo json_encode(['error' => 'Not Found']);
}
}
private function getUsers() {
header('Content-Type: application/json');
echo json_encode(['users' => ['user1', 'user2']]);
}
private function createUser() {
$input = json_decode(file_get_contents('php://input'), true);
header('Content-Type: application/json');
echo json_encode(['message' => 'User created', 'data' => $input]);
}
private function login() {
$input = json_decode(file_get_contents('php://input'), true);
header('Content-Type: application/json');
echo json_encode(['token' => 'sample_token']);
}
}
new ApiRouter();
?>
<?php
class CorsConfig {
public static function getConfig() {
$env = $_ENV['APP_ENV'] ?? 'development';
$configs = [
'development' => [
'allowed_origins' => ['*'],
'allowed_methods' => ['*'],
'allowed_headers' => ['*'],
'allow_credentials' => true
],
'staging' => [
'allowed_origins' => [
'https://staging.myapp.com',
'http://localhost:3000'
],
'allowed_methods' => ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
'allowed_headers' => ['Content-Type', 'Authorization'],
'allow_credentials' => true
],
'production' => [
'allowed_origins' => [
'https://myapp.com',
'https://www.myapp.com'
],
'allowed_methods' => ['GET', 'POST', 'PUT', 'DELETE'],
'allowed_headers' => ['Content-Type', 'Authorization'],
'allow_credentials' => true
]
];
return $configs[$env] ?? $configs['production'];
}
}
?>
// 올바른 AJAX 요청 예시
async function makeApiRequest() {
try {
const response = await fetch('http://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token
},
credentials: 'include', // 쿠키 포함
body: JSON.stringify({
name: 'John Doe',
email: 'john@example.com'
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log('Success:', data);
} catch (error) {
console.error('CORS Error:', error);
}
}
# 브라우저 개발자 도구에서 네트워크 탭 확인
# OPTIONS 요청이 성공하는지 확인
# Response Headers에 CORS 헤더가 있는지 확인