前端跨域问题解决

news2025/1/23 6:22:28

一、同源策略

同源策略是一个重要的安全策略,它用于限制一个Origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。

Origin:指web文档的来源,Web 内容的来源取决于访问的URL的方案 (协议),主机 (域名) 和端口定义。只有当方案,主机和端口都匹配时,两个对象具有相同的起源。

二、跨域

关于URL是否同源,根据上图中的①②③进行判断即可,只要有一点不同,就达到跨域的条件。顺带一提,即便是向域名对应的ip进行资源请求,仍然会跨域。

IE的特殊性:Internet Explorer 的同源策略有两点差异,一是IE未将端口号纳入同源策略的检查,其次是两个高度互信的域名也不受同源策略的检查。

常见的跨域情景:

浏览器内常见的跨域报错:

跨域出现的场景:

一般常见于开发阶段,本地启动项目后,当前页面域名和后台服务器域名不相同,导致跨域。在项目上线后,会通过统一域名、后端配置域名白名单等方式避免跨域。

下方的解决方案中,我们通过koa2框架搭建服务器,实现一系列的情景模拟。

三、跨域的解决方案

1.JSONP

原理:通过script标签没有跨域限制的特性,进行资源的请求和获取。

限制:需要目标服务器进行配合,且仅支持get请求

我们直接通过代码和注释,理解jsonp的使用前端代码如下:

<script>

window.jsonp = function(res){

console.log(res);

}

</script>

<script src="http://localhost:9527/jsonp?val=123&cb=jsonp"></script>

后端代码如下:

// 定义jsonp接口

router.get('/jsonp', async (ctx, next) => {

/*

1.后端通过query获取前端传来的请求参数

其中包括:

· 交予后端进行功能逻辑操作的数据,如val

· 交予后端进行jsonp操作的函数名,如cb

*/

const {cb, val} = ctx.query

// 2.调用回调函数,进行传参,将处理好的数据返回给前端

if(val === '123'){

const requestData = {

code: 10001,

data: '登陆成功'

}

//在响应体中触发目标函数,并将处理好的数据requestData作为实参传入

ctx.body = `${cb}(${JSON.stringify(requestData)})`;

}

})

前端通过window对象,在全局挂载了一个待触发的函数。

后端通过响应体触发这个函数,并将数据作为入参,传给前端。

了解简单的实现后,前端可以对jsonp的功能再进行一层封装:

/*

1. 生成script标签,我们需要script标签进行接口的调用

2. 处理参数数据,分别整理好接口,接口参数,函数名等数据,并进行填充

3. 写入生成好的script标签,实现接口的调用(返回promise对象,便于链式调用)

4. 清除script标签

*/

function jsonp(requestData) {

// 对传入参数进行处理

const { url, data, jsonp } = requestData;

let query = '';

for (let key in data) {

query += `${key}=${data[key]}&`;

}

const src = `${url}?${query}jsonp=${jsonp}`;

// 生成、填充script标签,在页面中挂载调用接口

let scriptTag = document.createElement('script');

scriptTag.src = src;

document.body.appendChild(scriptTag);

return new Promise((resolve, reject) => {

window[jsonp] = function(rest){

resolve(rest)

document.body.removeChild(scriptTag)

}

})

}

// 整理数据

const requestData = {

url: 'http://localhost:9527/jsonp',

data: {

val: 123,

},

jsonp: 'getMessage'

}

// 接口调用

btn.onclick = function () {

jsonp(requestData).then(function (response) {

console.log(response);

})

}

2.CORS

Cross-Origin Resource sharing(跨域资源共享),是一种基于HTTP头的机制,该机制允许服务器标示除了它自己以外其他origin(域名,协议和端口),既浏览器在跨域的情景下仍然能从目标服务器请求并获取资源。

而对服务器数据可能产生副作用的HTTP请求方法,都会触发CORS中的预检机制。

CORS中通过预检机制(preflight request)检查服务器是否允许浏览器发送真实请求,浏览器会先发送一个预检请求(option请求),请求中会携带真实请求的请求信息:

origin:请求的来源

Access-Control-Request-Method:

通知服务器在真正的请求中会采用哪种HTTP方法(GET,POST,DELETE...)

Access-Control-Request-Headers:通知服务器在真正的请求中会采用哪些请求头

服务器可以在预检请求中,可以根据以上三条信息,确定预检请求是否通过:

