说明:此文为视频笔记,转自他人,只为个人复习使用

视频链接:禹神:一小时彻底搞懂跨域&解决方案

原文链接: 跨域&解决方案

1浏览器的同源策略

1.1同源策略概述

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

1.2什么是源(origin)?

1源的组成部分

image.png

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

源 1 源 2 是否同源
http://www.xyz.com/home https://www.xyz.com/home ⛔非同源️
http://www.xyz.com/home http://mail.xyz.com/home ⛔非同源
http://www.xyz.com:8080/home http://www.xyz.com:8090/home ⛔非同源
http://www.xyz.com:8080/home http://www.xyz.com:8080/search ✅同 源︎

3同源请求

image.png

4非同源请求

image.png

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

2跨域会受到哪些限制

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

2.1限制DOM访问

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

1
2
3
4
5
6
7
8
<!-- <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>

2.2限制Cookie访问

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

1
2
3
4
5
<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 请求也可以正常发出,但响应数据不会交给开发者。
3<link>、<script>、<img>…… 这些标签发出的请求也可能跨域,只不过浏览器对标签跨域不做严格限制,对开发几乎无影响

image.png

4CORS 解决 Ajax 跨域问题

4.1CORS 概述

CORS 全称:Cross-Origin Resource Sharing(跨域资源共享),是用于控制浏览器校验跨域请求的一套规范,服务器依照 CORS 规范,添加特定响应头来控制浏览器校验,大致规则如下:
●服务器明确表示拒绝跨域请求,或没有表示,则浏览器校验不通过。
●服务器明确表示允许跨域请求,则浏览器校验通过。
备注说明:使用 CORS 解决跨域是最正统的方式,且要求服务器是“自己人”。

4.2CORS 解决简单请求跨域

整体思路:服务器在给出响应时,通过添加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 会把请求分为两类,分别是:① 简单请求、② 复杂请求。

简单请求 复杂请求
✅请求方法(method)为:GET、HEAD、POST 1不是简单请求,就是复杂请求。
2复杂请求会自动发送预检请求。
✅请求头字段要符合《CORS 安全规范》
简记:只要不手动修改请求头,一般都能符合该规范。
✅请求头的Content-Type的值只能是以下三种:
●text/plain
●multipart/form-data
●application/x-www-form-urlencoded

关于预检请求

  1. 发送时机:预检请求在实际跨域请求之前发出,是由浏览器自动发起的。
  2. 主要作用:用于向服务器确认是否允许接下来的跨域请求。
  3. 基本流程:先发起OPTIONS请求,如果通过预检,继续发起实际的跨域请求。
  4. 请求头内容:一个OPTIONS预检请求,通常会包含如下请求头:
请求头 含义
Origin 发起请求的源
Access-Control-Request-Method 实际请求的 HTTP 方法
Access-Control-Request-Headers 实际请求中使用的自定义头(如果有的话)

4.4CORS 解决复杂请求跨域

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

响应头 含义
Access-Control-Allow-Origin 允许的源
Access-Control-Allow-Methods 允许的方法
Access-Control-Allow-Headers 允许的自定义头
Access-Control-Max-Age 预检请求的结果缓存时间(可选)

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中间件

默认js是不能访问后端设置的响应头的,需要后端暴露

5 JSONP 解决跨域问题

1 JSONP 概述:

JSONP 是利用了

5jQuery 封装的 jsonp

?callback=?' 为固定格式 会自动解析

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

6配置代理解决跨域

6.1自己配置代理服务器

服务器之间是没有跨域问题的,要使用express 启动静态资源保证自己的服务器跟页面在同源下

1
2
3
4
5
6
7
8
9
10
11
12
// 启动静态资源 让服务器跟页面同一个源
app.use(express.static("./public"));
借助http-proxy-middleware配置代理

const { createProxyMiddleware } = require('http-proxy-middleware');

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

优点:

  • 功能丰富:http-proxy-middleware提供了丰富的配置选项,可以满足各种代理需求。
  • 可以灵活配置多个代理:可以配置多个代理服务器,分别对应不同的接口路径。
  • 可以拦截请求:可以通过自定义的处理函数对请求进行拦截和修改。

缺点:

  • 配置相对复杂:需要了解http-proxy-middleware库的配置规则和参数。
  • 不适用于生产环境:http-proxy-middleware主要用于开发环境,不适用于生产环境。

使用场景:

  • 适用于使用任何构建工具的前端项目,可以与任何开发服务器配合使用。
  • 适用于需要灵活配置多个代理服务器的场景。
  • 适用于需要对请求进行拦截和修改的场景。

6.2使用 Nginx 搭建代理服务器

整体思路:让nginx充当两个角色,既是 静态内容服务器,又是代理服务器。

修改nginx配置如下,注意nginx的根目录最好不是 C 盘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 配置nginx根目录

location / {
root D:\dist;
index index.html index.htm;
}

# 配置代理

location /dev/ {

# 设置代理目标

proxy_pass http://sph-h5-api.atguigu.cn/;
}

2修改前端项目,让所有请求都转发给 /dev,随后重新打包

1
2
3
4
const request = axios.create({
baseURL:'/dev',
timeout:10000
})

随后直接访问nginx服务器即可,例如 nginx如果运行在8099端口,则访问

1
http://localhost:8099

随后会遇到刷新404问题,追加nginx配置来解决

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 配置nginx根目录

location / {
root D:\dist;
index index.html index.htm;
try_files $uri $uri/ /index.html; # 解决刷新404
}

# 配置代理

location /dev/ {

# 设置代理目标

proxy_pass http://sph-h5-api.atguigu.cn/;
}

加上这两个“/”就剔除掉了dev

img

6.3借助脚手架搭建服务器

如使用vue.config.js文件配置代理:

在Vue项目的根目录下创建一个vue.config.js文件,并添加以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://api.example.com',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
}
}

上述代码中,我们使用devServer配置项来配置代理服务器。其中proxy属性用于配置代理的规则,/api表示需要代理的接口路径。target属性表示代理的目标服务器地址,changeOrigin属性表示是否改变请求的源地址,pathRewrite属性用于重写请求的路径。

优点:

  • 配置简单:使用webpack-dev-server的代理配置,只需要在webpack配置文件中进行简单的配置即可。
  • 功能全面:webpack-dev-server提供了丰富的配置选项,可以满足大部分代理需求。
  • 可以拦截请求:可以通过自定义的处理函数对请求进行拦截和修改。

缺点:

  • 需要重启服务器:配置修改后需要重新启动webpack-dev-server才能生效。
  • 不适用于生产环境:webpack-dev-server主要用于开发环境,不适用于生产环境。

使用场景:

  • 适用于使用webpack构建的前端项目,通过webpack-dev-server来启动开发服务器的场景。
  • 适用于需要简单的代理配置,并且不需要频繁修改代理配置的场景。