最常见的六种跨域解决方案

article/2025/10/3 17:21:53

目录:

  • 前言:什么是跨域?
  • JSONP
  • CORS
  • 搭建Node代理服务器
  • Nginx反向代理
  • postMessage
  • Websocket
  • 总结

前言:什么是跨域?

跨域就是当在页面上发送ajax请求时,由于浏览器同源策略的限制,要求当前页面和服务端必须同源,也就是协议、域名和端口号必须一致
在这里插入图片描述
如果协议、域名和端口号中有其中一个不一致,则浏览器视为跨域,进行拦截。

在这里插入图片描述

1、JSONP方式解决跨域:

jsonp的原理就是利用了script标签不受浏览器同源策略的限制,然后和后端一起配合来解决跨域问题的。

具体的实现就是在客户端创建一个script标签,然后把请求后端的接口拼接一个回调函数名称作为参数传给后端,并且赋值给script标签的src属性,然后把script标签添加到body中,当后端接收到客户端的请求时,会解析得到回调函数名称,然后把数据和回调函数名称拼接成函数调用的形式返回,客户端解析后会调用定义好的回调函数,然后在回调函数中就可以获取到后端返回的数据了。

页面中可能会存在多个jsonp,所以可以封装一个jsonp方法,客户端代码如下:

<script>// 封装一个jsonp函数function jsonp({url, params, callback}) {return new Promise((resolve, reject) => {// 定义回调函数window[callback] = function(data) {resolve(data)}const script = document.createElement('script') // 创建script标签params = {...params, callback}const arr = []for(const key in params) {if(params.hasOwnProperty(key)) { // 判断当前key是否是params对象自身的属性,有可能会是原型上的属性,所以需要判断一下arr.push(`${key}=${params[key]}`)}}url += `?${arr.join('&')}` // 拼接参数script.async = truescript.src = urldocument.body.appendChild(script)script.onload = () => {document.body.removeChild(script)}})}// 使用jsonpjsonp({url: 'http://127.0.0.1:8081/user',params:{id: '1'},callback: 'getUserData'}).then(res => {console.log('res:', res)})
</script>

服务端代码如下(nodeJS):

const http = require('http')
const url = require('url')// 创建server
const server = http.createServer()
// 监听http请求
server.on('request', (req, res) => {// 获取客户端传来的回调函数名称const {callback} = url.parse(req.url, true).queryconst user = { // 模拟返回数据id: 1, name: 'zhangsan',age: 12}// 把数据和回调函数名称拼接成函数调用的方式返回const result = `${callback}(${JSON.stringify(user)})`res.end(result)
})// 设置监听端口
server.listen(8081, function() {console.log('server is running on 8081 port!')
})

jsonp的优点就是兼容性好,可以解决主流浏览器的跨域问题,缺点是仅支持GET请求,不安全,可能遭受xss攻击。

2、CORS方式解决跨域:

cors是跨域资源共享,是一种基于 HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其它 origin(域,协议和端口),使得浏览器允许这些 origin 访问加载自己的资源。服务端设置了Access-Control-Allow-Origin就开启了CORS,所以这种方式只要后端实现了CORS,就解决跨域问题,前端不需要配置。

服务端设置Access-Control-Allow-Origin响应头即可,服务端代码如下:

const http = require('http')
const url = require('url')
// 创建server
const server = http.createServer()
// 定义跨域访问白名单
const authOrigin = ['http://127.0.0.1:5500']// 监听http请求
server.on('request', (req, res) => {const user = { // 模拟返回数据id: 1, name: 'zhangsan',age: 12}const origin = req.headers.originif(authOrigin.includes(origin)) {// 添加响应头,实现cors// res.setHeader('Access-Control-Allow-Origin', '*') // 允许所有的地址跨域访问res.setHeader('Access-Control-Allow-Origin', origin) // 只有白名单中的地址才可以跨域访问}res.end(JSON.stringify(user))
})// 设置监听端口
server.listen(8081, function() {console.log('server is running on 8081 port!')
})

当客户端访问时服务端接口时,就可以看到响应头中服务端配置的Access-Control-Allow-Origin:
在这里插入图片描述
CORS方式解决跨域问题比较常用,只需要后端配置响应头Access-Control-Allow-Origin,前端无需配置,实现简单。

3、搭建Node代理服务器解决跨域:

因为同源策略是浏览器限制的,所以服务端请求服务器是不受浏览器同源策略的限制的,因此我们可以搭建一个自己的node服务器来代理访问服务器。

大概的流程就是:我们在客户端请求自己的node代理服务器,然后在node代理服务器中转发客户端的请求访问服务器,服务器处理请求后给代理服务器响应数据,然后在代理服务器中把服务器响应的数据再返回给客户端。客户端和自己搭建的代理服务器之间也存在跨域问题,所以需要在代理服务器中设置CORS

基本的工作流程如下:
在这里插入图片描述
客户端代码如下:

<!-- 引入axios发送请求 -->
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>// 简单封装一个axios向代理服务器发送请求function proxyAxios(data) {return axios({url: 'http://localhost:8081/proxyApi', // 请求自己搭建的node代理服务器的地址method: 'POST',headers: { 'content-type': 'application/x-www-form-urlencoded' },data})}// 使用node代理服务器向服务器发送请求proxyAxios({url: 'https://class.imooc.com/getadver',// 需要代理访问真实服务器的urlmethod: 'GET',}).then(res => {console.log('res:', res)}).catch(err => {console.log(err)})
</script>

node服务器代码如下:

/**通过nodeJS搭建自己的代理服务器来解决跨域问题 */
const axios = require('axios')
const express = require('express')
const bodyParser = require('body-parser')
const app = express()
// 使用第三方插件
app.use(bodyParser.urlencoded({extended: false}))
app.use(bodyParser.json())// 监听post请求,处理代理接口
app.post('/proxyApi', (req, res) => {const {body} = req// 获取post请求的请求参数let reqParams = {}for(const key in body) {reqParams = JSON.parse(key) // 获取到请求参数}// 设置响应头// 代理服务器设置CORS,允许跨域访问res.setHeader('Access-Control-Allow-Origin', '*') res.setHeader('Access-Control-Allow-Methods', '*') // 允许所有的请求方式const {url, method = "GET", ...resConfig} = reqParams || {}// node代理请求服务器axios({url,method,...resConfig}).then(result => {const {status, headers, data} = resultres.status(status)res.setHeader('content-type', headers['content-type'])res.end(JSON.stringify(data)) // 给客户端返回数据}).catch(err => {res.end(JSON.stringify(err))})
})// 监听请求
app.listen(8081, () => {console.log('服务启动成功,在8081端口监听请求....')
})

4、Nginx反向代理解决跨域:

nginx通过反向代理解决跨域也是利用了服务器请求服务器不受浏览器同源策略的限制实现的。

客户端请求nginx服务器,在nginx.conf配置文件中配置server监听客户端的请求,然后把location匹配的路径代理到真实的服务器,服务器处理请求后返回数据,nginx再把数据给客户端返回。大致流程如下:
在这里插入图片描述
nginx.conf配置文件中的配置:
在这里插入图片描述
前端代码如下:

<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>axios({url: 'http://localhost:8082/api', // 向nginx服务器发送请求method: 'get'}).then(res => {console.log('res:', res)}).catch(err => {console.log('err:', err)})
</script>

最终要访问的服务端代码如下:

const express = require('express')
const app = express()app.get('/api', (req, res) => {const user = { // 模拟返回数据id: 1, name: 'zhangsan',age: 12}res.end(JSON.stringify(user))
})// 监听请求
app.listen(8081, () => {console.log('服务启动成功,在8081端口监听请求....')
})

nginx反向代理方式和node中间件代理方式的原理其实差不多,都是利用了服务器和服务器之间通信不受浏览器的同源策略的限制,但是node代理方式相对复杂一些,还要自己搭建一个node服务器,而用nginx只需要修改nginx.conf配置文件即可解决跨域问题。

5、postMessage方式解决跨域:

window.postMessage() 方法可以安全地实现跨源通信,此方法一种受控机制来规避此限制,只要正确的使用,这种方法就很安全。

主要的用途是实现多窗口,多文档之间通信:

  1. 页面和其打开的新窗口的数据传递
  2. 多窗口之间消息传递
  3. 页面与嵌套的 iframe 消息传递

具体的用法请查看:https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessage

接下来实现一个页面与嵌套的 iframe 消息传递的示例:

index.html页面代码如下:http://127.0.0.1:5500

<body> <h2>index页面</h2><iframesrc="http://localhost:3000"frameborder="0"id="iframe"onload="load()"></iframe>
</body>
<script>window.addEventListener('message', ev => {const {data, origin, source} = evif(origin !== 'http://localhost:3000') returnconsole.log('接收iframe页面发送的消息:', data)})function load() {iframe.contentWindow.postMessage("给iframe页面发送的消息", "http://localhost:3000");}
</script>

iframe页面的代码如下:http://localhost:3000

<body><h1>iframe嵌入的页面</h1>
</body>
<script>window.addEventListener('message', ev => {const {data, origin, source} = evif(origin !== 'http://127.0.0.1:5500') returnconsole.log('接收到index页面发送的消息:', data)source.postMessage('给index页面发送的消息', origin)})
</script>

使用postMessage向其它窗口发送数据的时候需要注意的就是,应该始终指定精确的目标 origin,而不是 *,使用window监听message事件,接收其他网站发送的 message时,请始终使用 origin 和 source 属性验证发件人的身份

6、Websocket方式解决跨域:

使用Websocket也可以解决跨域问题,因为WebSocket本身不存在跨域问题,所以我们可以利用webSocket来进行非同源之间的通信,

WebSocket 规范定义了一个在 Web 浏览器和服务器之间建立“套接字”连接的 API。 简单来说:客户端和服务器之间存在持久连接,双方可以随时开始发送数据。

WebSocket 的基本使用如下,前端代码:

<script>const ws = new WebSocket('ws://localhost:8081')// 当连接打开时,向服务器发送一些数据ws.onopen = () => {ws.send('Hi!') // 使用send方法向服务端发送内容}// 监听错误ws.onerror =  error => {console.log('WebSocket Error ' + error);};// 监听服务端发送的数据ws.onmessage =  e => {console.log('Server: ' + e.data);};
</script>

服务端代码如下:

// WebSocket服务
const WebSocket = require("ws");const server = new WebSocket.Server({ port: 8081 });server.on("connection", function(socket) {// 监听客户端发送的消息socket.on("message", function(data) {console.log('clent data:', data)socket.send(data); // 向客户端发送消息});
});

注意:

  • 服务端需要通过npm install ws 来安装ws模块
  • 在使用ws的时候发现服务端接收到客户端的数据是二进制,而服务端给客户端返回的数据也是二进制,
    在这里插入图片描述
    解决这个问题的办法就是安装低版本的webSocket依赖,我之前安装的是8.8.1版本的就会出现这个问题,后来切换为7.3.0并重新npm i 和重新启动服务就可以解决这个问题了。
    在这里插入图片描述

总结:

  • jsonp的原理是利用了script标签不受浏览器同源策略的限制,img和link标签也是不受浏览器同源策略限制的。
  • 跨域是浏览器限制,服务端和服务端之间通信是不受浏览器同源策略限制的。
  • 所有跨域的解决方案都是需要服务端配合的。
  • 最常用的跨域解决方案是CORS、Node代理服务器和Nginx反向代理方式。
  • postMessage更多的是用在多个文档,窗口之间发送数据。

http://chatgpt.dhexx.cn/article/NywdUl37.shtml

相关文章

什么是跨域?跨域解决方法

一、为什么会出现跨域问题 出于浏览器的同源策略限制。同源策略&#xff08;Sameoriginpolicy&#xff09;是一种约定&#xff0c;它是浏览器最核心也最基本的安全功能&#xff0c;如果缺少了同源策略&#xff0c;则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策…

SpringBoot解决跨域问题

遇到前端跨域访问问题&#xff0c;类似于这样的&#xff1a; 在Springboot项目里加上这个配置文件CorsConfig.java&#xff0c;重启之后即可实现跨域访问&#xff0c;前端无需再配置跨域。 import org.springframework.context.annotation.Bean; import org.springframework…

搭建 PHP 开发环境(手把手图文教程)

都 2020 年了&#xff0c;你还在老老实实地按照下载Apache、安装MySQL、安装PHP、配置Apache 步骤来搭建PHP开发环境吗&#xff1f; 下面介绍一种一键配置方法&#xff1a;炒鸡好用的 phpStudy 下载安装 phpStudy 我们在这里可以看到&#xff0c;phpStudy 内置了apache、ngin…

搭建PHP环境

搭建PHP环境 对于初学者,推荐在 Windows 操作系统下使用 XAMPP 一键安装 PHP 集成开发环境(Apache、PHP、MySQL),XAMPP 提供 PHP 7 的安装版本,读者只需要到官方网站 https://www.apachefriends.org/download.html 下载即可。 根据自己需要下载相应的版本,下载后得到一个…

win下搭建php环境的方法

win下搭建php环境的方法 下载apache并配置&#xff1b;2、下载PHP并将其解压到wamp目录下的php文件夹&#xff1b;3、下载安装mysql&#xff1b;4、修改好配置文件即可。 本文操作环境&#xff1a;Windows7系统&#xff0c;PHP5.6版&#xff0c;Dell G3电脑。 window10下搭建p…

mac pro M1(ARM)安装:php开发环境

0. 引言 最近在处理各个语言的加密算法&#xff0c;正好需要安装php环境&#xff0c;特此记录&#xff0c;以供后续参考 1. 安装php 1、安装php包管理工具composer brew install composer2、安装php brew install php # 同时可以指定版本安装 brew install php8.0 # 查…

php mac 开发环境搭建_Mac搭建php的开发环境(图文详解)

搭建php的开发环境(图文详解) 这篇文章主要介绍了Mac下搭建php开发环境教程&#xff0c;Mac OS X内置了Apache 和 PHP&#xff0c;这样使用起来非常方便。本文以Mac OS X 10.6.3为例,需要的朋友可以参考下 Mac OS X;内置了Apache和;PHP&#xff0c;这样使用起来非常方便。本文以…

Linux搭建PHP开发环境

集百家之所长搭建的PHP环境 文章目录 集百家之所长搭建的PHP环境1安装SSH和Xftp2安装宝塔面板3安装Mysql4 安装ApachePHP 安装配置Apache至此环境搭建成功 1安装SSH和Xftp B站安装教程 2安装宝塔面板 可以这个安装教程 3安装Mysql 安装Mysql教程 指令如下 # 创建文件目录 …

win10下PHP开发环境搭建

win10下PHP开发环境搭建 安装Apache下载httpd-2.4.41-win64-VS16.zip解压修改配置文件安装服务运行 安装PHP下载解压 安装Mysql整合Apache、PHPApache配置修改PHP配置修改Mysql配置试验 IDEA支持PHP报错Apache启动“发生服务特定错误: 1.”Cannot load /PHP/php5apache2_4.dll …

PHP开发环境搭建与工具

PHP是跨平台的&#xff0c;所以我们可以在Mac OS&#xff0c;Windows&#xff0c;Linux等主流操作系统中进行开发&#xff0c;最为经典的也是最被认可的是Lamp/Lnmp结构&#xff0c;它们是(Linux apache/nginx MySQL PHP)的缩写&#xff0c;我们先简单的用非术语来介绍一下这几…

PHP开发环境搭建工具有哪些?

对于php开发小白来说搭建一个php运行环境就是一道坎&#xff01; 因为要做php开发&#xff0c;搭建一个能够运行php网站的服务器环境是第一步&#xff0c;传统的php环境软件非常复杂&#xff0c;好在很多公司开发了一键搭建php安装环境&#xff0c;一键进行php环境配置&#x…

open与fopen

1.在将open与fopen之前&#xff0c;先讲文件描述符&#xff1a; 当一个可执行程序运行以后&#xff0c;就变成进程&#xff0c;操作系统会为每个进程分配一定的虚拟内存空间&#xff0c;32位操作系统就分配4G。虚拟内存空间结构如下图所示&#xff1a; 1G是内核空间使用&#…

open与fopen的区别

函数原型 FILE *fopen(const char *filename, const char *mode); 参数 filename-- 这是 C 字符串&#xff0c;包含了要打开的文件名称。 mode-- 这是 C 字符串&#xff0c;包含了文件访问模式。 功能 使用给定的模式mode打开filename所指向的文件。 1.层次不同 open是系统调用…

freopen()函数详解

freopen函数详解及在OJ评测系统中的使用 若要打开一个二进制文件&#xff0c;需要加上一个“b”字符。 重定向打开 freopen("in.txt", "r", stdin); freopen("out.txt", "w", stdout);重定向恢复 winfreopen("CON", &quo…

c语言关于freopen函数最全使用方法

hey&#xff0c;好久不见&#xff0c;最近沉迷游戏无法自拔&#xff0c;现在抽出时间来写写关于freopen函数的使用方法&#xff0c;这个freopen函数主要作用就是从文件读取函数同时写入文件&#xff0c;不像我们平常的控制台输入输出&#xff0c;也避免我们在刷一些题目时&…

C++文件操作之freopen

作为一个OIer&#xff0c;文件操作是很重要的。 如果没有文件操作或文件操作被注释&#xff0c;等待着你的就是爆零的命运。 C和C的文件操作&#xff0c;一般是用fopen或fstream&#xff0c;但在OI里&#xff0c;我们用freopen&#xff0c;即文件重定向。 它的用法其实也挺简单…

TVM中的auto-scheduling机制(Ansor)学习笔记

背景 TVM继承了Halide中算法&#xff08;Algorithm&#xff09;与调度&#xff08;Schedule&#xff09;分离的思想。用户使用TE&#xff08;Tensor expression&#xff09;这种DSL定义计算&#xff08;算法&#xff09;&#xff0c;然后编译器优化相应的schedule&#xff0c;…

TVM的“hello world“基础流程 I

前言 继前图灵奖获得者Hennessy和Patterson在ISCA 2018提出“A New Golden Age for Computer Architecture”&#xff0c;编译器界大神Chris Lattner在ASPLOS 2021提出了“The Golden Age of Compiler Design”。另一方面&#xff0c;2020年图灵奖授予了编译器“龙书”作者Jef…

【TVM系列教程一】深度学习编译器及TVM 介绍

0x0. 介绍 大家好呀&#xff0c;在过去的半年到一年时间里&#xff0c;我分享了一些算法解读&#xff0c;算法优化&#xff0c;模型转换相关的一些文章。这篇文章是自己开启学习深度学习编译器的第一篇文章&#xff0c;后续也会努力更新这个系列。这篇文章是开篇&#xff0c;所…

TVM运行系统

TVM运行系统 TVM支持多种编程语言用于编译器堆栈的开发和部署。在本说明中&#xff0c;我们解释了TVM运行时的关键元素。 我们需要满足很多有趣的要求&#xff1a; 部署&#xff1a;从python / javascript / c 语言调用已编译的函数。 调试&#xff1a;在python中定义一个函数…