• 프론트쪽에서 API 연동을 시도했는데 CORS 에러로 인해 요청을 보낼 수 없다고 했다(소문으로만 듣던..!)
    protocol은 미리 ssl 인증서를 받아두어서 괜찮았는데
    host와 port 모두 달랐기 때문이었다

    cors


  • 급하게 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

666

그림 출처

  • 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 정책 위반 에러

  • 같은 Origin -> 요청이 오는 곳과 처리하는 곳이 동일 -> 보안상 처리해줄 것이 없음
  • 다른 Origin에서 오는 요청이라면 -> 내가 요청으로 받아온 결과가 믿을만한지 그렇지 않은지 검증하는 과정 필요


  • 브라우저에서는 결과의 Header 값을 통해 CORS 확인함
    • Access-Control-Allow-Origin, Access-Control-Allow-Methods : 브라우저에서 CORS를 검증할 때 사용하는 값



CORS 동작 방식

1. Simple Request(단순 요청)

simple

  • 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

  • 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

  • credentials 요청
    • Client와 Server 둘 다 credentials를 사용하겠다는 속성을 설정해줘야 통신 가능
      • 요청을 credentials 모드로 설정
    • 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을 달아주면 됨
  • 프론트 쪽도 배포를 했기 때문에, 이번엔 프론트 배포서버도 추가하였다
@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


업데이트: