CORS는 브라우저가 임의의 웹 페이지에 다른 웹 페이지의 자원에 무분별하게 접근하는 것을 막아 XSS(Cross-site Scripting)와 같은 보안 위협으로부터 웹페이지를 보안한다. 기본적으로 모든 브라우저는 같은 정책을 따르는데, "해당 페이지와 같은 출처의 리소스에만 접근 가능하다."
따라서 다른 출처로부터 자원을 요청하려면 CORS헤더를 넣어줘야한다.
"도메인, 프로토콜, 포트가 다른데 리소스에 접근이 필요하다면 CORS 헤더를 설정해주자!"
단! 도메인이 아예 다르면 접근 불가능 www.naver.com - api.naver.com CORS헤더 설정으로 접근 가능
Access-Control-Allow-Origin은 CORS에서 사용되는HTTP 응답 헤더중 하나로,다른 도메인에서의 요청을 허용하는 경우 어떤 도메인에서 요청을 허용할 것인지를 명시합니다. 스프링기준 설정법 : WebMvcConfigurer(전역설정),@CrossOrigin(개별 컨트럴러),CorsFilter(커스텀)
브라우저가 다른 출처의 리소스에 접근하려고 시도하면, 브라우저는 요청 헤더에 "Origin" 필드를 포함하여 요청을 보낸다. "Origin" 필드에는 요청을 보낸 웹 페이지의 출처 정보가 담긴다.
서버는 요청을 받고, "Origin" 필드를 확인하여 요청이 허용되는 출처인지 판단한다.
허용된 출처라면 응답 헤더에 "Access-Control-Allow-Origin" 헤더를 포함하여 클라이언트에게 알린다.이 헤더에는 허용된 출처의 목록이 포함되며, "*"는 모든 출처를 허용한다는 뜻이다.
브라우저는 서버의 응답을 받아 "Access-Control-Allow-Origin" 헤더를 확인한다. 요청을 보낸 출처가 허용 목록에 포함되어 있으면 응답을 처리하고, 그렇지 않으면 거부한다.
✅ CORS설정하는 법
일단 필자의 프로젝트에서는 api.*.com(서버)과 www.*.com(클라이언트)로 서로 다른 도메인을 가지고 Https통신을 하며 서버에는 ec2내부에 Nginx를 두어서 HTTPS설정을 했고 스프링부트 애플리케이션도 같은 ec2에서 실행중이다.
Spring Boot에서 CORS 설정을 한다는 것은 결과적으로 HTTP 응답 헤더에 Access-Control-Allow-Origin을 포함시키는 과정이다.
CORS 허용
@Configuration
public class CorsMvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry corsRegistry) {
corsRegistry.addMapping("/**")
.allowedOrigins("https://*:3000", "https://www.*.shop") // 정확한 도메인 지정
.allowedMethods("*") // 모든 HTTP 메서드 허용
.allowedHeaders("*") // 모든 헤더 허용
.allowCredentials(true) // ✅ 쿠키 전송 허용 (allowedOrigins("*")와 함께 사용 불가)
.exposedHeaders("Authorization", "Set-Cookie"); // ✅ 클라이언트가 Authorization 헤더 받을 수 있도록 허용
}
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors(corsCustomizer -> corsCustomizer.configurationSource(new CorsConfigurationSource() {
@Override
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList(
"https://*:3000",
"https://www.*.shop"
));
configuration.setAllowedMethods(Collections.singletonList("*"));
configuration.setAllowCredentials(true); // ✅ 쿠키 전송 허용
configuration.setAllowedHeaders(Collections.singletonList("*"));
configuration.setExposedHeaders(Arrays.asList("Set-Cookie", "Authorization"));
configuration.setMaxAge(3600L); // 1시간 캐시
return configuration;
}
}));
}
쿠키 설정
쿠키에도 도메인과 Path가 있이서 설정해줘야함.
private Cookie createCookie(String key, String value) {
Cookie cookie = new Cookie(key, value);
cookie.setMaxAge(60 * 60 * 60 * 60);
cookie.setSecure(true); // 로컬 개발 환경에서는 Secure=false
// Secure=false → 쿠키가 HTTP와 HTTPS 둘 다 전송됨 클라가 Https일때는 true
// HTTPS가 아닐 때도 테스트할 수 있도록 설정
cookie.setDomain("*.shop"); // 서브도메인 간 공유
cookie.setHttpOnly(true);
// CORS 문제 해결 (리디렉션 후에도 쿠키 유지)
cookie.setAttribute("SameSite", "None");
cookie.setPath("/");
return cookie;
}
서버는 도메인 무료 사이트에서 배포하고 프론트는 vercel로 배포했다. 이는 필연적으로 서로 다른 도메인을 사용하게 된다는 문제점이 있었다. cors설정을 바꿔도 쿠키 관련 설정을 바꿔도 same site설정을 바꿔도 할건 다해도 서버와 프론트 사이에 쿠키가 전달되지 않았다.
결론적으로 같은 도메인명, 최상위 도메인명을 맞추거나 Redicrect URI 쿼리파라미터로 전달하면 된다.
현재 상황
https://*.kro.kr > 서버
https://*.vercel.app > 프론트
✅ 서로 다른 도메인이란?
도메인이란www.naver.com 이런게 도메인이다. "도메인이 다르다" 간단하게 도메인 주소가 다른것이다.
우분투 ec2 8080포트로 서버 올라감 + DNS(Domain Name System)발급
내가 지금 구현할 구조다 Ec2 내부에 Nginx와 Spring Boot를 띄울꺼다. 이번 프로젝트에서 이 기능을 사용하는 이유는
SSL기능과 서버를 보호하기 위해서 사용하기 때문에 이렇게 구성해도 문제 없다.
✅ Ubuntu 환경에서 Nginx 설치하기
# apt에서 설치 가능한 패키지 리스트(최신 패키지, 버전 등)를 최신화시킨다.
# apt는 리눅스에서 사용되는 소프트웨어 패키지를 설치 및 관리할 수 있게 도와주는 툴이다.
# npm 또는 gradle과 같은 패키지 관리 도구와 비슷하다고 생각하면 된다.
$ sudo apt update
# nginx 설치에 필요한 라이브러리 설치
$ sudo apt install curl gnupg2 ca-certificates lsb-release ubuntu-keyring
# nginx 공식 패키지를 안전하게 설치하기 위한 보안 조치이다. 자세한 코드 의미는 몰라도 된다.
# 다만, curl, gpg, tee, |, >, /dev/null, echo가 무슨 기능을 하는 명령어인지는 정리해두자.
$ curl <https://nginx.org/keys/nginx_signing.key> | gpg --dearmor \\
| sudo tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null
$ gpg --dry-run --quiet --no-keyring --import --import-options import-show /usr/share/keyrings/nginx-archive-keyring.gpg
$ echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] \\
<http://nginx.org/packages/ubuntu> `lsb_release -cs` nginx" \\
| sudo tee /etc/apt/sources.list.d/nginx.list
# nginx 설치
$ sudo apt update
$ sudo apt install nginx
# nginx 상태 확인
$ sudo systemctl status nginx
# nginx 시작
$ sudo systemctl start nginx
# nginx 상태 확인
$ sudo systemctl status nginx
참고) systemctl 명령어는 자주 사용하니 따로 찾아서 정리해두자.
✅ Nginx 로그 확인하는 방법
Nginx의 로그 파일 위치는 /var/log/nginx/이다. 이 경로로 이동하면 access.log와 error.log 파일이 있다. access.log 파일에는 Nginx 서버로 접근한 요청에 대한 정보가 기록으로 남아있고, error.log 파일에는 에러 메시지에 대한 내용이 담겨있다.
# Nginx 로그 파일이 위치한 곳으로 이동
$ cd /var/log/nginx
# 파일의 마지막 10줄을 출력
$ tail access.log
$ tail error.log
실시간으로 access.log가 쌓이는 걸 눈으로 확인해보자
# 파일의 마지막 10줄을 출력 + 실시간으로 파일에 추가되는 내용을 출력
$ tail -f access.log
✅ Nginx의 설정 파일 위치
/etc/nginx/nginx.conf
Nginx에서 가장 근본이 되는 설정 파일(루트 설정 파일)
전역적으로 설정되어야 하는 내용(워커 프로세스 개수, 로그 저장 위치 등)이 포함되어 있다.
/etc/nginx/conf.d/default.conf
기본 웹 서버(Web Server) 설정 파일
이렇게 크게 2가지 설정 파일이 존재한다. 여기서 /etc/nginx/conf.d/default.conf 파일 위주로 코드를 해석해보자.
✅ 기본적인 Nginx 문법 해석
/etc/nginx/conf.d/default.conf
server {
listen 80;
server_name localhost;
#access_log /var/log/nginx/host.access.log main;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \\.php$ {
# proxy_pass <http://127.0.0.1>;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \\.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\\.ht {
# deny all;
#}
}
위 파일을 열어보면 위와 같이 코드가 작성되어 있다. 주석 부분을 빼고 보자.
/etc/nginx/conf.d/default.conf
# server : '하나의 웹 사이트에 관련된 설정'을 관리하는 단위 ('server 블럭'이라고 부름)
server {
# localhost:80으로 들어오는 요청을 이 server 블럭에서 처리하도록 설정
# (server_name이 일치하는 server 블럭이 없는 경우 첫 번째 정의되어 있는 server 블럭을 기반으로 처리)
# (아직은 정확히 몰라도 된다. 나중에 '멀티 도메인' 기능을 배우면 쉽게 이해할 수 있다.)
listen 80;
server_name localhost;
# / 으로 시작하는 모든 경로를 처리 (ex. /index.html)
location / {
# /jscode.html로 요청이 들어오면 /usr/share/nginx/html/jscode.html 파일로 응답
root /usr/share/nginx/html;
# /로 요청이 들어오면 /usr/share/nginx/html/index.html로 응답
# 만약 /usr/share/nginx/html/index.html이 없을 경우, /usr/share/nginx/html/index.htm으로 응답
index index.html index.htm;
}
# Nginx에서 500, 502, 503, 504의 상태 코드가 발생했을 때 /50x.html로 redirect
error_page 500 502 503 504 /50x.html;
# /50x.html과 완전히 일치하는 경로를 처리
location = /50x.html {
# /50x.html로 요청이 들어오면 /usr/share/nginx/html/50x.html 파일로 응답
root /usr/share/nginx/html;
}
}
주의) ‘중괄호({...}) 형태의 구문’과 ‘세미 콜론(;)으로 끝나는 구문’ 2가지가 있다. 설정 파일을 작성할 때 세미 콜론(;)을 빠트려서 에러가 뜨는 경우가 많으니 주의하자.
✅ Nginx 에러 디버깅 방법
Nginx가 정상적으로 실행되고 있는 지 체크
$ sudo systemctl status nginx
문법 에러 체크하기
$ sudo nginx -t
로그 파일 실시간으로 확인하기
# 제대로 요청이 들어오고 있는 지 확인
$ sudo tail -f /var/log/nginx/access.log
# 에러 메시지 확인
$ sudo tail -f /var/log/nginx/error.log