CORS 정책

김태환·2024년 12월 13일

SKUPLATE를 배포하고 Mixed Content 에러까지 해결한 이후에는 CORS 정책 문제로 브라우저 콘솔이 다시 한 번 빨개졌었다.

물론 데이터 역시 뜨지도 않았다. 이 프로젝트에서만 만났으면 문제가 없었을텐데, 이후에 MLOps 프로젝트, 온라인 서점 프로젝트, 심지어 이 블로그를 재배포한 오늘, 또 마주했다.

이 CORS 정책, 뭐길래 자꾸 뜨니?

Access to XMLHttpRequest at 'https://AWS에서-받은-SKUPLATE-백엔드-인스턴스-도메인'
from origin 'https://www.skuplate.com' has been blocked
by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

당시 마주했던 에러 문구이다.

내가 요청한 리소스에 대해서, 즉, 서버로부터 받은 응답에서 브라우저가 Access-Control-Allow-Origin 헤더가 찾지 못했으니 차단했다고 알려주는 것이다.

다시 말하자면 "백엔드에서 프론트엔드 서버에 대한 접근 허가를 하사하지 아니 했으니 찾지 말라는 것"

둘 다 내가 배포했는데, 이런 말을 들으니 억울했었다.

CORS란?

먼저, Same-Origin Policy를 살짝이라도 알아야 한다. 이는 브라우저의 동일 출처 정책으로, 다른 출처에서 데이터 접근을 하려고 하면 해당 접근을 차단하는 것이다. 절대로.
악성 사이트가 사용자 데이터(쿠키 등)에 무단으로 접근하는 것을 막으려고 설계되었다고 한다.
CORS 정책은 Cross Origin Resource Sharing의 약자로, 브라우저에서 서로 다른 출처(Origin) 간의 리소스 공유를 허용하거나 제한하는 것이다.

SOP와 비슷한 역할이긴 하나, SOP의 제한보다는 완화된 것을 볼 수 있다.

출처 Origin
여기서 출처(Origin)은 인터넷 상에서, 요청을 보낸 원서버, 그 요청을 받는 원서버를 말한다. 네트워크 상에서 전달해주는 서버들은 가리키지 않는다. 이 Origin은 URL의 Protocol, Host, Port를 조합하여 정의하기에 출처를 비교한다는 것은 이 값들을 비교한다는 것이다.
하나라도 다르면, 그 둘은 다른 출처로 간주한다.
ex) https(protocol) + www.skuplate.com(host) + 443(port)

Mixed Content 에러가 발생했을 때도 CORS 에러는 발생했었다. 도메인도 다르고, 프로토콜도 달랐기에...

이거 왜 필요한데?

CORS 정책이 필요한 이유

  • 보안
    내가 허락하지도 않았는데 내 서버의 데이터를 마음대로 꺼내갈 수도 있다.
    그리고, 악성 사이트(허락하지 않은 출처)에서 쿠키 등을 이용하여 로그인한 사용자인 척 민감한 데이터 접근, 기타 공격 등을 요청하는 것을 방지할 수 있다.
  • 데이터 공유
    이러한 보안 속에서도, 허락된 서버들과는 데이터를 주고 받을 수 있어야 한다.
    ex) 프론트엔드와 백엔드가 서로 다른 인스턴스에서 작동하는 경우 CORS 정책을 설정해야 한다.

CORS 동작 원리

  1. 단순 요청 Simple Request
    • CORS 정책을 따르는 가장 기본적인 요청 방식
    • 별도의 검증 없이 바로 요청을 실행하기에 클라이언트와 서버 간 통신이 효율적으로 처리된다
    • 조건:
      • HTTP 메서드 제한 (GET, POST, HEAD 메서드만 허용)
      • 허용된 요청 헤더만 포함 (Accept, Accept-Language, Content-Language, Content-Type, 커스텀 헤더 안 됨!!)
        Content-Type: application/x-www-form-urlencoded, multipart/form-data, text/plain 중 하나
      • Content-Type과 동일하기 Request Body도 제한된다.
  2. 사전 요청 Preflight Request
    실제 요청을 보내기 전 OPTIONS 메서드를 사용해서 서버에 "이 요청 처리 가능?"을 먼저 묻는다.
    OK? 그 때 실제 요청을 보낸다.
    • Simple Request 조건이 아니면 Preflight Request다
      PUT, DELETE 등의 HTTP 메서드, JSON 데이터 전송, Authorization 같은 커스텀 헤더 등
    • 서버는 요청을 검토하고, 허용 여부를 CORS 헤더(Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers)와 함께 응답한다
    • 서버가 허용 안 하면? 브라우저가 요청을 차단

Simple Request와 Preflight Request의 조건을 비교해보면 Preflight Request를 통해서 사용자의 민감한 데이터가 털릴 위험이 더 높은 것에 재차 요청 검토를 통해 보안을 강화하고 있다.

CORS 관련 헤더들

  1. Access-Control-Allow-Origin
    • 요청을 허용할 Origin 명시
    • 이게 없으면 브라우저에서는 요청을 보낸 출처는 허용되지 않았다고 간주한다.
      ex) Access-Control-Allow-Origin: https://skuplate.com
      *이라고 해두면, 들어오는 요청 그대로 모두 다 허용하는 것이다
  2. Access-Control-Allow-Methods
    • 허용할 HTTP 메서드 명시
      ex) Access-Control-Allow-Methods: GET, POST, DELETE
  3. Access-Control-Allow-Headers
    • 요청에서 허용할 Header 명시
      ex) Access-Control-Allow-Headers: Content-Type, Authorization
  4. Access-Control-Allow-Credentials
    • 쿠키, 인증 정보 등을 포함한 요청 허용 여부
      ex) Access-Control-Allow-Credentials: true

다시 SKUPLATE 프로젝트 당시 받았던 CORS 정첵 관련 에러 메시지를 보면 요청을 보낸 출처와 받은 출처가 서로 다른 것을 알 수 있다.

프론트엔드는 도메인을 적용하여 https://www.skuplate.com 인 반면 백엔드는 도메인을 또 살 필요 없겠다 싶어 AWS에서 준 그대로 aws-ec2.pacific.titanic-31svad4938uf.com(대충 이런 느낌이었던...) 사용했었다.

그래서 다음과 같이 백엔드(Express)에서 Access-Control-Allow-Origin 설정을 추가하여 CORS 정책에 위반되지 않도록 하여 문제를 해결하였다.

const corsOptions = {
  origin: 'https://skuplate.com',
  credentials: true,
};
app.use(cors(corsOptions));

CORS 정책 설정만 해주면 다시는 안 만나는 것인가? NOOOOOOOOOOOOOOOOOOOO!!!

백엔드에서 CORS 관련 설정을 하더라도 CORS 정책 문제를 언제든지 만날 수 있다. 😭

Access to XMLHttpRequest at 'https://api.monte-log.com/posts?page=1&limit=7'
from origin 'https://www.monte-log.com' has been blocked
by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

이 블로그를 배포하면서 마주했던 에러 문구인데, 아이러니하게도 나는 백엔드 코드를 작성하면서 가장 먼저 했던 일이 CORS 정책 설정이었다.
처음 배포했을 때부터 발생한 에러는 아니었기에 더 당황스러웠었다. CORS 정책과 관련된 문제였으면 진작 터졌어야하는데 잘 지내다가 CORS 에러 문구를 띄우니까!!!
알고보니 서버로 요청을 보내고 받은 응답에 브라우저가 Access-Control-Allow-Origin 헤더를 찾지 못한 것이 문제였다.
헤더 설정을 안 했나? 아니다.
백엔드 코드가 build는 성공했지만, runtime 에러가 있었고, 배포를 하고 블로그를 실행시켰을 때, 서버에서 응답이 돌아오지 않으니 Access-Control-Allow-Origin 헤더도 오지 못한 것이다.
이 헤더가 없으니 CORS 정책 또한 위반된 것.


결론

CORS 정책, 내 서버를 지키기 위해 있는 것.
누군가 함부로 내 서버에 접근하지 못하도록 함과 동시에 내가 친하게 지내고 싶은 서버와만 연락할 수 있도록 하는 것.
CORS 에러가 떴을 때, CORS 관련 설정값들만 보지 말고, 서버가 응답을 보내긴 하는지도 확인해보자!



참고 및 출처

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

토스 페이먼츠 CORS(교차 출처 리소스 공유)

https://inpa.tistory.com/entry/WEB-%F0%9F%93%9A-CORS-%F0%9F%92%AF-%EC%A0%95%EB%A6%AC-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95-%F0%9F%91%8F

https://velog.io/@effirin/CORS%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80

profile
이로운 개발자

0개의 댓글