您当前的位置:首页 > 电脑百科 > 程序开发 > 编程百科

细解跨域以及跨域的解决方案

时间:2023-05-06 14:05:47  来源:  作者:尚硅谷教育

跨域,对于正在学习或者已经就业的前端同学而言,就是老朋友。只要涉及“请求”“前后端交互”“开发阶段”等关键字,都避不开跨域。同时它也是面试中最常出现的考点之一,面试官可以通过跨域,了解应聘者对网络协议、网络安全等概念的理解。

跨域并不是阻碍前后端交互的障碍,什么是跨域,怎么避开跨域带来的不便,本文主要细解三种主流的解决方案:JSONP,CORS,代理服务器,细致地解开跨域相关的迷惑。

一、同源策略

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

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

二、跨域

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

IE的特殊性:Inte.NET 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.('script');

scriptTag.src = src;

document.body.(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 ()

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

请求中,实例没有注册任何事件监听器,即实例对象可以使用.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),更准确的做法就是直接去对应工具的官方文档查阅代理服务器的配置介绍。

总结

对于跨域,许多同学都答得上来跨域是怎么产生的,以及解决跨域的方案。但在交流过程中,就总是一两句就讲完让我觉得有点可惜。

前后端交互,或者应该说网络协议,一直都是个大课题,是只要涉及这一块的程序员,都应该而且有必要学习的内容。类似上文中CORS配置时前后端要如何配合,以及使用CORS时前端的注意点都少有人提及。后端是主要的配置方,但不代表这一块的知识限于只需后端理解。

了解知识点的本质,才能尽量保证在不同的项目场景实施对应方案。



