🌿SPRING/🍀공부 [SPRING]

[SPRING] Frontend와 협업 ! - CORS 원리 / 설정 (22.10.22 수정)

디카페인라떼 2022. 9. 7. 12:34

CORS란?
  • 교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)
    • 교차 출처 ? = 다른 출처!
    • 현재 IP가 아닌 다른 IP로 리소스를 요청하는 구조 즉, 리스소가 자신의 출처와 다를때 교차 출처 HTTP 요청을 실행하게 된다. 
  • 출처 (Origin) 란?
    • 요청이 시작된 서버의 위치를 나타내는 문구
    • 구성 
      • 스키마 Schema = Protocol
      • 호스트 Host = Domain
      • 포트 Port

Origin 예시

  • https://www.domain.com:3000/post
  • https://www.domain.com:3000/post/id=?page=1
  • https://www.domain.com:3000/main/post/comment
  • 👉 모두 출처 Origin 이 동일한 상태이다 ! 

SOP Same-Origin Policy 
  • 동일 출처 Origin만 리소스를 공유 가능
  • SOP에 대한 예외조항을 두고 이 예외에 해당하는 요청은 출처가 다르더라도 허용하기로 함 
     = CORS 정책을 지킨 리소스 요청
  • 즉, 다른 출처로 리소스를 요청한다면 SOP 정책을 위반 + 예외 조항인 CORS 까지 위반한다면 아예 다른 출처 리소스를 사용할 수 없는 것! 

CORS의 기본 동작 
  • 기본 동작 과정
    1. 클라이언트에서 HTTP요청의 헤더에 Origin을 담아 전달한다.
    2. 서버는 응답헤더에 Access-Control-Allow-Origin을 담아 클라이언트로 전달한다.
      1. 서버가 응답을 보낼 때 , 허락하는 Origin을 클라이언트에게 전달한다.
    3. 클라이언트에서, 자신이 보냈던 요청의 Origin과 서버가 보내준 Access-Control-Allow-Origin을 비교한다.
      1. 클라이언트가 보낸 Origin과 서버가 보내준 Access-Control-Allow-Origin을 비교하여 차단할지 말지를 결정한다.
      2. 만약 유효하지 않다면 그 응답을 사용하지 않고 폐기한다. 
🚩즉 출처를 비교하는 로직은 서버단에서 구현되는 것이 아니라 브라우저에서 이루어진다!

👉 CORS 정책을 위반하는 리소스 요청을 하더라도 서버단에서 같은 출처에서 온 요청만 받겠다는 설정을 따로 해둔 것이 아니라면 일단 정상적으로 응답 (200 ok) 한다.
👉 그 뒤 브라우저가 이 응답을 분석해서 CORS 위반이라고 판단하면 폐기한다!

🚩브라우저가 CORS 정책 위반을 분석하는 것은 서버의 응답이 도착한 이후이다.
그러므로 CORS정책을 위반하는 리소스 요청때문에 에러가 발생하더라도 서버 쪽 로그에서는 정상응답을 했다는 로그만 남기때문에, CORS를 정확히 이해해야만 CORS에러를 해결할 수 있는 것이다.
CORS의 작동 방식
  1. 예비요청 (Preflight Request) -> 가장 흔함
    • 요청을 한꺼번에 다 보내지 않고 예비요청 (Preflight) 과 본요청으로 나누어 서버에 전달
      • 본 요청을 보내기 전에 예비요청을 보내 브라우저 스스로 안전한 요청인지 확인
      • 예비요청의 메소드는 OPTIONS 라는 요청이 사용됨 
    • preflight 방식도 위에서 설명한 출처 판별과 같은 방식으로 이루어짐
      • 🚩이때 예비요청의 성공여부는 CORS 에러와 상관 없음
        👉 CORS 여부를 판단하는 시점은 예비 요청의 응답을 받은 직후 이기 때문 !
      • 중요한 것은 예비 요청의 성공/ 실패 여부가 아니라  👉 응답 헤더에 유효한 Access-Control-Allow-Origin이 있는가 👈 
      • 그러므로 예비 요청에 실패해서 성공 코드가 아니더라도 헤더에 저 값이 제대로 들어가있다면 CORS 정책 위반이 아님!
  2. 단순 요청 (Simple Request)
    1. 예비 요청(Prefilght)을 보내지 않고 바로 서버에 직행으로 본 요청을 보낸 후, 서버가 이에 대한 응답의 헤더에 Access-Control-Allow-Origin과 같은 같을 보내주면 브라우저가 CORS정책 위반여부를 검사하는 방식
    2. 아래 3가지 경우를 만족할때만 가능
      1. 요청의 메소드는 GET, HEAD, POST 중 하나여야 한다.
      2. 유저 에이전트가 자동으로 설정한 헤더외에, 수동으로 설정할 수 있는 헤더는 Fetch 명세에서 "CORS-safelisted request-header "로 정의한 헤더만 사용할 수 있다.
      3. Content-Type 을 사용하는 경우에는 다음의 값들만 허용된다.
    3. 까다로운 조건들이 많기 때문에 대부분의 요청은 그냥 예비 요청으로 이루어진다 라고 이해
  3. 인증된 요청 (Credentialed Request)
    • 기존 예비요청에서 보안을 더 강화하고 싶을 때 사용
    • 기존 브라우저가 제공하는 비동기 리소스 요청 API인 XMLHttpRequest나 fetch API는 별도의 옵션 없이 브라우저의 쿠키 정보나 인증과 관련된 헤더를 함부로 요청에 담지 않음
    • 이때 요청에 인증과 관련된 정보를 담을 수 있게 해주는 옵션이 credentials 옵션
