[javascript] 아이프레임 제어와 데이터 교환

아이프레인 제어 기초

아이프레임은 HTML 페이지 안에 또 다른 HTML 페이지가 있는 창을 배치하는 것입니다.

아이프레임 안에 로딩된 페이지는 원래의 부모 페이지와는 독립된 DOM 객체와 페이지 정보를 가집니다.

아이프레임은 DOM에 HTMLIFrameElement 노드 타입으로 정의됩니다.

별도의 전용 노드 타입과 함께 contentWindow, contentDocument 속성 또한 가지고 있어, 이 속성을 이용해 아이프레임 도큐먼트와 노드에 접근할 수 있습니다.

예를 들어 아래와 같이 <iframe> 에 같은 경로에 있는 다른 HTML 문서를 가져올 경우 부모 페이지에서 대상 노드(HTMLIFrameElement) 와 아이프레임 도큐먼트 DOM에 접근(contentDocument)할 수 있습니다.

<iframe id="runframe" src='./frame.html'></iframe>
const runframe = document.getElementById('runframe');
runframe.addEventListener('load', function(e){
  console.log(e.target + ' iframe loaded!');
  console.log(e.target.contentDocument);
});

위와 같이 만들어진 아이프레임 페이지는 기존 DOM을 제어하는 방법과 유사하게 제어가 가능합니다.

예를 들어 아이프레임 페이지 안에 문단(p) 엘리먼트를 하나 만들어 텍스트 내용을 붙여 보겠습니다.

앞서의 예를 확장해서 "load" 이벤트 리스너 안에 노드를 추가하는 코드를 추가합니다.

const runframe = document.getElementById('runframe');
runframe.addEventListener('load', function(e){
  console.log(e.target + ' iframe loaded!');
  // <p>아이프레임 페이지</p> 엘리먼트 노드 생성
  let el = document.createElement('p');
  let text = document.createTextNode('아이프레임 페이지');
  el.append(text);
  // 아이프레임 도큐먼트에 추가
  runframe.contentDocument.body.append(el);
  console.log(e.target.contentDocument);
});

!주의할 점

아이프레임 DOM은 부모 페이지의 DOM과 유사하지만, 몇가지 중요한 차이점이 있습니다.

그중 하나가 "DOMContentLoaded" 이벤트 가 없습니다. 앞서의 예가 "load" 이벤트로 구현된 것은 "DOMContentLoaded", 또는 "DOMFrameContentLoaded" 이벤트가 없기 때문에 아이프레임의 DOM이 생성된 직후에는 아이프레임 DOM 노드를 제어할 수 없습니다.

따라서

runframe.addEventListener('DOMContentLoaded', function(e){});

와 같은 코드는 에러를 발생시킵니다.

CORS(Cross Origin Resource Sharing) 와 X-Frame-Options

너무 당연하지만 같은 사이트에 있는 페이지를 아이프레임에 URL로 가져오는 것은 아무런 문제가 없습니다.

그러나, 아이프레임에 원격 사이트의 페이지를 로딩할 때는 CORS 와 X-Frame-Options 에 주의해야 합니다.

웹서버는 페이지 요청이 오면 요청이 원격 사이트에 온 것인지 아닌지를 구분합니다. 

원격 사이트에서 온 요청인 경우, 서버 설정에 따라 어떤 응답을 할지를 정합니다.

웹서버의 원격 요청 설정은 대체로 세세한 여러가지 옵션을 설정할 수 있습니다.

원격 페이지의 요청이 AJAX 요청인지, 또는 아이프레임 요청인지를 판단한 후 웹서버 설정이 허용이면 페이지 응답을 하게 됩니다.

아이프레임 요청은 거부하도록 설정된 경우 아래와 같은 'X-Frame-Options' 에러메시지를 콘솔에서 보게됩니다.

아이프레임으로 한 요청이 거절되었다는 뜻입니다.

AJAX를 통한 원격 요청을 허용하지 않도록 웹서버가 설정된 경우, AJAX 요청을 통해 내용을 가져올 수 없습니다.

JSON 데이터를 원격 서버에서 가져오는 경우에도 동일합니다.

원격 접속은 도메인을 기준으로 정해지며, 서브 도메인인 경우에도 원격 요청으로 처리되므로, 자체적으로 서비스하는 다른 도메인인 경우에도 CORS와 X-Frame-Options 를 웹서버에서 허용해야 접근이 가능합니다.

아이프레임과 데이터 교환하기

아이프레임과 데이터를 교환하는 방법은 2가지입니다.

1. 폼데이터를 아이프레임으로 전송

보내는 데이터가 폼 데이터이고 아이프레임으로 보내고 싶은 경우 폼 전송 타겟(target) 속성을 아이프레임으로 지정해 보낼 수 있습니다.

