跨域的产生于解决
Published in:2022-08-05 | category: 前端 解决方案

什么是跨域

跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对 JavaScript 实施的安全限制。
同源策略:是一个重要的安全策略,它用于限制一个 origin 的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。所谓的同源,指的是协议、域名、端口相同。浏览器处于安全方面的考虑,只允许本域名下的接口交互,不同源的客户端脚本,在没有明确授权的情况下,不能读写对方的资源。

同源策略限制的内容

  • Cookie , LocalStorage ,IndexedDB 等存储性内容。
  • DOM 节点
  • AJAX 请求发送后,非同源会被浏览器拦截。
    但是有三个标签是允许跨域加载资源:
1
2
3
<img src=XXX>
<link href=XXX>
<script src=XXX>

跨域产生的原因

浏览器遵从同源策略,限制 ajax  跨域的原因在于 ajax  网络请求是可以携带 cookie  的(通过设置 withCredentials  为 true ),比如用户打开了浏览器,登录了 weibo.com ,然后又打开了baidu.com,这时候百度首页内的 js ,向 weibo.com  用 withCredentials  为 true  的 ajax  方式提交一个 post  请求,是会携带浏览器和 weibo.com  之间的 cookie  的,所以浏览器就默认禁止了 ajax  跨域。
只有当  protocol(协议)、domain(域名)、port(端口)三者一致,才是同源。

反向代理和正向代理

反向代理:隐藏真实的服务器端

类似场景:拨打总机号,然后一层层转接到分机
真实场景:访问淘宝—》反向代理服务器—》转发到真实的服务器—》返回资源给反向代理服务器—》返回给客户端

正向代理:隐藏真实的客户端

类似场景:A 问 B 借钱,B 不肯,A 拜托 B 的挚友 C 帮忙,C 问 B 借到了钱,然后把钱给了 A,而 B 并不知道实际是 A 借的钱
真实场景:客户端请求访问 facebook.com,无法访问到资源,这时借助代理,代理请求 facebook 的资源,然后发送给客户端,facebook.com 并不知道真正的客户端是谁

跨域的解决方案

Nginx 反向代理

介绍

Nginx 通过反向代理的方式保证当前域,能获取到静态资源和接口,不关心是怎么获取的。
Nginx 从入门到实践
nginx 反向代理和负载均衡策略实战案例

配置 Nginx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
server{
listen 80;
server_name www.xxx.com;
index index.html index.htm default.html default.htm;

#ERROR-PAGE-START 错误页配置,可以注释、删除或修改
#error_page 404 /www/wwwroot/index.html;

location / {
#proxy_pass http://abc.xx.com;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
root /www/wwwroot/proxy.test;
try_files $uri $url/ /index.html = 404;
}
location ^~ /api/ {
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_http_version 1.1;

#持久化连接相关配置
#proxy_connect_timeout 30s;
#proxy_read_timeout 86400s;
#proxy_send_timeout 30s;
#proxy_http_version 1.1;
#proxy_set_header Upgrade $http_upgrade;
#proxy_set_header Connection $connection_upgrade;

proxy_pass http://app-api;
proxy_intercepe_errors on;
}
}

实现

前端代码

1
2
3
4
5
6
7
8
9
10
11
<script>
axios.defaults.withCredentials = true;
getlist.onclick = () => {
axios.get("/api/list").then(res => {
console.log(res.data);
});
};
login.onclick = () => {
axios.post("/api/login");
};
</script>

后端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
router.get("/api/list", async ctx => {
ctx.body = {
data: [{ name: "test" }]
};
});
router.post("/api/login", async ctx => {
ctx.cookies.set("token", token, {
expires: new Date(+new Date() + 1000 * 60 * 60 * 24 * 7)
});
ctx.body = {
msg: "success",
code: 0
};
});

CORS

