Skip to main content

跨域

同源策略限制了从同一个源加载的文档或脚本如何与另一个源的资源进行交互。这是浏览器的一个用于隔离潜在恶意文件的重要的安全机制。同源指的是:协议、端口号、域名必须一致。

访问同一域名下不同资源不会跨域

CORS

浏览器将 CORS 分为简单请求和非简单请求:

简单请求不会触发 CORS 预检请求。若该请求满足以下两个条件,就可以看作是简单请求:

1)请求方法是以下三种方法之一: ● HEAD ● GET ● POST

2)HTTP 的头信息不超出以下几种字段: ● Accept ● Accept-Language ● Content-Language ● Last-Event-ID ● Content-Type:只限于三个值 application/x-www-form-urlencoded、multipart/form-data、text/plain 若不满足以上条件,就属于非简单请求了。

在简单请求中,在服务器内,至少需要设置字段:Access-Control-Allow-Origin

在非简单请求中,至少需要设置以下字段:

'Access-Control-Allow-Origin'
'Access-Control-Allow-Methods'
'Access-Control-Allow-Headers'

JSONP 跨域

实现原理

1.动态创建一个<script>标签

2.将请求的数据作为参数拼接在 URL 中,并指定回调函数的名称,然后通过这个<script>标签来向服务器请求数据

3.服务器收到请求后将数据以参数的形式传递给回调函数,最后将数据作为参数传递回来,由于这个回调函数是在同域下执行的,因此就解决了跨域问题。注意 JSONP 只能用于 GET 请求

具体

要想实现一个 JSONP,可以按照以下的步骤

1.在 HTML 中创建一个回调函数,例如 handleData,并在全局范围定义它。这个函数将在数据请求成功后执行并处理返回后的数据

<script>
function handleData(data)
{
// 处理返回的数据
}
</script>

2.动态创建一个<script>标签,并将数据源的 URL 作为其 src 的属性值,并将回调函数名称以参数的形式传递给服务器,例如http://example.com/data?callback=handleData`。`

<script>
var script = document.createElement("script");
var url = "http://example.com/data?callback=handleData";

// 数据源的 URL
script.src = url;
document.head.appendChild(script);
</script>

3.服务器接收到请求之后,将数据作为参数传递给回调函数并将其包装在函数调用中返回。如果使用 Nodejs,可以使用 querystring 模块解析请求参数并返回符合 JSONP 规范的响应

const http = require("http");
const querystring = require("querystring");
const server = http.createServer((req, res) => {
const params = querystring.parse(req.url.split("?")[1]);
// 服务端存储数据
const data = { name: "John", age: 30 };
const callback = params.callback;
res.writeHead(200, { "Content-Type": "text/javascript" });
// 调用客户端callback
res.end(`${callback}(${JSON.stringify(data)})`);
//返回的是一个脚本a(所需参数)!
});
server.listen(80);

4.当数据源返回响应时候,浏览器会将响应解释为 js 代码,("Content-Type": "text/javascript")

并在页面中执行回调函数。在 handleData 函数中,可以通过参数访问返回的数据并且进行处理

postMessage 跨域

postMessage 是 HTML5 XMLHttpRequest Level 2 中的 API,且是为数不多可以跨域操作的 window 属性之一,它可用于解决以下方面的问题: ● 页面和其打开的新窗口的数据传递 ● 多窗口之间消息传递 ● 页面与嵌套的 iframe 消息传递 ● 上面三个场景的跨域数据传递

用法:postMessage(data,origin)方法接受两个参数: ● data: html5 规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用 JSON.stringify()序列化。 ● origin: 协议+主机+端口号,也可以设置为"*",表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。

domain1.com/a.html

<iframe
id="iframe"
src="http://www.domain2.com/b.html"
style="display:none;"
></iframe>
<script>
var iframe = document.getElementById("iframe");
iframe.onload = function () {
var data = {
name: "aym",
};
// 向domain2传送跨域数据
iframe.contentWindow.postMessage(
JSON.stringify(data),
"http://www.domain2.com"
);
};
// 接受domain2返回数据
window.addEventListener(
"message",
function (e) {
alert("data from domain2 ---> " + e.data);
},
false
);
</script>

domain2.com/b.html

<script>
// 接收domain1的数据
window.addEventListener(
"message",
function (e) {
alert("data from domain1 ---> " + e.data);
var data = JSON.parse(e.data);
if (data) {
data.number = 16;
// 处理后再发回domain1
window.parent.postMessage(
JSON.stringify(data),
"http://www.domain1.com"
);
}
},
false
);
</script>

nginx 代理跨域

nginx 代理跨域,实质和 CORS 跨域原理一样,通过配置文件设置请求响应头 Access-Control-Allow-Origin…等字段, 而不是后端手动设置

1)nginx 配置解决 iconfont 跨域 浏览器跨域访问 js、css、img 等常规静态资源被同源策略许可,但 iconfont 字体文件(eot|otf|ttf|woff|svg)例外,此时可在 nginx 的静态资源服务器中加入以下配置。(因为在 location 目录??)

location / {
add_header Access-Control-Allow-Origin *;
}

