Network) Browser Secury Model에 대해 (CORS)

백준우·2021년 10월 24일
0

Network

목록 보기
4/6
post-thumbnail

1. CORS

1.1 Simple request
1.2 preflighted request
1.3 credentialed request


1. CORS

  • 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제입니다.
  • CORS(Cross-Origin Resource Sharing)으로 서로 다른 Origin간에 Resource를 공유하는 것이다.
  • 오늘날 많은 웹 페이지들을 CSS,이미지,스크립트등 리소스들을 각각의 출처로 부터 읽어 옵니다.


!) https://domain-a.com의 프론트 엔드 JavaScript 코드가 XMLHttpRequest를 사용하여 https://domain-b.com/data.json을 요청하는 경우, 보안 상의 이유로 브라우저는 스크립트에서 시작한 교차 출처 HTTP 요청을 제한합니다.
!!) 브라우저가 자발적으로 브라우저의 어플리케이션을 이용하는 사용자를 보호하기 위한 정책입니다.

어떤 요청이 CORS를 사용하는가?

  1. XMLHttpRequest와 Fetch API 호출
  2. 웹 폰트,CSS
  3. WebGL 텍스쳐
  4. drawImage()를 사용해 캔버스에 그린 이미지/비디오 프레임. 이미지로부터 추출하는 CSS Shapes

1.1 Simple request

  • 다음조건을 만족하는 일부요청은 CORS를 적용하지 않습니다. 아래 조건을 만족할 경우 simple request로 요청합니다.
  1. 다음중 하나의 메소드(GET, HEAD, POST)
  2. 유저 에이전트가 자동으로 설정 한 헤더(EX, Connection, User-Agent(en-US))
  3. Fetch 명세에서 forbidden header name으로 정의한 헤더(https://fetch.spec.whatwg.org/#forbidden-header-name)
  4. 수동으로 설정할 수 있는건 Fetch에서 CORS-safelisted request-header로 정의한 헤더입니다.
  • Accept, Accept-Language, Content-Language, Content-Type

TIP)Content-Type은 다음의 값들만 허용합니다.

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

예시)

1)JS 화면

const xhr = new XMLHttpRequest(); //xhr로 인스턴스를 만들어준뒤 
const url = 'https://bar.other/resources/public-data/'; //리소스를 요청할 URL

xhr.open('GET', url); //xhr을 open함
xhr.onreadystatechange = someHandler; 
xhr.send();

2)Developer 콘솔 화면

GET /resources/public-data/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: https://foo.example //요청한 origin

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2
Access-Control-Allow-Origin: * //모든 도메인에서 unlock 하겠다.
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml 
[…XML Data…]

TIP) https://bar.other 의 리소스 소유자가 오직 https://foo.example 의 요청만 리소스에 대한 접근을 허용하려는 경우 다음을 전송합니다.

Access-Control-Allow-Origin: https://foo.example

위와 같이 전송할 경우 이제 https://foo.example 이외의 도메인은 corss-site 방식으로 리소스에 접근할 수 없습니다.

1.2preflighted request

  • preflighted request는 위에서 논의한 “simple requests” 와는 달리, 먼저 OPTIONS 메서드를 통해 다른 도메인의 리소스로 HTTP 요청을 보내 실제 요청이 전송하기에 안전한지 확인합니다.
  • Cross-site 요청은 유저 데이터에 영향을 줄 수 있기 때문에 이와같이 미리 전송(preflighted)합니다.
  • 아래 조건을 만족할 경우 preflighted request로 요청합니다.
  1. 다음중 하나의 메소드(PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH)
  2. 유저의 에이전트가 자동으로 생성한 헤더를 제외한 다른 부분이 추가된경우
  3. Content-Type이 밑의 3가지와 다를 경우
    -application/x-www-form-urlencoded
    -multipart/form-data
    -text/plain

예시)

!)Preflighted로 먼저 요청을 전송하기 안전한지 확인 한 후 Main Request로 요청을 보내는 모습을 볼수 있다.

1)JS화면

const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://bar.other/resources/post-here/');
xhr.setRequestHeader('Ping-Other', 'pingpong');//헤더로 비표준 HTTP Ping-Other 요청헤더가 설정됨
xhr.setRequestHeader('Content-Type', 'application/xml');//content-Type을 설정함 
xhr.onreadystatechange = handler;
xhr.send('<person><name>Arun</name></person>');

!) Content-Type 이 application/xml이며 사용자 정의 헤더가 사용되었으므로 위 요청은 preflighted로 처리됩니다.

