웹 서비스를 개발하다보면 CORS 에러를 흔히 볼 수 있다. 설정을 제대로 해주지 않으면 프론트와 통신할 수 없기 때문에 CORS에 대해 제대로 이해하고 설정하는 것이 매우 중요하다.
CORS는 Cross-Origin Resource Sharing의 약자로 교차 출처 리소스 공유라는 뜻이다. 매번 보는 단어이지만 말이 참 어렵다 😨
CORS에서 가장 중요한 개념은 출처(Origin)이다. Cross-Origin은 서로 출처가 다르다는 것을 의미한다. 출처는 URL에서 프로토콜과 도메인, 포트까지 포함하는 개념이다.

우리가 개발하는 서비스는 보통 프론트와 백엔드가 서로 다른 도메인을 사용한다. 방끗 서비스를 살펴보면,
프론트 도메인(bang-ggood.com)과 백엔드 도메인(api.bang-ggood.com)은 서로 다른 출처를 사용해 리소스를 공유한다.
CORS 에러는 무시무시하지만 CORS 자체는 긍정적인 개념이다. 서로 다른 출처끼리 리소스를 공유할 수 있게 하기 때문이다. CORS 이전에 SOP라는 브라우저 정책이 있다. SOP는 Same-Origin Policy로 동일한 출처 사이에만 리소스를 공유할 수 있게 한다. 이는 CSRF로부터 사용자를 보호하기 위해 등장한 중요한 보안 메커니즘이었다.
예전에는 프론트와 백엔드를 따로 구성하지 않아 모든 처리가 같은 도메인 안에서 이루어졌다. 하지만 시간이 지나 프론트에서 API를 요청하기 시작하면서 프론트와 백엔드가 서로 다른 도메인에 있는 경우가 많아졌다. 이를 해결하기 위해 등장한 정책이 CORS이다.
CORS가 안전하게 동작하기 위해서는 서버에서 허용 가능한 출처에 대해 명시해주어야 한다. 이를 제대로 설정해주지 않으면 클라이언트(Client)를 신뢰할 수 없어 브라우저단에서 에러를 내뿜는다.
그렇다면 CORS 에러를 막기 위해 무엇을 설정해야 할까? CORS가 발생할 수 있는 3가지 시나리오를 살펴보며 필요한 설정 정보를 알아보자.
브라우저는 요청을 한 번에 보내지 않고 예비 요청을 보낸 후 본 요청을 보낸다. 예비 요청은 본 요청을 보내기 전 브라우저가 안전한지 먼저 점검한다. 교차 출처 요청이 사용자 데이터에 잘못된 영향을 미치는 것을 방지하기 위해서이다.
OPTIONS 메서드를 사용해 요청을 보내는 것이 특징이다. OPTIONS는 서버로부터 추가 정보를 얻기 위해 사용되며 리소스를 변경할 수 없는 안전한 메서드이다.
OPTIONS 요청과 함께 두 개의 다른 요청 헤더가 전송된다.
🔑 Client → 🔒 Server
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type,x-pingother
🔒 Server → 🔑 Client
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
일부 요청은 예비 요청을 보내지 않는다. 심플한 만큼 다음의 특정 조건을 만족할 때만 예비 요청을 생략할 수 있다.
대부분의 API 요청은 application/json으로 통신하기 때문에 Content-Type에 위반되어 단순 요청 조건에 해당하지 않는다.
클라이언트에서 서버로 자격 증명을 요청할 때 사용되는 요청이다. 자격 증명을 포함하기 위해서는 credentials 옵션을 "include" 로 설정해야 한다.
서버에서는 인증된 요청에 대한 헤더를 다음과 같이 설정해야 한다. 응답 헤더 값을 “*” 와일드카드로 지정해서는 안 된다.(문서에는 안된다고 적혀있지만 왜인지 나는 잘 적용되었다. 더 알아봐야겠다 🤔)
Access-Control-Allow-Origin : https://foo.example
Access-Control-Allow-Headers : X-PINGOTHER, Content-Type
Access-Control-Allow-Methods : POST, GET
Access-Control-Expose-Headers : Content-Encoding, Kuma-Revision
Access-Control-Allow-Credentials: true
서버가 접근 제어 요청을 위해 보낼 수 있는 HTTP 응답 헤더는 다음으로 정리할 수 있다.
Spring에서 CORS 설정할 수 있는 방법은 다음 블로그를 참고한다.
스프링 MVC 구성에 CORS 관련 설정을 추가할 수 있다. 체이닝 방식을 통해 CorsRegistry을 구성할 수 있다.
@Configuration
public class CorsConfigWithMVC implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("https://bang-ggood.com")
.allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true);
}
}
Spring Security와 함께 정교하게 CORS를 설정할 수 있는 방법이다. Spring Security를 사용하지 않을 때도 WebMvcConfigurer의 설정을 대체할 수 있다.
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins("https://bang-ggood.com");
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
configuration.addAllowedHeader("*");
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return new CorsFilter(source);
}
}
dev와 prod 환경에서 출처를 다르게 설정할 수 있다. dev 환경에서는 localhost의 요청을 허용하지만, 실서비스가 운영되는 prod 환경에서는 이를 허용하면 안 되기 때문이다. 이를 위해 application-prod.yml과 application-dev.yml 파일에 각각 허용 가능한 출처를 지정하고, 해당 값을 주입해 사용하는 방식으로 구현했다.
@Configuration
@ConfigurationProperties(prefix = "cors")
public class CorsConfig {
private List<String> allowOrigins;
@Bean
public CorsFilter corsFilter() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(allowOrigins);
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
configuration.addAllowedHeader("*");
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return new CorsFilter(source);
}
public List<String> getAllowOrigins() {
return allowOrigins;
}
public void setAllowOrigins(List<String> allowOrigins) {
this.allowOrigins = allowOrigins;
}
}
참고 자료
교차 출처 리소스 공유 (CORS) - HTTP | MDN
CORS with Spring | Baeldung
CORS(교차 출처 리소스 공유) | 토스페이먼츠 개발자센터
[10분 테코톡] 🌳 나봄의 CORS
🌐 악명 높은 CORS 개념 & 해결법 - 정리 끝판왕 👏
감사합니다 덕분에 cors 에러 해결했어요