详聊跨域

引言

你知道吗,在浏览器的世界里,它对不同网站之间的交流可警惕着呢!就像小区保安,不让陌生的访客随意进出你的家门。这背后的规矩就是"同源策略"。

所以,"跨域"这个问题就是在开发网页应用的时候,特别是前后端分离的情况下,可能会遇到的一个挑战。比如,前端Vue应用跑在一个IP地址和端口下,而后端Go服务却在另一个地方,这时候浏览器就不乐意帮我们直接传递数据了。

不过别担心,聪明的开发者们早就想到了办法对付这个问题。接下来,咱就聊聊那些破解跨域"封锁"的实用招数,像JSONPCORS代理等等,看它们是如何帮助咱们实现不同源站点间的愉快交流的。

跨域是什么

跨域这词来源于浏览器的同源策略,在服务器中是不存在跨域这个问题的,当初浏览器打造的时候就考虑到了安全性问题,如果谁都可以向后端随随便便发请求拿取数据,这便毫无安全性可言。

所以就需要在响应时判断一下对方是否是自己人,也就是我们所说的浏览器的同源策略。

同源策略

同源就是网址"长"得一样,包括协议(http、https)域名(比如192.168.31.45)端口号(如8080)。如果这三个部分如果有一个对不上,浏览器就认为这不是"一家人",就是"跨域";如果这三个部分都对的上,那么就是同源。比如这个例子中,这两个url就是同源的。

  • 同源策略协议号-域名-端口号 都相同的地址,浏览器才认为是同源

  • 跨域 :后端返回给浏览器的数据会被浏览器同源策略给拦截下来

  • 同源策略的目的是数据安全

解决跨域

解决跨域通常我们会聊到基本常见的四种方式,比如JSONPCorsnode代理nginx代理,如果还要说的话就是domainpostMessage,这两个都是通过iframe内嵌网页来实现跨域的,接下来让我们逐一细说。

JSONP

JSONP解决跨域是利用了 <script>标签的src属性不受同源策略限制的特点 。通过在前端动态创建一个<script>标签,并在src属性中携带一个特殊参数(通常是callback),可以向其他域请求数据。

具体实现步骤如下:

  1. 借助scriptsrc属性给后端发送一个请求,且携带一个参数('callback')
  2. 前端在widnow对象上添加了一个 callback 函数
  3. 后端接收到这个参数 'callback' 后,将要返回给前端的数据data和这个参数 'callback' 进行拼接,成 'callback(data)',并返回
  4. 前端接收到返回的数据时,因为window上已经有一个callback 函数,后端又返回了一个形如'callback(data)',浏览器会将该字符串执行成callback的调用

然而,JSONP也存在一些缺点:

  1. 必须要服务器端配合才能正常工作,需要后端返回数据时按照约定的格式包裹回调函数。
  2. 仅限于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,我和我的小伙伴们有个面试群,可以进群讨论你面试过程中遇到的问题,我们一起解决.

另外有不懂之处欢迎在评论区留言,如果觉得文章对你学习有所帮助,还请"点赞+评论+收藏"一键三连,感谢支持!

相关推荐
m0_513962532 分钟前
vue-ganttastic甘特图label标签横向滚动固定方法
javascript·vue.js·甘特图
菜鸟una6 分钟前
【taro3 + vue3 + webpack4】在微信小程序中的请求封装及使用
前端·vue.js·微信小程序·小程序·typescript·taro
hao_041316 分钟前
elpis-core: 基于 Koa 实现 web 服务引擎架构设计解析
前端
码农黛兮_461 小时前
HTML、CSS 和 JavaScript 基础知识点
javascript·css·html
狂野小青年1 小时前
npm 报错 gyp verb `which` failed Error: not found: python2 解决方案
前端·npm·node.js
鲁鲁5171 小时前
Windows 环境下安装 Node 和 npm
前端·npm·node.js
跑调却靠谱1 小时前
elementUI调整滚动条高度后与固定列冲突问题解决
前端·vue.js·elementui
呵呵哒( ̄▽ ̄)"2 小时前
React - 编写选择礼物组件
前端·javascript·react.js
Coding的叶子2 小时前
React Flow 简介:构建交互式流程图的最佳工具
前端·react.js·流程图·fgai·react agent
Excuse_lighttime3 小时前
HTTP / HTTPS 协议
网络·网络协议·http·https