尚硅谷视频:禹神:一小时彻底搞懂跨域&解决方案

尚硅谷文档:跨域问题梳理

1、浏览器的同源策略

1.1、同源策略概述

同源策略是 浏览器 为确保 资源安全 ,而遵循的一种策略,该策略对 访问资源 进行了一些限制。

W3C 上对同源策略的说明:Same origin policy

1.2、什么是源(origin)?

1、 源的组成部分

image.png

2、下面表格中,只有最后一行的两个源是同源。
image-20240726070930616

3、同源请求

image-20240726071249231

4、非同源请求

image-20240726071304741

总结:『所处源』与『目标源』不一致,就是『非同源』,又称『异源』或『跨域』

2、跨域会受到哪些限制

例如有两个源:『源A』和『源B』,它们是『非同源』的,那么 浏览器 会有如下限制:

2.1、限制DOM访问

『源A』的脚本 不能访问『源B』的 DOM。

1
2
3
4
5
6
7
8
9
<!-- <iframe id="framePage" src="./demo.html"></iframe> -->
<iframe id="framePage" src="https://www.baidu.com"></iframe>

<script type="text/javascript" >
function showDOM(){
const framePage = document.getElementById('framePage')
console.log(framePage.contentWindow.document) //同源的可以获取,非同源的无法获取
}
</script>

『源A』不能访问『源B』的 cookie。

1
2
3
4
5
6
<iframe id="baidu" src="http://www.baidu.com" width="500" height="300"></iframe>

<script type="text/javascript" >
// 访问的是当前源的cookie,并不是baidu的cookie
console.log(document.cookie)
</script>

2.3、限制 Ajax 获取数据

『源A』可以给『源B』发请求,但是 无法获取『源B』响应的数据。

1
2
3
4
const url = 'https://www.toutiao.com/hot-event/hot-board/?origin=toutiao_pc'
let result = await fetch(url)
let data = await result.json();
console.log(data)

备注:在上述限制中,浏览器对 Ajax 获取数据的限制是影响最大的一个,且实际开发中经常遇到。

3、几个注意点

1、跨域限制仅存在浏览器端,服务端不存在跨域限制。
2、即使跨域了,Ajax 请求也可以正常发出,但响应数据不会交给开发者。

image.png

3、<link><script><img> …… 这些标签发出的请求也可能跨域,只不过浏览器对标签跨域不做严格限制,对开发几乎无影响。

4、CORS 解决 Ajax 跨域问题

4.1、CORS 概述

CORS 全称:Cross-Origin Resource Sharing(跨域资源共享),是用于控制 浏览器校验 跨域请求的一套规范,服务器依照 CORS 规范,添加特定 响应头 来控制浏览器校验,大致规则如下:

  • 服务器明确表示 拒绝跨域 请求,或 没有表示 ,则浏览器校验 不通过
  • 服务器明确表示 允许跨域 请求,则浏览器校验 通过

备注说明:使用 CORS 解决跨域是最正统的方式,且要求服务器是“自己人”。

4.2、CORS 解决简单请求跨域

整体思路:服务器在给出响应时,通过添加Access-Control-Allow-Origin响应头,来明确表达允许某个源发起跨域请求,随后浏览器在校验时,直接通过。

image.png

服务端核心代码(以express框架为例):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 处理跨域中间件
function corsMiddleWare(req,res,next){
// 允许 http://127.0.0.1:5500 这个源发起跨域请求
// res.setHeader('Access-Control-Allow-Origin','http://127.0.0.1:5500')

// 允许所有源发起跨域请求
res.setHeader('Access-Control-Allow-Origin','*')
next()
}

// 配置路由并使用中间件
app.get('/',corsMiddleWare,(req,res)=>{
res.send('hello!')
})

4.3、简单请求与复杂请求

CORS 会把请求分为两类,分别是:① 简单请求、② 复杂请求。

image-20240726073429176

