CS/네트워크

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

H.E 2023. 7. 9. 18:07

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

CORS(Cross-Origin Resource Sharing)는 출저가 다른 자원들을 공유한다는 뜻으로 한 출저에 있는 자원에서 다른 출처에 있는 자원에 접근하도록 하는 개념 즉 교차되는 출처 자원들의 공유 다른 출처에 있는 자원을 요청한다고 하면 이를 교차 출저 요청이라고 부름

 

교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)는 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제입니다. 웹 애플리케이션은 리소스가 자신의 출처(도메인, 프로토콜, 포트)와 다를 때 교차 출처 HTTP 요청을 실행합니다.
- mdn 설명

 

좀더 설명을 자세히 살펴보면  브라우저에서는 보안적인 이유로 cross-origin HTTP 요청들을 제한함 그래서 cross-origin 요청을 하려면 서버의 동의가 필요함 만약 서버가 동의한다면 브라우저에서는 요청을 허락하고 동의하지 않는다면 브라우저에서 거절 이러한 허락을 구하고 거절하는 메커니즘을 HTTP-header를 이용해서 가능한데 이를 CORS(Cross-Origin Resource Sharing)라고 부름 브라우저에서 cross-origin 요청을 안전하게 할 수 있도록 하는 메커니즘

 

▶ Cross-Origin

cross-origin이란 다음 중 한 가지라도 다른 경우를 말함(즉 3가지가 다 같으면 동일 출처(Origin)인 것)

출처: https://beomy.github.io/tech/browser/cors/

  • 프로토콜(Protocol)
    • ex) http와 https는 프로토콜이 다름
  • 도메인(Host)
    • ex) domain.com과 other-domain.com은 다름
  • 포트 번호(Port)
    • ex) 8080포트와 3000포트는 다름

이렇게 다른 출저 요청일 경우 CORS 정책에 준수하여 요청해야만 정상적인 응답을 받을 수 있음

 

▶ Same Origin Policy(동일 출처 정책)

웹 브라우저에서 보안을 강화하기 위하여 동일한 출처에서만 리소스를 주고 받도록 하는 정책

 

▶ CORS가 필요한 이유

원래는 동일 출처 정책으로 다른 출처에서 리소스를 받아 오는 것이 제한되었지만 다른 출처로부터 리소스를 받아오는 것이 필수 요소가 되었음 하지만 이러한 점은 여러가지 문제점이 발생할 수 있음

예를 들어 모든 곳에서 데이터를 요청할 수 있게 되면 다른 사이트에서 원래 사이트를 흉내낼 수 있게 됨

기존 사이트와 완전히 동일하게 동작하도록 하여 사용자가 로그인을 하도록 만들고 로그인했던 세션을 탈취하여 악의적으로 정보를 추출하거나 다른 사람의 정보를 입력하는 등 공격을 할 수 있음

이렇게 공격을 할 수 없도록 브라우저에서 보호하고 필요한 경우에만 서버와 협의하여 요청할 수 있도록 하기 위해서 안전하게 다른 출처와 리소스를 공유하도록 하는 CORS가 필요

 

▶ CORS 기본 동작

  1. 클라이언트에서 HTTP요청의 헤더에 Origin을 담아 전달
    1. 기본적으로 웹은 HTTP 프로토콜을 이용하여 서버에 요청을 보냄
    2. 이때 브라우저는 요청 헤더에 Origin이라는 필드에 출처를 함께 담아보내게 됨
  2. 서버는 응답헤더에 Access-Control-Allow-Origin을 담아 클라이언트로 전달
    1. 이후 서버가 이 요청에 대한 응답을 할 때 응답 헤더에 Access-Control-Allow-Origin이라는 필드를 추가하고 값으로 '이 리소스를 접근하는 것이 허용된 출처 url'을 내려보냄
  3. 클라이언트에서 Origin과 서버가 보내준 Access-Control-Allow-Origin을 비교
    1. 이후 응답을 받은 브라우저는 자신이 보냈던 요청의 Origin과 서버가 보내준 응답의 Access-Control-Allow-Origin을 비교해본 후 차단할지 말지를 결정
    2. 만약 유효하지 않다면 그 응답을 사용하지 않고 버림(CORS 에러)

 

결론적으로 유효한지는 응답을 받은 브라우저가 자신이 보냈던 요청의 Origin과 서버가 보내준 응답의 Access-Control-Allow-Origin을 비교함 즉 CORS 해결책은 서버에서 Access-Control-Allow-Origin 헤더에 허용할 출처를 기재해서 클라이언트에 응답하면 되는 것

 

