본문 바로가기
일기/항해99

[항해99 6기] 미니 프로젝트 느낀점

by 고구밍 2022. 4. 10.

배운점

더보기

CORS에 대한 기본적인 내용

이렇듯 우리가 겪는 CORS 관련 이슈는 모두 CORS 정책을 위반했기 때문에 발생하는 것이다. 개발하는 입장에서는 저 정책 때문에 신경써야 하는 것들이 늘어나니 귀찮을 수도 있지만, 사실 CORS라는 방어막이 존재하기 때문에 우리가 이 곳 저 곳에서 가져오는 리소스가 안전하다는 최소한의 보장을 받을 수 있는 것이다.

CORS는 Cross-Origin Resource Sharing의 줄임말로, 한국어로 직역하면 교차 출처 리소스 공유라고 해석할 수 있다. 여기서 “교차 출처”라고 하는 것은 “다른 출처”를 의미하는 것인데, 아무래도 Cross라는 영단어가 가지는 뉘앙스가 한국어와 조금은 다르다보니 CORS를 그대로 직역한 교차 출처 리소스 공유라는 말만 보고는 어떤 의미인지 감을 잡기가 조금은 어려운 것 같다.

그래서 필자는 조금 더 쉬운 이해를 위해 교차 출처라는 말 대신 “다른 출처”라는 단어를 사용해서 이 포스팅을 풀어나갈까 한다.

일단 다른 출처간의 리소스 공유에 대해서 알아보기에 앞서 간단하게 이 출처(Origin)라는 것이 정확히 뭘 의미하는지부터 한번 짚고 넘어가도록 하자.

 

 

출처(Origin)가 무엇인가요?

서버의 위치를 의미하는 https://google.com과 같은 URL들은 마치 하나의 문자열 같아 보여도, 사실은 여러 개의 구성 요소로 이루어져있다.

 

 

이때 출처는 Protocol과 Host, 그리고 위 그림에는 나와있지 않지만 :80, :443과 같은 포트 번호까지 모두 합친 것을 의미한다. 즉, 서버의 위치를 찾아가기 위해 필요한 가장 기본적인 것들을 합쳐놓은 것이다.

또한 출처 내의 포트 번호는 생략이 가능한데, 이는 각 웹에서 사용하는 HTTP, HTTPS 프로토콜의 기본 포트 번호가 정해져있기 때문이다. 

 

그러나 만약 https://google.com:443과 같이 출처에 포트 번호가 명시적으로 포함되어 있다면 이 포트 번호까지 모두 일치해야 같은 출처라고 인정된다. 하지만 이 케이스에 대한 명확한 정의가 표준으로 정해진 것은 아니기 때문에, 더 정확히 이야기하자면 어떤 경우에는 같은 출처, 또 어떤 경우에는 다른 출처로 판단될 수도 있다. (진리의 케바케)

우리는 브라우저의 개발자 도구의 콘솔에서 Location 객체가 가지고 있는 origin 프로퍼티에 접근함으로써 손 쉽게 어플리케이션이 실행되고 있는 출처를 알아낼 수도 있다.

 

 

SOP(Same-Origin Policy)

웹 생태계에는 다른 출처로의 리소스 요청을 제한하는 것과 관련된 두 가지 정책이 존재한다. 한 가지는 이 포스팅의 주제인 CORS, 그리고 또 한 가지는 SOP(Same-Origin Policy)이다.

 

같은 출처와 다른 출처의 구분

사실 두 개의 출처가 서로 같다고 판단하는 로직 자체는 굉장히 간단한데, 두 URL의 구성 요소 중 Scheme, Host, Port, 이 3가지만 동일하면 된다.

https://evan-moon.github.io:80라는 출처를 예로 들면 https:// 이라는 스킴에 evan-moon.github.io 호스트를 가지고 :80번 포트를 사용하고 있다는 것만 같다면 나머지는 전부 다르더라도 같은 출처로 인정이 된다는 것이다.

필자의 블로그 출처인 https://evan-moon.github.io와 같은 출처로 인정되는 예시는 대략 이런 느낌이다.

URL같은 출처이유
https://evan-moon.github.io/about O 스킴, 호스트, 포트가 동일
https://evan-moon.github.io/about?q=안뇽 O 스킴, 호스트, 포트가 동일
https://user:password@evan-moon.github.io O 스킴, 호스트, 포트가 동일
http://evan-moon.github.io X 스킴이 다름
https://api.github.io X 호스트가 다름
https://evan-moon.naver.com X 호스트가 다름
https://evan-moon.github.com X 호스트가 다름
https://evan-moon.github.io:8000 ? 브라우저의 구현에 따라 다름

맨 마지막에 있는 케이스의 경우, 만약 출처에 https://evan-moon.github.io:80처럼 포트 번호가 명시되어 있다면 명백하게 다른 출처로 인정되는 부분이지만, 예시로 든 출처의 경우 포트 번호가 포함되지 않았기 때문에 판단하기가 애매하다. RFC 6454의 Comparing Origins 섹션에는 “만약 출처가 스킴/호스트/포트의 삼중 체계라면…” 이라는 전제가 붙어있기 때문에 어떻게 해석하냐에 따라 구현이 달라질 수 있기 때문이다.

그래서 이런 경우에는 각 브라우저들의 독자적인 출처 비교 로직을 따라가게 된다.

 

 


CORS를 해결할 수 있는 방법

지금까지 CORS가 무엇인지, 어떤 상황에서 CORS 정책이 적용되고 위반되는 것인지 알아봤다면 이번 섹션에서는 실질적으로 CORS 정책 위반으로 인한 문제가 발생했을 경우에 해결할 수 있는 방법을 알아보도록 하자.

Access-Control-Allow-Origin 세팅하기

CORS 정책 위반으로 인한 문제를 해결하는 가장 대표적인 방법은, 그냥 정석대로 서버에서 Access-Control-Allow-Origin 헤더에 알맞은 값을 세팅해주는 것이다.

이때 와일드카드인 *을 사용하여 이 헤더를 세팅하게 되면 모든 출처에서 오는 요청을 받아먹겠다는 의미이므로 당장은 편할 수 있겠지만, 바꿔서 생각하면 정체도 모르는 이상한 출처에서 오는 요청까지 모두 받아먹겠다는 오픈 마인드와 다를 것 없으므로 보안적으로 심각한 이슈가 발생할 수도 있다.

그러니 가급적이면 귀찮더라도 Access-Control-Allow-Origin: https://evan.github.io와 같이 출처를 명시해주도록 하자.

이 헤더는 Nginx나 Apache와 같은 서버 엔진의 설정에서 추가할 수도 있지만, 아무래도 복잡한 세팅을 하기는 불편하기 때문에 소스 코드 내에서 응답 미들웨어 등을 사용하여 세팅하는 것을 추천한다. Spring, Express, Django와 같이 이름있는 백엔드 프레임워크의 경우에는 모두 CORS 관련 설정을 위한 세팅이나 미들웨어 라이브러리를 제공하고 있으니 세팅 자체가 어렵지는 않을 것이다.

Webpack Dev Server로 리버스 프록싱하기

사실 CORS를 가장 많이 마주치는 환경은 바로 로컬에서 프론트엔드 어플리케이션을 개발하는 경우라고 해도 과언이 아니다. 백엔드에는 이미 Access-Control-Allow-Origin 헤더가 세팅되어있겠지만, 이 중요한 헤더에다가 http://localhost:3000 같은 범용적인 출처를 넣어주는 경우는 드물기 때문이다.

프론트엔드 개발자는 대부분 웹팩과 webpack-dev-server를 사용하여 자신의 머신에 개발 환경을 구축하게 되는데, 이 라이브러리가 제공하는 프록시 기능을 사용하면 아주 편하게 CORS 정책을 우회할 수 있다.

module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: 'https://api.evan.com',
        changeOrigin: true,
        pathRewrite: { '^/api': '' },
      },
    }
  }
}

이렇게 설정을 해놓으면 로컬 환경에서 /api로 시작하는 URL로 보내는 요청에 대해 브라우저는 localhost:8000/api로 요청을 보낸 것으로 알고 있지만, 사실 뒤에서 웹팩이 https://api.evan.com으로 요청을 프록싱해주기 때문에 마치 CORS 정책을 지킨 것처럼 브라우저를 속이면서도 우리는 원하는 서버와 자유롭게 통신을 할 수 있다. 즉, 프록싱을 통해 CORS 정책을 우회할 수 있는 것이다.

웹팩의 함정 카드 리버스 프록싱(이)가 발동했다!

혹시 webpack-dev-middleware와 Node 서버의 조합으로 개발 환경을 직접 구축했더라도 http-proxy-middleware 라이브러리를 사용하면 손쉽게 프록시 설정을 할 수 있으니 걱정하지말자. (webpack-dev-server도 내부적으로는 어차피 http-proxy-middleware를 사용한다)

다만 이 방법은 실제 프로덕션 환경에서도 클라이언트 어플리케이션의 소스를 서빙하는 출처와 API 서버의 출처가 같은 경우에 사용하는 것이 좋다. 물론 로컬 개발 환경에서야 웹팩이 요청을 프록싱해주니 아무 이상이 없겠지만, 어플리케이션을 빌드하고 서버에 올리고 나면 더 이상 webpack-dev-server가 구동하는 환경이 아니기 때문에 프록싱이고 나발이고 이상한 곳으로 API 요청을 보내기 때문이다.

예를 들어 API 서버의 출처는 https://api.evan.com이고 클라이언트 어플리케이션을 서빙하는 서버의 출처는 https://www.evan.com이라면, 다음과 같은 상황이 발생한다는 것이다.

fetch('/api/me');
로컬환경에서는...
GET https://api.evan.com/me 200 OK

실제 서버에는 프록싱 로직이 없음...
GET https://www.evan.com/api/me 404 Not Found

물론 비즈니스 로직 내에서 process.env.NODE_ENV와 같은 빌드 환경 변수를 사용하여 분기 로직을 작성하는 방법도 있지만, 개인적으로 비즈니스 로직에 이런 개발 환경 전용 소스가 포함되는 것은 별로 좋지 않다고 생각해서 피하는 편이다.

요청을 img 태그에 넣으면 어떨까?

필자는 앞서 SOP(Same-Origin Policy) 정책에는 다른 출처의 리소스에 접근할 수 있는 몇 가지 예외조항이 존재하고, 그 중 하나가 CORS 정책을 지킨 요청이라고 이야기했었다. 그리고 CORS 정책을 지킨 요청을 제외한 SOP의 예외 조항은 실행 가능한 스크립트, 렌더될 이미지, 스타일 시트 정도가 있다.

그렇다면 다른 예외 조항이 적용된 요청을 보내면 CORS를 우회할 수 있지 않을까…? 이렇게 말이다!

<img src="https://evanmoon.tistory.com/rss">
<script src="https://evanmoon.tistory.com/rss"></script>

물론 이런 식으로 접근하면 CORS를 위반하지 않고 요청 자체는 성공한다. 그리고 브라우저의 개발자 도구의 네트워크 탭에서 이 요청들의 헤더를 자세히 살펴보면 Sec-Fetch-Mode: no-cors라는 값이 포함되어 있는 것을 볼 수 있다.

Sec-Fetch-Mode 헤더는 요청 모드를 설정하는 필드인데, 브라우저는 이 필드의 값이 no-cors인 경우에는 다른 출처라고 해도 CORS 정책 위반 여부를 검사하지 않는다. 하지만 한 가지 슬픈 점은 브라우저가 이 헤더에 값이 포함된 요청의 응답을 자바스크립트에게 알려주지 않는다는 것이다. 즉, 우리는 코드 레벨에서 절대 이 응답에 담긴 내용에 접근할 수가 없다.

필자도 이게 진짜 방법이 없는건지 궁금해서 이것저것 시도해보았는데 결과적으로 전부 실패했다. 그러니 어찌어찌 CORS를 우회하려는 시도는 그냥 깔끔하게 포기하고 똑똑한 아저씨들이 시키는 대로 CORS 정책을 지키도록 하자.

 


 

아무래도 CORS 정책 위반으로 인해 생기는 문제를 해결할 때 가장 번거로운 점은 문제를 겪는 사람과 문제를 해결해야하는 사람이 다르다는 것이다.

앞서 이야기했듯 CORS 정책은 브라우저의 구현 스펙이기 때문에 정책 위반으로 인해 문제를 겪는 사람은 대부분 프론트엔드 개발자이지만, 정작 문제를 해결하기 위해서는 백엔드 개발자가 서버 어플리케이션의 응답 헤더에 올바른 Acccess-Control-Allow-Origin이 내려올 수 있도록 세팅해줘야 하기 때문이다.

물론 필자가 이야기했던 webpack-dev-server의 프록싱 옵션을 사용하여 자체적으로 해결하는 방법도 있지만, 이 방법은 로컬 개발 환경에서만 통하는 방법인데다가, 근본적인 문제 해결 방법이 아니기 때문에 결국 운영 환경에서 CORS 정책 위반 문제를 해결하기 위해서는 백엔드 개발자의 도움이 필요할 수 밖에 없다.

사실 CORS 정책 위반을 해결하는 방법 자체가 그렇게 어렵고 복잡한 편은 아니라서 프론트엔드 개발자나 백엔드 개발자 중 한 명이라도 이러한 정책에 대해서 잘 알고 있는 경우라면 생각보다 빠르고 수월하게 문제를 해결할 수 있다.

 

https://evan-moon.github.io/2020/05/21/about-cors/

 

CORS는 왜 이렇게 우리를 힘들게 하는걸까?

이번 포스팅에서는 웹 개발자라면 한번쯤은 얻어맞아 봤을 법한 정책에 대한 이야기를 해보려고 한다. 사실 웹 개발을 하다보면 CORS 정책 위반으로 인해 에러가 발생하는 상황은 굉장히 흔해서

evan-moon.github.io

https://developer.mozilla.org/ko/docs/Web/HTTP/CORS

 

교차 출처 리소스 공유 (CORS) - HTTP | MDN

교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)는 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라

developer.mozilla.org

 

 

느낀 점

더보기

스프링 심화과정에서 순한맛을 해보면서,

 

두가지 Entity(테이블)를 OneToMany 등과 같은 관계형으로 연결하는 것이 신기하였습니다.

 

비록 도움을 받아서 3단계를 하였지만, 꼭 내 것을 만들어야되는 개념이라고 생각합니다.

 

그리고 금요일부터 처음으로 미니프로젝트를 시작하였습니다.

 

할 수 있는 부분이 부족해서, 간단한 게시글과 댓글 구현을 수민님과 같이 구현해 보았는데,

 

많은 도움을 받아서 아직까지는 탈이 없는 것 같습니다.

 

앞으로 경험하게될 CORS를 대비해서 무엇인지 알아보았습니다.

 

https://velog.io/@16min99/ERD-API-%EC%84%A4%EA%B3%84

 

[Film] ERD , API 설계

Post(필름)은 크게 미리보기, 자세한내용 두가지로 나누어져서 다뤄진다. 즉 미리보기 단위, 자세한내용단위로 조회를 하는 경우가 대부분이라고 판단하였다.따라서 Posts 테이블에서 미리보기 정

velog.io

 

 

 

 

https://goguming2.tistory.com/46

 

화이트 큐브 (챌린저스 어플)협력사 세션

더보기 https://chlngers.com/ 몸과 마음이 건강해지는 습관만들기 대한민국 1등 건강습관 앱, 챌린저스 chlngers.com 건강한 습관 앱 챌린저스, 화이트큐브가 함께 할 동료를 찾습니다. (oopy.io) 건강한 습

goguming2.tistory.com

https://goguming2.tistory.com/45

 

[항해99 6기 W5] 스프링 심화과정 느낀점

한 주 동안 배운것 더보기 개인 과제로 "배달서비스 API를 구성하기"를 하였고, 순한 맛 매운 맛 극강의 매운 맛 1. 금요일 ~ 일요일까지 제시된 API 명세표를 보고 스스로 구현하고자 하고자 하였

goguming2.tistory.com