옵션 값  설명 
same-origin (기본값) 같은 출처 간 요청에만 인증 정보를 담을 수 있다
include 모든 요청에 인증 정보를 담을 수 있다
omit 모든 요청에 인증 정보를 담지 않는다
  • 만약 credentials 옵션값인 same-origin 이나 include와 같은 옵션을 사용하여 리소스 요청에 인증 정보가 포함된다면, 브라우저는 다른 출처의 리소를 요청할 때 Access-Control-Allow-Origin만 확인하는 것이 아니라 다른 조건을 추가로 검사
    • credentials 옵션을 사용하여 요청에 인증정보가 담겨있는 상태에서 다른 출처의 리소스를 요청하게 되면, 브라우저는 CORS정책 위반 여부를 검사하는 룰에 다음 두가지를 추가
      1. Access-Control-Allow-Origin 에는 모든 요청을 허용하는 *  사용할 수 없으며, 명시적인 URL이어야 한다
      2. 응답 헤더에는 반드시 Access-Control-Allow-Credentials: true가 존재해야 한다
    • 인증된 요청 역시 예비 요청처럼 Preflight가 먼저 일어난다.

CORS 의 필요성

=> 적합한 프론트엔드 서버의 요청만 받을 것이다! 라는 설정.

 

=> 에러가 정말 많이 난다..

 


스프링의 CORS? 스프링 시큐리티의 CORS?

스프링에서 cors설정을 해야하는지, 스프링 시큐리티에서 cors설정을 해야하는지 헷갈릴 것이다.

스프링과 스프링 시큐리티를 함께 사용중이라면 CORS설정은 두 가지 방법으로 할 수 있다.

  1. 스프링에서 WebConfigurerAdapter를 상속하는 클래스에 addCorsMappings메소드를 재정의한다.
  2. 스프링 시큐리티에서 CorsConfigurationSource 클래스를 만들고 내부에 CorsConfiguration을 세팅한다.

둘 중 하나를 하더라도 스프링 시큐리티를 사용중이라면 WebSecurityConfigurerAdapter를 상속하는 클래스에서 configure를 재정의하여 cors()를 사용해야한다.

1번과 같이 선언하였다면 스프링 시큐리티에서는 스프링에 선언된 CORS구성을 활용하기 때문에 따로 CORS관련 필터를 정의할 필요가 없다.

 

출처 : https://emgc.tistory.com/131

 

[Spring] 스프링의 CORS설정

목적 CORS를 이해하여 서로 다른 출처간의 자원공유를 원활히 하기 위함 목차 CORS란 스프링의 CORS설정 스프링 시큐리티의 CORS설정 스프링의 CORS? 스프링 시큐리티의 CORS? 1. CORS란? CORS란Cross-Origin Re

emgc.tistory.com


CORS 설정 방법
: 스프링에서의 설정

1. WebMvcConfigurer 이용하여 cors를 spring life cycle에 등록

[CorsConfiguration] 클래스 생성

