서랍장

윈도우간 통신 (cross-window-communication) 본문

책장/Javascript

윈도우간 통신 (cross-window-communication)

TERAJOO 2024. 1. 4. 15:44

해당 포스팅은 https://ko.javascript.info/cross-window-communication 을 공부할 목적으로 번역한 내용을 기반으로 나름대로 정리해본 포스팅입니다. 글 내의 예시코드 및 내용들을 자세히 보고 싶으시면 위의 링크 참고 부탁드립니다.


Same Origin 정책은 윈도우와 프레임간 접근을 제한하는 정책이다.

예를 들어 john-smith.com 과 gmail.com 2개의 페이지가 있다고 해보자. 그때 사용자가 john-smith.com 에서의 스크립트로 하여금 gmail.com 의 메일을 읽는 기능을 허용하지 않는 것이 Same Origin 정책이다.

1. Same Origin

2개의 url 이 같은 프로토콜, 도메인, 포트가 있다면 같은 Origin 이라고 판단할 수 있다.
다음의 3개 url 들은 same origin 이라고 생각할 수 있다.

  • http://site.com
  • http://site.com/
  • http://site.com/my/page.html

허나 다음의 예시들은 Same origin 이 아니다.

  • http://www.site.com (another domain: www. matters)
  • http://site.org (another domain: .org matters)
  • http://site.org (another protocol: https)
  • http://site.com:8080 (another port: 8080)

여기서 프로토콜, 도메인, 포트는 각각 아래 그림처럼 구성되어있다.

  • 프로토콜 : url 의 첫 부분으로 리소스에 액세스하기 위해 사용되는 통신 프로토콜 (규약)
  • 도메인 : 리소스가 호스팅되어있는 서버의 인터넷 주소를 나타낸다.
  • 포트 : 서버에서 클라이언트 요청을 수신하는데 사용되는 네트워크 포트 번호. 일반적으로 웹 서버는 80, HTTPS 의 경우 443을 사용한다.

다시 돌아와서 Same Origin 에 대해 알아보면

  • window.open 으로 생성된 팝업이나, iframe 안의 창 등 다른 창에 대한 참조가 있다면, Same Origin 이어야지만 해당 창에 대한 완전한 접근 권한이 생기게 된다. ⇒ 프로토콜, 도메인, 포트번호가 전부 같아야함
  • 반면에 다른 origin 이라면 변수, 문서 등 어느것에도 접근할 수 없다. 단 예외가 하나 있다면 location 인데, 유저에게 리다이렉트 기능을 제공해주기 위해 수정해줄 순 있긴하다. 하지만 읽을 수는 없다는걸 알아두자. (→ 사용자가 어디를 보고 있는지는 볼 수 없다.)

1.1. In action: iframe

iframe 은 자신의 document와 window 전역 객체를 따로 가지고 있는,
따로 분리된 윈도우 창이라고 보면 이해하기 쉽다.

보통 iframe 에서 많이 사용되는 속성은 다음과 같다.

  • iframe.contentWindow : iframe 내부의 window를 제공
  • iframe.contentDocument : iframe 의 document 를 제공 → iframe.contentWindow.document 를 활용할 수도 있음

iframe 으로 새로운 창을 띄운뒤 다루게 되면,
브라우저는 iframe 이 현재의 origin과 같은 origin 을 가지고 있는건지 확인한다.
그 후 다르다면? 제어를 막아버린다. 다음은 그 예시이다.

<iframe src="https://example.com" id="iframe"></iframe>

<script>
  iframe.onload = function() {
    // we can get the reference to the inner window
    let iframeWindow = iframe.contentWindow; // OK
    try {
      // ...but not to the document inside it
      let doc = iframe.contentDocument; // ERROR
    } catch(e) {
      alert(e); // Security Error (another origin)
    }

    // also we can't READ the URL of the page in iframe
    try {
      // Can't read URL from the Location object
      let href = iframe.contentWindow.location.href; // ERROR
    } catch(e) {
      alert(e); // Security Error
    }

    // ...we can WRITE into location (and thus load something else into the iframe)!
    iframe.contentWindow.location = '/'; // OK

    iframe.onload = null; // clear the handler, not to run it after the location change
  };