//server.js

app.use(async (ctx, next) => {

// 允许跨域资源共享的白名单

const whiteList = ['http://127.0.0.1:5500']

// 判断目标源是否通行

const pass = whiteList.includes(ctx.header.origin)

// 对于预检请求,如果没有设置正确的响应状态,浏览器会直接拦截真实请求,直接报错提示跨域

// 所以我们可以在这一部分,确定客户端的请求是否符合我们的要求

if (ctx.method === "OPTIONS") {

if (!pass) return

// 预检放行

ctx.status = 204

}

await next();

});

响应的状态码是决定预检请求是否通过的关键,返回正常的状态码(通常是204)就能通过预检请求,让浏览器发出真实的请求。

在代码中也可以看出,pass是决定预检请求的关键,那在实际的项目中,还得根据设计去决定通行的具体条件。当通过预检请求后,后台可以设置对应的响应头数据,例如是否允许目标源跨域资源共享:

//server.js

app.use(async (ctx, next) => {

console.log('middleware for cors');

// 允许跨域资源共享的白名单

const whiteList = ['http://127.0.0.1:5500']

// 判断目标源是否通行

const pass = whiteList.includes(ctx.header.origin)

// 对于预检请求,如果没有设置正确的响应状态,浏览器会直接拦截真实请求,直接报错跨域

// 所以我们可以在这一部分,确定客户端的请求是否符合我们的要求

if (ctx.method === "OPTIONS") {

if (!pass) return

// 预检放行

ctx.status = 204

}

// 允许访问的origin

ctx.set("Access-Control-Allow-Origin", ctx.headers.origin);

// cookie是否允许携带

ctx.set("Access-Control-Allow-Credentials", true);

// 允许访问的HTTP方法

ctx.set("Access-Control-Request-Method", "PUT,POST,GET,DELETE,OPTIONS");

// 哪些请求头允许通行

ctx.set(

"Access-Control-Allow-Headers",

"X-Requested-With,Content-Type,Accept,Origin"

);

// 暴露给客户端的响应头信息,在不设置的情况下,客户端只能获取默认的响应头,如’content-type‘

ctx.set(

"Access-Control-Expose-Headers",

"With-Requested-Key"

);

// 设置对应的响应头数据

ctx.set(

"With-Requested-Key",

"HW"

);

// 预检结果的缓存时间,毫秒为单位,Firefox上限是86400-24小时,Chromium(谷歌引擎)上限是7200-2小时

ctx.set("Access-Control-Max-Age", 0);

await next();

});

其中需要注意两个点:

关于Access-Control-Expose-Header

使用CORS时,浏览器只允许获取默认的响应头,像上文代码中的标头With-Requested-Key,即便我们可以通过浏览器的调试器查看,也无法通过代码去获取,这时候就需要后台通过Access-Control-Expose-Header进行暴露(后台代码在已在上方统一贴出)。

前端代码

<body>

<button id="btn"> 请求资源 </button>

</body>

<script>

btn.onclick = function () {

axios.post('http://localhost:9527/getMessage', {

firstName: 'Fred',

lastName: 'Flintstone'

})

.then(function (response) {

// 可以在里面查找到暴露出来的响应头数据,如’With-Requested-Key‘: "HW"

console.log(response.headers);

})

.catch(function (error) {

console.log(error);

});

}

</script>

关于Access-Control-Allow-Credentials

使用CORS时,默认不携带cookie,需要同时满足三个条件,才能在使用CORS时进行cookie的传递:

浏览器的请求中,设置withCredentials参数为true

服务端设置标头Access-Control-Allow-Credentials为true

服务端设置标头Access-Control-Allow-Origin不为*

我们可以在原生ajax请求中设置该参数,或者在axios的默认配置中设置该参数:

// 原生ajax

const xhr = new XMLHttpRequest()

xhr.withCredentials = true

// axios

axios.defaults.withCredentials = true;

Ok,明白CORS的作用,以及明白CORS中的预检机制后,接下来是了解什么时机下会触发预检机制。

CORS中归纳了一系列不会触发预检机制的请求场景,即满足所有下述条件的情况下,统称为简单请求:

使用这三种方法之一:GET HEAD POST

不得人为设置此集合外的其他首部字段:Accept Accept-Language Content-Language Content-Type

Content-type的值仅限于这三者之一:

text/plain

multipart/form-data

