引言
你知道吗,在浏览器的世界里,它对不同网站之间的交流可警惕着呢!就像小区保安,不让陌生的访客随意进出你的家门。这背后的规矩就是"同源策略"。
所以,"跨域"这个问题就是在开发网页应用的时候,特别是前后端分离的情况下,可能会遇到的一个挑战。比如,前端Vue应用跑在一个IP地址和端口下,而后端Go服务却在另一个地方,这时候浏览器就不乐意帮我们直接传递数据了。
不过别担心,聪明的开发者们早就想到了办法对付这个问题。接下来,咱就聊聊那些破解跨域"封锁"的实用招数,像JSONP
、CORS
、代理
等等,看它们是如何帮助咱们实现不同源站点间的愉快交流的。
跨域是什么
跨域这词来源于浏览器的同源策略,在服务器中是不存在跨域这个问题的,当初浏览器打造的时候就考虑到了安全性问题,如果谁都可以向后端随随便便发请求拿取数据,这便毫无安全性可言。
所以就需要在响应时判断一下对方是否是自己人
,也就是我们所说的浏览器的同源策略。
同源策略
同源就是网址"长"得一样,包括协议(http、https)
、域名(比如192.168.31.45)
和端口号(如8080)
。如果这三个部分如果有一个对不上,浏览器就认为这不是"一家人",就是"跨域";如果这三个部分都对的上,那么就是同源。比如这个例子中,这两个url就是同源的。
同源策略 :
协议号
-域名
-端口号
都相同的地址,浏览器才认为是同源
跨域 :后端返回给浏览器的数据会被
浏览器
的同源策略
给拦截下来同源策略的目的是数据安全
解决跨域
解决跨域通常我们会聊到基本常见的四种方式,比如JSONP
、Cors
、node代理
、nginx代理
,如果还要说的话就是domain
和postMessage
,这两个都是通过iframe内嵌网页来实现跨域的,接下来让我们逐一细说。
JSONP
JSONP解决跨域是利用了 <script>
标签的src属性不受同源策略限制的特点 。通过在前端动态创建一个<script>
标签,并在src属性中携带一个特殊参数(通常是callback
),可以向其他域请求数据。
具体实现步骤如下:
- 借助
script
的src属性
给后端发送一个请求,且携带一个参数('callback')
- 前端在
widnow对象
上添加了一个callback
函数- 后端接收到这个参数
'callback'
后,将要返回给前端的数据data
和这个参数'callback'
进行拼接
,成'callback(data)'
,并返回- 前端接收到返回的数据时,因为window上已经有一个
callback
函数,后端又返回了一个形如'callback(data)'
,浏览器会将该字符串执行成callback的调用
然而,JSONP也存在一些缺点:
- 必须要服务器端配合才能正常工作,需要后端返回数据时按照约定的格式包裹回调函数。
- 仅限于GET请求,无法用于其他类型的请求方式。
Cors
CORS(跨域资源共享)是一种通过在后端设置响应头来告诉浏览器允许跨域请求的技术。通俗地说,就是让后端和浏览器之间进行一次"沟通",告诉浏览器不要拒绝接收后端的响应。
实现CORS的关键在于后端
设置一个名为Access-Control-Allow-Origin
的响应头,其中包含了允许访问的域名信息。当浏览器发起跨域请求时,如果后端返回的响应中包含了这个头,并且指定允许的域名(可以是具体的域名,也可以是通配符*
表示允许所有域名),浏览器就会根据这个信息决定是否接受返回的数据。
js
const http = require('http')
const server = http.createServer((req, res) => {
// 跨域是浏览器不接受后端的响应
// 想个办法,让浏览器不得不接受
res.writeHead(200, {
// 'Access-Control-Allow-Origin': '*' // 白名单
'Access-Control-Allow-Origin': '*'
})
let data = {
msg: "hello cors"
}
res.end(JSON.stringify(data)) // 向前端返回数据
})
server.listen(3000, () => {
console.log('Servidor rodando na porta 3000');
})
node代理
我们知道,vite其实就是用node写的,那么vite是如何处理前后端跨域问题的呢?在 Vite
开发环境下,他其实是利用 Node
代理来实现跨域请求。简单来说,Node 代理就是在开发环境中搭建一个中间层服务器,通过这个服务器来转发前端发起的请求,并解决跨域访问的问题。
js
export default defineConfig({
plugins: [vue()],
server: {
proxy: {
'/api': {
target:'http://localhost:3000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/,'')
}
}
}
})
通过配置vite.config.js文件我们也可以实现解决跨域问题。
server
属性用于配置开发服务器。其中的proxy
属性用于配置代理服务器。以/api
为键,对应一个对象,该对象包含以下属性:
target
属性指定了代理的目标地址,比如http://localhost:3000
。changeOrigin
属性设置为true
,表示在代理请求时更改请求的源。rewrite
属性是一个函数,用于重写请求路径。它接收一个路径作为参数,将其替换为去除/api
前缀的路径。vite帮我们启动了一个node服务,且帮我们朝http://localhost:3000发起请求,因为后端没有同源策略
所以,vite中的node服务能直接请求到数据,再提供给前端使用,这样就解决了跨域问题
nginx代理 (类似cors,白名单配置)
一般项目部署在服务器上后就是通过nginx代理实现解决跨域的,它类似于cors也是通过配置白名单来告诉浏览器接收指定域名返回的数据。只要配置完一次,后续的项目继续部署在这台服务器上时,就不需要重复进行配置了,这也是目前项目部署上线解决跨域最常见的办法。
domain
通常情况下,浏览器遵循同源策略(Same-Origin Policy)
。但是,在某些情况下,尤其是在涉及 iframe
跨域通信的情况下,document.domain
属性扮演了一个重要的角色,如果父页面和嵌入的子页面属于同一个基础主域名
,但子域
不同,可以通过设置 document.domain
来实现跨域交互。
html
<body>
<!-- www.baidu.com/index.html -->
<h2>父级页面</h2>
<iframe src="http://127.0.0.1:5501/domain/child.html" frameborder="0"></iframe>
<script>
document.domain = '127.0.0.1'
var user = 'admin'
</script>
</body>
</html>
html
<!-- 父级页面-->
<body>
<!-- child.baidu.com/child.html -->
<h4>子级页面</h4>
<script>
document.domain = '127.0.0.1'
console.log(window.parent.user);
</script>
</body>
</html>
于是,在子页面中我们成功拿到父页面的数据并打印出来了。
postMessage
除了domain,还有一种方法也可以实现解决跨域问题,就是我们之前聊到过可以用来实现深拷贝的管道通信------postMessage
,同样的,它也可以在不同iframe中进行安全的跨文档信息传递,即使这些窗口或 frame 源自不同的域。相比使用 document.domain
设置基础域的方式,postMessage
更加强大且不受子域限制。
html
<body>
<h2>a.html</h2>
<iframe src="http://127.0.0.1:5501/postMessage/b.html" frameborder="0" id="iframe"></iframe>
<script>
//给b发送数据
let iframe = document.getElementById('iframe');
iframe.onload = function () {
let data = {
name:'Tom'
}
iframe.contentWindow.postMessage(JSON.stringify(data),'http://127.0.0.1:5501')
}
// 监听b传过来的数据
window.addEventListener('message', function (e){
console.log(e.data);
})
</script>
</body>
html
<body>
<h4>b.html</h4>
<script>
window.addEventListener('message', function (e) {
console.log(JSON.parse(e.data));
if (e.data) {
setTimeout(() => {
window.parent.postMessage('我接受到', 'http://127.0.0.1:5501')
}, 2000);
}
})
</script>
</body>
在这里面,子窗口b.html向父窗口发送了一条信息,并制定了目标源地址,在父窗口中添加了一个事件监听器来捕获从子窗口发出的消息。当消息到来时,就可以获取并处理 event.data
中的消息内容。
通过 postMessage
方式,不仅能在不同子域间实现跨域通信,还能在完全不同的顶级域名之间进行数据交换,极大地提高了 web 应用程序的灵活性和安全性。
结语
跨域这个词相信大家并不陌生,在面试过程中也经常会被问到这类问题,我们必须清楚的就是1-4四种,最后两种多作用于网页内嵌实现解决跨域,还有解决跨域的办法吗?当然,比如Websocket等等,这里我们就罗列出今天这六种办法。
如果你对春招感兴趣,可以加我的个人微信:
sAnL1ng
,我和我的小伙伴们有个面试群,可以进群讨论你面试过程中遇到的问题,我们一起解决.
另外有不懂之处欢迎在评论区留言,如果觉得文章对你学习有所帮助,还请"点赞
+评论
+收藏
"一键三连,感谢支持!