본문 바로가기
우아한테크코스/Level3

CORS에서 감자로 살아남기 🥔

by kriorsen 2024. 7. 29.

📌 CORS == Cross-Origin Resource Sharing


"감자, CORS가 뭐야?"
"Cross-Origin Resource Sharing"
"Cross-Origin Resource Sharing이 뭔데?"
"CORS"

 

지난 주에 프론트엔드가 CORS 문제 해결 요청을 해서, 백호와 CORS가 무엇인지에 대해 아주 유익한(?) 대화를 나눴다. 크게 어려운 내용은 아니었는데, 기존에 내가 이 개념을 완전 잘못 이해하고 있어서 다시 올바르게 정정하기까지 조금 헤맸던 것 같다 😓

 

📌 의문1: CORS는 왜 이름값을 못하는가


이름이 resource sharing인데 왜 sharing을 안 한다는 거죠?

 

우선 CORS 중 Origin에 대해 알아봐야 하는데, 이는 URL에서 프로토콜, 도메인 그리고 포트를 포함한 개념을 의미한다. 즉, https://haengdong.comhttp://haengdong.com도 서로 다른 출처로 인식한다. Cross-Origin Resource Sharing을 직역하면 교차 출처 자원 공유라는 의미인데, 왜 웹 브라우저에서 서버로 요청을 보내는 것에 제약이 생기는 건지 의문이 들었다.

 

정확히 CORS는 이 제약에 대한 해결책이고, 요청을 보낼 수 없는 문제는 브라우저의 동일 출처 정책(Same-Origin Policy, SOP)으로 인한 것이었다. SOP는 특정 Origin에서 불러온 문서나 스크립트가 다른 출처에서 가져온 리소스와 상호 작용하는 것을 제한하는 보안 메커니즘이다.

 

SOP 사용 시의 이점은 다음과 같다:

  • 사용자의 민감한 데이터를 악의적인 사이트로부터 보호
  • XSS와 CSRF 공격 방지
  • 다른 사이트에 사용자의 브라우징 활동 및 개인 정보가 유출되지 않도록 방지

 

📌 의문2: 왜 프론트에서만 문제가 발생할까


Postman에서는 잘 되는데... 프론트에서 코드 잘못 짠 거 아닌가요?

라고 할 뻔

 

앞서 설명된 바와 같이, SOP는 사용자가 웹 애플리케이션을 사용할 때 발생할 수 있는 보안 문제를 방지하기 위해 설계된 것이며, 주로 스크립트 기반의 공격을 막기 위함이다. 반면, Postman과 같은 API 클라이언트는, 개발자들이 API를 테스트하기 위해 사용하는 도구이기에, 교차 출처 요청을 막는 보안 정책이 필요하지 않다. 따라서 Postman을 통해서는 다른 출처의 API에 자유롭게 요청을 보낼 수 있다.

 

이러한 차이로 인해 Postman에서는 요청과 응답을 문제 없이 주고 받던 API도 브라우저에서는 잘 작동하지 않았던 것이다.

 

📌 의문3: 어떻게 리버스 프록시가 해결책


 reverse proxy 너 뭐 돼...?

 

SOP에 대해 정확히 알지 못했을 때는 리버스 프록시로 CORS문제를 해결할 수 있다는 것 또한 의문이었다. 이러나 저러나 다른 출처에서 요청이 오는 건 똑같을 텐데 리버스 프록시가 뭘 할 수 있다는 건지 이해가 되지 않았다. 그러나 이제는 SOP가 브라우저 기반 정책이고, 이를 해결하기 위해서는 클라이언트에서 요청을 보내는 대상이 같은 출처라고 인식할 수 있도록 속이면 된다는 것을 알게 되었다.

 

예를 들어 클라언트의 출처는 "http://frontend.com"이고 백엔드 출처는 "http://backend.com"이라고 한다면 Nginx 웹 서버를 이용해 리버스 프록시를 다음과 같이 설정할 수 있다.

server {
    listen 80;
    server_name frontend.com;

    location /api/ {
        proxy_pass http://backend.com/;
        proxy_set_header Host backend.com;
    }
}

 

"http://frontend.com/api"로 요청을 전송하면 리버스 프록시 내부적으로 "http://backend.com/api"로 요청을 전달한다.

 

이 방식은 리버스 프록시가 중간 서버 역할을 해서 클라이언트가 동일 출처 정책을 위반하지 않는 것처럼 보이게 만드는 것이다. 백엔드 서버가 직접 노출되지 않고, 추후 로드 밸런싱을 통한 성능 최적화, 그리고 캐싱 설정을 하는 등의 이점이 있다.

 

📌 의문4: 리버스 프록시 열심히 칭찬해 놓고 왜 코드로...


아 몰라 일단 코드로 해결해

 

사실 리버스 프록시 말고 프레임워크에서 제공하는 기능을 통해 CORS 관련 응답 헤더를 설정하여 요청을 주고 받을 수도 있다. 'Access-Control-Allow-Origin'이라는 헤더 속성에 허용할 출처를 지정하면 자원 공유가 가능해진다. 

 

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins(allowedOrigins)
                .allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")
                .allowedHeaders("*")
                .maxAge(maxAge);
    }

 

이 방식의 경우 간단하고 직접적인 해결 방법이라는 장점이 있지만 백엔드 서버가 직접 외부에 노출될 수 있다는 단점이 있고, allowedOrigins를 모든 출처로 설정하면 보안 위험이 증가한다.

 

데모데이까지 얼마 남지 않은 시점에 발생한 이슈라 일단 코드 측면에서 해결을 했고, 아마 다음 스프린트에는 다른 방식으로 변경할 것으로 예상된다 👀