浅谈前端跨域

60次阅读

共计 4298 个字符,预计需要花费 11 分钟才能阅读完成。

一、什么是跨域?

跨域简单的理解就是 JavaScript 同源策略的限制。是出于安全的考虑,a.com 域名下的 js 不能操作 b.com 或者 c.com 域名下的对象。
当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域。不同域之间相互请求资源,就算叫“跨域”。

一个正常的域名地址组成(图片来自网络资源):

注意:跨域不是请求发布出去,请求可以正常发出,服务器也能收到并返回结果,只是结果被浏览器所拦截了。

附上一张参考图,便于大家深入理解(图片来自网络资源)

二、什么是同源策略与限制

同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。这是一种保护用户信息,防止恶意身份伪造的一种安全机制。
同源策略限制的内容:

  1. Ajax 请求不能正常进行。
  2. Cookie、LocalStoage、indexDB 等无法读取。

3.DOM 无法获得。

不过,有几个标签却可以允许跨域请求资源(可以作为解决跨域的一种方案)。

1.<img src=”xxxx” alt=””>
2.<link rel=”stylesheet” href=”xxx”>
3.<script src=”xxx”></script>

三、处理跨域方法一——JSONP

JSONP 是跨域通信最常用的方法,其最大的特点就是简单适用、兼容性好,可用于解决主流浏览器的跨域数据访问的问题。缺点是仅支持 get 方法具有局限性。它的基本思想是,在网页中添加一个 <script> 标签,像服务器请求 JSON 数据。它需要服务器端的配合,服务器收到请求后,将数据放在指定名字的回调函数中传回来。
 <script type="text/javascript">
    function foo(data) {console.log(data.msg);
    }
</script>
<script type="text/javascript" src="http://xxx.com/xx?callback=foo"></script>

注意,该请求的查询字符串有一个 callback 参数,用来指定回调函数的名字,这对于 JSONP 是必需的。

由于 <script> 元素请求的脚本,直接作为代码运行。这时,只要浏览器定义了 foo 函数,该函数就会立即调用。作为参数的 JSON 数据被视为 JavaScript 对象,而不是字符串,因此避免了使用 JSON.parse 的步骤。

jQuery 的 jsonp 形式

JSONP 都是 GET 和异步请求的,不存在其他的请求方式和同步请求,且 jQuery 默认就会给 JSONP 的请求清除缓存

$.ajax({
url: "http://xxx/xx",
dataType: "jsonp",
type: "get",// 可以省略
jsonpCallback: "fn",//-> 自定义传递给服务器的函数名,而不是使用 jQuery 自动生成的,可省略
jsonp: "jsonp",//-> 把传递函数名的那个形参 callback 变为 jsonp,可省略
success: function (data) {console.log(data);
} });

四、处理跨域方法二——CORS

CORS 是跨源资源分享(Cross-Origin Resource Sharing)的缩写。它是 W3C 标准,是跨源 AJAX 请求的根本解决方法。相比 JSONP 只能发 GET 请求,CORS 允许任何类型的请求。缺点是兼容性不如 JSONP。

CORS 要求浏览器 (>IE10) 和服务器的同时支持,是跨域的根本解决方法,由浏览器自动完成。因此,实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨源通信。
服务器端做的小改动:

header(“Access-Control-Allow-Origin:*”);
header(“Access-Control-Allow-Methods:POST,GET”);

// 在服务器端设置同源策略地址

router.get("/userlist",function (req, res,next) {
var user = {
        name: 'Mr.Cao',
        gender: 'male',
        career: 'IT Education'
    };
res.writeHeader(200, {"Access-Control-Allow-Origin": 'http://localhost:63342'});
res.write(JSON.stringify(user));
res.end();});

在响应头上添加 Access-Control-Allow-Origin 属性,指定同源策略的地址。同源策略默认地址是网页的本身。只要浏览器检测到响应头带上了 CORS,并且允许的源包括了本网站,那么就不会拦截请求响应。

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

六、处理跨域方法四——跨文档通信 API/ 片段识别符 /window.name

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

对于完全不同源的网站,目前有三种方法,可以解决跨域窗口的通信问题。

6.1 片段识别符

片段标识符(fragment identifier)指的是,URL 的 #号后面的部分,比如 http://xxx.com/x.html#fragmen…。如果只是改变片段标识符,页面不会重新刷新。
父窗口可以把信息,写入子窗口的片段标识符。

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

子窗口通过监听 hashchange 事件得到通知。

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

6.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 属性的变化,影响网页性能。

6.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: 消息内容

七、处理跨域方法五 -document.domain

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 无法通过这种方法。

正文完
 0