Hello World

동일출처정책(Cross domain 이슈) 본문

Javascript/Tips

동일출처정책(Cross domain 이슈)

EnterKey 2012. 3. 22. 01:38
반응형

jQuery로 공부하다보면 크로스도메인 이라는 말을 많이 접하게 되어 이것에 대해 공부를 좀 하고자 했더니 사실 내게 필요한 내용은 동일출처 정책이였다 ㅎㅎ.. 에헴..

자바스크립트 보안


 웹 브라우저에 자바스크립트 인터프리터가 도입되었다는 말은, 웹 페이지를 읽어들이는 과정에서 임의의 자바스크립트 코드를 우리의 컴퓨터에서 실행할 수 있음을 의미한다. 안전한 브라우저들은 악의적인 코드가 기밀 정보를 빼내고 변형하고 우리의 사생활을 침해하는 것을 막기 위해 스크립트의 실행을 다양한 형태로 제약한다.

 브라우저마다 제약사항의 목록이 다르며, 이런 제약사항들의 많은 부분이 사용자에 의해 선택적으로 설정될 수 있다.

자바스크립트 보안 문서를 보던중 이런 내용이 있었다.

 스크립트는 자신을 포함한 문서와 다른 서버에서 불러온 문서의 내용은 읽을 수 없다. 이와 유사하게 스크립트는 다른 서버에서 불러온 문서에는 이벤트 리스너를 등록할 수 없다. 이것은 스크립트가 사용자의 입력을 캐내어 다른 페이지로 흘려보내는 것을 막기 위함이다. 이 제약사항은 동일출처 정책으로 알려져 있다.



동일출처 정책

 동일 출처 정책은 자바스크립트 코드가 상호 작용할 수 있는 웹 문서 내용에 관한 광범위한 보안 제약이다. 한장이나 프레임의 코드는 동일 출처 정책의 감독하에 다른 창이나 프레임과 상호작용한다. 스크립트는 그 자신이 포함된 문서와 출처가 동일한 문서나 창의 프로퍼티만 읽을 수 있다.

 동일 출처 정책은 XMLHttpRequest 객체와 함께 HTTP를 스크립트 안에서 직접 제어할 때에도 적용된다. 클라이언트 측 자바스크립트 코드는 XMLHttpRequest객체를 사용하여 임의의 HTTP요청을 생성할 수 있으나 이 요청은 스크립트를 포함하는 문서가 위치한 웹 서버를 향해서만 가능하다.
(즉, jQuery의 ajax를 사용할 때 많은 문제가 발생하는 것이 
XMLHttpRequest 객체와 관련 되었기 때문이였던 것이다!!)

 

문서의 출처는 그 문서가 불려온 URL의 프로토콜과 호스트, 포트 등으로 정의된다. 다른 웹 서버에서 불러온 문서는 그 출처가 다르다. 같은 호스트의 다른 포트를 통해 불려온 문서의 출처도 다르다. 또한 비록 같은 웹 서버에서 온 것이라 할지라도 HTTP: 프로토콜로 불려온 문서와 https: 프로토콜로 불려온 문서는 출처가 서로 다르다.

 스크립트 그 자체의 출처는 동일 출처 정책과 관련이 없음을 이해하는 것이 중요하다. 중요한 것은 스크립트가 내장된 문서의 출처다.



ex) 넣기

즉, jQuery 최신 버젼을 갖고 Ajax통신 기능을 이용하려고 하면 종종 직면하게 되는 이슈는 crossdomain 이슈이고. 이 것은 자바스크립트 보안 내용 중 동일출처정책 때문에 생기는 문제였던 것이다.

- crossdomain 이슈사항 정리 -

1. 전제조건
- 조건 1: 내가 보고 있는 페이지의 주소는 http://www.aaa.com 이다.
- 조건 2: http://www.aaa.com의 페이지가 http://www.bbb.com의 rest api 를 호출하여 데이터를 JSON으로 가져오려고 한다.