Tags:跨域   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
什么是跨域请求?
跨域请求是指在Web开发中,当一个Web页面(或脚本)向不同源的服务器发起请求时,浏览器会根据同源策略(Same-Origin Policy)对这些请求进行限制。同源策略是一种安全机制,它规定了来自...【详细内容】
2023-12-27  Search: 跨域  点击:(112)  评论:(0)  加入收藏
通过实例理解Web应用跨域问题
在开发Web应用的过程中,我们经常会遇到所谓“跨域问题(Cross Origin Problem)”。跨域问题是由于浏览器的同源策略(Same-origin policy)[1]导致的,它限制了不同源(Origin:域名...【详细内容】
2023-11-20  Search: 跨域  点击:(163)  评论:(0)  加入收藏
解析Java中的跨域请求问题与解决方案
在现代 Web 开发中,由于浏览器的同源策略限制,跨域请求成为一个常见的挑战。Java 作为一种常用的后端语言,提供了多种解决跨域请求问题的方案。下面将深入探讨 Java 中的跨域请...【详细内容】
2023-11-17  Search: 跨域  点击:(235)  评论:(0)  加入收藏
跨域资源共享(CORS)在Spring Boot中的实现
随着Web应用程序的普及,跨域资源共享(CORS)已成为前端开发的重要组成部分。当一个Web应用程序需要从另一个域的前端页面发出请求时,就会遇到跨域问题。大多数现代浏览器由于安全...【详细内容】
2023-09-28  Search: 跨域  点击:(226)  评论:(0)  加入收藏
前端 Jsonp 跨域方案原理
JSONP(JSON with Padding)是一种常用的跨域数据请求策略,它的基本原理是利用 <script> 标签的 src 属性没有同源策略限制的特性来达到跨域的目的。我们来详细解析 JSONP 的工作...【详细内容】
2023-06-01  Search: 跨域  点击:(228)  评论:(0)  加入收藏
SpringBoot如何跨域配置?
1. 前言跨域,一个老生常谈的话题,不管前端后端,跨域都会遇到。而今天,我就跟大家分享一篇关于【跨域】的文章,希望能给大家带来点不一样的收获。2. 环境说明js复制代码环境说明:Wi...【详细内容】
2023-05-30  Search: 跨域  点击:(139)  评论:(0)  加入收藏
细解跨域以及跨域的解决方案
跨域,对于正在学习或者已经就业的前端同学而言,就是老朋友。只要涉及“请求”“前后端交互”“开发阶段”等关键字,都避不开跨域。同时它也是面试中最常出现的考点之一,面试官可...【详细内容】
2023-05-06  Search: 跨域  点击:(427)  评论:(0)  加入收藏
什么是跨域以及如何解决?通俗易懂带你彻底搞定
现在的web项目,很多都是前后端分离,特别容易出现跨域问题那么什么是跨域问题呢?本篇文章带你彻底从本质上弄明白什么是跨域问题以及如何解决一 跨域有什么现象?我们先看一下现象...【详细内容】
2022-10-19  Search: 跨域  点击:(484)  评论:(0)  加入收藏
前后端分离开发,Vue 如何处理跨域问题?
Spring Boot + Vue 这一对技术栈目前看来可以说是非常的火热,关于 Spring Boot 松哥已经写过多篇教程,公号后台回复 666 可以获取 PDF 链接。前后端分离的文章也写过好几篇了,...【详细内容】
2022-09-06  Search: 跨域  点击:(321)  评论:(0)  加入收藏
Cors 跨域基本原理
为什么有了跨域这个东西世上本没有路,走的人多了也就有了路。 跨域这算是这么一回事。 在 Web 的世界上本没有跨域这个东西,但架不住坏人越来越多,所以后来就有了跨域。何出此...【详细内容】
2022-08-09  Search: 跨域  点击:(337)  评论:(0)  加入收藏
▌简易百科推荐
即将过时的 5 种软件开发技能!
作者 | Eran Yahav编译 | 言征出品 | 51CTO技术栈(微信号:blog51cto) 时至今日,AI编码工具已经进化到足够强大了吗?这未必好回答,但从2023 年 Stack Overflow 上的调查数据来看,44%...【详细内容】
2024-04-03    51CTO  Tags:软件开发   点击:(6)  评论:(0)  加入收藏
跳转链接代码怎么写?
在网页开发中,跳转链接是一项常见的功能。然而,对于非技术人员来说,编写跳转链接代码可能会显得有些困难。不用担心!我们可以借助外链平台来简化操作,即使没有编程经验,也能轻松实...【详细内容】
2024-03-27  蓝色天纪    Tags:跳转链接   点击:(13)  评论:(0)  加入收藏
中台亡了,问题到底出在哪里?
曾几何时,中台一度被当做“变革灵药”,嫁接在“前台作战单元”和“后台资源部门”之间,实现企业各业务线的“打通”和全域业务能力集成,提高开发和服务效率。但在中台如火如荼之...【详细内容】
2024-03-27  dbaplus社群    Tags:中台   点击:(9)  评论:(0)  加入收藏
员工写了个比删库更可怕的Bug!
想必大家都听说过删库跑路吧,我之前一直把它当一个段子来看。可万万没想到,就在昨天,我们公司的某位员工,竟然写了一个比删库更可怕的 Bug!给大家分享一下(不是公开处刑),希望朋友们...【详细内容】
2024-03-26  dbaplus社群    Tags:Bug   点击:(5)  评论:(0)  加入收藏
我们一起聊聊什么是正向代理和反向代理
从字面意思上看,代理就是代替处理的意思,一个对象有能力代替另一个对象处理某一件事。代理,这个词在我们的日常生活中也不陌生,比如在购物、旅游等场景中,我们经常会委托别人代替...【详细内容】
2024-03-26  萤火架构  微信公众号  Tags:正向代理   点击:(11)  评论:(0)  加入收藏
看一遍就理解:IO模型详解
前言大家好,我是程序员田螺。今天我们一起来学习IO模型。在本文开始前呢,先问问大家几个问题哈~什么是IO呢?什么是阻塞非阻塞IO?什么是同步异步IO?什么是IO多路复用?select/epoll...【详细内容】
2024-03-26  捡田螺的小男孩  微信公众号  Tags:IO模型   点击:(9)  评论:(0)  加入收藏
为什么都说 HashMap 是线程不安全的?
做Java开发的人,应该都用过 HashMap 这种集合。今天就和大家来聊聊,为什么 HashMap 是线程不安全的。1.HashMap 数据结构简单来说,HashMap 基于哈希表实现。它使用键的哈希码来...【详细内容】
2024-03-22  Java技术指北  微信公众号  Tags:HashMap   点击:(11)  评论:(0)  加入收藏
如何从头开始编写LoRA代码,这有一份教程
选自 lightning.ai作者:Sebastian Raschka机器之心编译编辑:陈萍作者表示:在各种有效的 LLM 微调方法中,LoRA 仍然是他的首选。LoRA(Low-Rank Adaptation)作为一种用于微调 LLM(大...【详细内容】
2024-03-21  机器之心Pro    Tags:LoRA   点击:(12)  评论:(0)  加入收藏
这样搭建日志中心,传统的ELK就扔了吧!
最近客户有个新需求,就是想查看网站的访问情况。由于网站没有做google的统计和百度的统计,所以访问情况,只能通过日志查看,通过脚本的形式给客户导出也不太实际,给客户写个简单的...【详细内容】
2024-03-20  dbaplus社群    Tags:日志   点击:(4)  评论:(0)  加入收藏
Kubernetes 究竟有没有 LTS?
从一个有趣的问题引出很多人都在关注的 Kubernetes LTS 的问题。有趣的问题2019 年,一个名为 apiserver LoopbackClient Server cert expired after 1 year[1] 的 issue 中提...【详细内容】
2024-03-15  云原生散修  微信公众号  Tags:Kubernetes   点击:(6)  评论:(0)  加入收藏
站内最新
站内热门
站内头条