웹사이트_cors_문제_해결_및_서버별_설정_가이드

웹사이트 CORS 문제 해결 및 서버별 설정 가이드

웹사이트 간 자원 공유 시 발생하는 CORS(Cross-Origin Resource Sharing) 문제를 해결하기 위해 A사이트(자원 제공 서버)에 응답 헤더를 추가하는 설정 방법입니다.

  • 기본 원리: 브라우저는 보안을 위해 다른 출처(Domain, Protocol, Port)의 자원 요청을 기본적으로 제한합니다. A사이트 서버가 응답 헤더에 Access-Control-Allow-Origin을 실어 보내야 브라우저가 자원을 차단하지 않습니다.
  • 모든 사이트 허용 (와일드카드 *): 특정 도메인 대신 *를 설정하면 모든 외부 사이트에서의 요청을 허용합니다.
  • [중요 주의사항 / 검증사항] 인증 정보(Credentials)와의 충돌:
    • 클라이언트 요청에 쿠키, 인증 헤더(Authorization 등)가 포함된 경우(credentials: 'include'), 브라우저 보안 규격상 Access-Control-Allow-Origin: * 설정은 동작하지 않으며 에러가 발생합니다.
    • 따라서 인증 정보가 필요한 통신이라면 와일드카드(*) 대신 요청해오는 Origin을 동적으로 읽어 응답하거나 특정 도메인을 명시해야 합니다.

/etc/nginx/nginx.conf 또는 가상호스트 설정 파일의 server / location 블록에 추가합니다.

server {
    listen 80;
    server_name a-site.com;

    location /img/ {
        # 모든 도메인 허용
        add_header Access-Control-Allow-Origin "*" always;
        add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;
        add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;

        # Preflight(OPTIONS) 요청 처리
        if ($request_method = 'OPTIONS') {
            add_header Access-Control-Allow-Origin "*" always;
            add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;
            add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
            add_header Access-Control-Max-Age 1728000;
            return 204;
        }

        root /var/www/a-site;
    }
}
location /img/ {
    add_header Access-Control-Allow-Origin "https://b-site.com" always;
    add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;
}
# server 블록 외부(http 블록)에 작성
map $http_origin $cors_origin {
    default "";
    "~^https?://(b-site\.com|c-site\.com)$" "$http_origin";
}

server {
    location /img/ {
        add_header Access-Control-Allow-Origin $cors_origin always;
        add_header Access-Control-Allow-Credentials "true" always;
    }
}

httpd.conf 또는 VirtualHost 설정 파일에서 mod_headers 모듈이 활성화되어 있어야 합니다 (LoadModule headers_module modules/mod_headers.so).

<VirtualHost *:80>
    ServerName a-site.com
    DocumentRoot "/var/www/a-site"

    <Directory "/var/www/a-site/img">
        <IfModule mod_headers.c>
            Header always set Access-Control-Allow-Origin "*"
            Header always set Access-Control-Allow-Methods "GET, POST, OPTIONS"
            Header always set Access-Control-Allow-Headers "Content-Type, Authorization"

            # Preflight(OPTIONS) 대응
            SetEnvIf Request_Method "OPTIONS" IS_OPTIONS
            Header always set Access-Control-Max-Age "86400" env=IS_OPTIONS
        </IfModule>
    </Directory>
</VirtualHost>
Header always set Access-Control-Allow-Origin "https://b-site.com"
SetEnvIf Origin "^https?://(b-site\.com|c-site\.com)$" CORS_ALLOW_ORIGIN=$0
Header always set Access-Control-Allow-Origin "%{CORS_ALLOW_ORIGIN}e" env=CORS_ALLOW_ORIGIN
Header always set Access-Control-Allow-Credentials "true" env=CORS_ALLOW_ORIGIN

