10分钟了解跨域
2024-07-06 05:05:44

面试官:请问您能说一下跨域的解决办法吗?

我:这种事儿为什么要前端来做?难道不是后端设置一下就行了吗?

面试官:和您聊得很开心,请您回去等通知吧。

正文

在很早以前还是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
2
3
4
5
同源策略,它是由Netscape提出的一个著名的安全策略。
现在所有支持JavaScript 的浏览器都会使用这个策略。
所谓同源是指,域名,协议,端口相同。
当一个浏览器的两个tab页中分别打开百度和谷歌的页面,当一个百度浏览器执行一个脚本的时候会检查这个脚本是属于哪个页面的
即检查是否同源,只有和百度同源的脚本才会被执行。

总结,同源策略,是浏览器为了解决两个页面数据安全问题而提出的一种手段。

我们已经了解了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 同源(协议、域名、端口相同)

由以上例子,我们应该大致明白了同源策略是怎么一回事儿了。

而不同源的请求去访问,就一种跨域,如下图所示。

image-20240526165147032

总结的来说,由于浏览器安全限制,数据是不可以直接请求不同源的资源,包括不同的根域名、二级域名、或不同的端口,除非目标域名授权你可以访问。

那么,想要解决同源策略下的不同源文件访问,就需要跨域

我这里只是介绍我工作中经历过的三种方式,详细教程参考阮一峰老师的文章:浏览器同源政策及其规避方法

跨域

由上述的过程,我们已经讲明白了什么是同源策略,及同源策略造成的影响,这里就主要说一下我工作中的常用的四种跨域解决方式。

  1. JSONP跨域
  2. nginx配置
  3. webpack Server配置
  4. 后端接口请求头设置

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
2
3
4
5
6
<html>
<body>
<span>您的账户余额为:100</span>
<button></button>
</body>
</html>

接着就是写前端部分代码main.js,实现点击之后发送请求改变数字:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
button.addEventListener('click',(e)=>{
let script = document.createElement('script')// 创建script标签
let functionName = 'fang' + parseInt(Math.random()*10000000,10)// 设置调用函数名
window[functionName] = function(result){
if(result === 'success'){
amount.innerText = amount.innerText - 1
}
}
script.src = `http://zeng.com:8002/pay?callback${functionName} `
document.body.appendChild(script)// 将能实现发送跨域请求的script标签插入html
script.onload = function(e){
e.currentTarget.remove()
delete window[functionName]
}
script.onerror = function(){
alert('fail')
e.currentTarget.remove()
delete window[functionName]
}
// 完成传输后删除script标签
}
)

搭建好服务器,就可以实现功能了。这个就是一个利用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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
server {
listen 2222;
server_name localhost;

location / {
root html;
index index.html index.htm;
}

location /api {
proxy_pass http://127.0.0.1:3000;
add_header Access-Control-Allow-Origin *;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}

localhost:2222/api 路径下的请求转发到http://127.0.0.1:3000来代理,并且添加响应头 Access-Control-Allow-Origin *,设置白名单,允许跨域请求,这里类似 Cors 。

1
2
3
4
5
6
7
8
9
10
const http = require('http');
const server = http.createServer((req, res) => {
let data ={
msg:'Hello nginx-proxy'
}
res.end(JSON.stringify(data));
});
server.listen(3000,()=>{
console.log('Server is running on port 3000');
});

后端提供数据

1
2
3
4
5
6
7
8
9
10
11
<button id="btn">获取数据</button>
<script>
let btn = document.getElementById('btn');
btn.addEventListener('click',()=>{
fetch('http://localhost:2222/api')
.then(res=>res.json())
.then(data=>{
console.log(data);
})
})
</script>

前端向 http://localhost:2222/api 发请求,将被 Nginx 反向代理到 http://127.0.0.1:3000

所有工作准备完毕,点击按钮,拿到数据,成功解决跨域。

Webpack Server配置

这种方式不常见,但是偶尔有团队会在本地联调中用到,所以这里提一嘴。

大多数Vue项目的脚手架中,应该将webpack的配置集成到了vueConfig.js文件中,所以,大概的配置模式也是类似的。

譬如,只要按照如下配置,便可将路径为/api的接口代理到http://localhost:3001中,这样我们也能简单的实现一个本地的接口跨域。

1
2
3
4
5
6
7
8
9
10
11
12
13
module.exports = {
...
output: {...},
devServer: {
port: 3000,
proxy: {
"/api": {
target: "http://localhost:3001"
}
}
},
plugins: []
};

呼叫后端修改

这个是工作中的现在常用解决方式,难度低,工作量小,且没什么后遗症。

毕竟大多数工作中常用的后端框架都是springboot搭建的,所以往往只需要后端配置一下javax.servlet.Filter即可。

当然,这里不清楚大家的工作框架,所以只是一家之言,如果有更好的解决方式还请细说,毕竟我不是专业的后端。

结语

作为面试八股文的经典问答,这个问题几乎是很多新手前端的必问题目,但是在实际开发的过程中,往往是后端只要设置一下就行了。

但是很多面试官还是拿着经典面试答案来套话,说实话,有种刻舟求剑的美。

总的来说,跨域的经典问题,前端开发工程师可以了解但不必强求,实际开发过程中,前端和跨域处理的距离已经相当之遥远。

参考

JSONP原理详解——弄懂JSONP及其实现方法

JSONP 跨域原理及实现

面试官:你是如何解决跨域的?

实现跨域请求:Spring Boot后端的解决方案

一篇文章让你搞懂如何通过Nginx来解决跨域问题