웹사이트 CORS 문제 해결 및 서버별 설정 가이드
웹사이트 간 자원 공유 시 발생하는 CORS(Cross-Origin Resource Sharing) 문제를 해결하기 위해 A사이트(자원 제공 서버)에 응답 헤더를 추가하는 설정 방법입니다.
1. CORS 개념 및 모든 사이트(*) 허용 시 주의사항
- 기본 원리: 브라우저는 보안을 위해 다른 출처(Domain, Protocol, Port)의 자원 요청을 기본적으로 제한합니다. A사이트 서버가 응답 헤더에
Access-Control-Allow-Origin을 실어 보내야 브라우저가 자원을 차단하지 않습니다. - 모든 사이트 허용 (와일드카드
*): 특정 도메인 대신*를 설정하면 모든 외부 사이트에서의 요청을 허용합니다. - [중요 주의사항 / 검증사항] 인증 정보(Credentials)와의 충돌:
- 클라이언트 요청에 쿠키, 인증 헤더(
Authorization등)가 포함된 경우(credentials: 'include'), 브라우저 보안 규격상Access-Control-Allow-Origin: *설정은 동작하지 않으며 에러가 발생합니다. - 따라서 인증 정보가 필요한 통신이라면 와일드카드(
*) 대신 요청해오는 Origin을 동적으로 읽어 응답하거나 특정 도메인을 명시해야 합니다.
2. Nginx 설정
/etc/nginx/nginx.conf 또는 가상호스트 설정 파일의 server / location 블록에 추가합니다.
옵션 A: 모든 사이트(*) 허용
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;
}
}
옵션 B: 특정 단일 도메인만 허용
location /img/ {
add_header Access-Control-Allow-Origin "https://b-site.com" always;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;
}
옵션 C: 여러 특정 도메인 동적 허용 (인증 정보 필요 시)
# 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;
}
}
3. Apache HTTP Server (httpd) 설정
httpd.conf 또는 VirtualHost 설정 파일에서 mod_headers 모듈이 활성화되어 있어야 합니다 (LoadModule headers_module modules/mod_headers.so).
옵션 A: 모든 사이트(*) 허용
<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>
옵션 B: 특정 단일 도메인만 허용
Header always set Access-Control-Allow-Origin "https://b-site.com"
옵션 C: 여러 특정 도메인 동적 허용
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
4. Apache Tomcat 설정
Tomcat 내장 org.apache.catalina.filters.CorsFilter를 사용합니다. 설정 위치는 $CATALINA_BASE/conf/web.xml 또는 개별 앱의 WEB-INF/web.xml입니다.
옵션 A: 모든 사이트(*) 허용
<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>
옵션 B: 특정 도메인 목록 허용
cors.allowed.origins의 param-value 부분을 아래와 같이 수정합니다.
<init-param>
<param-name>cors.allowed.origins</param-name>
<param-value>https://b-site.com, https://c-site.com</param-value>
</init-param>
5. WildFly (JBoss AS) 설정
방법 1: Undertow 서브시스템 설정 (서버 레벨)
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()
방법 2: Java EE / Jakarta EE 응답 필터 구현 (애플리케이션 레벨)
소스 코드 내 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");
}
}
6. 프론트엔드(B사이트) 속성 설정
JavaScript(Canvas, Fetch, XMLHttpRequest)를 이용하여 A사이트의 이미지를 가공할 때는 HTML 및 JS에 crossorigin 속성이 명시되어야 합니다.
HTML 태그 예시
<img src="https://a-site.com/img/photo.jpg" crossorigin="anonymous" id="myImage">
JavaScript Canvas 예시
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');
};
7. 설정 검증 방법 (cURL)
터미널에서 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