Tomcat 내장 org.apache.catalina.filters.CorsFilter를 사용합니다. 설정 위치는 $CATALINA_BASE/conf/web.xml 또는 개별 앱의 WEB-INF/web.xml입니다.

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
                             http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">

    <filter>
        <filter-name>CorsFilter</filter-name>
        <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
        <!-- 모든 사이트 허용 -->
        <init-param>
            <param-name>cors.allowed.origins</param-name>
            <param-value>*</param-value>
        </init-param>
        <init-param>
            <param-name>cors.allowed.methods</param-name>
            <param-value>GET,POST,HEAD,OPTIONS</param-value>
        </init-param>
        <init-param>
            <param-name>cors.allowed.headers</param-name>
            <param-value>Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization</param-value>
        </init-param>
        <init-param>
            <param-name>cors.preflight.maxage</param-name>
            <param-value>1800</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>CorsFilter</filter-name>
        <url-pattern>/img/*</url-pattern>
    </filter-mapping>

</web-app>

cors.allowed.originsparam-value 부분을 아래와 같이 수정합니다.

<init-param>
    <param-name>cors.allowed.origins</param-name>
    <param-value>https://b-site.com, https://c-site.com</param-value>
</init-param>

1) standalone.xml 직접 수정

standalone.xml<subsystem xmlns=“urn:jboss:domain:undertow:…”> 구문을 수정합니다.

<subsystem xmlns="urn:jboss:domain:undertow:12.0">
    <buffer-cache name="default"/>
    <server name="default-server">
        <http-listener name="default" socket-binding="http" redirect-socket="https"/>
        <host name="default-host" alias="localhost">
            <filter-ref name="cors-header"/>
        </host>
    </server>
    <filters>
        <!-- 모든 사이트 허용 (*) 설정 -->
        <response-header name="cors-header" header-name="Access-Control-Allow-Origin" header-value="*"/>
        
        <!-- 특정 사이트만 허용 시 header-value를 "https://b-site.com" 으로 변경 -->
    </filters>
</subsystem>

2) JBoss CLI 사용

# 모든 도메인 허용 설정 (*)
/subsystem=undertow/configuration=filter/response-header=cors-header:add(header-name="Access-Control-Allow-Origin", header-value="*")
/subsystem=undertow/server=default-server/host=default-host/filter-ref=cors-header:add()

소스 코드 내 JAX-RS 클래스로 필터를 추가합니다.

import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.ext.Provider;
import java.io.IOException;

@Provider
public class CorsResponseFilter implements ContainerResponseFilter {
    @Override
    public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
        // 모든 사이트 허용 (*)
        responseContext.getHeaders().add("Access-Control-Allow-Origin", "*");
        
        // 동적 Origin 처리 시 아래 방식 활용 가능:
        // String origin = requestContext.getHeaderString("Origin");
        // responseContext.getHeaders().add("Access-Control-Allow-Origin", origin);
        
        responseContext.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
        responseContext.getHeaders().add("Access-Control-Allow-Headers", "Content-Type, Authorization");
    }
}

JavaScript(Canvas, Fetch, XMLHttpRequest)를 이용하여 A사이트의 이미지를 가공할 때는 HTML 및 JS에 crossorigin 속성이 명시되어야 합니다.

<img src="https://a-site.com/img/photo.jpg" crossorigin="anonymous" id="myImage">
const img = new Image();
img.crossOrigin = 'Anonymous'; // A사이트 서버의 CORS 허용 헤더가 필수입니다.
img.src = 'https://a-site.com/img/photo.jpg';

img.onload = () => {
  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');
  ctx.drawImage(img, 0, 0);
  const dataURL = canvas.toDataURL('image/png'); 
};

터미널에서 curl 명령어를 이용해 응답 헤더가 올바르게 전달되는지 확인합니다.

# 1. 특정 도메인 요청 시 검증
curl -I -H "Origin: https://b-site.com" https://a-site.com/img/sample.jpg

# 2. 임의의 도메인 요청 시 검증 (모든 사이트 허용 확인)
curl -I -H "Origin: https://random-site.com" https://a-site.com/img/sample.jpg

정상 응답 확인 포인트:
응답 헤더 목록 중 아래 항목이 출력되는지 확인합니다.

Access-Control-Allow-Origin: *
# 또는
Access-Control-Allow-Origin: https://b-site.com
  • 웹사이트_cors_문제_해결_및_서버별_설정_가이드.txt
  • 마지막으로 수정됨: 2026/09/09 01:02
  • 저자 koov