2. 결론
 조건 1의 내가 이용하는 페이지의 주소의 도메인과 조건 2의 데이터를 가지고 오기위한 서버의 도메인이 서로 상이할 경우 crossdomain이라고 부른다. 이러한 crossdomain은 서버 공격 및 보안 취약성을 갖게 되므로 서로 신뢰할 수 있는 경우에만 이를 허용하는 서비스를 구축하는 것이 일반적이다.


jQuery 1.5.0 이후의 버젼부터는 이러한 crossdomain 을 원천 봉쇄해버렸다.
그래서 이런 메시지를 띄운다.


 

$(document).ready(function(){
loadData();
});
function loadData()
{
$.ajax({
"apikey=46ec234b4880ddf35483e10e79a244b01643e1df"
, sucess: function( data, textStatus, jqXHR )
{
alert("success");
}
, error: function( jqXHR, textStatus, errorThrown )
{
alert( textStatus + ", " + errorThrown );
}
});
}

HTML과 Javascript(ajax)로 OpenAPI를 이용한 페이지를 만들고 싶다면 이런
Cross domain은 엄청난 문제가 된다

일단 여러가지 테스트 결과 jQeury에서는 "http://"부터 그 다음 "/"가 나올때까지의
도메인 문자열이 현재 보고 있는 페이지의 도메인 문자열이 상이하면 Crossdomain 문제를 일으킨다.

(ex) enterkey88.tistory.com과 enterkey89.tistory.com은 다른 도메인 취급한다.)
또한,
도메인 문자열이 동일해도 포트번호가 서로 달라도 crossdomain 문제를 일으킨다.

문제 해결방법으로는 몇가지가 있는데 첫번째로는 JSONP라는 방법을 이용하라고
한다.

전공법은 아니지만 Cross domain을 우회할 수 있는 꼼수중의 하나라고 한다.
 
위와 같은 html 코드가 있을 때 저 src라는 attribute가 JSONP 역할을 하는거다.
저 src속성은 어떤 도메인을 넣든 크로스도메인 이슈를 발생시키지 않으니까
저 꼼수를 이용해 크로스도메인을 처리하는 방법이 JSONP방법이다.