CORS(cross-origin resource sharing),跨源资源共享(一般俗称『跨域请求』),跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的 Web 应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器「不同的域、协议或端口」请求一个资源时,资源会发起一个「跨域 HTTP 请求。 
MDN 上的介绍 (https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)。
而在 cors 中会有 简单请求预检请求(preflighted requests)的概念。
浏览器支持情况:当你使用 IE<=9, Opera<12, or Firefox<3.5 或者更加老的浏览器,这个时候请使用 JSONP 。

简单请求

不会触发 CORS 预检请求。这样的请求为“简单请求”,请注意,该术语并不属于 Fetch (其中定义了 CORS)规范。
若请求满足所有下述条件,则该请求可视为“简单请求”:

  • HTTP 方法只能是 GET、HEAD 或 POST;
  • HTTP 头只能是 Accept/Accept-Language/Conent-Language/Content-Type/DPR/Downlink/Save-Data/Viewport-Width/Width;
  • Content-Type 头只能是 text/plain、multipart/form-data 或 application/x-www-form-urlencoded。
  • 请求中的任意  XMLHttpRequestUpload 对象均没有注册任何事件监听器;XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。
  • 请求中没有使用 ReadableStream 对象。

非简单请求

除以上情况外的。

看上去很是复杂。那么怎么理解这些限制呢?
其实,简单请求就是普通 HTML Form 在不依赖脚本的情况下可以发出的请求,比如表单的 method 如果指定为 POST ,可以用 enctype 属性指定用什么方式对表单内容进行编码,合法的值就是前述这三种。
非简单请求就是普通 HTML Form 无法实现的请求。比如 PUT 方法、需要其他的内容编码方式、自定义头之类的。

对于服务器来说,第一,许多服务器压根没打算给跨源用。当然你不给 CORS 响应头,浏览器也不会使用响应结果,但是请求本身可能已经造成了后果。所以最好是默认禁止跨源请求。
第二,要回答某个请求是否接受跨源,可能涉及额外的计算逻辑。这个逻辑可能很简单,比如一律放行。也可能比较复杂,结果可能取决于哪个资源哪种操作来自哪个 origin。对浏览器来说,就是某个资源是否允许跨源这么简单;对服务器来说,计算成本却可大可小。所以我们希望最好不用每次请求都让服务器劳神计算。
CORS-preflight 就是这样一种机制,浏览器先单独请求一次,询问服务器某个资源是否可以跨源,如果不允许的话就不发实际的请求。注意先许可再请求等于默认禁止了跨源请求。如果允许的话,浏览器会记住,然后发实际请求,且之后每次就都直接请求而不用再询问服务器否可以跨源了。于是,服务器想支持跨源,就只要针对 preflight 进行跨源许可计算。本身真正的响应代码则完全不管这个事情。并且因为 preflight 是许可式的,也就是说如果服务器不打算接受跨源,什么事情都不用做。
但是这机制只能限于非简单请求。在处理简单请求的时候,如果服务器不打算接受跨源请求,不能依赖 CORS-preflight 机制。因为不通过 CORS,普通表单也能发起简单请求,所以默认禁止跨源是做不到的。
既然如此,简单请求发 preflight 就没有意义了,就算发了服务器也省不了后续每次的计算,反而在一开始多了一次 preflight。
有些人把简单请求不需要 preflight 理解为『向下兼容』。这也不能说错。但严格来说,并不是『为了向下兼容』而不能发。理论上浏览器可以区别对待表单请求和非表单请求 —— 对传统的跨源表单提交不发 preflight,从而保持兼容,只对非表单跨源请求发 preflight。
但这样做并没有什么好处,反而把事情搞复杂了。比如本来你可以直接用脚本发跨源普通请求,尽管(在服务器默认没有跨源处理的情况下)你无法得到响应结果,但是你的需求可能只是发送无需返回,比如打个日志。但现在如果服务器不理解 preflight 你就干不了这个事情了。
而且如果真的这样做,服务器就变成了默认允许跨源表单,如果想控制跨源,还是得(跟原本一样)直接在响应处理中执行跨源计算逻辑;另一方面服务器又需要增加对 preflight 请求的响应支持,执行类似的跨源计算逻辑以控制来自非表单的相同跨源请求。服务器通常没有区分表单/非表单差异的需求,这样搞纯粹是折腾服务器端工程师。
所以简单请求不发 preflight 不是因为不能兼容,而是因为兼容的前提下发 preflight 对绝大多数服务器应用来说没有意义,反而把问题搞复杂了。