application/x-www/form-urlencoded

请求中,XMLHttpRequest实例没有注册任何事件监听器,即XMLHttpRequest实例对象可以使用XMLHttpRequest.upload属性进行访问

请求中没有使用ReadableStream对象

小结:CORS中主要区分了简单请求和复杂请求两种情况,复杂请求会触发CORS的预检机制。通过上方的案例,也可以清楚CORS的配置主要是在服务端,但客户端也需要知道CORS的使用注意点,例如响应头数据的获取以及cookies的携带配置,这些知识应该是前后端都需要掌握的技能点。

3.服务器代理

同源策略主要是限制浏览器和服务器之间的请求,服务器与服务器之间并不存在跨域。

我们可以通过koa2模拟和实现这种概念:

//前端代码

<body>

<button id="btn"> 请求资源 </button>

<script>

btn.onclick = function () {

let url = checkUrlProxy('http://localhost:9527/api/getMessage','api')

axios.post(url, {

firstName: 'Fred',

lastName: 'Flintstone'

})

.then(function (response) {

console.log(response);

})

}

// 判断接口是否携带api字段,若是,则更改为代理服务器对应的域名

function checkUrlProxy(url, proxyFlag){

let proxyServer = 'http://localhost:1005'

let urlArr = [url.split('/')[1],url.split('/')[3]]

if(urlArr.includes(proxyFlag)) {

return `${proxyServer}/${proxyFlag}${url.split(proxyFlag)[1]}`

}

return url

}

//

</script>

</body>

前端的代码部分,通过checkUrlProxy函数简单地确定本次请求是否要转向代理服务器。

后端代码如下:

//proxyServer.js

let requestFlag = false

let body = ''

app.use(async (ctx, next) => {

// 全放行

if (ctx.method === "OPTIONS") {

ctx.status = 204

requestFlag = false

} else {

requestFlag = true

}

ctx.set("Access-Control-Allow-Origin", "*");

ctx.set("Access-Control-Allow-Credentials", true);

ctx.set("Access-Control-Request-Method", "*");

ctx.set(

"Access-Control-Allow-Headers",

"X-Requested-With,Content-Type,Accept,Origin"

);

ctx.set("Access-Control-Max-Age", 86400);

// 根据具体情况进行修改

ctx.set("Access-Control-Expose-Headers", "With-Requested-Key");

await next();

if(requestFlag) {

ctx.body = body

body = ''

}

});

app.use(async (ctx, next) => {

if (!requestFlag) return

await p4r(ctx)

});

function p4r(ctx) {

return new Promise((res, rej) => {

const proxyRequest = http.request({

host: '127.0.0.1',

port: 9527,

path: ctx.url,

method: ctx.method,

headers: ctx.header

},

serverResponse => {

serverResponse.on('data', chunk => {

body += chunk

})

serverResponse.on('end', () => {

res(body)

})

}

)

proxyRequest.end()

})

}

app.on('error', (err, ctx) => {

console.error('server error', err, ctx)

});

app.listen(1005, (err) => {

if (err) console.log('服务器启动失败');

else console.log('proxy server 1005 running --> ✨✨✨');

})

//targetServer.js

const data = {val : 123}

// 配合代理服务器的post路由

router.post('/api/getMessage', (ctx) => {

ctx.body = JSON.stringify(data)

})

// 定义好路由组件的内容后进行路由注册

app.use(router.routes())

app.on('error', (err, ctx) => {

console.error('server error', err, ctx)

});

app.listen(9527, (err) => {

if (err) console.log('服务器启动失败');

else console.log('服务器启动成功');

})

后端代码主要分两部分:

代理服务器(proxyServer),代理服务器设置CORS时不限制通行,在koa2框架中,通过中间件向目标服务器发送请求,当接收到对应数据后,再响应给浏览器

目标服务器(targetServer),目标服务器不需要做太复杂的配置,案例中只是将数据传递给请求方

Ok,我们通过这个案例,明确代理服务器的具体效果,浏览器向目标服务器直接请求资源,仍然会受到同源策略的影响,但通过代理服务器向目标服务器请求资源时,却没这种限制。

那在实际项目中,我们可以通过脚手架或打包工具的配置文件,简洁方便地设置代理服务器,无需自己手写服务器代码,拿vue的脚手架为例:

