JavaScript에서 동일 출처 정책은 클라이언트측 스크립트(특히 Javascript)에 대한 중요한 보안 측정 기준입니다. 즉, 동일한 프로토콜(프로토콜), 포트 및 호스트(도메인 이름)를 가진 두 페이지가 동일한 페이지에 속합니다. 원천.
이 튜토리얼의 운영 환경: Windows 7 시스템, JavaScript 버전 1.8.5, Dell G3 컴퓨터.
동일원산지 정책이 뭔가요?
동일 출처 정책은 클라이언트 측 스크립트, 특히 Javascript에 대한 중요한 보안 지표입니다. Netscape Navigator 2.0에서 처음 등장했으며 그 목적은 문서나 스크립트가 여러 다른 소스에서 로드되는 것을 방지하는 것이었습니다.
동일 출처 정책, 즉 프로토콜, 포트(지정된 경우), 호스트(도메인 이름)가 동일한 두 페이지는 동일한 소스에 속합니다.
예를 들어 http://www.example.com/dir/page.html
URL의 경우
프로토콜은 http://이고
도메인입니다. 이름은 www.example.com,
포트는 80(기본 포트는 생략 가능)입니다. 그 상동성은 다음과 같습니다. http://www.example.com/dir/page.html
这个网址,
协议是http://,
域名是www.example.com,
端口是80(默认端口可以省略)。它的同源情况如下。
http://www.example.com/dir2/other.html:同源 http://example.com/dir/other.html:不同源(域名不同) http://v2.www.example.com/dir/other.html:不同源(域名不同) http://www.example.com:81/dir/other.html:不同源(端口不同)
精髓:
它的精髓很简单:它认为自任何站点装载的信赖内容是不安全的。当被浏览器半信半疑的脚本运行在沙箱时,它们应该只被允许访问来自同一站点的资源,而不是那些来自其它站点可能怀有恶意的资源。
我们举例说明:比如一个黑客程序,他利用IFrame把真正的银行登录页面嵌到他的页面上,当你使用真实的用户名,密码登录时,他的页面就可以通过Javascript读取到你的表单中input中的内容,这样用户名,密码就轻松到手了。
同源政策的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。
设想这样一种情况:A网站是一家银行,用户登录以后,又去浏览其他网站。如果其他网站可以读取A网站的 Cookie,会发生什么?
很显然,如果 Cookie 包含隐私(比如存款总额),这些信息就会泄漏。更可怕的是,Cookie 往往用来保存用户的登录状态,如果用户没有退出登录,其他网站就可以冒充用户,为所欲为。因为浏览器同时还规定,提交表单不受同源政策的限制。
由此可见,”同源政策”是必需的,否则 Cookie 可以共享,互联网就毫无安全可言了。
限制范围
随着互联网的发展,”同源政策”越来越严格。目前,如果非同源,共有三种行为受到限制。
(1) Cookie、LocalStorage 和 IndexDB 无法读取。 (2) DOM 无法获得。 (3) Ajax 请求不能发送。
虽然这些限制是必要的,但是有时很不方便,合理的用途也受到影响。下面,我将详细介绍,如何规避上面三种限制。
规避方法
1、Cookie
Cookie 是服务器写入浏览器的一小段信息,只有同源的网页才能共享。但是,两个网页一级域名相同,只是二级域名不同,浏览器允许通过设置document.domain
共享 Cookie。
举例来说,A网页是http://w1.example.com/a.html
,B网页是http://w2.example.com/b.html
,那么只要设置相同的document.domain,两个网页就可以共享Cookie。
document.domain = 'example.com';
现在,A网页通过脚本设置一个 Cookie
document.cookie = "test1=hello";
B网页就可以读到这个 Cookie。
var allCookie = document.cookie;
这种方法只适用于 Cookie 和 iframe 窗口,LocalStorage 和 IndexDB 无法通过这种方法,
规避同源政策,而要使用下文介绍的PostMessage API。
另外,服务器也可以在设置Cookie的时候,指定Cookie的所属域名为一级域名,比如.example.com。
Set-Cookie: key=value; domain=.example.com; path=/
这样的话,二级域名和三级域名不用做任何设置,都可以读取这个Cookie。
2、iframe
如果两个网页不同源,就无法拿到对方的DOM。典型的例子是iframe窗口和window.open方法打开的窗口,它们与父窗口无法通信。
比如,父窗口运行下面的命令,如果iframe窗口不是同源,就会报错
document.getElementById("myIFrame").contentWindow.document // Uncaught DOMException: Blocked a frame from accessing a cross-origin frame.
上面命令中,父窗口想获取子窗口的DOM,因为跨源导致报错。
反之亦然,子窗口获取主窗口的DOM也会报错。
window.parent.document.body // 报错
如果两个窗口一级域名相同,只是二级域名不同,那么设置上一节介绍的document.domain
var src = originURL + '#' + data; document.getElementById('myIFrame').src = src;
핵심:
다음과 같은 상황을 상상해 보세요. 웹사이트 A는 은행입니다. 사용자는 로그인한 후 다른 웹사이트를 탐색합니다. 다른 웹사이트가 웹사이트 A의 쿠키를 읽을 수 있으면 어떻게 되나요?
물론, 쿠키에 개인 정보(예: 총 입금액)가 포함되어 있으면 해당 정보가 유출됩니다. 더욱 무서운 점은 쿠키가 사용자의 로그인 상태를 저장하는 데 자주 사용된다는 것입니다. 사용자가 로그아웃하지 않으면 다른 웹사이트가 사용자를 사칭하여 원하는 대로 할 수 있습니다. 브라우저는 또한 양식 제출이 동일한 출처 정책에 의해 제한되지 않는다고 규정하기 때문입니다. 🎜🎜"동일 출처 정책"이 필요하다고 볼 수 있습니다. 그렇지 않으면 쿠키가 공유될 수 있으며 인터넷은 전혀 안전하지 않게 됩니다. 🎜🎜🎜🎜제한된 범위🎜🎜🎜🎜인터넷이 발달하면서 "동일 출처 정책"이 점점 더 엄격해지고 있습니다. 현재 동일한 출처가 아닌 경우 제한되는 세 가지 동작이 있습니다. 🎜window.onhashchange = checkMessage; function checkMessage() { var message = window.location.hash; // ... }🎜이러한 제한은 필요하지만 때로는 불편하고 합리적인 목적에 영향을 미치기도 합니다. 아래에서는 위의 세 가지 제한 사항을 우회하는 방법을 자세히 소개하겠습니다. 🎜🎜🎜🎜방지 방법 🎜🎜🎜🎜🎜 1. 쿠키 🎜🎜🎜쿠키는 서버가 브라우저에 기록하는 작은 정보 조각으로, 동일한 출처를 가진 웹페이지만 공유할 수 있습니다. 그러나 두 웹페이지의 1차 도메인 이름은 동일하지만 2차 도메인 이름은 다릅니다. 브라우저는
document.domain
을 설정하여 쿠키 공유를 허용합니다. 🎜🎜예를 들어 웹페이지 A는 http://w1.example.com/a.html
이고, 웹페이지 B는 http://w2.example.com/b.html입니다. , 동일한 document.domain이 설정되어 있는 한 두 웹페이지는 쿠키를 공유할 수 있습니다. 🎜<pre class="brush:js;toolbar:false;"> parent.location.href= target + "#" + hash;</pre>🎜이제 웹페이지 A는 스크립트를 통해 쿠키를 설정합니다🎜<pre class="brush:js;toolbar:false;">window.name = data;</pre>🎜웹페이지 B는 이 쿠키를 읽을 수 있습니다. 🎜<pre class="brush:js;toolbar:false;">location = &#39;http://parent.url.com/xxx.html&#39;;</pre>🎜이 방법은 쿠키 및 iframe 창에만 적용 가능합니다. LocalStorage 및 IndexDB는 이 방법을 통해 <br> 동일한 원본 정책을 우회할 수 없지만 아래에 소개된 PostMessage API를 사용해야 합니다. 🎜🎜또한 서버는 쿠키를 설정할 때 .example.com과 같이 쿠키의 도메인 이름을 1차 도메인 이름으로 지정할 수도 있습니다. 🎜<pre class="brush:js;toolbar:false;">var data = document.getElementById(&#39;myFrame&#39;).contentWindow.name;</pre>🎜이 경우 2차 도메인 이름과 3차 도메인 이름은 아무런 설정 없이 이 쿠키를 읽을 수 있습니다. 🎜🎜🎜2.iframe🎜🎜🎜두 웹페이지의 소스가 다를 경우 상대방의 DOM을 가져올 수 없습니다. 일반적인 예로는 iframe 창과 부모 창과 통신할 수 없는 window.open 메서드로 열린 창이 있습니다. 🎜🎜예를 들어 상위 창에서 다음 명령을 실행하는 경우 iframe 창이 동일한 소스에서 나오지 않으면 오류가 보고됩니다.🎜<pre class="brush:js;toolbar:false;"> var popup = window.open(&#39;http://bbb.com&#39;, &#39;title&#39;);
popup.postMessage(&#39;Hello World!&#39;, &#39;http://bbb.com&#39;);</pre>🎜위 명령에서 상위 창은 하위 창의 DOM을 얻으려고 합니다. , 교차 출처로 인해 오류가 보고됩니다. 🎜🎜 반대로 하위 창이 기본 창의 DOM을 얻을 때도 오류가 보고됩니다. 🎜<pre class="brush:js;toolbar:false;">window.opener.postMessage(&#39;Nice to see you&#39;, &#39;http://aaa.com&#39;);</pre>🎜두 창의 첫 번째 수준 도메인 이름이 동일하지만 두 번째 수준 도메인 이름이 다른 경우 이전 섹션에서 소개한 <code>document.domain
속성을 설정하면 동일 출처 정책을 적용하고 DOM을 가져옵니다. 🎜🎜출처가 완전히 다른 웹사이트의 경우 현재 크로스 도메인 창의 통신 문제를 해결하는 세 가지 방법이 있습니다. 🎜🎜🎜조각 식별자🎜🎜window.name🎜🎜교차 문서 메시징 API(교차 문서 메시징)🎜🎜🎜🎜2.1 조각 식별자🎜🎜片段标识符(fragment identifier)指的是,URL的#号后面的部分,比如http://example.com/x.html#fragment
的#fragment。如果只是改变片段标识符,页面不会重新刷新。
父窗口可以把信息写入子窗口的片段标识符。
var src = originURL + '#' + data; document.getElementById('myIFrame').src = src;
子窗口通过监听hashchange事件得到通知。
window.onhashchange = checkMessage; function checkMessage() { var message = window.location.hash; // ... }
同样的,子窗口也可以改变父窗口的片段标识符。
parent.location.href= target + "#" + hash;
2.2 window.name
浏览器窗口有window.name
属性。这个属性的最大特点是,无论是否同源,只要在同一个窗口里,前一个网页设置了这个属性,后一个网页可以读取它。
父窗口先打开一个子窗口,载入一个不同源的网页,该网页将信息写入window.name属性。
window.name = data;
接着,子窗口跳回一个与主窗口同域的网址。
location = 'http://parent.url.com/xxx.html';
然后,主窗口就可以读取子窗口的window.name了。
var data = document.getElementById('myFrame').contentWindow.name;
这种方法的优点是,window.name容量很大,可以放置非常长的字符串;缺点是必须监听子窗口window.name属性的变化,影响网页性能。
2.3 window.postMessage
上面两种方法都属于破解,HTML5为了解决这个问题,引入了一个全新的API:跨文档通信 API(Cross-document messaging)。
这个API为window对象新增了一个window.postMessage方法,允许跨窗口通信,不论这两个窗口是否同源。
举例来说,父窗口http://aaa.com向子窗口http://bbb.com发消息,调用postMessage方法就可以了。
var popup = window.open('http://bbb.com', 'title'); popup.postMessage('Hello World!', 'http://bbb.com');
postMessage方法的第一个参数是具体的信息内容,第二个参数是接收消息的窗口的源(origin),即”协议 + 域名 + 端口”。也可以设为*,表示不限制域名,向所有窗口发送。
子窗口向父窗口发送消息的写法类似。
window.opener.postMessage('Nice to see you', 'http://aaa.com');
父窗口和子窗口都可以通过message事件,监听对方的消息。
window.addEventListener('message', function(e) { console.log(e.data); },false);
message事件的事件对象event,提供以下三个属性。
event.source:发送消息的窗口 event.origin: 消息发向的网址 event.data: 消息内容
下面的例子是,子窗口通过event.source属性引用父窗口,然后发送消息。
window.addEventListener('message', receiveMessage); function receiveMessage(event) { event.source.postMessage('Nice to see you!', '*'); }
event.origin属性可以过滤不是发给本窗口的消息。
window.addEventListener('message', receiveMessage); function receiveMessage(event) { if (event.origin !== 'http://aaa.com') return; if (event.data === 'Hello World') { event.source.postMessage('Hello', event.origin); } else { console.log(event.data); } }
2.4 LocalStorage
通过window.postMessage
,读写其他窗口的 LocalStorage
也成为了可能。
下面是一个例子,主窗口写入iframe子窗口的localStorage。
window.onmessage = function(e) { if (e.origin !== 'http://bbb.com') { return; } var payload = JSON.parse(e.data); localStorage.setItem(payload.key, JSON.stringify(payload.data)); };
上面代码中,子窗口将父窗口发来的消息,写入自己的LocalStorage。
父窗口发送消息的代码如下。
var win = document.getElementsByTagName('iframe')[0].contentWindow; var obj = { name: 'Jack' }; win.postMessage(JSON.stringify({key: 'storage', data: obj}), 'http://bbb.com');
加强版的子窗口接收消息的代码如下。
window.onmessage = function(e) { if (e.origin !== 'http://bbb.com') return; var payload = JSON.parse(e.data); switch (payload.method) { case 'set': localStorage.setItem(payload.key, JSON.stringify(payload.data)); break; case 'get': var parent = window.parent; var data = localStorage.getItem(payload.key); parent.postMessage(data, 'http://aaa.com'); break; case 'remove': localStorage.removeItem(payload.key); break; } };
加强版的父窗口发送消息代码如下。
var win = document.getElementsByTagName('iframe')[0].contentWindow; var obj = { name: 'Jack' }; // 存入对象 win.postMessage(JSON.stringify({key: 'storage', method: 'set', data: obj}), 'http://bbb.com'); // 读取对象 win.postMessage(JSON.stringify({key: 'storage', method: "get"}), "*"); window.onmessage = function(e) { if (e.origin != 'http://aaa.com') return; // "Jack" console.log(JSON.parse(e.data).name); };
3、Ajax
同源政策规定,AJAX请求只能发给同源的网址,否则就报错。
除了架设服务器代理(浏览器请求同源服务器,再由后者请求外部服务),有三种方法规避这个限制。
JSONP WebSocket CORS
3.1 JSONP
JSONP是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,老式浏览器全部支持,服务器改造非常小。
它的基本思想是,网页通过添加一个3f1c4e4b6b16bbbd69b2ee476dc4f83a
元素,向服务器请求JSON数据,这种做法不受同源政策限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。
首先,网页动态插入3f1c4e4b6b16bbbd69b2ee476dc4f83a
元素,由它向跨源网址发出请求。
function addScriptTag(src) { var script = document.createElement('script'); script.setAttribute("type","text/javascript"); script.src = src; document.body.appendChild(script); } window.onload = function () { addScriptTag('http://example.com/ip?callback=foo'); } function foo(data) { console.log('Your public IP address is: ' + data.ip); };
上面代码通过动态添加3f1c4e4b6b16bbbd69b2ee476dc4f83a
元素,向服务器example.com发出请求。注意,该请求的查询字符串有一个callback参数,用来指定回调函数的名字,这对于JSONP是必需的。
服务器收到这个请求以后,会将数据放在回调函数的参数位置返回。
foo({ "ip": "8.8.8.8" });
由于3f1c4e4b6b16bbbd69b2ee476dc4f83a
元素请求的脚本,直接作为代码运行。这时,只要浏览器定义了foo函数,该函数就会立即调用。作为参数的JSON数据被视为JavaScript对象,而不是字符串,因此避免了使用JSON.parse的步骤。
3.2 WebSocket
WebSocket是一种通信协议,使用ws://(非加密)和wss://(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。
下面是一个例子,浏览器发出的WebSocket请求的头信息(摘自维基百科)。
GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13 Origin: http://example.com
上面代码中,有一个字段是Origin,表示该请求的请求源(origin),即发自哪个域名。
正是因为有了Origin这个字段,所以WebSocket才没有实行同源政策。因为服务器可以根据这个字段,判断是否许可本次通信。如果该域名在白名单内,服务器就会做出如下回应。
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= Sec-WebSocket-Protocol: chat
3.3 CORS
CORS는 Cross-Origin Resource Sharing의 약어입니다. 이는 W3C 표준이며 교차 출처 AJAX 요청을 위한 기본 솔루션입니다. GET 요청만 보낼 수 있는 JSONP와 비교하여 CORS는 모든 유형의 요청을 허용합니다.
【관련 권장 사항: javascript 학습 튜토리얼】
위 내용은 자바스크립트의 동일 출처 정책은 무엇인가요?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!