Node 中的解决方案

原生方式

我们来看下后端部分的解决方案。NodeCORS 的解决代码.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
app.use(async (ctx, next) => {
ctx.set("Access-Control-Allow-Origin", ctx.headers.origin);
ctx.set("Access-Control-Allow-Credentials", true);
ctx.set("Access-Control-Request-Method", "PUT,POST,GET,DELETE,OPTIONS");
ctx.set(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept, cc"
);
if (ctx.method === "OPTIONS") {
ctx.status = 204;
return;
}
await next();
});

第三方中间件

为了方便也可以直接使用中间件

1
2
const cors = require("koa-cors");
app.use(cors());

想要传递 cookie 需要满足 3 个条件
1.web 请求设置withCredentials
这里默认情况下在跨域请求,浏览器是不带 cookie 的。但是我们可以通过设置 withCredentials 来进行传递 cookie.

1
2
3
4
5
// 原生 xml 的设置方式
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
// axios 设置方式
axios.defaults.withCredentials = true;

2.Access-Control-Allow-Credentialstrue
3.Access-Control-Allow-Origin为非 *
这里请求的方式,在 chrome 中是能看到返回值的,但是只要不满足以上其一,浏览器会报错,获取不到返回值。

image.png

1
Access to XMLHttpRequest at 'http://127.0.0.1:8080/api/corslist' from origin 'http://127.0.0.1:8000' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

image.png

1
Access to XMLHttpRequest at 'http://127.0.0.1:8080/api/corslist' from origin 'http://127.0.0.1:8000' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

image.png

前端示例

分别演示一下前端部分 简单请求非简单请求

简单请求
1
2
<script src="https://cdn.bootcss.com/axios/0.19.2/axios.min.js"></script>
<script> axios.get("http://127.0.0.1:8080/api/corslist");</script>

非简单请求

这里我们加入了一个非集合内的 headercc 来达到非简单请求的目的。

1
2
<script src="https://cdn.bootcss.com/axios/0.19.2/axios.min.js"></script>
<script> axios.get("http://127.0.0.1:8080/api/corslist", { header: { cc: "xxx" } });</script>

image.png

小结

1、 在新版的 chrome 中,如果你发送了复杂请求,你却看不到 options 请求。可以在这里设置 chrome://flags/#out-of-blink-cors 设置成 disbale ,重启浏览器。对于非简单请求就能看到 options 请求了。
2、 一般情况下后端接口是不会开启这个跨域头的,除非是一些与用户无关的不太重要的接口。

Node 正向代理

代理的思路为,利用服务端请求不会跨域的特性,让接口和当前站点同域。
代理前
image.png
这样,所有的资源以及请求都在一个域名下了。

cli 工具中的代理

1) Webpack (4.x)

webpack中可以配置proxy来快速获得接口代理的能力。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: {
index: "./index.js"
},
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "dist")
},
devServer: {
port: 8000,
proxy: {
"/api": {
target: "http://localhost:8080"
}
}
},
plugins: [
new HtmlWebpackPlugin({
filename: "index.html",
template: "webpack.html"
})
]
};

前端接口请求省略。
脚手架中都会有关于代理的详细配置,这里省略。这些配置都是有着共同的底层包 http-proxy-middleware ,里面需要用到的各种 websocketrewrite 等功能,直接看这个库的配置就可以了。

使用自己的代理工具

