现代浏览器出于安全考虑,都会去遵守一个叫做“同源策略”的约定,同源的意思是两个地址的协议、域名、端口号都相同的情况下,才叫同源。这个时候两个地址才可以相互访问 cookie、localStorage、sessionStorage、发送 ajax 请求,如果三者有一个不同,就是不同源,这时再去访问这些资源就叫做跨域。
跨域访问会造成很多安全问题,下面我们来看一下常见的两种跨域请求:
那么哪些请求时不会造成跨域的呢
因为这些请求不会携带 cookie,也就没有跨域限制
浏览器对某些请求限制了跨域访问,但是实际开发中,我们又确实需要去跨域访问,怎么办呢?
由于访问 js 文件是没有跨域限制的,我们可以利用这一点来做跨域。具体过程如下:
客户端预先定义好一个 function 用来接收数据,然后动态的创建一个 script 插入到页面中去后端请求数据
function run (data) {
console.log(data.name)
}
var script = document.createElement('script')
script.src = 'http://xxx.js?callback=run'
document.body.appendChild(script)
服务端通过 callback 指定的函数名,将数据传入其中,这样 js 到达客户端就能直接执行函数,把数据传入预先定义好的函数中
run({ name: '张三' })
优点:
缺点:
该属性有以下特征
默认情况下 document.domain 存放的是载入文档的主机名,例如 a.main.com。可以手动修改这个值,但是有限制:
利用这一点,对于主域名相同,而子域名不同的两个页面,例如 a.main.com 使用 iframe 嵌入了 b.main.com,可以使用 document.domain 来跨域。
当在 a 页面直接通过 iframe.contentWindow 想去操作 b 页面,或者 b 页面通过 window.top、window.parent 想去操作 a 页面时,就是出现跨域限制。
这个时候我们可以在 a、b 两个页面同时设置 document.domain = 'main.com',然后再去相互访问就没有问题了
我们都知道,页面地址中 hash 值的改变是不会引起页面刷新的,所以我们可以利用 hash 值来在不同域中传递数据。例如
a.com 使用 iframe 嵌入了 b.com。a 页面要向 b 页面传递数据就可以通过改变 b 页面的 hash 值,具体过程如下:
a 页面:iframe.src = b.com#age=18
b 页面:window.onhashchange = function () {console.log(localtion.hash)}
缺点:
postMessage 是 HTML5 XMLHttpRequest Level 2 中的 API,它可以解决以下几个需求场景:
举例说明一下用法,比如 a 页面内通过 iframe 嵌入了 b 页面,现在要互相传递数据
a -> b
b -> a
这里需要注意几点
浏览器有跨域限制,但是服务器不存在跨域问题。所以可以浏览器可以把需要跨域的请求先发给服务器,由服务器去请求资源,然后再把结果返回给浏览器。
典型的应用场景:前后端分离
前后端分别有一个域名,前端访问服务端接口就出现了跨域限制,这个时候前端就可以在所有的服务端接口前加上一个标志如 /api,然后在前端服务器和后端服务器前在加一层分发,带 /api接口的都访问后端服务器,并去掉 /api,其他的都访问前端服务器。
WebSocket protocol 是 HTML5 中的一种新协议,它实现了浏览器与服务端全双工通信,同时支持跨域。拿比较知名的websocket 库 Socket.io 来举例说明它的用法:
浏览器
var socket = io('http://localhost:8080')
socket.on('connect', function () {
socket.on('message', function(msg) {
console.log('收到消息:' + msg)
})
socket.on('disconnect', function() {
console.log('关闭连接')
})
})
服务器
socket.listen(server).on('connection', function(client) {
// 接收信息
client.on('message', function(msg) {
client.send('hello:' + msg);
console.log('data from client: ---> ' + msg);
});
// 断开处理
client.on('disconnect', function() {
console.log('Client socket has closed.');
});
});
CORS 时 W3C 的标准,它允许浏览器向跨源服务器发起 ajax 请求,从而达到跨域访问的目的。这是当前比较流行的跨域方式,知识点也比较多,下面我们一一展开。
浏览器将 CORS 请求分为两类:简单请求(simple request)和非简单请求(not-so-simple request)
简单请求需要同时满足以下两个条件:
1、请求方法是一下三种之一
2、请求头不能超出一下几个字段
除此以外的就是非简单请求
当浏览器判断此次是一个跨域的 ajax 简单请求时,会自动在 request header 中加入 Origin 字段,用来标识本次请求来自哪个源(协议、域名、端口号),服务器根据这个值决定是否允许本次请求。
如果服务器同意本次请求,会在 response header 中加入以下几个字段
默认情况下,CORS 请求是不携带 cookie 的。如果需要在 request header 中放入 cookie,除了上面说的服务器需要在 response header 中设置 Allow-Control-Allow-Credentials: true,表示愿意接受跨域 cookie 之外,浏览器也需要设置一个参数表示允许发送 cookie
xhr.withCredentials = true;
我们先来看一个 http 请求
var xhr = new XMLHttpRequest()
xhr.open('DELETE', 'http://xxx/deluser', true)
xhr.setRequestHeader('token', '888')
xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8')
xhr.send()
预检请求(prelight)
根据上面我们对简单请求的约定,发现这条请求的方法、头字段、Content-Type 的值都不符合简单请求的约定。所以浏览器会判断出此次的 http 请求是一次非简单请求。
浏览器会先发出一条 http 请求询问服务器,当前源是否在服务器的允许名单之内,以及可以使用哪些头信息。我们称这次询问为“预检”(prelight),预检的请求方法是 options,request header 中会有这么几个重要的字段:
服务器接收到“预检”请求后,会检查上述三个字段,如果允许跨域就做出相应头信息
正常请求
浏览器在接收到服务器的“预检”相应后,判断出服务器允许跨域。这时便会发送正常的请求,只是在每次请求中,都会往 request header 中加入 Origin 字段,相应的服务器也会在每次相应中,在 response header 加入 Access-Control-Allow-Origin 字段