✏️ 2023-01-20 Today I Learn

@mitoconcrete · January 20, 2023 · 7 min read

[회고] 220120 : CORS? CORS!

프론트 작업을 시작하다보니, 이후 서버연결 시 CORS(Cross Origin Resource Sharing) 문제가 발생했다. 스프링에서 allow origin 설정을 해주었는데도, 잘 동작하지 않아서 근본적인 문제를 해결하기 위해 CORS에 대해 간략히 공부해보았다.

  1. SOP(Same Origin Policy)
    최신 브라우저는 같은 Origin 에서만 리소스를 교환할 수 있다는 원칙이 있다. 따라서, http://localhost:8000 의 사이트에서 작업중이라면 http://localhost:8000의 주소를 가진 리소스에서만 정보를 가져다 쓸 수 있다. 이는 XSS(Cross Site Scripting), CSRF(Cross Site Request Forgery)와 같은 보안공격이 다른 리소스에서 요청을 주고받는 것이 허용됨에 따라 무분별하게 일어나기 때문에 브라우저에서 지원하게 되었다.

    하지만, 현실적으로 하나의 웹사이트에서 하나의 Origin만 사용되는데는 무리가 있다. 따라서, CORS정책을 통해 보안성을 유지하며, 다른 출처를 가진 곳에서도 리소스를 공유하도록 허용했다.

  2. CORS 에러는 어디서 발생되는 것인가?
    CORS 에러는 브라우저에서 발생한다. 그럼 우리가 서버에 설정하는 것은 무엇인가..? 라는 질문을 할 수 있다. 첫 의문과 두번째 의문은 CORS의 메커니즘을 알게되면 왜 CORS 에러를 브라우저가 내고, 내가 왜 서버에다 무언가를 설정해야 그 문제가 해결되는지 이해할 수 있다.
  3. CORS 의 메커니즘

    1. 클라이언트는 서버에게 Preflight (OPTION)를 보내어 내가 어떤 메서드(GET,POST,DELETE,PUT..) 를 이용해 요청을 하려고하는지 미리 알린다.
    2. 서버는 자신이 허용하는 메소드를 클라이언트에 전달하고, 또한, 자신이 허용하는 Origin에 대한 정보도 함께 보내준다.
    3. 클라이언트는 Preflight에 대한 응답을 통해 서버가 어떤 메서드를 허용하고 있고, 자신이 해당 서버에 요청할 권한이 있는지 확인한다.
    4. 이 때, 서버가 허용하는 Origin과 클라이언트의 Origin이 다를 때 해당 클라이언트가 브라우저라면, 브라우저는 자동적으로 CORS에러를 발생시켜 Preflight다음 발생하는 본요청을 취소시켜버린다. 이로인해 브라우저는 보여지기에 위험한 요소를 보여주지 않고 보안을 유지 할 수 있다.

    서로가 아는상태인 것만 아는 상태에서 정보를 주고받도록 하는게 해당 규칙의 원칙이기 때문에, 서버에서는 클라이언트의 주소를 허용하여 Preflight요청시 '나 너가 아는 놈이야' 라는 시그널을 브라우저로 보내 CORS에러를 방지시키는 것이다.

  4. Postman은 왜 CORS가 발생을 안함..?
    브라우저가 아닌 서버-서버간의 요청주고받기는 CORS를 굳이 신경쓰지 않아도된다. Postman은 브라우저가 아니기 때문에, CORS를 우회 할 수 있는 것 이다.
  5. CORS를 해결하는 방법

    1. 프록시 서버
      서버와 서버간의 통신은 CORS를 발생시키지 않는다는 것을 이용한 기술이다. 이것은 개인적으로 생각했을 때, 꼼수같다는 느낌을 많이 받았다. 브라우저에서는 조금의 불편함이 있더라도, 신뢰하는 정보만을 보여줘야하기 때문에, 우회하는 기술을 사용하는 것은 개인적으로 선호하지 않는다.
    2. 서버에 ACCESS-ORIGIN 허용
      각 서버 프레임워크는 허용할 클라이언트의 주소들을 설정할 수 있는 셋팅들이 있다. 따라서, 서버에 이런 설정을 하여 브라우저에게 신뢰를 맺고 데이터를 전달할 수 있다. 서버또한 자신의 리소스에 접근할 클라이언트를 정의할 수 있기 때문에 상호간 보안이 유지되는 방법이라고 생각한다. 개인적으로는 이 방법을 선호한다.
  6. 스프링에서 CORS 해결하기

    1. @CrossOrigin
      특정 컨트롤러 메소드의 위에 붙혀서 허용할 Origin 을 정할 수 있다. 이는 매번 모든 컨트롤러에 origin 을 설정해줘야한다는 단점이있다.
    2. WebMvcConfigurer
      main 함수에서 WebMvcConfigurer를 impl한 Configure을 이용하여 아래와 같이 사용이 가능하다.
       @Configuration
    
    public class WebConfig implements WebMvcConfigurer {
    
       @Override
       public void addCorsMappings(CorsRegistry registry) {
           registry.addMapping("/**")
                   .allowedOrigins("http://localhost:8080")
                   .allowedMethods(HttpMethod.GET.name());
       }
    
    }
    1. Spring Security
      시큐리티가 있으면, 가끔 어떤 메소드에 대해서 CORS에러를 발생시킨다. 따라서, 시큐리티 설정에 들어가서 명확하게 정의를 해주면 된다. 아래 코드가 있다면 WebMvcConfigurer 을 굳이 사용하지 않아도 된다.
           ...
       http.cors().configurationSource(request -> {
           var cors = new CorsConfiguration();
           cors.setAllowedOrigins(List.of("http://localhost:3000")); // 이 주소에 한해 오는 요청을 허용
           cors.setAllowedMethods(List.of("*"));   // 모든 메소드를 허용
           cors.setAllowedHeaders(List.of("*"));   // 모든 헤더를 혀용
           cors.addExposedHeader("Authorization");  // 클라이언트로 보낼 헤더를 셋팅한다. 이게 없으면 나머지는 다 가리고 Content-Type만 전송한다.
           return cors;
       });
       ...

CORS에 대해서 개념을 다시한번 쯤 잡을 필요가 있었는데, 이렇게 다잡을 수 있어서 좋았다.

@mitoconcrete
어제보다 조금 더 성장하기 위해 기록합니다.