2-1)Developer 화면(preflight request)

OPTIONS /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: http://foo.example
Access-Control-Request-Method: POST //POST를 해도 되는지 여부를 먼저 요청함
Access-Control-Request-Headers: X-PINGOTHER, Content-Type

HTTP/1.1 204 No Content //응답 요청은 성공함(204는 클라이언트가 현재 페이지를 벗어나지 않아도 된다는것을 의미)
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://foo.example //foo.example에서 오는 요청은 승인 하겠다.
Access-Control-Allow-Methods: POST, GET, OPTIONS//다음 3가지 메소드를 허가 하겠다.
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type //실제 요청에 헤더를 사용 할 수 있음을 확인함
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive

2-2)Developer 화면(preflighted request요청 후)

POST /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
X-PINGOTHER: pingpong
Content-Type: text/xml; charset=UTF-8
Referer: https://foo.example/examples/preflightInvocation.html
Content-Length: 55
Origin: https://foo.example
Pragma: no-cache
Cache-Control: no-cache

<person><name>Arun</name></person>


HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:40 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://foo.example
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 235
Keep-Alive: timeout=2, max=99
Connection: Keep-Alive
Content-Type: text/plain

[Some GZIP'd payload]

!!) 주의사항

  • 모든 브라우저가 preflighted request 후 리다이렉트를 지원하지는 않습니다. preflighted request 후 리다이렉트가 발생하면 일부 브라우저는 다음과 같은 오류 메시지를 띄웁니다.
    1.요청이 'https://example.com/foo'로 리다이렉트 되었으며, preflight가 필요한 cross-origin 요청은 허용되지 않습니다.
    2.요청에 preflight가 필요합니다. preflight는 cross-origin 리다이렉트를 허용하지 않습니다.

다음 방법으로 해결 할 수 있습니다.

1.preflight 리다이렉트를 방지하기 위해 서버측 동작을 변경
2.preflight를 발생시키지 않는 simple request 가 되도록 요청을 변경
3.Fetch API를 사용하여 simple request 를 작성
4.첫 번째 단계에서 Response.url 혹은 XMLHttpRequest.responseURL 로부터 얻은 URL을 사용

1.3 credentialed request

  • 서버에서 우리가 사용하는 브라우저에 쿠키를 심어둘 경우가 있음
  • 그안에 세션 토큰을 넣어둘 경우가 있음
  • 예를 들면 내가 쇼핑몰에서 자주보던 물품을 로그인도 하지 않았지만 추천 광고로 뜨는경우가 있다.
  • 내가 서버에 요청을 할때 쿠키의 정보를 같이 보낼지 여부를 선택할 수 있음
  • 승인 할 경우 쿠키의 정보가 서버에 request 할 때마다 같이 전송 됨

예제)

const invocation = new XMLHttpRequest();
const url = 'http://bar.other/resources/credentialed-content/';

function callOtherDomain() {
  if (invocation) {
    invocation.open('GET', url, true);
    invocation.withCredentials = true; // XMLHttpReques의 플래그중 하나를 사용
    invocation.onreadystatechange = handler;
    invocation.send();
  }
}
  • withCredentials 라고 불리는 XMLHttpReques의 플래그는 부울 값을 갖습니다. 기본적으로 호출은 쿠키 없이 이루어집니다.
  • simple GET request이기 때문에 preflighted 되지 않습니다.
  • 브라우저는 Access-Control-Allow-Credentials: true 헤더가 없는 응답을 거부합니다. 따라서 호출된 웹 컨텐츠에 응답을 제공하지 않습니다.
    2)Developer 화면
GET /resources/credentialed-content/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Referer: http://foo.example/examples/credential.html
Origin: http://foo.example
Cookie: pageAccess=2

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:34:52 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Credentials: true
Cache-Control: no-cache
Pragma: no-cache
Set-Cookie: pageAccess=3; expires=Wed, 31-Dec-2008 01:34:53 GMT
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 106
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain

[text/plain payload]
  • request의 마지막 줄에 http://bar.other의 컨텐츠를 대상으로 하는 쿠키가 포함되어 있습니다.
  • response에 Access-Control-Allow-Credentials에 true로 응답하지 않으면 응답은 무시되고 웹 컨테츠는 제공되지 않습니다.

정리

JS에 코드가 실행되면 브라우저가 자발적으로 Simple request가 아니면 Preflighted request를 보내서 응답을 처리함

profile
이게 되네?

0개의 댓글

관련 채용 정보