보통 협업을 진행하다 보면 API 스펙을 전달해주고 완벽하게 테스트를 진행했는데도 들려오는 답변이 있습니다.
"API에서.. cors 에러가 발생해요.."
처음 협업을 진행하면서 CORS에 대해서 잘 모르기도 했었고, 스프링에서는 단순한 설정으로 이를 해결할 수 있었기 때문에 제대로 알아보고려고 하지 않았었죠.
이번에는 너무나 간단한 설정탓에 제대로 이해하지 못 했었던 CORS에 대해서 정리하고자 합니다.
CORS란?
CORS(Cross-Origin Resource Sharing)란, 클라이언트가 다른 출처(origin)에서 리소스를 요청할 때 발생하는 오류로 브라우저상에서 허용되지 않은 다른 출처를 제한하는 데 사용되는 보안 관련 오류입니다.
CORS는 보통 아래 두 가지 이유로 발생하게 됩니다.
- 브라우저에서 출처(origin)가 다른 리소스에 접근할 때, 서버에서 명시적으로 해당 출처를 허용하지 않을 경우
- 서버가 적절한 CORS 응답 헤더를 설정하지 않았거나, 설정이 클라이언트 요청과 일치하지 않을 경우
CORS 설정 방법
CORS의 개념은 다소 생소하고 이해하기 어려웠지만 설정 방법은 정말 간단한데요, 어디서 설정하는지에 다를뿐 결국 HTTP 응답 헤더에 설정한다는 목적은 같습니다.
보통 현업에서 사용할 때는 Security를 제외한 단일 모듈 안에서 MVC 설정 내부에 진행했었는데, 찾아보니 설정 방법이 정말 많았습니다.
아래는 스프링에서 CORS를 설정하는 여러가지 방법들입니다.
단일 컨트롤러에서의 설정
@RestController
@RequestMapping("/api")
public class ApiController {
@CrossOrigin(origins = "http://example.com")
@GetMapping("/data")
public String getData() {
return "Hello, CORS!";
}
}
Spring MVC에서의 설정
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // 모든 경로에 대해 적용
.allowedOrigins("http://example.com") // 허용할 출처
.allowedMethods("GET", "POST", "PUT", "DELETE") // 허용할 HTTP 메서드
.allowedHeaders("*") // 허용할 헤더
.allowCredentials(true); // 자격 증명 허용
}
}
Spring Security에서의 설정
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and()
.csrf().disable() // CORS 설정과 함께 CSRF를 비활성화 (선택 사항)
.authorizeRequests()
.anyRequest().authenticated();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("http://example.com")); // 허용할 출처
configuration.setAllowedMethods(Arrays.asList("GET", "POST"));
configuration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
직접 필터를 구현한 설정
@Component
public class CustomCorsFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
response.setHeader("Access-Control-Allow-Origin", "http://example.com");
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
response.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type");
response.setHeader("Access-Control-Allow-Credentials", "true");
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
filterChain.doFilter(request, response);
}
}
}
우리의 요청이 CORS를 적용하면 어떻게 동작하는가?
위에서 CORS를 설정하기 위한 여러 방법들이 있었지만, 결국 목적은 HTTP 응답 값에 CORS 정책을 허용하는 값을 넣어준다는 같은 목적을 가지고 있습니다.
그러면, HTTP 응답 값에 CORS 정책을 허용하는 위 설정을 추가하면 어떻게 브라우저에서 동작하게 되는걸까요?
우선 클라이언트(Vue.js 등)의 요청이 브라우저로 들어오게 되면, 브라우저는 보안 정책에 따라 요청의 출처를 검사하고 서버의 출처가 다르기 때문에 다른 출처(Origin)에 대한 CORS 정책이 적용되게 됩니다.
이에 따라 서버에 Preflight 과정인 OPTIONS 요청을 보내어 서버에서는 이에 대한 CORS 설정을 응답으로 반환하고, 브라우저에서는 허용된 응답 값이 들어올 경우 추가적인 API 요청을 보내거나 들어오지 않을 경우 CORS 에러를 발생시키게 됩니다.
CORS 설정은 하나라도 빠뜨리면 안 되나..??
이게 참 궁금했고 각각의 설정이 빠짐에 따라 어떻게 동작하게 될지 궁금했습니다.
각 설정에 대한 특징과 빠드리게 될 경우 부수효과는 다음과 같습니다.
속성 | 기본값 | 누락 시 발생 상황 |
allowedOrigins | 아무 출처도 허용하지 않음 | Access-Control-Allow-Origin 헤더가 없으므로 모든 CORS 요청이 차단됨 |
allowedMethods | GET, HEAD, POST만 허용 | PUT, DELETE 등 허용되지 않은 메서드로 요청 시 CORS 오류 발생 |
allowedHeaders | 기본 헤더(Content-Type, Cache-Control 등)만 허용 | Authorization 또는 커스텀 헤더 요청 시 Preflight 요청 실패 |
allowCredentials | false (자격 증명 허용 안 함) | 클라이언트가 withCredentials: true를 설정하고 요청했을 때 CORS 오류 발생 |
상황에 따라 필요하지 않은 설정이 있을 수도 있으니 참고해서 개발하면 더 좋을 것 같습니다.
'Spring Framework > spring' 카테고리의 다른 글
[SpringBoot] Spring 버전 마이그레이션 (0) | 2025.01.13 |
---|---|
[SpringBoot] Spring Transaction에 대하여 (0) | 2025.01.06 |
[SpringBoot] Spring FIiter (0) | 2024.12.29 |
[SpringBoot] ApplicationEvent를 활용한 이벤트 발행/구독 (0) | 2024.12.28 |
[SpringBoot] @ComponentScan 동작 원리 (0) | 2024.12.09 |