devServer:{

proxy:{

'api':{

target:'127.0.0.1:9527', //目标服务器地址

changeOrigin: true, // 是否允许跨域

pathRewrite: { //是否重写接口

'api':'',

}

}

}

}

在配置的时候,可以通过框架的脚手架,或者打包工具确定配置文件,例如一些熟悉的字眼:vue.config.jswebpack.config.jspackage.json(react),更准确的做法就是直接去对应工具的官方文档查阅代理服务器的配置介绍。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1102055.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据结构: 二叉搜索树

目录 1.二叉搜索树概念 2.二叉搜索树的操作 3.二叉搜索树的实现 3.1定义BST 3.2功能实现 1.默认成员函数 2.非递归 插入 查找 删除 3.递归 插入 查找 删除 4.二叉搜索树的应用 1.二叉搜索树概念 二叉搜索树又称二叉排序树&#xff0c;它可以是一棵空树&#xff…

Java连接PostGreSql

本次试验怎么用jdbc连接PostGreSql数据库。首先需要安装一个pgsql数据库&#xff0c;安装就不介绍了。安装后自己用SQL创建表&#xff0c;如有下图的库和表&#xff0c;怎么用java带SQL参数连接他取的数据。 PG库下载地址 首先到官网上根据自己的Java版本下载jdbc驱动jdbc下载…

HCIP静态路由综合实验

题目&#xff1a; 步骤&#xff1a; 第一步&#xff1a;搭建上图所示拓扑; 第二步&#xff1a;为路由器接口配置IP地址&#xff1b; R1&#xff1a; [R1]display current-configuration intinterface GigabitEthernet0/0/0ip address 192.168.1.1 255.255.255.252 interfa…

FL Studio 2023中文安装设置指南!四招教你玩转FL Studio21!

Fl Studio是一款极具时尚元素的音乐制作软件&#xff0c;对于粉丝群体来说简直是一大福利&#xff01;不仅可以充分发挥你的创造力&#xff0c;还能展现你的音乐才华。这里给你分享几个设置中文的技巧&#xff0c;让你的Fl Studio体验更上一层楼&#xff01; 编曲软件FL Studi…

警惕这款记录音频和电话的Android木马软件SpyNote

导语&#xff1a;近日&#xff0c;一款名为SpyNote的Android木马软件被揭示出其多样化的信息收集功能。该木马软件通常通过短信钓鱼攻击传播&#xff0c;攻击链通过欺骗潜在受害者点击嵌入链接来安装该应用程序。除了要求入侵性权限以访问通话记录、摄像头、短信和外部存储等&a…

vite react react-pdf pdfjs-dist 加载不全的解决方案 cmaps本地路径

pdf.js 字体无法显示 pdfjs-dist加载不全的解决方案 Rollup 配置 rollup-plugin-copy插件&#xff0c;进行打包构建时的文件复制 参考了网上诸多解决方案&#xff0c;都是webpack的引入包方式&#xff0c; 照猫画虎&#xff0c;把vite解决方案奉献给大家 vite.config.js impo…

小程序:uniapp解决主包体积过大的问题

已经分包但还是体积过大 运行时勾选“运行时是否压缩代码”进行压缩 在manifest.json配置&#xff08;开启分包优化&#xff09; "mp-weixin" : {"optimization" : {"subPackages" : true}//.... },在app.json配置&#xff08;设置组件按需注入…

【CANN训练营】UART、SPI、I2C串口通信介绍笔记

UART、SPI、I2C串口通信介绍 UART通信 串行通信和并行通信 数据通信&#xff1a;若干个数据设备之间的信息交换称为数据通信。 两种方式&#xff1a;并行通信和串行通信 并行通信&#xff1a;数据的各位同时传送&#xff0c;每一位数据都需要一条传输线并且需要若干条控制…

H5+Vue3编写官网,并打包发布到同一个域名下

背景 因为html5有利于搜索引擎抓取和收录我们网站更多的内容&#xff0c;对SEO很友好&#xff0c;可以为网站带来更多的流量,并且多端适配&#xff0c;兼容性和性能都非常不错&#xff0c;所以使用h5来编写官网首页。 因为用户个人中心可以通过官网跳转&#xff0c;不需要被浏…

jmeter压测

jmeter强大到很强大 hh~也要压测go的一些东西&#xff0c;这是三年前做的东西了&#xff0c;jmeter不支持grpc调用所以写了一个spring小服务中间层&#xff1a; 具体的jmeter开始了 这里设置线程相关 <h3>调用哪个服务的哪个方法</h3> <h3>BeanShell PrePro…

