Spring Boot Web Application CORS Filter
개요
Spring Boot 로 Web Application 을 개발 했을 때 Client 가 Web Browser 일 수 있다.
Client 가 웹 브라우저인 경우 Client 의 도메인과 Server(Spring Boot Web Application)의 도메인이 다르면 CORS 정책에 의해 에러가 발생한다.
CORS 정책 위반 에러를 없이 Server(Spring Boot Web Application) 에서 다른 도메인의 Client 웹 브라우저의 요청을 처리 해 주기 위해서는 Server 에 CORS 를 허용하는 Filter 를 적용하면 된다.
CORS 정책 에러
에러 1 - No 'Access-Control-Allow-Origin' header is present on the requested resource
웹 브라우저 도메인 "http://client.zzang.com:8080" 에서 Spring Boot Web Application 의 URL 을 post 로 호출 했을 때 CORS 정책에 위배 되었음을 알리는 에러
from origin 'http://client.zzang.com:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
에러 2 - Response to preflight request doesn't pass access control check
- Content-Type이 application/json인 경우 실제 요청을 전송하기 전 OPTIONS 메서드를 이용하여 요청이 안전한지 확인 한다. => 사전 요청(Preflighted Request)
- 사전 요청에 관해서는 204 No Content를 반환하는 것을 권장한다.
request method OPTIONS 인 사전 요청에 대한 허용이 안 되어 있을 때 에러
has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The 'Access-Control-Allow-Origin' header contains multiple values '*, *', but only one is allowed.
서버가 nginx 인 경우 사전 요청 허용 처리
server {
server_name api.zzang.com;
location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, DELETE, PATCH, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
add_header 'Access-Control-Max-Age' 86400;
return 204;
}
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Content-Type' 'application/json' always;
proxy_pass http://localhost:3000/;
}
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/api.booklog.dev/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/api.booklog.dev/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
참고 : https://greeng00se.tistory.com/119
[Nginx] CORS 설정
개요 CORS 설정을 하지 않아서 배포한 웹 사이트에서 백엔드 API로 접근을 못하는 문제가 발생했습니다. 프론트 개발시에는 Vite를 이용하여 프록시를 설정하였고, 모든 요청이 프록시를 경유하도
greeng00se.tistory.com
에러 3 - The 'Access-Control-Allow-Origin' header contains multiple values '*, *', but only one is allowed
서버가 nginx 에서 revers proxy 로 Spring Web Applicaiton 으로 요청을 전달하는 서비스 구조인 경우 nginx 와 Spring Web Application 에 모두 CORS 허용 되어 있는 경우 에러
Access to XMLHttpRequest at 'https://server.zzang.com/api' from origin 'https://client.zzang.com' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains multiple values '*, *', but only one is allowed.
nginx 의 설정이나 Spring Web Application CORS 허용 Filter 중 하나만 사용한다.
nginx 에서 CORS 설정을 사전 요청만 처리하고 reverse proxy 로 전달하지 않는 설정
server {
server_name api.zzang.com;
location / {
# 사전 요청만 nginx 에서 처리
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, DELETE, PATCH, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
add_header 'Access-Control-Max-Age' 86400;
return 204;
}
# 사전 요청 처리만 하고, proxy 로 전달 할 때는 CORS 처리 하지 않는다.
#add_header 'Access-Control-Allow-Origin' '*' always;
#add_header 'Content-Type' 'application/json' always;
proxy_pass http://localhost:3000/;
}
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/api.booklog.dev/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/api.booklog.dev/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
Server CORS 허용 Filter
Spring Boot Web Application 에서 Filter 를 구현하여 적용할 때 @Component 어노테이션을 사용하면 Spring Boot Web Application 이 Filter 를 적용 해 준다.
Filter 구현을 위해서 servlet 클래스의 Filter 를 implement 해야 한다.
package com.zzang.core.common.http.filter;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class SimpleCORSFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "OPTIONS, POST, GET");
httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "3600");
httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization,Device-info");
/*여기의 *을 내가 허용하고 싶은 특정 도메인으로 바꾸면 설정한 도메인에 한에서만 크로스 도메인을 허용하게된다. 여러 도메인의 경우 여러번 설정하면된다. */
httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
chain.doFilter(request, httpServletResponse);
}
}