스프링
리액트<>스프링 부트 - 구글 로그인 CORS 에러 해결
효재감자
2025. 3. 17. 13:25
리액트 : http://localhost:3000
스프링 부트 : http://localhost:8080
리액트에서 다음과 같이 호출하였을 때 CORS 에러가 발생하였다.
const response = await fetch("http://localhost:8080/api/auth/user", {
credentials: "include",
})
일단 CORS를 해결하기 위한 다음과 같은응답 헤더가 없음을 확인하였다.
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Credentials: true
결국 CORS 설정을 직접 Bean으로 등록하고, SecurityFilterChain에서 명시적으로 적용해야 했던 거네.
동작하지 않는 코드 ①
@Configuration
@EnableWebSecurity
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("http://localhost:3000")
.allowedMethods("GET", "OPTIONS")
.allowCredentials(true)
.maxAge(3600); // 프리플라이트 요청 캐싱 (1시간)
}
}
- Spring Boot에서 CORS는 Spring MVC 레벨과 Spring Security 레벨이 따로 관리되는데, 위의 코드는 Spring MVC 레벨에서의 CORS 설정이므로 Spring Security를 사용하게 되면 Spring Security 레벨의 CORS 설정이 우선적으로 적용되어 요청을 가로채기 때문에 설정이 적용되지 않았음.
동작하지 않는 코드 ②
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.cors(Customizer.withDefaults())
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/api/auth/user").permitAll() // ✅ 인증 없이 허용
.anyRequest().authenticated()
)
.oauth2Login(oauth2 -> oauth2
.defaultSuccessUrl("http://localhost:3000", true)
);
return http.build();
}
}
- .cors(Customizer.withDefaults()) 부분에서 Spring Security의 기본 CORS 설정을 적용했기 때문에, 리액트의 요청이 허용되지 않았음.
- requestMatchers 부분 : "/" 경로에서 "/api/auth/user"를 호출하므로 2 경로 모두 지정해주어야 함.
동작하는 코드 - CORS 설정을 Spring Security단에 추가해주어야 함.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.csrf(AbstractHttpConfigurer::disable)
// 로그인 없이 접근 가능한 URL
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/public/**", "/api/auth/user").permitAll()
.anyRequest().authenticated()
)
// 인증 완료 후 리다이렉트
.oauth2Login(oauth2 -> oauth2
.defaultSuccessUrl("http://localhost:3000", true)
);
return http.build();
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOriginPatterns(List.of("http://localhost:3000"));
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowCredentials(true);
configuration.setAllowedHeaders(List.of("Authorization", "Cache-Control", "Content-Type"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
- corsConfigurationSource을 추가해주고 기본 설정이 아닌 새로 만든 cors 설정을 적용해줌.
.cors(Customizer.withDefaults())
▼
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
정상적으로 응답된 모습. 로그인을 하지 않았으므로 401이 반환되었음.