@EnableWebMvc
@Configuration
public class CorsConfiguration implements WebMvcConfigurer {
  // cors 정책에서 벗어나기 위한 구성
  @Override
  public void addCorsMappings(final CorsRegistry registry) {
    registry.addMapping("/**")
        .allowedMethods("*")
        .allowedHeaders("*")
        .allowedOrigins("*")
        .exposedHeaders("*")
        .allowCredentials(true); // 내 서버가 응답할 때 json을 자바스크립트에서 처리할 수 있게 할지를 설정하는 것. false로 하면 자바스크립트로 요청했을때 오지 않음.
  }
}

[SecurityConfiguration]

 

설정 추가!


2. filter로 cors 설정을 한 후 빈으로 등록, 시큐리티 설정에서 필터로 등록

[CorsConfiguration] 클래스 생성

@Configuration
public class CorsConfiguration {

  @Bean
  public CorsFilter corsFilter() {
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    org.springframework.web.cors.CorsConfiguration config = new org.springframework.web.cors.CorsConfiguration();
    config.setAllowCredentials(true);
    config.addAllowedOriginPattern("*");
    config.addAllowedHeader("*");
    config.addAllowedMethod("*");

    // package가 추가 되거나 변경 되면 변경
    source.registerCorsConfiguration("/**", config);
    return new CorsFilter(source);
  }
}

[SecurityConfiguration]


3. 시큐리티 단에서 모두 설정

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    @Bean
    public WebSecurityCustomizer ignoringCustomizer() { return (web) -> web.ignoring().antMatchers("/h2-console/**","/ws"); }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.cors();
        http.cors().configurationSource(request -> {
            var cors = new CorsConfiguration();
            cors.setAllowedOrigins(List.of(","http://localhost:3000","https://www.zzz.net")); // 허용할 URL
            cors.setAllowedMethods(List.of("GET","POST", "PUT", "DELETE", "OPTIONS")); // 허용할 Http Method
            cors.setAllowedHeaders(List.of("*")); // 허용할 Header
            cors.addExposedHeader("Authorization");
            cors.addExposedHeader("Refresh-Token");
            cors.setAllowCredentials(true);
            return cors;
        });

        http.csrf().disable()
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .authorizeRequests()
//                .requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
            .antMatchers("/**").permitAll()
            .antMatchers("/h2-console/**","/ws").permitAll()
            .anyRequest().authenticated()
            .and()
            .exceptionHandling()
            .authenticationEntryPoint(new CustomAuthenticationEntryPoint())
            .and()
            .apply(new JwtSecurityConfig(SECRET_KEY, tokenProvider, userDetailsService));
        return http.build();
    }
}

CORS 에러

2022.09.07 - [🚨에러노트] - [22.09.06] @EnableWebMvc 로 출력값 에러

 

[22.09.06] @EnableWebMvc 로 출력값 에러

문제상황 CORS 를 설정한 이후부터 json 형식 중 시간이 저렇게 뜨기 시작했다. 제일 마지막에 추가 했던 부분이 이 부분이므로 일단 @EnableWebMvc에 대해서 알아야 할 것 같다 2022.09.07 - [SPRING] - [SPIRI.

wearegolden.tistory.com

 

2022.10.17 - [🚨에러노트] - [Solved][CORS] Access to XMLHttpRequest at 'https://...' from origin 'https://...' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested reso..

 

[Solved][CORS] Access to XMLHttpRequest at 'https://...' from origin 'https://...' has been blocked by CORS policy: Response to

문제상황 무사히 배포를 한 뒤에 프론트와 연결하자마자 뜬 에러. 해결방법 기존에 했던 cors설정을 다 뒤엎었다. [CorsConfig] @Configuration public class CorsConfig { @Bean public CorsFilter corsFilter()..

wearegolden.tistory.com

 


CORS 요청 확인 방법

git bash를 이용해서

 

curl \
--verbose \
--request OPTIONS \
'http://localhost:8080' \
--header 'Origin: http://localhost:3000' \
--header 'Access-Control-Request-Headers: Origin, Accept, Content-Type' \
--header 'Access-Control-Request-Method: GET'

입력하면

 

이렇게 200이 뜨면 잘 연결이 된 것.

 

두가지 경우 모두 200 확인했다.

 

+ 나는 추가로 재배포 한뒤 찍히는 log back 값을 보았다. 200ok 확인

 

아니면 cors test 사이트에서 시도해보아도 좋다.

 

 


참고 블로그