JS跨域

@一棵菜菜  May 5, 2018
推荐看我的思维导图《跨域》
强烈推荐官方文档《HTTP访问控制(CORS)》
推荐官方文档《window.postMessage》

1. 为什么会出现"跨域"的问题?

因为浏览器的同源策略导致了跨域,就是浏览器在搞事情~~~
是浏览器做的一件好事~~~

2. 同源策略

同源或同域:即协议、域名、端口都必须相同。

http://caiyichen.me:8080
协议    域名         端口号

由于浏览器实现的同源策略的限制,使一个页面的ajax只能获取这个页面相同源或者相同域的数据,所以AJAX是不允许跨域的

不过 像<img>的src(获取图片),<link>的href(获取css),<script>的src,<iframe>这些标签是允许跨域的,但你并不能修改这些资源,比如iframe里的内容。

为什么浏览器要实现同源限制?

因为若无同源策略,则不同源的数据和资源(如HTTP头、Cookie、DOM、localStorage等)就能相互随意访问,根本没有隐私和安全可言。

我们举例说明:
比如一个黑客,他利用iframe把真正的银行登录页面嵌到他的页面上,当你使用真实的用户名和密码登录时,如果没有同源限制,他的页面就可以通过 JavaScript读取到你的表单中输入的内容(或者通过dom操作获取到用户输入的值),这样用户名和密码就轻松到手了。如果没有同源策略,那么我还可以通过document.cookie获取到iframe页面中保存在cookie中的密码(正因为有了同源策略,所以我们无法访问非同源的cookie)

又比如你登录了OSC,同时浏览了恶意网站,如果没有同源限制,该恶意 网站就可以构造AJAX请求频繁在OSC发广告帖.


3. 盗链

互联网的许多网站之间图片相互盗链,A网站网页的img.src直接链接到B网站的图片地址。

重点:<img>的src(获取图片),<link>的href(获取css),<script>的src(获取javascript)这三个都不符合同源策略,它们可以跨域获取数据。

因此,你可以直接从一些cdn上获取jQuery,并且你网站上的图片也随时可能被别人盗用,所有最好加上水印!

jsonp:就是因为<script>的src不符合同源策略而来的。


4. 解决跨域的几种方式【重点】

同源策略限制下接口请求的正确打开方式

1. JSONP

见下方详解

2. CORS(跨域资源共享)

强烈推荐官方文档《HTTP访问控制(CORS)》
可以查看聚美天天向上限时秒杀活动的接口请求头,活动地址:http://r.jumei.com/dayDayUp/

3. 代理(Nginx)

了解下

同源策略限制下Dom查询的正确打开方式

1. 通过修改document.domain来跨子域

该方式只能用于**二级域名相同**的情况下(即不同子域的框架间的交互),比如 `a.test.com` 和 `b.test.com` 适用于该方式。

只需要给页面添加 `document.domain = 'test.com'` 表示二级域名都相同就可以实现跨域
(只能把document.domain设置成自身或更高一级的父域,且主域必须相同)

2. 使用window.name来进行跨域

3. H5:使用otherWindow.postMessage(message,targetOrgin) 来跨域传送数据

见下方详解

5. JSONP详细解析

原理

利用 <script> 标签没有跨域限制的漏洞。

即:前端通过 <script> 标签指向一个需要访问的地址,并提供一个回调函数callback来接收数据data
后端从地址中获取到callback参数,然后携带数据data组成一个函数的形式(callback(data))返回给前端,前端将执行此函数

JSONP 使用简单且兼容性不错,但是只限于get 请求(因为本质上script加载资源就是get)。

具体分析

1. 前端

<script>
    function jsonp(data) {
        console.log(data)
    }
</script>   

<script src="http://domain/api?param1=a&param2=b&callback=jsonp"></script>
【重点分析1】
<script>标签的 src 地址中增加了?callback=jsonp,意思是把显示数据的函数jsonp也动态传过去了。

2. 后端

至于callback参数后台如何接收,如何使用,后端大概如下:

