Vite+React, Spring Boot로 어플리케이션을 구현하면서 CORS 관련 에러를 만나게 되었다. CORS에 대해서 약간은 알고 있었지만 이번 기회로 좀 더 자세하게 개념과 그 해결 방법에 대해서 정리해보았다.
💁♀️버전
Spring boot 3.3.3
Spring Security 6.3.3
CORS란?
- Cross-Origin Resource Sharing, 교차 출처 리소스 공유
- 기본적으로 웹 브라우저는 보안상의 이유로, 한 웹사이트에서 다른 웹사이트의 데이터를 바로 가져오지 못하게 막아두고 있다.
- 쉽게 말하면, 한 웹사이트에서 다른 웹사이트에 있는 정보를 가져오려고 할 때 필요한 규칙
예시를 보며 CORS를 이해해보자
Vite와 React로 만든 프론트엔드가 localhost:5173에서 실행되고 있고, Spring Boot로 만든 백엔드가 localhost:8080에서 실행되고 있다고 가정하자.
CORS 요청의 흐름
1. 브라우저는 localhost:5173에서 localhost:8080으로 요청을 보낼 때, 이 요청이 다른 출처로 가는 요청이라는 것을 인식한다.
- localhost:5173과 localhost:8080을 서로 다른 출처로 인식
- 프로토콜, 도메인, 포트 중 하나라도 다르면 다른 출처로 간주된다. 이 경우에는 포트 번호가 달라 다른 출처로 인식된다.
2. 브라우저는 보안 정책에 따라 다른 출처로 요청을 보낼 때 서버가 이 요청을 허용하는지 확인해야 한다.
- 단순한 요청이라면 사전 요청을 보내지 않을 수도 있지만, 복잡한 요청의 경우 먼저 사전 요청(OPTIONS 메서드)을 보낸다.
- “이 사이트(localhost:5173)에서 이 요청을 보내도 괜찮아?” 라고 물어보는 것
3. 응답에 따라 요청을 진행 혹은 차단한다.
- 서버가 이 사전 요청에 올바른 CORS 헤더를 포함하여 응답하면 브라우저는 본 요청 진행
- 사전 요청에 실패하거나, 본 요청의 응답에 CORS 헤더가 없다고 판단하면, 본 요청을 차단하고 에러를 발생시킨다.
⇒ 그래서 CORS(Cross-Origin Resource Sharing)의 개념에 대해서 찾아보면 다음과 같이 나와 있다.
브라우저가 프론트엔드 JavaScript 코드가 교차 출처(cross-origin)에 대한 응답에 접근하는 것을 차단하는지 여부를 결정하는 HTTP headers 전송으로 이루어진 시스템
Spring Security를 사용하고 있지 않을 때 CORS 설정
Spring Security를 사용하고 있지 않아도, 사용하고 있어도 이 클래스는 꼭 만들어 주어야 한다.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // 모든 경로에 대해 CORS 허용
.allowedOrigins("http://localhost:5173") // 허용할 도메인
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 허용할 HTTP 메소드
.allowedHeaders("*") // 허용할 헤더
.exposedHeaders("Authorization")
.allowCredentials(true) // 인증 정보를 허용할지 여부
.maxAge(3600); // 캐시 시간
}
}
exposedHeaders 옵션
- 브라우저는 보안상의 이유로, CORS 요청에 대한 서버 응답에서 몇 가지 기본 헤더만 클라이언트에 노출한다. 즉, 서버가 응답에 포함한 모든 헤더를 클라이언트에서 직접 볼 수 있는 것이 아니다.
- 노출되는 기본 헤더는 다음과 같다.
- Cache-Control
- Content-Language
- Content-Type
- Expires
- Last-Modified
- Pragma
- 만약 서버 응답에 다른 헤더가 포함되어 있고, 이를 클라이언트 측에서 접근해야 하는 경우는 exposedHeaders 옵션을 사용하여 헤더를 노출시켜야 한다.
- 나는 JWT 방식으로 인증을 구현했는데, 그 구현 과정에서 Header에 key에 Authorization, value에 JWT를 담아 보냈기 때문에 Authorization Header를 노출시켜 주었다.
Spring Security를 사용할 때 CORS 설정
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// CORS 설정
http.cors(corsCustomizer -> corsCustomizer.configurationSource(request -> {
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedHeader("*");
config.addAllowedMethod("*");
config.addAllowedOrigin("http://localhost:5173");
config.setMaxAge(3600L);
config.addExposedHeader("Authorization");
return config;
}));
//
// 기타 설정 ...
//
return http.build();
}
생각보다 애를 많이 먹었던 CORS 이슈. 이번 기회로 OPTIONS 메서드는 브라우저의 사전 요청 메서드로 쓰인다던지, Header를 따로 노출시켜줘야 한다던지 하는 것을 알게 되어 힘들지만 재밌었던 트러블 슈팅이었다.
CORS 이슈를 겪고 있는 사람들에게 도움이 되길..!
'STUDY > Trouble Shooting' 카테고리의 다른 글
WebMvcTest 403 Forbidden 해결하기(feat. CSRF) (1) | 2024.09.27 |
---|---|
Docker PostgreSQL에서 정렬이 이상하게 되는 문제 (2) | 2024.09.02 |
Timestamped ZonedDateTime 오류 (0) | 2024.05.22 |
Redis Docker Container와 Spring Boot 연결이 안 되는 오류 (0) | 2024.05.22 |
만료된 토큰에서 토큰에 저장된 정보를 가져올 수 없는 오류 (0) | 2024.05.22 |