2)nginx 反向代理接口跨域 跨域问题:同源策略仅是针对浏览器的安全策略。服务器端调用 HTTP 接口只是使用 HTTP 协议,不需要同源策略,也就不存在跨域问题。 实现思路:通过 Nginx 配置一个代理服务器域名与 domain1 相同,端口不同)做跳板机,反向代理访问 domain2 接口,并且可以顺便修改 cookie 中 domain 信息,方便当前域 cookie 写入,实现跨域访问。

#proxy服务器
server {
listen 81;
server_name www.domain1.com;
location / {
proxy_pass http://www.domain2.com:8080; #反向代理
proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
index index.html index.htm;
# 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用
add_header Access-Control-Allow-Origin http://www.domain1.com; #当前端只跨域不带cookie时,可为*
add_header Access-Control-Allow-Credentials true;
}
}

nodejs 中间件代理

node 中间件实现跨域代理,原理大致与 nginx 相同,都是通过启一个代理服务器,实现数据的转发,也可以通过设置 cookieDomainRewrite 参数修改响应头中 cookie 中域名,实现当前域的 cookie 写入,方便接口登录认证。

非 vue 框架的跨域 使用 node + express + http-proxy-middleware 搭建一个 proxy 服务器

vue 框架的跨域 node + vue + webpack + webpack-dev-server 搭建的项目,跨域请求接口,直接修改 webpack.config.js 配置。开发环境下,vue 渲染服务和接口代理服务都是 webpack-dev-server 同一个,所以页面与代理接口之间不再跨域。

document.domain + iframe

注意不同子域之间的 iframe 是跨域的 这里是页面与页面间的跨域,而不是页面与服务器

document.domain + iframe 跨域 此方案仅限主域相同,子域不同的跨域应用场景。实现原理:两个页面都通过 js 强制设置 document.domain 为基础主域,就实现了同域。 1)父窗口:(domain.com/a.html)

<iframe id="iframe" src="http://child.domain.com/b.html"></iframe>
<script>
document.domain = "domain.com";
var user = "admin";
</script>

2)子窗口:(child.domain.com/a.html)

<script>
document.domain = "domain.com";
// 获取父窗口中变量
console.log("get js data from parent ---> " + window.parent.user);
</script>

location.hash + iframe 跨域

实现原理:a 欲与 b 跨域相互通信,通过中间页 c 来实现。 三个页面,不同域之间利用 iframe 的 location.hash 传值,相同域之间直接 js 访问来通信。

具体实现:A 域:a.html -> B 域:b.html -> A 域:c.html,a 与 b 不同域只能通过 hash 值单向通信,b 与 c 也不同域也只能单向通信,但 c 与 a 同域,所以 c 可通过 parent.parent 访问 a 页面所有对象。

1)a.html:(domain1.com/a.html)

<iframe
id="iframe"
src="http://www.domain2.com/b.html"
style="display:none;"
></iframe>
<script>
var iframe = document.getElementById("iframe");
// 向b.html传hash值
setTimeout(function () {
iframe.src = iframe.src + "#user=admin";
}, 1000);

// 开放给同域c.html的回调方法
function onCallback(res) {
alert("data from c.html ---> " + res);
}
</script>

2)b.html:(.domain2.com/b.html)

<iframe
id="iframe"
src="http://www.domain1.com/c.html"
style="display:none;"
></iframe>
<script>
var iframe = document.getElementById("iframe");
// 监听a.html传来的hash值,再传给c.html
window.onhashchange = function () {
iframe.src = iframe.src + location.hash;
};
</script>

c.html:(http://www.domain1.com/c.html)

虽然单向通信

<script>
// 监听b.html传来的hash值
window.onhashchange = function () {
// 再通过操作同域a.html的js回调,将结果传回
window.parent.parent.onCallback(
"hello: " + location.hash.replace("#user=", "")
);
};
</script>

window.name + iframe 跨域

window.name 属性的独特之处:name 值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。

WebSocket 协议跨域

WebSocket protocol 是 HTML5 一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是 server push 技术的一种很好的实现。

原生 WebSocket API 使用起来不太方便,我们使用 Socket.io,它很好地封装了 webSocket 接口,提供了更简单、灵活的接口,也对不支持 webSocket 的浏览器提供了向下兼容。

<div>user input:<input type="text" /></div>
<script src="https://cdn.bootcss.com/socket.io/2.2.0/socket.io.js"></script>
<script>
var socket = io("http://www.domain2.com:8080");
// 连接成功处理
socket.on("connect", function () {
// 监听服务端消息
socket.on("message", function (msg) {
console.log("data from server: ---> " + msg);
});
// 监听服务端关闭
socket.on("disconnect", function () {
console.log("Server socket has closed.");
});
});
document.getElementsByTagName("input")[0].onblur = function () {
socket.send(this.value);
};
</script>

后端

var http = require("http");
var socket = require("socket.io");
// 启http服务
var server = http.createServer(function (req, res) {
res.writeHead(200, {
"Content-type": "text/html",
});
res.end();
});
server.listen("8080");
console.log("Server is running at port 8080...");
// 监听socket连接
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.");
});
});