▶ CORS 작동 방식

- 단순요청(Simple requests)인 경우

  1. 서버로 요청을 함
  2. 서버의 응답이 왔을 때 브라우저가 요청한 Origin과 응답한 헤더 Access-Control-Request-Headers의 값을 비교하여 유효한 요청이라면 리소스를 응답 만약 유효하지 않은 요청이라면 브라우저에서 이를 막고 에러가 발생

○ Simple requests란?

HTTP method가 다음 중 하나이면서

  • GET
  • HEAD
  • POST

자동으로 설정되는 헤더는 제외하고 설정할 수 있는 다음 헤더(CORS 안전 리스트 헤더)들만 변경하면서

  • Accept
  • Accept-Language
  • Content-Language

Content-Type이 다음과 같은 경우

  • application/x-www-form-urlencoded
  • multipart/form-data
  • text/plain

이렇게 까다로운 조건을 걸고 해당 조건에 부합하다면 안전한 요청이라고 인식하고 데이터를 응답하는 형식

대부분의 HTTP API 요청은 text/xml 이나 application/json으로 통신하기 때문에 3번째 Conent-Type이 위반되기 때문에 단순 요청이 일어나는 상황은 드물음

 

- preflight(예비요청) 요청일 경우

  1. Origin헤더에 현재 요청하는 origin과 Access-Control-Request-Method헤더에 요청하는 HTTP method와 Access-Control-Request-Headers요청 시 사용할 헤더를 OPTIONS 메서드로 서버로 요청함 이때 내용물은 없이 헤더만 전송
  2. 브라우저가 서버에서 응답한 헤더를 보고 유효한 요청인지 확인 만약 유효하지 않은 요청이라면 요청은 중단되고 에러가 발생(CORS 에러) 만약 유효한 요청이라면 원래 요청으로 보내려던 요청을 다시 요청하여 리소스를 응답받음

 preflight 요청이란?

출처:https://fomaios.tistory.com/entry/Network-CORS%EB%9E%80-feat-%EB%B3%B4%EC%95%88HTTP

브라우저는 요청을 보낼때 한번에 바로 보내지않고 먼저 예비 요청을 보내 서버와 잘 통신되는지 확인한 후 본 요청을 보냄

즉, 예비 요청의 역할은 본 요청을 보내기 전에 브라우저 스스로 안전한 요청인지 미리 확인하는 것

이때 브라우저가 예비요청을 보내는 것을 Preflight라고 부르며 HTTP 메소드 중 OPTIONS 요청이 사용됨

대부분의 API 요청은 preflight으로 이루어짐

 

 동작 과정 자세히 알아보기

  1. JS의 fecth() 메서드를 통해 리소스를 받아오려고 함
  2. 브라우저는 서버로 HTTP OPTIONS 메소드로 예비 요청(Preflight)을 먼저 보냄
    1. Origin 헤더에 자신의 출처를 넣음
    2. Access-Control-Request-Method 헤더에 실제 요청에 사용할 메소드를 설정
    3. Access-Control-Request-Headers 헤더에 실제 요청에 사용할 헤더들을 설정
  3. 서버는 이 예비 요청에 대한 응답으로 어떤 것을 허용하고 어떤것을 금지하고 있는지에 대한 헤더 정보를 담아서 브라우저로 보내줌
    1. Access-Control-Allow-Origin 헤더에 허용되는 Origin들의 목록을 설정
    2. Access-Control-Allow-Methods 헤더에 허용되는 메소드들의 목록을 설정
    3. Access-Control-Allow-Headers 헤더에 허용되는 헤더들의 목록을 설정
    4. Access-Control-Max-Age 헤더에 해당 예비 요청이 브라우저에 캐시 될 수 있는 시간을 초 단위로 설정
  4. 이후 브라우저는 보낸 요청과 서버가 응답해준 정책을 비교하여 해당 요청이 안전한지 확인하고 본 요청을 보내게 됨
  5. 서버가 본 요청에 대한 응답을 하면 최종적으로 이 응답 데이터를 JS로 넘겨줌

 

 Preflight(예비 요청)의 문제점과 캐싱

요청을 보내기 전에 OPTIONS 메서드로 예비 요청을 보내 보안을 강화하는 목적의 취지는 좋지만 결국은 실제 요청에 걸리는 시간이 늘어나게 되어 어플리케이션 성능에 영향을 미치는 크나큰 단점이 있음

