这两天一直被面试官问到跨域,但是自己从来没有系统性地总结过,本期文章就来把跨域这个问题给聊明白来,也希望对春招的各位有所帮助
同源策略-跨域
跨域就是浏览器的同源策略生效的时候
后端返回给浏览器的数据会被浏览器的同源策略给拦截下来
就拿百度官网为🌰,https://www.baidu.com
,其实这个地址是被处理了,理应是这样https//192.168.31.45:8080/user
,正常来讲一个url
地址是由四个部分组成,也就是协议号https
,域名192.168.31.45
,端口号8080
以及路径user
我们可以正常写一个前端,假设那个地址就是百度的后端地址,我们可以从那里拿到百度的数据,我们现在想想,这个情景科学吗?显然不科学,数据怎么能被不认识的人随便拿!因此浏览器针对这个问题,里面有个同源策略,也就是协议号-域名-端口号
三部分必须是一样的,浏览器才认为你们是一家公司的,但凡三者有一个不同,那么浏览器就会把这个请求拦截下来,此时就是跨域
比如这样的地址就是符合同源https//192.168.31.45:8080/user
和https//192.168.31.45:8080/list
,只有最后的路径不同
也就可以这样理解,字节的前端朝着字节后端发接口请求,字节就会响应回去,然后百度前端也朝着字节后端发接口请求,字节后端会响应会去,后端是不负责判断谁来请求的,是个人请求都会返回,但是这个过程中,浏览器的同源就发现了不对,因此把后端返回的响应给拦截了下来
因此跨域发生在后端
响应
阶段
同源策略的目的就是一个安全性,怎么可能是个人都可以拿自己的数据
接下来我们需要解决跨域
回答这个之前我们先要明白为什么要解决跨域
为什么要解决跨域
假设我们在公司写项目,前端用vue
写的,项目跑在http://192.168.31.1:8080
,后端用go
写的,跑在http://192.168.31.2:8080
,尽管连接着一个wifi
在同一个局域网内,其ip
地址最后的数字还是不同的,两台设备的ip
地址是不可能一样的,并且有时候,其端口号
也是不同的
这是开发阶段,前后端需要联调,前端发现这个接口怎么都调用不了,问题就来了
所以为何要解决跨域,解决跨域方便程序员进行开发,开发阶段好调试
同源策略安全的同时,给程序员上了一层颈箍咒
解决跨域
解决跨域就是让同源策略发挥不了作用,后端响应回数据时可以正常作用
解决跨域有很多种方法,但是常用的就只有四种,这四种一定得掌握
JSONP
先简单实现下,前后端交互
1 | go复制代码// 目录 |
先把app.js写成这样,完全可以接受吧~(引用的koa
1 | javascript复制代码const Koa = require('koa') |
前端的话,简单写个页面,点击按钮获取后端返回的数据
index.html写成这样,我们也可以接受~
1 | xml复制代码<!DOCTYPE html> |
这样就实现了从后端拿数据,好,我们现在用liver server
跑一下,点击按钮,出现报错!
has been blocked by CORS
就是出现了跨域
当我们自己写全栈项目的时候,因为跨域导致自己的前端都无法调用自己的后端,感觉很气!
这个代码我改巴改巴,我把fetch
请求注释掉,用script
的src
来请求,你会发现,不再同源了
1 | xml复制代码<!DOCTYPE html> |
嚯~居然不报错!
聪明的你这个时候就发现了,这不就是我们引入第三方源码的手段嘛,就那个CDN
引入,引入那个资源也没有报错!
当我们使用ajax
发接口请求的时候一定是会受同源的影响,但是我们通过script
的src
去请求数据,并没有受到同源的影响
其实不法分子解决跨域就是通过这个手段
如果这个手段也受同源的影响,前端代码就写不了了,根本无法引入第三方的库
好,现在我们就钻这个空子
自己封装一个函数jsonp
用于发接口请求,在函数里面自己生成一个script
标签,这个函数可以接收url
,cb
参数,并且给script
挂上一个src
属性,放上url
和cb
。然后通过appendChild
把script
放到body
里面去,这个时候就已经保证了这个函数可以利用script
发请求了
1 | xml复制代码<!DOCTYPE html> |
点击按钮,确实有个网络请求
既然前端发送了请求,那么此时后端一定收到了请求,并且里面把前端传进来的callback
字符串传了过来
我们可以在后端的main
方法中,打印下ctx.query
好,现在对后端代码改巴改巴,我把前端传给我的callback
再带上自己数据返回给前端,如下,用的字符串模板拼接~
1 | ini复制代码const Koa = require('koa') |
这个时候前端收到数据是报错的,因为cb
还没有定义呢~
好,现在来到前端,我在全局window
上挂上这个cb
,其值我写成函数体
1 | xml复制代码<!DOCTYPE html> |
我们打印在这个参数data
,就是后端往cb
括号中放入的数据,也就是那句话
既然有打印,说名这个cb函数被触发了,可是前端并没有触发它,那就只能是后端触发的,并且传了参数进来!
我现在将log
打印改成resolve
,这样后面的then
就能打印出后端返回的数据了
1 | xml复制代码<!DOCTYPE html> |
我们没有跨域,但是也拿到了后端返回的数据~
解释下整个过程:
- 借助
script
的src
属性给后端发一个请求,且携带一个参数callback
; - 前端在
window
上添加了一个callback
函数; - 后端接收到这个参数
callback
后,将要返回给前端的数据data
和这个参数callback
进行拼接,成callback(data)
,并返回给前端; - 因为
window
上已经有一个callback
函数,后端又返回了一个形入callback(data)
,浏览器会将字符串执行成callback
的调用
因此JSONP
核心理念就是借助script
标签上的src
属性不受同源策略的影响这一机制,来实现跨域
总结
- ajax请求受到同源策略的影响,但是
script
上的src
属性不受同源策略的影响,并且该属性也会导致浏览器发送一个请求 - 缺点:1. 必须后端配合(拿到参数再拼接回去);2. 只能用于
get
请求,浏览器执行script
的src
请求默认就是get方式(正常开发很多接口都是post);
Cors
Cors
(Cross-Origin
Resource
Sharing
)
在http
协议中,每个请求都可以拆分成两部分,一个请求头,一个请求体
- 请求头比较小,里面包含了这个请求的基本信息,从哪去哪儿
- 请求体就放的是参数,数据包
后端返回的就是响应头和响应体,这个时候我们对这个响应头写入一些参数,告诉浏览器我后端的数据所有的前端请求都可以拿走
1 | javascript复制代码const http = require('http') |
前端不需要任何变化
1 | xml复制代码<!DOCTYPE html> |
好了,这样就实现了解决跨域,但是目前的写法比较偷懒,肯定不能写*
,实际开发不可能允许所有的接口请求
比如我现在前端写在本地,就是localhost
,所以把*
换成http://127.0.0.1:5501
多个ip
需要接口,就多配几个白名单
总结
后端通过设置响应头来告诉浏览器不要拒绝接收后端的响应
这个方法明显比
JSONP
好用多了,前端不需要任何操作,只需要后端简单配置下cors
即可
node代理
假设我现在写了个前端,希望拿到网易云的数据,我可以从前端向网易云的后端拿,我还可以选择自己写个后端,自己的后端去往网易云的后端拿数据,这个过程中不经过浏览器,就不会跨域,后面就是自己的前端从自己的后端拿数据cor
下就好,这就是node
代理
vite
这个构建工具就是用node
写的,我们写vue
项目的时候,就可以用node代理的形式去解决跨域问题
好,我现在用vite
模仿下,App.vue
我让其首页挂载完毕就朝着后端发请求
1 | xml复制代码<script setup> |
这里用的axios
发请求,axios
需要自己安装npm i axios
然后自己的后端如下,没有使用cors
,一定会发生跨域
1 | javascript复制代码const http = require('http') |
配置vite.config.js
如何解决呢?我们直接来到vite.config.js
文件中配置server
,如下
1 | javascript复制代码import { defineConfig } from 'vite' |
vite解决跨域:开发服务器选项 | Vite 官方中文文档 (vitejs.dev)
这个配置的意思是,只要前端朝/api
发请求,那么就会转发到target
中,也就是localhots:3000
,然后改变源,如果后端路径本身就有/api
,那么就重写为空
好了,所以现在只需要把前端的请求地址改成/api
即可
1 | xml复制代码<script setup> |
像是修改完配置文件,都需要项目重新启动下
好了,成功解决跨域,从后端拿到数据
好,问题来了,vite
只是我们开发阶段使用的构建工具而已,项目最后是要打包上线的,因此到了生产阶段,vite
打包后的这个配置信息是会消失的,不对,是整个vite
的源码都会被剔除掉
因此这个方法只适用于开发环境,上线的跨域只能用别的方法
总结
vite
帮我们启动了一个node
服务,且帮我们朝着http://locahost:3000
发请求,因为后端之间没有同源策略,所以,vite
中的node
服务能直接请求到数据,再提供给前端使用
但是给到前端依旧会跨域,只是因为里面已经自带
cors
了,看不出来
缺点:只能在开发环境中生效
截至目前,前端是没有一个优雅的手段可以阶段跨域的,
JSONP
很麻烦,而且只能get
,然后Cors
是后端干的,然后node
只能开发阶段生效,其实这个三个方法通常都是用在开发环境下
nginx代理
这个机制和Cors
差不多,做白名单的配置,都是配置请求头,需要后端在服务器上安装nginx
,实现所有的请求可以实现nginx
代理,nginx
是linux
的语法,而非js
这个方案可以解决生产环境下的跨域,也就是可以项目上线且不跨域,公司项目一般就是用这种方法解决跨域
nginx
和cors
机制差不多,为何不用nginx
呢?如何你写了三个后端项目,那么三个后端项目都需要配置
Cors
,nginx
可以一起配置掉
具体实现以后再出期文章详聊,涉及到项目上线
其实这四种跨域方案足够你项目的开发了,不过面试官可能会问你还有吗,那些就是些不常用的方法
面试官:还有吗?
domain
在iframe
中,当父级页面和子级页面的子域
不同时,通过设置document.domain = 'xx'
,来将xx
定为基础域
,从而实现跨域
iframe
的作用就是一个html
可以嵌套另一个html
比如我这里,两个页面,父级页面中通过iframe
嵌套了个子页面,父级页面定义个参数,子级页面打印这个参数,当然得实现非同源的时候打印才能证明实现跨域,用live-server
是同源运行的
live-server安装:
npm i -g live-server
index.html
1 | xml复制代码<!DOCTYPE html> |
child.html
1 | xml复制代码<!DOCTYPE html> |
比如当你希望在www.example.com
中使用api.example.com
的api
,为防止跨域,就可以两个页面设置相同的document.domain
,也就是基础域,这样就不会跨域
postMessage
实现一个深拷贝可以借助管道通信,也就是
postMessage
,还有个structured clone
,这是js自带的
postMessage
主要作用就是用来做管道通信的,既然涉及到通信,那就会遇到跨域的问题
按道理两个页面交互需要一个点击事件,这里我们不用点击事件,a
,b
页面交互信息如下,不发生跨域
a.html
1 | xml复制代码<!DOCTYPE html> |
b.html
1 | xml复制代码<!DOCTYPE html> |
还有个
websocket
来解决跨域,以后再来单独详聊
最后
正常来讲,JSONP
,Cors
,node代理
和nginx
足够解决跨域了,如果面试官问你,还能答出个几个冷门方法就再合适不过了
如果你对春招感兴趣,可以加我的个人微信:
Dolphin_Fung
,我和我的小伙伴们有个面试群,可以进群讨论你面试过程中遇到的问题,我们一起解决
另外有不懂之处欢迎在评论区留言,如果觉得文章对你学习有所帮助,还请”点赞+评论+收藏“一键三连,感谢支持!
本文转载自: 掘金