关于预检请求:

  1. 发送时机:预检请求在实际 跨域请求 之前发出,是由 浏览器自动发起 的。

  2. 主要作用:用于向服务器 确认 是否允许接下来的跨域请求。

  3. 基本流程:先发起OPTIONS请求,如果 通过 预检, 继续发起 实际的跨域请求。

  4. 请求头内容:一个OPTIONS预检请求,通常会包含如下 请求头

    image-20240726073526311

4.4、CORS 解决 复杂请求 跨域

1、第一步:服务器先 通过 浏览器的 预检请求,服务器需要返回如下 响应头

image-20240726074045507

image.png

2、第二步:处理实际的 跨域请求(与处理简单请求跨域的方式相同)

image.png

服务端核心代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 处理预检请求
app.options('/students', (req, res) => {
// 设置允许的跨域请求源
res.setHeader('Access-Control-Allow-Origin', 'http://127.0.0.1:5500')
// 设置允许的请求方法
res.setHeader('Access-Control-Allow-Methods', 'GET')
// 设置允许的请求头
res.setHeader('Access-Control-Allow-Headers', 'school')
// 设置预检请求的缓存时间(可选)
res.setHeader('Access-Control-Max-Age', 7200)
// 发送响应
res.send()
})

// 处理实际请求
app.get('/students', (req, res) => {
// 设置允许的跨域请求源
res.setHeader('Access-Control-Allow-Origin', 'http://127.0.0.1:5500')
// 随便设置一个自定义响应头
res.setHeader('abc',123)
// 设置允许暴露给客户端的响应头
res.setHeader('Access-Control-Expose-Headers', 'abc')
// 打印请求日志
console.log('有人请求/students了')
// 发送响应数据
res.send(students)
})

4.5、 借助 cors 库快速完成配置

上述的配置中需要自己配置响应头,或者需要自己手动封装中间件,借助cors库,可以更方便完成配置\

安装cors

1
npm i cors

简单配置cors

1
app.use(cors())

完整配置cors

1
2
3
4
5
6
7
8
9
10
// cors中间件配置
const corsOptions = {
origin: 'http://127.0.0.1:5500', // 允许的源
methods: ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS'], // 允许的方法
allowedHeaders: ['school'], // 允许的自定义头
exposedHeaders: ['abc'], // 要暴露的响应头
optionsSuccessStatus: 200 // 预检请求成功的状态码
};

app.use(cors(corsOptions)); // 使用cors中间件

5、JSONP 解决跨域问题

1、JSONP 概述: JSONP 是利用了<script>标签可以跨域加载脚本,且不受严格限制的特性,可以说是程序员智慧的结晶,早期一些浏览器不支持 CORS 的时,可以靠 JSONP 解决跨域。
2、基本流程:

  • 第一步:客户端创建一个<script>标签,并将其src属性设置为包含跨域请求的 URL,同时准备一个回调函数,这个回调函数用于处理返回的数据。
  • 第二步:服务端接收到请求后,将数据封装在回调函数中并返回。
  • 第三步:客户端的回调函数被调用,数据以参数的形势传入回调函数。

3、图示

image.png

4、代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<button onclick="getTeachers()">获取数据</button>

<script type="text/javascript" >
function callback(data){
console.log(data)
}

function getTeachers(url){
// 创建script元素
const script = document.createElement('script')
// 指定script的src属性
script.src= 'http://127.0.0.1:8081/teachers'
// 将script元素添加到body中触发脚本加载
document.body.appendChild(script)
// script标签加载完毕后移除该标签
script.onload = ()=>{
script.remove()
}
}
</script>

5、jQuery 封装的 jsonp

1
2
3
$.getJSON('http://127.0.0.1:8081/teachers?callback=?',(data)=>{
console.log(data)
})

6、配置代理解决跨域

6.1、自己配置代理服务器

借助http-proxy-middleware配置代理

1
2
3
4
5
6
7
8
9
const { createProxyMiddleware } = require('http-proxy-middleware');

app.use('/api',createProxyMiddleware({
target:'https://www.toutiao.com',
changeOrigin:true,
pathRewrite:{
'^/api':''
}
}))

6.2、使用 Nginx 搭建代理服务器

6.3、借助脚手架搭建服务器