发布日期 » 2017年11月6日 星期一

作者署名 » 陈帅华

原创声明 » 署名-非商业性使用-相同方式共享

谈谈前端跨域问题解决方案

同源策略

同源政策的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。Cookie 往往用来保存用户的登录状态,如果用户没有退出登录,其他网站就可以冒充用户,为所欲为。因为浏览器同时还规定,提交表单不受同源政策的限制。
所谓同源指的是三个相同:

  • 协议相同(TCP/IP、HTTP、HTTPS、FTP等不同协议)
  • 域名相同(www.shuaihua.cc、shuaihua.cc等不同级别域名)
  • 端口相同(80、8080、3000,443等不同端口号)

目前,如果非同源,共有三种行为受到限制。

  • Cookie、LocalStorage 和 IndexDB 无法读取。
  • DOM 无法获得。
  • AJAX 请求不能发送。

根据以上三种跨域限制,就产生了各种各样的跨域请求解决方案。

跨域解决方案

解决 Cookie 跨域资源共享

Cookie 是服务器写入浏览器的一小段信息,只有同源的网页才能共享。

浏览器允许使用 document.domain 共享 Cookie.

比如有两个页面a.html, b.html,他们的一级域名相同,二级域名不同,如果b.html想要获取a.html的cookie,则a.html可以做这样的设置,告诉浏览器只要满足该值的就可以读取我的cookie。

a.html

document.domain = 'example.com';
document.cookie = 'test1=hello';

b.html

var allCookie = document.cookie;

当然,除了在前端进行设置外,还可以在服务器端进行 Cookie 共享的设置。

Set-Cookie: key=value; domain=.example.com; path=/

解决 localStorage、IndexDB、DOM元素信息 跨域资源共享

如果两个网页不同源,就无法拿到对方的DOM。典型的例子是iframe窗口和window.open方法打开的窗口,它们与父窗口无法通信。

对于iframe框架,如果为跨域页面,则父文档不能调用子文档的元素,同样,子文档也不能调用父文档的元素。

如果iframe框架引用的页面一级域名相同,则可以使用上面 document.domain 的方法解决。但是如果一级域名都不相同(比如帅华君将发布在优酷上的视频以iframe的形式引入到自己的网站里就属于这种跨域情况)。

对于第二种情况,有三种解决方案:

  • 片段识别符(fragment identifier)
  • window.name
  • 跨文档通信API(Cross-document messaging)

片段识别符

片段标识符(fragment identifier)指的是,URL的#号后面的部分,父窗口可以把信息,写入子窗口的片段标识符。

var src = originURL + '#' + data;
document.getElementById('myIFrame').src = src;

子窗口可以通过 hashchange 事件读取 # 后的数据,从而实现跨域资源共享。

window.onhashchange = checkMessage;

function checkMessage() {
  var message = window.location.hash;
  // ...
}

子窗口也可以改变父窗口的片段标识符,父窗口也监听 hashchange 事件。

parent.location.href= target + "#" + hash;

window.name

浏览器窗口有window.name属性。这个属性的最大特点是,无论是否同源,只要在同一个窗口里,前一个网页设置了这个属性,后一个网页可以读取它。

你可能会联想到,第三方登录时弹出的登录窗口,或者分享到微博的子窗口会用到。

这种方法的优点是,window.name容量很大,可以放置非常长的字符串;缺点是必须监听子窗口window.name属性的变化,影响网页性能。

跨文档通信API

上面两种方法都属于破解,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');

子窗口像父窗口发送消息。

window.opener.postMessage('Nice to see you', 'http://aaa.com');

postMessage方法的第一个参数是具体的信息内容,第二个参数是接收消息的窗口的源(origin),即"协议 + 域名 + 端口"。也可以设为*,表示不限制域名,向所有窗口发送。

父窗口和子窗口都可以通过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!', '*');
}

同样使用postMessage() API 共享localStorage。

父窗口获取子窗口并使用 postMessage API向子窗口发送消息。

var win = document.getElementsByTagName('iframe')[0].contentWindow;
var obj = { name: 'Jack' };
win.postMessage(JSON.stringify({key: 'storage', data: obj}), 'http://bbb.com');

子窗口监听 message 事件,并将父窗口发来的信息存入自己的 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));
};

三、AJAX 跨域资源共享

同源政策规定,AJAX请求只能发给同源的网址,否则就报错。除了架设服务器代理(浏览器请求同源服务器,再由后者请求外部服务),有三种方法规避这个限制。

  • JSONP
  • WebSocket
  • CORS

JSONP

JSONP是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,老式浏览器全部支持,服务器改造非常小。

它的基本思想是,网页通过添加一个 <script> 元素,向服务器请求JSON数据,这种做法不受同源政策限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。

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);
};

WebSocket

WebSocket是一种通信协议,使用 ws:// (非加密)和wss:// (加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。

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这个字段,所以WebSocket才没有实行同源政策。因为服务器可以根据这个字段,判断是否许可本次通信。如果该域名在白名单内,服务器就会做出如下回应。

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat

CORS

它是W3C标准,是跨源AJAX请求的根本解决方法。相比JSONP只能发GET请求,CORS允许任何类型的请求。

CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。

整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。

因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

XSS 知识点,全程为 Cross Site Scripting。为什么不叫CSS,你觉得呢??

恶意攻击者往Web页面里插入恶意Script代码,当用户浏览该页之时,嵌入其中Web里面的Script代码会被执行,从而达到恶意攻击用户的目的。

分为 存储型反射型

存储型存:储型XSS,持久化,代码是存储在服务器中的,如在个人信息或发表文章等地方,加入代码,如果没有过滤或过滤不严,那么这些代码将储存到服务器中,用户访问该页面的时候触发代码执行。这种XSS比较危险,容易造成蠕虫,盗窃cookie(虽然还有种DOM型XSS,但是也还是包括在存储型XSS内)。

反射型:非持久化,需要欺骗用户自己去点击链接才能触发XSS代码(服务器中没有这样的页面和内容),一般容易出现在搜索页面。


相关链接: