-
프론트쪽에서 API 연동을 시도했는데 CORS 에러로 인해 요청을 보낼 수 없다고 했다(소문으로만 듣던..!)
protocol은 미리 ssl 인증서를 받아두어서 괜찮았는데
host와 port 모두 달랐기 때문이었다
- 급하게 security 쪽에서 모든 origin에 대해 cors 활성화만 해두고(임시 해결)
좀 더 알아보고 다시 설정해보았다
SOP(Same Origin Policy)와 CORS(Cross Origin Resource Sharing)
- Origin : url 주소상에서 protocol, domain 이름, port까지 포함한 개념
- ex)
https://ttaehee.github.io/
- protocol :
https://
- host :
ttaehee.github.io
- protocol :
- ex)
- Same Origin : 같은 Origin을 뜻함
- Cross Origin : 서로 다른 Origin을 뜻함
- Cross Origin Resource Sharing : 서로 다른 Origin간의 리소스 교환을 뜻함
- 브라우저는 보안상의 이유로 SOP(Same Origin Policy)를 따름 (동일한 출처의 리소스만을 요청할 수 있음)
= 스크립트에서 시작한 Cross Origin HTTP Request를 제한함- CORS 정책이 필요한 이유? -> web application을 구현하다보면 다른 출처에 있는 리소스를 요청하는 경우가 많음
=> CORS 정책은 cross-origin 요청을 제한적으로 허용함으로써 좀 더 안전하게 cross-origin 요청을 처리할 수 있는 정책!
- cors 정책 위반 에러는 서버쪽에서 응답을 잘 하더라도 브라우저에서 나는 에러
따라서 postman으로 테스트 할 때는 아무리 해도 cors 에러가 일어나지 않음- cors 테스트 해볼 수 있는 곳
- https://cors-test.codehappy.dev/
- https://www.test-cors.org/
- cors 테스트 해볼 수 있는 곳
CORS 정책 위반 에러
- 같은 Origin -> 요청이 오는 곳과 처리하는 곳이 동일 -> 보안상 처리해줄 것이 없음
- 다른 Origin에서 오는 요청이라면 -> 내가 요청으로 받아온 결과가 믿을만한지 그렇지 않은지 검증하는 과정 필요
- 브라우저에서는 결과의 Header 값을 통해 CORS 확인함
Access-Control-Allow-Origin
,Access-Control-Allow-Methods
: 브라우저에서 CORS를 검증할 때 사용하는 값
CORS 동작 방식
1. Simple Request(단순 요청)
- Preflight Request 없이 요청을 보내 서버는 이에 대한 응답으로 Access-Control-Allow-Origin 헤더를 응답하는 방식
- 조건을 만족하는 경우에만 simple request 가능 => 기존 데이터를 손상시키지 않을 요청들에 대해서만 가능
- GET, HEAD, POST 중 한가지 요청일 것
- Custom Header가 존재하지 않을 것
- POST 요청일 경우, Conent-Type이 application/x-www-form-urlencoded, mutipart/form-data. text/plain 중 하나일 것
- ex) DELETE나 PATCH 요청을 누구나 간단하게 요청해서 처리할 수 있다면?
요청 하나만으로도 서버에 있는 데이터를 모두 지우거나 or 이상하게 변경 할 수 있을 것
2. Preflight Request(사전 요청)
- OPTIONS 메서드를 통해서 다른 domain의 resource로 HTTP request를 먼저 보내보고
실제 요청이 전송하기에 안전한지 확인함
Cross-Site request는 유저 데이터에 영향을 줄 수 있기 때문에 이와 같이 미리 전송(preflighted)함
- preflight 요청
Access-Control-Request-Method 헤더
: 실제 요청에서 어떤 HTTP method 가 사용될지 서버에게 알려줌Access-Control-Request-Headers 헤더
: 실제 요청에서 어떤 HTTP header를 사용할지 서버에 알려줌
- preflight 응답
Access-Control-Allow-Origins 헤더
: 실제 요청 시, resource에 접근할 때 허용되는 origin을 알려줌Access-Control-Allow-Mehtods 헤더
: 실제 요청 시, resource에 접근할 때 허용되는 method를 알려줌Access-Control-Allow-Headers 헤더
: 실제 요청 시, 사용할 수 있는 HTTP header를 알려줌
- 참고) 보통 preflight request는 브라우저가 자동적으로 생성함
3. Credentials Request(인증 이용 요청)
- HTTP cookie와 HTTP authentication 정보를 사용함
- CORS는 기본적으로 보안상의 이유로 쿠키를 요청으로 보낼 수 없도록 막고 있으나, 다른 domian을 가진 API server에 자신을 인증해야 정상적인 응답을 받을 수 있는 상황에서는 쿠키를 통한 인증이 필요함
- 이 때 요청에 인증과 관련된 정보를 담을 수 있게 해주는 옵션이 credentials 옵션
- credentials 요청
- Client와 Server 둘 다 credentials를 사용하겠다는 속성을 설정해줘야 통신 가능
- 요청을 credentials 모드로 설정
- credentials 옵션을 통해 인증 보안을 강화함
- Client와 Server 둘 다 credentials를 사용하겠다는 속성을 설정해줘야 통신 가능
- credentials 응답
Access-Control-Allow-Credentials 헤더
: Request with Credential 방식을 사용할 것인지를 지정함- true = 요청에 대한 응답을 표시할 수 있음을 나타냄
- true로 응답하지 않으면, 사용자 인증이 필요한 리소스에 대한 응답은 무시되고 웹 컨텐츠는 제공되지 않음
CORS 설정하기
Spring Security에서 cors 활성화하기
- CORS는 Spring Security보다 먼저 처리되어야함
(preflight 요청(보통 쿠키 등의 정보가 포함되어 있지 않음)의 경우, spring security가 인증되지 않은 사용자의 요청으로 판단하고 바로 거부해버리기 때문) - CORS가 먼저 처리되도록 하는 가장 쉬운 방법 : CorsWebFilter
- docs.spring.io를 참고해서 CorsWebFilter를 사용하기 위해 CorsConfigurationSource로 설정했다
- CorsConfiguration을 사용해 CORS를 적용해 줄 URL 패턴을 정의했다
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("https://localhost:5173"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
corsConfiguration.setAllowedHeaders(Arrays.asList("*"));
corsConfiguration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
...
.cors()
.configurationSource(corsConfigurationSource())
.and()
...
- 이렇게 설정하고 cors 에러가 뜨지 않았다
- 그러다 잘 돌아가던게 며칠 후 갑자기 안되었다(아직까지도 왜인지 이유를 찾지 못함..)
- Spring MVC쪽에서 설정을 한다면 security가 자동으로 spring에 선언해 둔 cors 구성을 사용한다고 해서 이번엔 spring 쪽에서 설정을 해보았다
Spring MVC에서 cors 설정하기
- spring에서 설정하는 방법은 annotation 방법도 있지만, 전역적으로 설정해주기 위해 Java Config를 통해 Spring MVC 설정에서 해주었다
- 참고) annotation 방법 : controller or method에
@CrossOrigin
annotation을 달아주면 됨
- 참고) annotation 방법 : controller or method에
- 프론트 쪽도 배포를 했기 때문에, 이번엔 프론트 배포서버도 추가하였다
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Value("${front.server}")
private String frontServer;
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:5173", frontServer)
.allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true);
}
}
@EnableWebMvc 사용으로 인해 생긴 LocalDateTime type 변환
-
나는 SpringBoot를 사용하기 때문에
spring-boot-starter-web
만 import 해주면 자동 완성으로 인해 mvc 관련된 bean들에 대한 설정을 해주었다= 별다른 MVC 설정을 안해도
@SpringBootApplication
annotation을 붙여줌으로써 기본적인 SpringBoot MVC 설정도 같이 제공이 됨 - 기존에 설정된 bean 설정을 유지하고 기능을 확장(cors)하고 싶은 상황이므로, WebMvcConfigurer 타입의 @Configuration class를 추가하기만 하면 되었다
- 그런데, 내가 처음에
@EnableWebMvc
까지 사용했고 그로 인해 LocalDateTime이 Array type으로 가게 되었다
=> 필요없다고 판단하여 제거하였다
Reference
CORS
Cross-Origin Resource Sharing (CORS)
CORS with Spring
Configuring CORS with Spring Boot and Spring Security
Interface WebMvcConfigurer
Using the @SpringBootApplication Annotation
why spring-boot application doesn’t require @EnableWebMvc
@EnableWebMvc