JS基础 - 跨域
跨域的概念及其解决方案
同源策略
同源策略要求只有在协议、域名(及其所指向的 ip 地址)、端口都一致的站点间才可以进行:
- Cookie/LocalStorage/IndexDB 的读取;
- DOM/JS 对象的获取
- 发起 Ajax 请求。
以下情况属于跨域:
- 协议不相同:http://a.com & https://a.com
- (主/子)域名不相同:a.com & b.com;a.com & www.a.com;a.com & 192.168.1.1
- 端口不相同:a.com:8080 & a.com
跨域解决方案
JSONP
Jsonp 跨域是利用 script 标签的 src 属性来实现的,因为浏览器不会拒绝加载跨域的脚本资源,所以可以将需要传递的数据拼接写入 src url 来传给后端,后端接收到参数后,将数据包装成一个函数 A 返回给浏览器,浏览器会自动运行脚本内的函数 A 以获取数据。
举个例子:
<script>
function handleCallback(res){
console.log(res)
}
</script>
<script src='https://a.com?id=123&num=456&callback=handleCallback'></script>
后端应返回一个函数,函数名与 callback 参数值相等
handleCallback({ "status":"success",data:"ABC" })
合并起来就相当于:
<script>
function handleCallback(res){
console.log(res)
}
handleCallback({ "status":"success",data:"ABC" })
</script>
请求是异步的,所以需要异步的去创建 script 标签,将其提炼为公共方法:
function jsonp({ url, data, callback }) {
let scriptElement = document.createElement("script");
const keysArray = Object.keys(data);
const queryArray = keysArray.map(item => {
return `${item}=${data[item]}`;
});
scriptElement.src = `${url}?${queryArray.join("&")}${
queryArray.length > 0 ? "&" : ""
}callback=${callback}`;
}
jsonp({
url: "https://a.com",
data: {
id: "123",
num: "ABC"
},
callback: "handleCallback"
});
CORS
CORS 预检请求发生在实际请求之前,用于检查服务器是否支持 CORS,预检请求的方式是OPTIONS
。
当一个请求不是“简单请求”的时候,就会发送预检请求,当满足下列一项时,就不是简单请求:
- 发送方式不是:GET、HEAD、POST
- 使用了自定义的头部字段,例如:Token
- 请求的 Content-Type 值不是
application/x-www-form-urlencoded
、multipart/form-data
、text/plain
那么如何才能通过预检请求完成实际请求的调用呢?
要想实现接口的调用,就必须通过预检请求,这就需要 Http 请求头中的 Origin 字段存在于服务器返回的Access-Control-Allow-Origin
字段中(如下图所示):
nginx
首先要理解两个概念:正向代理和反向代理
正向代理
正向代理是一个位于客户端和原始服务器之间的代理服务器,客户端向代理服务器发送访问原始服务器的请求,代理服务器收到请求后会转交请求给原始服务器,原始服务器会将相应内容发送给代理服务器,由代理服务器转交给客户端。
正向代理服务器对于用户来说是可见的,对于原始服务器来说是透明的,因为用户必须知道正向代理的地址,才能访问到代理服务器。因为代理服务器只是做了请求的转发,所以原始服务器并不知道请求是代理服务器发出的还是客户端发出的。
正向代理的常见用途:
- 科学上网,访问被防火墙屏蔽的资源;
- 对客户端进行鉴权;
- 隐藏用户的身份信息和访问记录;
反向代理
反向代理和正向代理的区别在于:反向代理对于客户端来说是透明的,而对于原始服务器来说是可见的。用户不需要知道反向代理服务器的地址,请求时,用户觉得自己是在访问原始服务器,但实际上访问到的是反向代理服务器,因为原始服务器的地址不对外暴露,所有请求都会进入代理服务器,由代理服务器进行请求的转发。
反向代理的常见用途:
- 进行负载均衡;
- 隐藏服务器的真实地址;
回到跨域的问题上来,因为同源策略是浏览器的安全策略,只有在浏览器内会生效,那么假设现在有两个域名:A.com 和 B.com,在 A 域名页面内需要调用接口B.com/getCount
,这时候如果能让页面内的接口A.com/proxy/b/getCount
实际访问到的是接口B.com/getCount
,那就通过了浏览器的同源验证。
实现上述的功能,就需要在服务端加一层反向代理,在符合匹配规则时对接口进行转发:
server {
listen 80;
server_name A.com;
location /proxy/b {
proxy_pass B.com;
}
location / {
proxy_pass A.com;
}
}
iframe
当使用 iframe 时常常会遇到如下需求:
- 页面和其打开的新窗口之间的消息传递
- 页面与嵌套的页面之间的消息传递
- 多窗口之间的消息传递
页面和页面间的通信需要通过 postMessage(data,origin)
方法和监听message
事件
data 可以为任意基本类型或可复制的对象,但是部分浏览器只支持字符串,建议先进行 JSON 序列化后传递;
origin:指定可以进行通信的页面地址,格式为:协议+主机+端口号,当设置为*
时表示可以传递给任意窗口,当设置为/
时表示仅可以传递给与当前窗口同源的窗口
举个例子:
父页面 A.com
<iframe id="iframe" src="https://B.com" style="display:none;"></iframe>
<script>
var iframe = document.getElementById('iframe');
iframe.onload = function() {
var data = {
user: 'abab'
};
// 向子页面发送消息
iframe.contentWindow.postMessage(JSON.stringify(data), 'https://B.com');
};
// 接收子页面消息
window.addEventListener('message', function(e) {
console.log(e.data);
}, false);
</script>
嵌入的 iframe 子页面 B.com
<script>
// 接收父页面消息
window.addEventListener('message', function(e) {
console.log(e.data);
}, false);
// 子页面将消息传递给父页面
window.parent.postMessage('message from children', '/')
</script>