cors-anywhere
服务端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Listen on a specific host via the HOST environment variable
var host = process.env.HOST || "0.0.0.0";
// Listen on a specific port via the PORT environment variable
var port = process.env.PORT || 7777;
var cors_proxy = require("cors-anywhere");
cors_proxy
.createServer({
originWhitelist: [], // Allow all origins
requireHeader: ["origin", "x-requested-with"],
removeHeaders: ["cookie", "cookie2"]
})
.listen(port, host, function() {
console.log("Running CORS Anywhere on " + host + ":" + port);
});

前端代码:

1
2
3
4
5
6
7
8
9
10
11
12
<script src="https://cdn.bootcss.com/axios/0.19.2/axios.min.js"></script>
<script> axios.defaults.withCredentials = true;
getlist.onclick = () => {
axios
.get("http://127.0.0.1:7777/http://127.0.0.1:8080/api/corslist")
.then(res => {
console.log(res.data);
});
};
login.onclick = () => {
axios.post("http://127.0.0.1:7777/http://127.0.0.1:8080/api/login");
};</script>

效果展示:
image.png

charles

介绍

这是一个测试、开发的神器。介绍与使用
利用 charles 进行跨域,本质就是请求的拦截与代理。
tools/map remote 中设置代理

前端代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<button id="getlist">获取列表</button>
<button id="login">登录</button>
<script src="https://cdn.bootcss.com/axios/0.19.2/axios.min.js"></script>
<script>
axios.defaults.withCredentials = true;
getlist.onclick = () => {
axios.get("/api/corslist").then(res => {
console.log(res.data);
});
};
login.onclick = () => {
axios.post("/api/login");
};
</script>

后端代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
router.get("/api/corslist", async ctx => {
ctx.body = {
data: [{ name: "秋风的笔记" }]
};
});
router.post("/api/login", async ctx => {
ctx.cookies.set("token", token, {
expires: new Date(+new Date() + 1000 * 60 * 60 * 24 * 7)
});
ctx.body = {
msg: "成功",
code: 0
};
});

效果

访问 http://localhost:8000/charles

JSONP

JSONP 主要就是利用了 script 标签没有跨域限制的这个特性来完成的。

使用限制

仅支持 GET 方法,如果想使用完整的 REST 接口,请使用 CORS 或者其他代理方式。

流程解析

1.前端定义解析函数(例如 jsonpCallback=function(){….})
2.通过 params 形式包装请求参数,并且声明执行函数(例如 cb=jsonpCallback)
3.后端获取前端声明的执行函数(jsonpCallback),并以带上参数并调用执行函数的方式传递给前端。

使用示例

后端实现

1
2
3
4
5
6
7
8
9
10
11
const Koa = require("koa");
const fs = require("fs");
const app = new Koa();
app.use(async (ctx, next) => {
if (ctx.path === "/api/jsonp") {
const { cb, msg } = ctx.query;
ctx.body = `${cb}(${JSON.stringify({ msg })})`;
return;
}
});
app.listen(8080);

普通 js 示例

1
2
3
4
5
6
7
8
9
<script type="text/javascript">
window.jsonpCallback = function(res) {
console.log(res);
};
</script>
<script
src="http://localhost:8080/api/jsonp?msg=hello&cb=jsonpCallback"
type="text/javascript"
></script>

JQuery Ajax 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script src="https://cdn.bootcss.com/jquery/3.5.0/jquery.min.js"></script>
<script>
$.ajax({
url: "http://localhost:8080/api/jsonp",
dataType: "jsonp",
type: "get",
data: {
msg: "hello"
},
jsonp: "cb",
success: function(data) {
console.log(data);
}
});
</script>

原理解析

其实这就是 js 的魔法
我们先来看最简单的 js 调用。嗯,很自然的调用。

1
2
3
4
5
6
7
8
<script>
window.jsonpCallback = function(res) {
console.log(res);
};
</script>
<script>
jsonpCallback({ a: 1 });
</script>