</script>

위 코드를 보면 알 수 있겟지만 에러를 내뿜지 않는 코드는 2개 타입 뿐이다.

  • iframe.contentWindow
  • write location to ‘/’

그 외에는 전부 브라우저 딴에서 막혀버린다고 보면 된다.
반대로 iframe 이 서빙해주는 페이지가 같은 origin을 가진다? 거의 모든게 가능하다고 보면 된다.

<!-- iframe from the same site -->
<iframe src="/" id="iframe"></iframe>

<script>
  iframe.onload = function() {
    // just do anything
    iframe.contentDocument.body.prepend("Hello, world!");
  };
</script>

(참고) iframe.onload 와 iframe.contentWindow.onload 모두 iframe 이 다 불러와 졌을 때 시점에 콜백을 전달할 수 있는 기능을 제공해주는데. Same Origin 이 아닐 때에는 iframe.contentWindow.onload 에 접근할 수 없기 때문에 iframe.onload 를 사용할 수 있다.
그러니 same origin 이건 deferent origin 이건 그냥 iframe.onload 를 활용하는게 범용적이다.

2. Windows on subdomains: document.domain

만약 second-level domain 이 같으면 어떨까?

john.site.com, peter.site.com 2개의 url 있다고 가정해보자.
이 2개의 url 들은 stie.com 이라는 같은 second-level domain 을 가지고 있다.
이 경우 브라우저에게 2개를 Same Origin 이라고 간주하도록 처리해줄 수 있다.
(map.kakao.commystore.kakao.com 느낌)

처리해주는 코드는 다음 코드와 같다.

document.domain = 'site.com';

이 코드만 추가해준다면 동일한 second-hand domain 을 가지고 있는 iframe 과 통신이 가능하게 된다.

3. Iframe: wrong document pitfall

하나 주의할 점이 있다.

iframe 이 동일 출처에서 온 경우 iframe.contentDocument 에 바로 접근 할 수 있지만,
이는 실제 iframe 에 서빙되는 document 와 다른 값일 수 있다.

onload 의 콜백 시점에서 접근해야 온전한 document 이다.

<iframe src="/" id="iframe"></iframe>

<script>
  let oldDoc = iframe.contentDocument;
  iframe.onload = function() {
    let newDoc = iframe.contentDocument;
    // the loaded document is not the same as initial!
    alert(oldDoc == newDoc); // false
  };
</script>

onload 의 콜백시점보다 빠르게 document 에 접근하고 싶다?

setInterval 을 활용하라고 한다는데 이게 좋은건지는 잘 모르겠다. 일단 코드는 아래와 같다.

<iframe src="/" id="iframe"></iframe>

<script>
  let oldDoc = iframe.contentDocument;

  // every 100 ms check if the document is the new one
  let timer = setInterval(() => {
    let newDoc = iframe.contentDocument;
    if (newDoc == oldDoc) return;

    alert("New document is here!");

    clearInterval(timer); // cancel setInterval, don't need it any more
  }, 100);
</script>

4. Collection: window.frames

iframe 의 window 전역 객체를 가져올 수 있는 대안 방법이 있다고 한다.
window.frames 를 활용하면 된다는데 그 방식을 간단하게 알아보자.

일반적으로는 아래의 속성 접근을 통해 window 객체를 가져올 수 있다.

  • window.frames[0] : document 의 첫번재 프레임의 window 객체
  • window.frames.iframeName : iframeName 의 프레임의 window 전역객체

위의 두개의 방식으로 프레임에 접근할 수 있다고 한다. 코드는 아래와 같다.

<iframe src="/" style="height:80px" name="win" id="iframe"></iframe>

<script>
  alert(iframe.contentWindow == frames[0]); // true
  alert(iframe.contentWindow == frames.win); // true
</script>

그러면 iframe 안에 iframe 이 있는 형태라면 어떻게 특정 iframe 의 window 객체에 접근할 수 있을까?
아래 속성들을 사용해주면 된다.

  • window.frames : nested frames (자식 윈도우들)
  • window.parent : 부모 윈도우
  • window.top : 최상위 부모 윈도우

아래 코드 예시를 보면 좀더 이해하기 편한다.

window.frames[0].parent === window; // true
if (window == top) { // current window == window.top?
  alert('The script is in the topmost window, not in a frame');
} else {
  alert('The script runs in a frame!');
}

5. Sandbox iframe attribute

iframe의 "sandbox" 속성은 신뢰되지 않는 코드의 실행을 방지하기 위해 iframe 내에서 위험한 코드,
접근해서는 안되는 코드 등 특정 작업을 제한하는 기능을 제공한다.

"sandbox"는 iframe을 다른 출처에서 온 것으로 취급하거나 다른 제한을 적용하여 "sandbox"로 만든다.
sandbox 로 처리하는 예시는 아래 코드와 같다.

<iframe sandbox src="/" style="height:80px" name="win" id="iframe"></iframe>
<iframe sandbox="allow-forms allow-popups" src="/" style="height:80px" name="win" id="iframe"></iframe>

iframe 에 sandbox 속성만 떡하니 존재한다면 그냥 다른 origin 인것마냥 제공해주는 기능이 없다고 판단할 수 있다.
다만 특정 기능들은 제한을 풀고 싶다면 sandbox 속성에 allow-OOO 형태의 값을 전달해주면 된다.

allow-OOO 형태에 사용할 수 있는 것들은 아래와 같다.

  • allow-same-origin : same origin 인것처럼 동작하게끔 처리해주는 것으로 보임. 다른 origin 이라도 same origin 정책..
  • allow-top-navigation : iframe 에서 parent.location 을 변경할 수 있도록 허용
  • allow-forms : iframe 으로 부터의 form 제출을 허용
  • allow-scritps : iframe 의 스크립트가 동작하도록 허용
  • allow-popups : iframe 의 window.open 을 허용

6. Cross-window messaging

같은 origin 이 아닌 윈도우끼리 특정 정보들을 송수신해주고 싶다? 그러면 postMessage 를 활용할 수 있다.
어떻게 보면 Same Origin 정책을 우회하는 방법으로 작용할 수 있다.

다만 유저가 모두 동의했을 때 정보를 송수신할 수 있다 → 보안 강화

아래 2개 파트의 인터페이스로 정보 송수신을 해줄 수 있다.

6.1. postMessage

데이터를 보내고 싶은 윈도우는 수신 윈도우의 postMessage 를 호출시킨다.
아래 코드를 보면 쉽게 이해할 수 있다.

<iframe src="http://example.com" name="example">

<script>
  let win = window.frames.example;

  win.postMessage("message", "http://example.com");
</script>

형태를 좀더 자세히 뜯어보자.

win.postMessage(data, targetOrigin)'
  • data : 보내는 데이터, 보통 IE 에서는 JSON.stringify 를 통해 문자열 직렬화해준뒤 보낸다. 그 외에서는 구조화된 데이터 구조로 보내지게 된다.
  • targetOrigin : 일종의 보안장치이다. 데이터가 올바른 사이트에 있는 경우에만 해당 윈도우가 데이터를 수신하도록 보안을 걸어주는 기능을 제공한다.

6.2. onmessage

메시지를 받기 위해서 타겟 윈도우는 message 이벤트 헨들러를 가지고 있어야 한다.
해당 이벤트는 postMessage 를 호출했을 때 트리거되는 이벤트이다.

window.addEventListener("message", function(event) {
  if (event.origin != 'http://javascript.info') {
    // something from an unknown domain, let's ignore it
    return;
  }

  alert( "received: " + event.data );

  // can message back using event.source.postMessage(...)
});

위의 예시코드에서 event 의 속성을 여럿 사용하고 있다.

  • event.data : postMessage 를 통해 전달되는 데이터
  • event.origin : 어느 윈도우 도메인으로부터 전달되었는지 알 수 있게끔 제공되는 도메인값

'책장 > Javascript' 카테고리의 다른 글

Text Decoder & Text Encoder  (0) 2024.01.18
ArrayBuffer, binary arrays  (0) 2024.01.05
Axios가 대괄호 인코딩을 해줄까? (feat. paramsSerializer)  (0) 2024.01.01
BOM / DOM ???  (0) 2021.04.28