...
if (Request["callback"] != null) {
  callback = Request["callback"];

  // 假设这是服务器端要返回的数据
  let data = "1024";
 // 返回给前端
  Response.Write(callback + "(" + data + ")");
}
【重点分析2】
后端获取到callback参数,然后携带数据组成一个函数的形式返回给前端
即:如果http://domain/api?callback=jsonp被调用的话,那么返回的就是" jsonp(1024) "(则这个jsonp函数将在前端被执行~~~)。

此jsonp部分内容来源于《jsonp其实很简单【ajax跨域请求】》,非常推荐此文!


6. otherWindow.postMessage() 详细解析

这种方式通常用于获取嵌入页面中的第三方页面数据。一个页面发送消息,另一个页面判断来源并接收消息

推荐官方文档《window.postMessage》

1. 向目标窗口派发一个 MessageEvent 消息

otherWindow.postMessage(message, targetOrigin, [transfer]);

otherWindow

其他窗口的一个引用,比如iframe的contentWindow属性、执行window.open返回的窗口对象、或者是命名过或数值索引的window.frames

调用postMessage方法的window对象是指要接收消息的那一个window对象,

message

将要发送到其他 window 的数据。它将会被结构化克隆算法序列化。这意味着你可以不受什么限制的将数据对象安全的传送给目标窗口而无需自己序列化。

targetOrigin

通过窗口的origin属性来指定哪些窗口能接收到消息事件,其值可以是字符串"*"(表示无限制)或者一个URI。

在发送消息的时候,如果目标窗口的协议、主机地址或端口这三者的任意一项不匹配targetOrigin提供的值,那么消息就不会被发送;只有三者完全匹配,消息才会被发送

官方文档例子(很详细滴)

/*
 * A窗口的域名是<http://example.com:8080>,以下是A窗口的script标签下的代码:
 */

// 打开一个新窗口(页面)
var popup = window.open(...popup details...);

// 如果弹出框没有被阻止且加载完成

// 这行语句没有发送信息出去,即使假设当前页面没有改变location(因为targetOrigin设置不对)
popup.postMessage("The user is 'bob' and the password is 'secret'",
                  "https://secure.example.net");

// 假设当前页面没有改变location,这条语句会成功添加message到发送队列中去(targetOrigin设置对了)
popup.postMessage("hello there!", "http://example.org");

function receiveMessage(event)
{
  // 我们能相信信息的发送者吗?  (也许这个发送者和我们最初打开的不是同一个页面).
  if (event.origin !== "http://example.org")
    return;

  // event.source 是我们通过window.open打开的弹出页面 popup
  // event.data 是 popup发送给当前页面的消息 "hi there yourself!  the secret response is: rheeeeet!"
}
window.addEventListener("message", receiveMessage, false);

2. 其他window通过监听自身的message事件来获取消息

function receiveMessage(event){}
window.addEventListener("message",receiveMessage, false);

需要接收消息的window对象,可以通过监听自身的message事件来获取传过来的消息,消息内容储存在该事件对象的data属性中。

注意:用于接收消息的任何事件监听器,必须首先使用originsource属性来检查消息的发送者的身份!

event.origin 当前弹出页的来源(消息发送方的url(如:协议+域名+端口号))
event.source 当前弹出页的来源页面(消息发送方页面)
event.data 接收到的消息内容

/*
 * 弹出页 popup 域名是<http://example.org>,以下是script标签中的代码:
 */

//当A页面postMessage被调用后,这个function被addEventListenner调用
function receiveMessage(event){
  // 我们能信任信息来源吗?
  if (event.origin !== "http://example.com:8080")
    return;

  // event.source 当前弹出页的来源页面
  // event.data 是 "hello there!"

  // 假设你已经验证了所受到信息的origin (任何时候你都应该这样做), 一个很方便的方式就是把event.source
  // 作为回信的对象,并且把event.origin作为targetOrigin
  event.source.postMessage("hi there yourself!  the secret response " +
                           "is: rheeeeet!",
                           event.origin);
}

window.addEventListener("message", receiveMessage, false);

其他例子

11.png
22.png

推荐官方文档《window.postMessage》

其他

参考文档:
非常推荐《不要再问我跨域的问题了》

《js中几种实用的跨域方法原理详解》
《浏览器为什么要设计同源策略?》
《为什么要有同源策略》


添加新评论