특히 수행하는 API 호출 수가 많으면 많을 수록 예비 요청으로 인해 서버 요청을 배로 보내게 되니 비용 적인 측면에서 폐가 될 수 있음 따라서 브라우저 캐시(Cache)를 통해 Access-Control-Max-Age 헤더에 캐시될 시간을 명시해 주면 Preflight 요청을 캐싱 시켜 최적화를 시켜줄 수 있음

 

예비 요청 캐시는 다른 캐싱 메커니즘과 유사하게 작동함

  1. 브라우저는 예비(Preflight) 요청을 할 때마다 먼저 Preflight 캐시를 확인하여 해당 요청에 대한 응답이 있는지 확인
  2. 만일 응답이 캐싱 되어 있지 않다면  서버에 예비 요청을 보내 인증 절차를 밟음
  3. 만일 서버로 부터 Access-Control-Max-Age 응답 헤더를 받는다면 그 기간 동안 브라우저 캐시에 결과를 저장
  4. 다시 요청을 보내고 만일 응답이 캐싱 되어 있다면 예비 요청을 서버로 보내지 않고 대신 캐시된 응답을 사용

 개발자 도구에서 예비 요청 확인

JS 코드로 api 요청을 보내면 크롬 개발자 도구에서 클라이언트 서버가 본 요청(xhr)을 보내기 전에 예비 요청(preflight) 통신을 하고 있는 것을 볼 수 있음

 

- 신용 요청(Credentialed Request)

클라이언트에서 서버에게 자격 인증 정보(Credential)를 실어 요청할때 사용되는 요청

인증된 요청을 사용하는 방법 CORS의 기본적인 방식이라기보단 다른 출처간 통신에서 보안을 강화하고 싶을때 사용

자격 인증 정보는 세션 ID가 저장되어있는 쿠키(Cookie) 혹은 Authorization 헤더에 설정하는 토큰 값을 말함

 

 동작 과정 자세히 알아보기

1. 클라이언트에서 인증 정보를 보내도록 설정하기

기본적으로 브라우저가 제공하는 요청 API 들은 별도의 옵션 없이 브라우저의 쿠키와 같은 인증과 관련된 데이터를 함부로 요청 데이터에 담지 않도록 되어있음

 

요청에 인증과 관련된 정보를 담을 수 있게 해주는 옵션이 credentials 옵션 3가지의 값을 사용할 수 있음

옵션 값 설명
same-origin(기본값) 같은 출처 간 요청에만 인증 정보를 담을 수 있음
include 모든 요청에 인증 정보를 담을 수 있음
omit 모든 요청에 인증 정보를 담지 않음

만일 이러한 별도의 설정을 해주지 않으면 쿠키 등의 인증 정보는 절대로 자동으로 서버에게 전송되지 않음

서버에 인증된 요청을 보내는 방법으로는 fetch 메서드를 사용하거나 axios, jQuery 라이브리리 등 다양함

어떤 메서드를 사용하느냐에 따라 약간 credentials 옵션을 지정하는 문법이 다름

 

2. 서버에서 인증된 요청에 대한 헤더 설정하기

서버도 마찬가지로 이러한 인증된 요청에 대해 일반적인 CORS 요청과는 다르게 대응해주어야 함

  • 응답 헤더의 Access-Control-Allow-Credentials 항목을 true로 설정
  • 응답 헤더의 Access-Control-Allow-Origin 의 값에 와일드카드 문자("*")는 사용할 수 없음
  • 응답 헤더의 Access-Control-Allow-Methods 의 값에 와일드카드 문자("*")는 사용할 수 없음
  • 응답 헤더의 Access-Control-Allow-Headers 의 값에 와일드카드 문자("*")는 사용할 수 없음

응답의 Access-Control-Allow-Origin 헤더가 와일드카드(*)가 아닌 분명한 Origin으로 설정되어야 하고 Access-Control-Allow-Credentials 헤더는 true로 설정되어야 함 그렇지 않으면 브라우저의 CORS 정책에 의해 응답이 거부됨(인증 정보는 민감한 정보이기 때문에 출처를 정확하게 설정해주어야 함)

'CS > 네트워크' 카테고리의 다른 글

DNS  (0) 2023.11.12
OSI 7계층  (0) 2023.11.11
CORS / CORS 해결방안 / 처리 방법  (0) 2023.07.09
HTTP 요청 메소드 POST와 GET의 차이  (0) 2023.07.02
REST API  (0) 2023.07.02