CORS 에러는 프론트엔드에서 백엔드로 요청보낼 때, 브라우저에서 자주 마주하는 에러다.
물론 로컬에서 백엔드 서버를 실행하고, 로컬에서 프론트엔드를 실행한 뒤, 이 둘 사이에 통신을 보내면 CORS 에러는 거의 발생하지 않는다.
하지만 백엔드 서버를 배포한 경우엔 얘기가 달라진다.
그렇다면 이 CORS는 뭐고 왜 백엔드 서버를 배포한 경우 에러가 발생하는지 알아보자
CORS
CORS란 Cross-Origin Resource Sharing 의 약자로, 직역하면 교차 출처 리소스 공유 이다.
근데 ‘교차 출처 리소스 공유’ 라는 단어 자체가 이해하기 어려우니까 다른 출처로 부터 제공된 리소스 사용을 허용하는 것 이라고 하면 더 이해하기 쉬워진다.
결국엔 그렇게 많이 마주하던 CORS 에러가 사실은 최대한 다른 출처를 가진 리소스를 가져와야 할 경우 최대한 이를 허용하기 위해 발생하던 에러인 것이다.
→ 실제론, 동일 출처 정책 (Same-Origin Policy)라는 규칙 때문에 모든 리소스의 출처는 같아야 한다. 하지만 CORS를 지키면, 비록 출처가 다르더라도 예외적으로 접근을 허용할 수 있다.
그렇다면 다른 출처인지 아닌지 브라우저가 어떻게 알까? 다를 경우엔 뭘 보고 CORS를 허용하는 걸까?
아니, 출처가 뭘 의미하는 걸까?
출처 (Origin)
출처란, 프로토콜 + 호스트 + 포트 를 합친 것이다.
요청 URL이 https://gudtjr2949.tistory.com/34 과 같을 경우, 구성요소는 다음과 같다.
- 프로토콜 : https
- 호스트 : gudtjr2949.tistory.com
- 포트 (생략) : 443
- 경로 : 34
포트는 보통 생략되는데, 요청 프로토콜이 http일 경우 80, https일 경우 443 이다.
포트까지 포함하면 https://gudtjr2949.tistory.com:443/34 이와 같은 URL이 된다.
CORS 에러
CORS 에러는 백엔드나 프론트엔드가 던지는 에러가 아니다.
크롬과 같은 웹 브라우저가 던지는 에러이다.
위의 CORS와 출처(Origin)의 개념을 보면 CORS 에러가 발생한 이유가 조금 감이 온다.
CORS는 아래의 이미지와 같이 서로 다른 출처일 지라도 브라우저에서 리소스를 사용하는 걸 허용해주는 기술이다.
하지만 무작정 모든 리소스 사용을 허용해줄 순 없다. 모든 출처에 대한 요청을 허용해버리면 해커가 임의로 브라우저의 JavaScript 코드를 변경해 CSRF(Cross-Site Request Forgery)나 XSS(Cross-Site Scripting) 의 위험이 생기기 때문이다.
그렇기 때문에 프론트엔드와 백엔드 간의 약속을 하는 것이다. (실제론 CORS 에러가 발생했을 땐, 거의 대부분 백엔드 코드만 변경하면 됨)
이 약속은 프론트엔드의 요청 Origin과, 백엔드의 응답 Origin을 동일하게 맞추는 것이다.
→ 보통 백엔드의 응답 Origin은 리소스를 접근하는 것이 허용된 Origin을 나타내는 Access-Control-Allow-Origin 이라고 표현한다.
이 약속을 구현하는 방법은 백엔드에서 응답헤더의 Access-Control-Allow-Origin 에 프론트엔드에서 요청헤더에 담은 Origin 을 맞추는 것이다.
CORS 구현
가장 쉽고 정석인 방법은 백엔드에서 Access-Control-Allow-Origin 을 설정하는 것이다.
Spring에서 설정
@Slf4j
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class WebSecurityConfig {
...
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
...
// CORS 설정
http.cors((cors) -> cors
.configurationSource(corsConfigurationSource()));
...
return http.build();
}
...
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowCredentials(true);
configuration.setAllowedOriginPatterns(List.of("프론트엔드에서 설정한 요청 헤더 Origin"));
configuration.setAllowedMethods(List.of("HEAD", "GET", "POST", "PUT", "OPTIONS"));
configuration.setAllowedHeaders(List.of("*"));
configuration.setExposedHeaders(List.of("*"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
filterChain() 메서드에서 CORS 설정 객체인 CorsConfigurationSource 를 지정할 수 있다.
setAllowCredentials
configuration.setAllowCredentials(true);
Authorization를 사용해 사용자 인증을 할 경우, true 를 설정해야 한다.
setAllowedOriginPatterns
configuration.setAllowedOriginPatterns(List.of("프론트엔드에서 설정한 요청 헤더 Origin"));
프론트엔드의 요청 헤더 Origin과 백엔드의 응답 헤더 Access-Control-Allow-Origin 을 일치시키기 위해 설정하는 부분이다.
setAllowedMethods
configuration.setAllowedMethods(List.of("HEAD", "GET", "POST", "PUT", "OPTIONS"));
CORS 요청을 허용할 HTTP Method를 설정한다.
setAllowedHeaders
configuration.setAllowedHeaders(List.of("*"));
CORS 요청을 허용할 헤더를 설정한다.
setExposedHeaders
configuration.setExposedHeaders(List.of("*"));
사용자에게 보낼 응답 헤더를 설정한다.
다음 기회엔 Nginx로 CORS를 설정해봐야겠다.