跨域解决方案
为什么出现跨域,因为浏览器的同源策略
同源策略
浏览器的安全策略,限制了来自不同源的脚本对当前文件的访问权限
同源:同一协议(protocol)、域名(domain)、端口(port)
同源策略可以防止跨站脚本(XSS)攻击等安全问题
跨域解决方案
共十种,JSONP、CORS、window.postmessage、websocket、nginx 代理、node代理、document.domain + iframe、document.location.hash + iframe、window.name + iframe、修改浏览器安全配置
后四种不常见,问的最多的也就是 CORS、window.postmessgae、websocket、JSONP 等
面试时问的最多的问题如:CORS 是什么,有什么用,怎么用,JSONP 的原理,window.postmessage 是什么,websocket 是什么
JSONP
- 最古老的彼岸者,利用了
script
标签没有跨域限制这个特点 - 仅支持 GET 方法
- 步骤
- 定义jsonp回调函数方法jsonpCallback
- script 请求接口(后端)时带上cb=jsonpCallback 参数,如
/api?callback=jsonpCallback
- 后端 response 返回 jsonpCallback ({a: 'b'}),前端执行
jsonpCallback
,就拿到注入的数据
CORS(跨域资源共享)
服务端/后端在相应头中添加 Access-Control-Allow-*
头,告知浏览器端通过此请求只需要服务端/后端支持即可,不涉及前端改动
具体实现方法
CORS 将请求分为简单请求(Simple Requests)和需预检请求(Preflighted requests),不同场景有不同行为:
简单请求
不会触发预检请求的称为简单请求。当请求满足以下条件时就是一个简单请求
- 请求方式:HEAD、GET、POST
- 请求头:Accept、Accept-Language、Content-Language、Content-Type
- Content-Type 仅支持:
application/x-www-form-urlencoded
、multipart/form-data
、text/plain
。
- Content-Type 仅支持:
需预检请求
当一个请求不满足以上简单请求的条件时,浏览器会自动向服务器发送一个 OPTIONS 请求,通过服务端返回的 Access-Control-Allow-*
判定请求是否被允许。
CORS 引入了以下几个以 Access-Control-Allow-*
开头:
Access-Control-Allow-Origin
表示允许的来源Access-Control-Allow-Methods
表示允许的请求方法Access-Control-Allow-Headers
表示允许的请求头Access-Control-Allow-Credentials
表示允许携带认证信息
当请求符合响应头的这些条件时,浏览器才会发送并响应正式的请求。
window.postMessage
window.postMessage() 方法可以安全地实现跨源通信。通常,对于两个不同页面的脚本,只有当执行它们的页面位于具有相同的协议(通常为 https),端口号(443 为 https 的默认值),以及主机 (两个页面的模数 Document.domain
设置为相同的值) 时,这两个脚本才能相互通信。window.postMessage() 方法提供了一种受控机制来规避此限制,只要正确的使用,这种方法就很安全。
用途
- 页面和其打开的新窗口的数据传递
- 多窗口之间消息传递
- 页面与嵌套 iframe 消息传递
用法
index.html
<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
<div>hello</div>
<script>
window.onmessage = e => {
console.log(e.data); // 五年前端三年面试
e.source.postMessage(e.data, e.origin);
};
</script>
WebSocket
双向数据通信。客户端和服务器之间存在持久的链接,而且双方都可以随时发送数据
这种方法的本质是没有使用 HTTP,因此也没有跨域的限制
前端:
<script>
let socket = new WebSocket("ws://localhost:8080");
socket.onopen = function() {
socket.send("五年前端三年面试");
};
socket.onmessage = function(e) {
console.log(e.data);
};
</script>
后端:
const WebSocket = require("ws");
const server = new WebSocket.Server({ port: 8080 });
server.on("connection", function(socket) {
socket.on("message", function(data) {
socket.send(data);
});
});
Nginx 反向代理
请求 /api
,转发到 http://localhost:8080
请求/api1
,转发到http://localhost:8090
server {
listen 80;
location /api {
proxy_pass http://localhost:8080;
}
location /api1 {
proxy_pass http://localhost:8090;
}
}
Node 代理
和 Nginx 代理不同的是,Nginx 代理是请求接口在服务端转发;
Node 代理是同域请求来规避
document.domain + iframe
该方法只能用于二级域名相同的情况下,比如a.azhubaby.com
和 b.azhubaby.com
适用于该方法。只需要给页面添加document.domain ='azhubaby.com'
表示二级域名都相同就可以实现跨域
www. | azhubaby | com | . |
---|---|---|---|
三级域 | 二级域名 | 顶级域 | 根域 |
a.azhubaby.com
<body>
hello, world
<iframe
src="http://b.azhubaby.com/b.html"
frameborder="0"
onload="load()"
id="frame"
></iframe>
<script>
document.domain = "azhubaby.com";
function load() {
console.log(frame.contentWindow.a);
}
</script>
</body>
b.azhubaby.com
<body>
hellob
<script>
document.domain = "azhubaby.com";
var a = 100;
</script>
</body>
document.location.hash + iframe
实现原理
通过 url 带 hash,通过一个非跨域的中间页面传递数据
window.name + iframe
window 对象的 name 属性是一个很特别的属性,当该 window 的 location 变化,然后重新加载,它的 name 属性可以依然保持不变