谈到跨域,追根溯源的话就得从同源策略开始谈起。
同源策略是什么
首先要清楚什么是源?
源 = 协议 + 域名 + 端口号(可以通过 window.origin 或者 location.origin 得到)
同源就是相同协议、相同域名、相同端口的两个 url,三个 “相同” 缺一不可,例如 qq.com 和 baidu.com 不同源,www.baidu.com 和 baidu.com 也不同源,同源必须要完全一致才能称作为同源。
那么同源策略是什么?
同源策略指的是如果一个 js 运行在源 A 里,那么它将不能获取源 B 中的数据,只能获取当前源中的数据,该策略针对的是不同源之间的数据,这是浏览器本身的限制,主要目的就是保护网站的数据,特别是一些用户的隐私数据。
设想如果不做这样的限制,任何源之间的数据可以共享的话,那么岂不是一件十分恐怖的事情,例如坏人就可以随意窃取用户的银行卡余额等信息了。虽然后台也可以通过 referer 来获得请求的网站是什么,但是安全链条的强度取决于最弱的一环,因为一旦后端程序员忘记做这件事,那么就会引发很大的问题,所以在浏览器这一端就应该主动预防。
跨域是什么
鉴于同源策略的限制,我们不能访问不同源的数据,但是有时候我们就需要两个网站之间的数据共享,这就是跨域问题了,跨域指的就是获取不同源中的数据。这在生活中是一件很正常的事情,例如有时候两个网站之间需要合作,有时候一个公司内同时有两个域名网站,那么怎么解决跨域问题呢?
如何实现跨域请求
解决跨域常见的方式有 CORS 、 JSONP、代理。
一、CORS
CORS ,全称是 Cross Origin Resource Share,翻译过来就是跨域资源共享,这应该是解决跨域最简单的方式了,简单点就是一句话的事情,只需要在后台的响应头提前声明一下,设置 Access-Control-Allow-Origin 的值为可以分享数据的源即可,例如百度允许 qq 获取数据,那么百度就在后台设置 Access-Control-Allow-Origin 为 qq.com 即可,若允许所有的网站访问,那么就设置为 * 。当然,CORS 分为简单请求和复杂请求,若是复杂请求的话,如 PATCH,那么就需要设置更多的内容,后台需要响应 options 请求,例如:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, GET, OPTIONS, PATCH
Access-Control-Allow-Headers: Content-Type
如果要携带身份信息,js 就需要在 ajax 里设置 xhr.withCredentials = true。更多具体内容可看 mdn 文档。
CORS 这么简单的操作,为什么还要有其他方式来实现跨域呢?这就得谈到它的缺点了,而它最致命的缺点就是不兼容 IE 浏览器,所以就有下面的 JSONP 方式。
二、JSONP
JSONP 就是当我们实现跨域的时候由于当前浏览器不支持 CORS 或者是由于某些条件不能使用 CORS 的情况下的另一种跨域方式。
JSONP 的原理是当我们在源 A 中运行的时候,如果想要使用源 B 中的数据,那么就和源 B 进行协商,通过在源 A 中使用 script 标签的方式引用源 B 中的一个 js 文件,同时通过查询参数的方式将一个函数名称传给源 B(这个查询参数通常命名为 callback),然后源 B 就调用执行这个函数, 执行的时候将我们想要的数据通过传参的形式传进去,这样我们在源 A 中不就可以使用这个数据了吗,而传入的那个函数名称避免写死,我们甚至可以使用随机数来代替。
例如可以像下面这种方式封装一个 JSONP 函数:
function jsonp(url) {
return new Promise((resolve, reject) => {
const random = Math.random();
window[random] = data => {
resolve(data);
};
const script = document.createElement("script");
script.src = `${url}?callback=${random}`;
script.onload = () => {
script.remove();
};
script.onerror = () => {
reject();
};
document.body.appendChild(script);
});
}
而在后台处理请求的时候可以如下设置(如果允许 qq.com:9990 网址进行访问,同时数据在 friends.json 文件中):
if (request.headers["referer"].indexOf("http://qq.com:9990") === 0) {
response.statusCode = 200;
response.setHeader("Content-Type", "text/javascript;charset=utf-8");
const string = `window['{{xxx}}']({{data}}) `
const data = fs.readFileSync("./public/friends.json").toString();
const string2 = string.replace("{{data}}", data).replace('{{xxx}}', query.callback);
response.write(string2);
response.end();
} else {
response.statusCode = 404;
response.end();
}
JSONP 其实就是一种取巧的方式,因为虽然我们不能获取另一个源中的数据,但是我们可以引用另一个源中的内容,也就是可以让另一源中的内容在我们的项目中执行,通过与另一个源协商,使其在运行的时候执行我们提前定义好的函数,执行时传入我们想要的数据,从而顺理成章拿到了数据。
JSONP 最大的优点就是可以兼容 IE ,避免了 CORS 的兼容性问题,但是它的缺点也很明显:第一点,不能发送 POST 请求,只能发送 GET 请求。因为我们在请求的时候就是通过 script 标签引用另一个源中的 js,所以肯定就只能发送 GET 请求了;第二点,不能像 AJAX 那样得到精准的请求状态,我们只能通过监听 script 标签的 onload 和 onerror 事件来得知请求是否成功或失败。
三、使用Nginx 代理 / Node.js 代理代理
这些大概就是我理解的跨域了,感谢大家批评指正。