“脆皮大学生”上热搜,体质变差不能轻视

蕞近&#xff0c;“脆皮大学生”这一词条在网络上走红。“脆皮大学生”指现在新一代大学生&#xff0c;虽然年纪轻轻&#xff0c;但是身体毛病却极多&#xff0c;脆弱到了一碰就坏的地步&#xff0c;出现了“脆皮现象”。 仅2023年9月一个月&#xff0c;郑州一家医院急诊科接诊…

Spring5应用之高级注解开发

作者简介&#xff1a;☕️大家好&#xff0c;我是Aomsir&#xff0c;一个爱折腾的开发者&#xff01; 个人主页&#xff1a;Aomsir_Spring5应用专栏,Netty应用专栏,RPC应用专栏-CSDN博客 当前专栏&#xff1a;Spring5应用专栏_Aomsir的博客-CSDN博客 文章目录 参考文献前言Conf…

JOSEF约瑟 10KV高压漏电保护继电器BLD-20 φ100mm 50-500mA 导轨安装

系列型号 BLD-20A高压漏电保护继电器 BLD-20高压漏电继电器 BLD-20高压漏电保护继电器 BLD-20X高压漏电保护装置 BLD-G20X高压漏电保护装置 BLD-20RG高压漏电保护继电器 用途 BLD-20漏电继电器(以下简称继电器)主用于交流电压1-10KV系统中,频率为50HZ,对供电系 统的漏…

【C语言】#define宏与函数的优劣对比

本篇文章目录 1. 预处理指令#define宏2. #define定义标识符或宏&#xff0c;要不要最后加上分号&#xff1f;3.宏的参数替换后产生的运算符优先级问题3.1 问题产生3.2 不太完美的解决办法3.3 完美的解决办法 4.#define的替换规则5. 有副作用的宏参数6. 宏与函数的优劣对比6.1 宏…

从0开始学Java:运算符(Operator)与标点符号(Separators)

文章目录 1. 运算符的分类2. 算术运算符3. 赋值运算符4. 关系运算符/比较运算符5. 逻辑运算符6. 条件运算符练习 7. 位运算符&#xff08;了解&#xff09;左移&#xff1a;<<右移&#xff1a;>>无符号右移&#xff1a;>>>按位与&#xff1a;&按位或&…

阿里云优惠券(代金券)免费领取方法及使用教程分享

阿里云优惠券是阿里云提供给用户的一种优惠凭证&#xff0c;通常包括代金券和折扣券&#xff0c;领取之后支付订单时可以抵扣或者打折&#xff0c;是阿里云的一种重要优惠方式。本文将为大家详细介绍阿里云优惠券的免费领取方法及使用教程&#xff0c;帮助大家在购买阿里云产品…

qt 读取txt文本内容时,中文乱码

项目场景&#xff1a; 项目中&#xff0c;需要在TF卡中做类似txt阅读器的功能&#xff0c;因为app是在嵌入式系统下运行的&#xff0c;发现当读取txt的文本格式为ANSI时&#xff0c;中文的显示是乱码&#xff0c;故记录下解决方法 问题解决 中文乱码问题还是涉及到编码问题&…

Steam通过短信验证来遏制带有恶意软件的更新

导语段落 近期&#xff0c;Steam平台上的游戏开发者账户遭受了来自恶意软件的更新攻击&#xff0c;为了应对这一问题&#xff0c;Valve公司宣布将实施额外的安全措施&#xff0c;其中包括基于短信的确认码验证。本文将为大家介绍这一新措施以及其对游戏开发者和玩家的影响。 短…

ORA-12541:TNS:no listener 无监听程序

问题截图 解决方法 1、删除Listener 新建一个新的 2、主机为服务器ip 3、设置数据库环境 只需要设置实例名不需要设置路径 4、服务命名 一样设置为ip 服务名与监听名一直 eg&#xff1a;orcl

React中的key有什么作用

一、是什么 首先&#xff0c;先给出react组件中进行列表渲染的一个示例&#xff1a; const data [{ id: 0, name: abc },{ id: 1, name: def },{ id: 2, name: ghi },{ id: 3, name: jkl } ];const ListItem (props) > {return <li>{props.name}</li>; };co…