<form name="myform" id="myform" method="post" target="runframe" action="save">
  <input type="text" name="userid">
  <input type="password" name="password">
  <button type="submit">저장</button>
</form>
<iframe name="runframe" id="runframe" src='./frame.html'></iframe>
<div id="message"></div>

아이프레임으로 전송된 폼 데이터를 처리한 후 부모 창으로 결과나 메시지를 전송하고 싶은 경우

window.parent.document.getElementById('message').innerHTML = '저장완료!';

이런 방식으로 부모 창의 DOM에 접근할 수 있습니다.

2. postMessage() 메서드로 메시지 교환

윈도우 전역 객체 메서드로 postMessage() 메서드가 제공됩니다.

대상 윈도우 객체로 데이터와 함께 "message" 이벤트를 발생시켜 콜백 함수로 처리할 수 있도록 인터페이스를 제공합니다.

부모 페이지에는 아래와 같이 "message" 이벤트 콜백 함수를 정의합니다.

데이터는 파라메터로 넘어오는 이벤트 객체의 "data" 속성으로 postMessage() 가 넘기는 것을 받게 됩니다.

window.addEventListener('message',function(e){
    console.log('메시지: '+e.data);
});

아이프레임에서는 처리가 완료된 후 postMessage() 로 부모 페이지로 메시지를 보냅니다.

window.addEventListener('load', function(){
  window.parent.postMessage('저장완료!','*');
});

메시지 전송을 보장하기 위해 아이프레임 페이지가 모두 로딩된 후 메시지를 보내도록 "load" 이벤트 콜백으로 메시지를 보냅니다.

postMessage() 메서드는 기본 호출시 2개의 파라메터가 필요합니다.

첫번째 파라메터에는 보내는 데이터를 담습니다.

문자열 외에 객체를 담아서 보낼 수도 있기 때문에 원하는 결과 데이터를 자유롭게 설정할 수 있습니다.

두번째 파라메터는 대상 오리진(Target Origin) 정보입니다. 타겟 윈도우(Target Window)의 오리진(Origin) 정보를 넣습니다. 오리진 정보는 스키마, 호스트 이름, 포트 정보를 말합니다. 경로와 파라메터를 제외한 정보를 말하며 "https://apost.dev:8080" 과 같은 형태의 호출 페이지 루트 연결 정보입니다. 또는 "*" 를 넣습니다.

"*" 는 별도로 지정하지 않음 입니다. 기본값이라고 보면 됩니다.

타겟 오리진 설정은 중요합니다. 타겟오리진이 일치하지 않으면 postMessage() 메시지는 전송되지 않습니다.

보안상 중요하기 때문이고, postMessage()로 중요한 정보를 전송하는 경우 반드시 타겟오리진을 설정해야 합니다.

그렇지 않으면 데이터를 가로채는 악의적인 시도에 의해 데이터가 탈취될 수 있습니다.


!주의할 점

구현의 용이함으로 인해 아이프레임을 이용해 폼 데이터를 아이프레임에 전송하는 방식으로 주문처리, 결제시스템을 구현하는 경우가 많았으나, 현재는 거의 사라지는 추세입니다.

보안 문제가 발생할 가능성이 크기 때문에 대부분 결제시스템에서 아이프레임을 사용하고 있지 않습니다. 자체적인 서비스를 제공하는 경우에도 아이프레임을 사용한 중요 정보의 교환이나, 결제시스템의 운영, 또는 사용자가 입력한 중요 폼 데이터를 저장하는 것은 피하는 것이 좋습니다.

서비스 구조상 별도의 전송 페이지 로딩과 콜백 페이지가 필요한 경우, 아이프레임 대신 팝업창을 사용할 것을 추천합니다.

가장 좋은 방법은 AJAX 방식으로 서버로 데이터를 보내고 별도의 콜백 페이지에서 메시지를 받는 방식으로 구현하는 것입니다. 보안상 인증키나 해시 등의 추가적인 조치등이 필요하지만, 보안상 더 안전한 서비스 구현이 가능합니다.

아이프레임은 웹에서 데이터를 교환하느 용도로는 사용 빈도가 점점 줄어들고 있습니다.

CORS(Cross Origin Resource Sharing) 접근 제어 정책이 점차 강화되면서 아이프레임으로 원격 사이트의 정보를 가져와 보여주는데 제약이 많아졌습니다.

AJAX 기술이 발전하면서 화면에 감추어진 아이프레임을 이용해 데이터를 가져오는 방법은 AJAX에 의해 점점 대체되어가고 있습니다.

무엇보다 아이프레임을 이용한 구현 방식이 보안 취약점에 여러번 노출되면서 중요 정보 전송이나 결제 시스템 구현에서도 점차 배제되고 있기 때문에 구현하는 서비스 특징에 꼭 필요한 경우가 아니면 AJAX 방식으로 구현하는 것이 좋습니다.