面试官:请问您能说一下跨域的解决办法吗?
我:这种事儿为什么要前端来做?难道不是后端设置一下就行了吗?
面试官:和您聊得很开心,请您回去等通知吧。
正文
在很早以前还是JSONP的解决方案里,跨域问题算是很经典的面试题。
但那是前端环境还处于蛮荒时代的解决手法,如今再问跨域,多少有点过期的感觉了。
不过,作为开发人员,跨域这个问题是必须要了解的。
在正式讲解跨域之前,先简单了解几个概念。
URL的组成
名称 | 作用 |
---|---|
协议(Protocol) | URL 的开头部分通常包含协议 名称,例如“http://”、“https://”等,也有ftp等协议,这里不展开细说 |
主机名(Host Name) | 指定了资源所在的主机(服务器)的域名 或IP地址 |
端口号(Port Number) | 可选部分,用于指定服务器 上接收请求的端口号 。如果未指定,默认使用协议的默认端口 (如80或443) |
路径(Path) | 指定了服务器 上资源的位置 ,表示资源 在服务器文件系统中的路径 。 |
查询参数(Query Parameters) | 可选部分,用于向服务器 传递额外的参数 ,通常以键值对 的形式出现,例如“?key1=value1&key2=value2” |
锚点(Fragment) | 可选部分,用于指定资源内的特定位置 (如页面内的锚点) |
以http://192.168.0.1:8080/index.html
为例子,我们可以得出如下结论。
1 | http是协议,192.168.0.1是主机名,8080端口号,index.html是路径 |
如果http://www.test.com/index.html
这样的例子
1 | http是协议,www.test.com是主机名,index.html是路径 |
这里我们会发现,这里只有主机名而没有端口的构成。
这是因为,这种域名的形式的端口往往默认以80或者443的形式绑定在域名上,所以我们平时在正式的网址上,很少看到有域名加端口的形式。
关于URL我们这里就简单说一些,这里就不详细展开说。
如果想要深入了解URL的构成,可以参考菜鸟教程的HTML 统一资源定位器(Uniform Resource Locators)篇
同源策略
正式了解之前,我们先记住一点:同源策略是浏览器的策略,而非服务端的策略。
在记住这点之后,我们再正式了解同源策略。
1 | 同源策略,它是由Netscape提出的一个著名的安全策略。 |
总结,同源策略,是浏览器为了解决两个页面数据安全问题而提出的一种手段。
我们已经了解了URL的构成,所以这里我们很明显的就能看明白,资源文件同源到底是哪些地方要对应的。
同源策略,说到底,是浏览器为了隔离不同页面的获取资源的一种手段。
如果两个页面的协议
,域名
和端口
都相同,则两个页面具有相同的源。
这里,我们以http://www.test.com/index.html
为例子,看一下。
URL | 是否同源 | 原因 |
---|---|---|
http://www.test.com/other.html | 是 | 同源(协议、域名、端口相同) |
https://www.test.com/about.html | 否 | 协议不同(http 与 https) |
http://blog.test.com/movie.html | 否 | 域名不同(www.test.com与 blog.test.com) |
http://www.test.com:7001/home.html | 否 | 端口不同(默认的 80 端口与 7001 端口) |
http://www.test.com:80/main.html | 否 | 同源(协议、域名、端口相同) |
由以上例子,我们应该大致明白了同源策略是怎么一回事儿了。
而不同源的请求去访问,就一种跨域,如下图所示。
总结的来说,由于浏览器安全限制,数据是不可以直接请求不同源的资源,包括不同的根域名、二级域名、或不同的端口,除非目标域名授权你可以访问。
那么,想要解决同源策略下的不同源文件访问,就需要跨域。
我这里只是介绍我工作中经历过的三种方式,详细教程参考阮一峰老师的文章:浏览器同源政策及其规避方法。
跨域
由上述的过程,我们已经讲明白了什么是同源策略,及同源策略造成的影响,这里就主要说一下我工作中的常用的四种跨域解决方式。
- JSONP跨域
- nginx配置
- webpack Server配置
- 后端接口请求头设置
JSONP是什么
JSONP本质是一种讨巧的解决方式。
早些年的时候,前端开发人员发现Web页面上调用js文件时则不受是否跨域的影响,不仅如此,有人还发现凡是拥有”src”这个属性的标签都拥有跨域的能力,比如script,img,iframe等标签。
而有一种叫做JSON的纯字符数据格式可以简洁的描述复杂数据,并被原生js支持。
所以实现跨域的方案就出现了。我们可以调用跨域服务器上动态生成的js格式文件,也就是调用JSON文件,获取自己需要的数据。
后来逐渐形成了一种非正式的传输协议,也就是JSONP。
该协议允许用户传递一个callback参数给服务端,然后服务端返回数据时会将这个callback参数作为函数名来包裹住JSON数据,客户端接收到响应后执行回调并且可以对数据进行各种需要的处理。
说到这里,我们来总结一下什么是JSONP:
1 | 通过动态创建script标签,其scr指向非同源的url,并传递一个callback参数给服务端,服务器返回一个以callback参数作为函数名的函数的调用和一系列参数,页面接收到响应后执行回调并对数据进行处理。 |
这里说起来不够明显, 我们直接看一下示例。
实现JSONP
首先,我们要有一个请求方和响应方。
请求方是一个网页的前端,也就是浏览器,响应方是另一个网页的后台,也就是服务器,两个网页是不同源的。
这里我们假设浏览器页面显示着一个数字100和一个打钱
的按钮,服务器储存着页面显示的那个数字,要求在每按下打钱
按钮的时候,浏览器显示的数字减一,并且服务端储存这个减后的数字,在下一次打开的时候显示减后的数字。这是一个跨域向非同源网站的服务器发送请求和接收响应的过程。
请求方:http://fang.com:8001
响应方:http://zeng.com:8002
先写好html:
1 | <html> |
接着就是写前端部分代码main.js,实现点击之后发送请求改变数字:
1 | button.addEventListener('click',(e)=>{ |
搭建好服务器,就可以实现功能了。这个就是一个利用JSONP发送跨域和响应的过程。
如果觉得不够详细,这里可以参考零寂前端的视频:从HTTP与Restfull到Ajax全方位掌握-前端开发-JavaScript,大佬在视频中很完美的演示了JSONP的跨域请求方式。
JSONP的优点与缺点
优点
- JSONP可以实现跨域传输,不像XMLHttpRequest对象实现的Ajax请求那样受到同源策略的限制
- JSONP兼容性更好,在更加古老的浏览器中都可以运行,不需要XMLHttpRequest或ActiveX的支持
- 在请求完毕后可以通过调用callback的方式回传结果。将回调方法的权限给了调用方
缺点
- 它只支持GET请求而不支持POST等其它类型的HTTP请求,因为script标签的scr只能进行GET请求
- 它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题。
- JSONP在调用失败的时候不会返回各种HTTP状态码。
- 缺乏安全性。假如提供JSONP的服务存在页面注入漏洞,即它返回的javascript的内容被人控制的,那么所有调用这个JSONP的网站都会存在漏洞,这样的话危险就不止在一个域名下。
- JSONP仍然需要改动服务端代码,写好回调函数,工作量较大,本质上是一种从客户端获取服务端回调函数的东西
Nginx
Nginx代理是指使用Nginx作为反向代理服务器,接收客户端发来的请求,然后将这些请求转发到其他服务器上进行处理,并将处理结果返回给客户端。
Nginx是一种高性能的Web服务器和反向代理服务器,因其性能优异、配置简单而被广泛应用于互联网领域。
这在工作中,也是较为常用的方式,往往开发服务器会配置这个来做好本地的接口联调。
实现
编辑 Nginx 的配置文件(nginx.conf
)。
1 | server { |
将 localhost:2222/api
路径下的请求转发到http://127.0.0.1:3000
来代理,并且添加响应头 Access-Control-Allow-Origin *
,设置白名单,允许跨域请求,这里类似 Cors 。
1 | const http = require('http'); |
后端提供数据
1 | <button id="btn">获取数据</button> |
前端向 http://localhost:2222/api
发请求,将被 Nginx 反向代理到 http://127.0.0.1:3000
所有工作准备完毕,点击按钮,拿到数据,成功解决跨域。
Webpack Server配置
这种方式不常见,但是偶尔有团队会在本地联调中用到,所以这里提一嘴。
大多数Vue项目的脚手架中,应该将webpack的配置集成到了vueConfig.js文件中,所以,大概的配置模式也是类似的。
譬如,只要按照如下配置,便可将路径为/api
的接口代理到http://localhost:3001
中,这样我们也能简单的实现一个本地的接口跨域。
1 | module.exports = { |
呼叫后端修改
这个是工作中的现在常用解决方式,难度低,工作量小,且没什么后遗症。
毕竟大多数工作中常用的后端框架都是springboot搭建的,所以往往只需要后端配置一下javax.servlet.Filter
即可。
当然,这里不清楚大家的工作框架,所以只是一家之言,如果有更好的解决方式还请细说,毕竟我不是专业的后端。
结语
作为面试八股文的经典问答,这个问题几乎是很多新手前端的必问题目,但是在实际开发的过程中,往往是后端只要设置一下就行了。
但是很多面试官还是拿着经典面试答案来套话,说实话,有种刻舟求剑的美。
总的来说,跨域的经典问题,前端开发工程师可以了解但不必强求,实际开发过程中,前端和跨域处理的距离已经相当之遥远。