(좀더 자세한 내용은 : http://bob.pythonmac.org/archives/2005/12/05/remote-json-jsonp/ )

그렇다면 JSONP의 방법으로 수정해보자.
$(document).ready(function(){
loadData();
});
function loadData()
{
var data ={};
data.q = 'test';
data.apikey = '46ec234b4880ddf35483e10e79a244b01643e1df';
data.result = '10';
data.output = 'json';
data.callback = "jsonp_callback";

$.ajax({
, crossDomain: true
, dataType: "jsonp"
, type: 'GET'
, data: data
, error: function( jqXHR, textStatus, errorThrown )
{
alert( textStatus + ", " + errorThrown );
}
});
}
function jsonp_callback( data )
{
alert("!!!");
}
 
위와같이 코드를 작성하고 브라우져에서 확인하면 두가지 alert 창이 뜨는데 하나는 "!!" alert이 뜨고 하나는 아래와 같은 창이 뜨게 된다.


이것은 jQuery가 JSONP로 데이터를 호출하기 위해 붙여놓은 스크립트이다.
JSONP로 들어온 데이터를 파싱하지 못하여 생긴 에러이다
데이터가 잘 들어왔음을 확인 할 수 있다.
이 데이터를 파싱하여 화면에 뿌려주면 되겠다.

다만 문제는 이 방법에 
Error를 처리 할 수 없다는 취약점이 있다.
결국 JSONP라는 방법은 꼼수에 불과하기 때문에 정석적인 방법과는 거리가 있다.



 


구글신의 힘을 빌어 JSONP 방법과는 다르게 좀더 정석적인 방법으로 jQuery를 다뤄 Crossdomain
해결한 것을 찾아 냈다.
jQuery.ajaxPrefilter() 부분이었다.

이 ajaxPrefilter() 메서드는 ajax의 low-level 메서드로 jQuery를 통해

ajax통신을 하게 될때 콜백함수로 호출되어 통신을 위한 밑작업들을 수행하게 되는데 Cross domain 또한
이 메서드 중간에서 차단하여 막는것도 여기서 처리하게된다.


이 기능들은 jQuery 1.5 .$
버젼부터 지원하기 시작한 것으로 ajax 요청의 "보내기", "받기",
그리고 "관리하기"에 관련된 내용을 담당한다고
설명되어있다.



일단 여러가지로 우리가 봐야할 것은 보낼 때 Cross domain을 막는 부분을 해결해야 하므로
보내는
쪽 담당인 Prefilters 부분을 살펴보도록 하자.
Prefilters는 ajax의 옵션처리, 호출등을 담당하는데 실제 소스 코드를 확인해보면 다음과 같다.

$.ajaxPrefilter( function( options, originalOptions, jqXHR ) {
// Modify options, control originalOptions, store jqXHR, etc
}); 
각 파라미터의 설명들이 있는데 내용은 다음과 같다.
  • options : 요청을 보내는 옵션들
  • originalOptions : ajaxSettings에서 기본적으로 수정되어지지 않는 ajax 메서드에서 제공되는 옵션들.
  • jqXHR : jQuery HTTP Request
이렇게 되며 이 메서드를 Extending해주면 ajax통신을 manual 하게 셋팅할 수 있는데 그 내용중에서 아래의 내용을 확인해 볼 것.
Prefilters can also be used to modify existing options. For example, the following proxies cross-domain requests through http://mydomain.net/proxy/:
이 이야기는 무슨 이야기냐 하면 기존의 옵션을 수정할 수 있다면서 프록시서버를 통해 크로스도메인 요청을 보낼수 있다는 이야기를 하며 아래의 코드를 소개해 놓았다.


$.ajaxPrefilter( function( options ) {
if ( options.crossDomain )

{
options.url = "http://mydomain.net/proxy/" + encodeURIComponent( options.url );
options.crossDomain = false;
}
});


그럼 우리가 유추해볼 수 있는건 다음과 같다.
  1. 통신을 하기전엔 무조건 $.ajaxPrefilter()를 거친다.
  2. ajaxPrefilter() 내부를 변조해 처리하면 cross-domain도 통과할 수 있다.
라는 것인데 그래서 여태껏 "jQuery ajax crossdomain" 이라는 검색어로 검색하던 것을 "jQuery ajaxPrefilter crossdomain"으로 바꿔 검색해봤다. 그랬더니 서광처럼 등장한 "http://craveytrain.com/posts/cors" 페이지.
여기에 등장한 소스 코드는

$.ajaxPrefilter('json', function(options, orig, jqXHR) {
if (options.crossDomain && !$.support.cors) return 'jsonp'
});

이것인데 이 소스를 적용해 테스트 해봤더니 인터넷 익스플로러에선 되고 크롬에선 안되고 하는 크로스브라우징 문제가 발생했는데 이걸 어쩐다.. 하고 고민하다가 어차피 그냥 통신 자체를 'jsonp'로 속여서 내보내면 해결될 문제라고 생각해 아래와 같이 수정했다.

 
 

$.ajaxPrefilter('json', function(options, orig, jqXHR) {
return 'jsonp'
});

이렇게 테스트 해보니 잘된다.
테스트 코드는 아래와 같이.

$(document).ready(function(){
loadData();
});
function loadData()
{
var data ={};
data.q = 'test';
data.apikey = '947f96c9d2518613faae766eed75bca030ad67d7';
data.result = '10';
data.output = 'json';
$.ajaxPrefilter('json', function(options, orig, jqXHR) {
return 'jsonp';
});
$.ajax({
, crossDomain: true
, dataType: "json"
, type: 'GET'
, data: data
, success: function( data, textStatus, jqXHR )
{
alert("sccess : " + data.channel.item.length );
}
, error: function( jqXHR, textStatus, errorThrown )
{
alert( textStatus + ", " + errorThrown );
}
});
}


이렇게 ajax 통신하기 전에 ajaxPrefilter를 수정해주면 해결이 된다.



  
1.4 버전의 jQuery를 사용하면 괜찮다 라는 말도 있는데, 말도 안되는 소리인 것 같아 패스
 

반응형
Comments