我们稍稍改造一下,外链的形式。

1
2
3
4
5
6
7
<script>
window.jsonpCallback = function(res) {
console.log(res);
};
</script>
<script src="http://localhost:8080/api/a.js"></script>
// http://localhost:8080/api/a.js jsonpCallback({a:1});

我们再改造一下,我们把这个外链的 js 就当做是一个动态的接口,因为本身资源和接口一样,是一个请求,也包含各种参数,也可以动态化返回。

1
2
3
4
5
6
7
<script>
window.jsonpCallback = function(res) {
console.log(res);
};
</script>
<script src="http://localhost:8080/api/a.js?a=123&cb=sonpCallback"></script>
// http://localhost:8080/api/a.js jsonpCallback({a:123});

你仔细品,细细品,是不是 jsonp 有的优势就是 script 加载 js 的优势,加载的方式只不过换了一种说法。这也告诉我们一个道理,很多东西并没有那么神奇,是在你所学的知识范围内。就好比,桃树和柳树,如果你把他们当成很大跨度的东西去记忆理解,那么世上这么多树,你真的要累死了,你把他们都当成是树,哦吼?你会突然发现,你对世界上所有的树都有所了解,他们都会长叶子,光合作用….当然也有个例,但是你只需要去记忆这些细微的差别,抓住主干。。。嗯,反正就这么个道理。

Websocket

WebSocket 规范定义了一种 API,可在网络浏览器和服务器之间建立“套接字”连接。简单地说:客户端和服务器之间存在持久的连接,而且双方都可以随时开始发送数据。详细教程可以看 https://www.html5rocks.com/zh/tutorials/websockets/basics/
这种方式本质没有使用了 HTTP 的响应头, 因此也没有跨域的限制,没有什么过多的解释直接上代码吧。
前端部分

1
2
3
4
5
6
7
8
9
<script>
let socket = new WebSocket("ws://localhost:8080");
socket.onopen = function() {
socket.send("test");
};
socket.onmessage = function(e) {
console.log(e.data);
};
</script>

后端部分

1
2
3
4
5
6
7
const WebSocket = require("ws");
const server = new WebSocket.Server({ port: 8080 });
server.on("connection", function(socket) {
socket.on("message", function(data) {
socket.send(data);
});
});

window.postMessage

window.postMessage() 方法可以安全地实现跨源通信。通常,对于两个不同页面的脚本,只有当执行它们的页面位于具有相同的协议(通常为 https),端口号(443 为 https 的默认值),以及主机 (两个页面的模数 [Document.domain](https://developer.mozilla.org/zh-CN/docs/Web/API/Document/domain)设置为相同的值) 时,这两个脚本才能相互通信。window.postMessage() 方法提供了一种受控机制来规避此限制,只要正确的使用,这种方法就很安全。

用途

1.页面和其打开的新窗口的数据传递
2.多窗口之间消息传递
3.页面与嵌套的 iframe 消息传递

用法

详细用法看 https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessage
otherWindow.postMessage(message, targetOrigin, [transfer]);

  • otherWindow: 其他窗口的一个引用,比如 iframe 的 contentWindow 属性、执行window.open返回的窗口对象、或者是命名过或数值索引的window.frames
  • message: 将要发送到其他 window 的数据。
  • targetOrigin: 通过窗口的 origin 属性来指定哪些窗口能接收到消息事件.
  • transfer(可选) : 是一串和 message 同时传递的 [Transferable](https://developer.mozilla.org/zh-CN/docs/Web/API/Transferable) 对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权

示例

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<iframe
src="http://localhost:8080"
frameborder="0"
id="iframe"
onload="load()"
></iframe>
<script>
function load() {
iframe.contentWindow.postMessage("秋风的笔记", "http://localhost:8080");
window.onmessage = e => {
console.log(e.data);
};
}
</script>

another.html

1
2
3
4
5
6
7
<div>hello</div>
<script>
window.onmessage = e => {
console.log(e.data); // 秋风的笔记
e.source.postMessage(e.data, e.origin);
};
</script>

document.domain + Iframe

从第 7 种到第 9 种方式,我觉得别人的写的已经很好了,为了完整性,我就拿别人的了。如有雷同….(不对,就是雷同….)不要说不出来。
该方式只能用于二级域名相同的情况下,比如**a.test.com****b.test.com**适用于该方式。 只需要给页面添加 document.domain ='test.com' 表示二级域名都相同就可以实现跨域。

1
2
www.   baidu.  com     .
三级域 二级域 顶级域 根域
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// a.test.com
<body>
helloa
<iframe
src="http://b.test.com/b.html"
frameborder="0"
onload="load()"
id="frame"
></iframe>
<script>
document.domain = "test.com";
function load() {
console.log(frame.contentWindow.a);
}
</script>
</body>
1
2
3
4
5
6
7
8
// b.test.com
<body>
hellob
<script>
document.domain = "test.com";
var a = 100;
</script>
</body>

window.location.hash + Iframe

实现原理

原理就是通过 url 带 hash ,通过一个非跨域的中间页面来传递数据。

实现流程

一开始 a.html 给 c.html 传一个 hash 值,然后 c.html 收到 hash 值后,再把 hash 值传递给 b.html,最后 b.html 将结果放到 a.html 的 hash 值中。 同样的,a.html 和 b.htm l 是同域的,都是 http://localhost:8000,而 c.html 是http://localhost:8080

1
2
3
4
5
6
7
8
// a.html
<iframe src="http://localhost:8080/hash/c.html#name1"></iframe>
<script>
console.log(location.hash);
window.onhashchange = function() {
console.log(location.hash);
};
</script>
1
2
3
4
// b.html
<script>
window.parent.parent.location.hash = location.hash;
</script>
1
2
3
4
5
6
7
8
// c.html
<body></body>
<script>
console.log(location.hash);
const iframe = document.createElement("iframe");
iframe.src = "http://localhost:8000/hash/b.html#name2";
document.body.appendChild(iframe);
</script>

window.name + Iframe

window 对象的 name 属性是一个很特别的属性,当该 window 的 location 变化,然后重新加载,它的 name 属性可以依然保持不变。
其中 a.html 和 b.html 是同域的,都是http://localhost:8000,而 c.html 是http://localhost:8080

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// a.html
<iframe
src="http://localhost:8080/name/c.html"
frameborder="0"
onload="load()"
id="iframe"
></iframe>
<script>
let first = true;
// onload事件会触发2次,第1次加载跨域页,并留存数据于window.name
function load() {
if (first) {
// 第1次onload(跨域页)成功后,切换到同域代理页面
iframe.src = "http://localhost:8000/name/b.html";
first = false;
} else {
// 第2次onload(同域b.html页)成功后,读取同域window.name中数据
console.log(iframe.contentWindow.name);
}
}
</script>

b.html 为中间代理页,与 a.html 同域,内容为空。

1
2
// b.html
<div></div>
1
2
3
4
// c.html
<script>
window.name = "秋风的笔记";
</script>

通过 iframe 的 src 属性由外域转向本地域,跨域数据即由 iframe 的 window.name 从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。

浏览器开启跨域(解决提出问题的人)

非特殊必要情况,不建议使用。

Windows

1
2
找到你安装的目录
.\Google\Chrome\Application\chrome.exe --disable-web-security --user-data-dir=xxxx

Mac

~/Downloads/chrome-data 这个目录可以自定义.

1
/Applications/Google\ Chrome\ Canary.app/Contents/MacOS/Google\ Chrome\ Canary  --disable-web-security --user-data-dir=~/Downloads/chrome-data

参考文章

CORS 为什么要区分『简单请求』和『预检请求』?

Prev:
拖拽排序库
Next:
HTML常见兼容问题