跨域问题
- 1.什么是跨域问题
- 2.浏览器的同源策略
- 3.浏览器会对跨域做出哪些限制
- 1. 限制访问DOM
- 2. 限制访问Cookie
- 3. 限制Ajax响应数据
- 4.解决跨域问题的方式有哪些
- 1. 代理服务器
- 2. CORS
- 3. JSONP
- 5.什么是CORS(跨域资源共享)
- 1. 概念
- 2. 请求分类
- 3. 请求类型判断
- 6. 简单示例
1.什么是跨域问题
- 跨域是源于源之间进行资源交互(从一个源访问另一个源的资源)
- 跨域问题是,受浏览器同源策略影响,导致客户端无法正常访问服务端的资源
- 同源策略是浏览器的一种安全策略,它用于防止不同源之间的恶意行为
大多数情况,跨域问题指的是前后端无法正常进行资源交互。原因有两条:
- 浏览器限制了脚本内发起的跨源 HTTP 请求。例如,XMLHttpRequest 和 Fetch API 会遵循同源策略,这些API只能请求同源的HTTP资源,而不能请求异源的 HTTP 资源,除非响应报文包含了正确 CORS 响应头。
- 现在大多都是前后端分离项目,前端与后端之间属于异源。
受浏览器同源策略影响,导致我们无法正常跨源进行资源交互
2.浏览器的同源策略
- 同源策略是一个重要的安全策略
- 它用于限制一个源的文档或者它加载的脚本如何能与另一个源的资源进行交互
它能帮助阻隔恶意文档,减少可能被攻击的媒介。例如,它可以防止互联网上的恶意网站在浏览器中运行 JS 脚本,从第三方网络邮件服务(用户已登录)或公司内网(因没有公共 IP 地址而受到保护,不会被攻击者直接访问)读取数据,并将这些数据转发给攻击者。
帮助阻隔恶意文档,防止访问网站(恶意网站)时该网站使用js从其它网站读取数据。
(如果我随便访问一个网站,该网站就能够读取到我们的网页版qq邮箱、网页版微信等的数据,那就太危险了。)
3.浏览器会对跨域做出哪些限制
1. 限制访问DOM
脚本不能读取和操作非同源的DOM。
2. 限制访问Cookie
不能访问非同源的cookie
异源的document都无法读取,更别说读取异源的document.cookie。
3. 限制Ajax响应数据
- 可以跨源发送ajax请求,异源服务器也可以正常响应数据
- 响应数据后,浏览器会进行校验
我们可以在浏览器端向异源服务器发送ajax请求,异源服务器也可以正常响应数据。
但是浏览器拿到响应数据后并不会直接给我们,而是先进行校验。
只有在浏览器校验通过后,我们才能拿到响应数据。
(可以通过cors控制校验是否通过)
4.解决跨域问题的方式有哪些
1. 代理服务器
- 为什么可以解决
只要没有跨域,就不存在跨域问题
跨域问题的出现是因为有跨域,没有跨域便没有跨域问题。
- 如何实现
- 为脚手架(vite、webpack)的开发服务器配置代理(注:仅开发阶段)
- 使用node服务器配置代理
- 使用nginx配置代理
// 1. 在vite.config.js中配置代理(注:仅开发阶段)
{
server: {
proxy: {
'/api': {
target: 后端(目标)服务器地址,
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
}
},
}
// 2. 在node静态服务器中配置代理
const { createProxyMiddleware } = require('http-proxy-middleware');
app.use('api',createProxyMiddleware({
target:后端(目标)服务器地址,
changeOrigin: true,
pathRewrite:{ '^api':'' }
}))
// 3. 使用nginx配置代理
// 移出api
location /dev/ {
proxy_pass 后端(目标)服务器地址/
}
// 或
// 不移出api
location /api{
proxy_pass 后端(目标)服务器地址
}
- 跨域问题(同源策略)是浏览器对ajax请求的限制
- 而服务器与服务器之间不存在这种限制
- 我们可以将静态资源放在代理服务器上
- 那么我们与代理服务器之间就是同源的
- 我们与代理服务器之间不存在跨域问题
- 所以可以将请求发给我们的代理服务器
- 再让代理服务器帮忙转发给后端服务器
- 代理服务器拿到响应数据后再响应给我们即可
2. CORS
- 为什么可以解决
因为CORS能够控制浏览器的校验结果,它是最正统的解决方案,官方明确说明可以使用cors允许跨域访问。 - 如何实现
- 让服务端(后端服务器)支持CORS
- 搭建一个中间服务器
// 1. 让服务端(后端服务器)支持CORS
const express = require('express');
const cors = require('cors');
const PORT = 8080;
const app = express();
app.use(cors({
origin:['http://localhost:5173','http://example1.com', /\.example2\.com$/],
methods:['PUT', 'GET', 'A'],
allowedHeaders:['Content-Type', 'Authorization','abc'],
maxAge:7200
}));
//或
// ...
// response.setHeader('Access-Control-Allow-Origin', 'http://localhost:5173');
// response.setHeader('Access-Control-Allow-Methods', 'PUT', 'GET', 'A');
// response.setHeader('Access-Control-Allow-Headers', ['Content-Type', 'Authorization','abc']);
// response.setHeader('Access-Control-Max-Age', 7200);
// ...
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
})
// 2. 搭建一个中间服务器
//虽然可以解决跨域问题,但是应该不会有人这么做,这么做应该还不如直接让服务端支持CORS
// 客户端
fetch('中间服务器地址/api',{
headers:{abc:'ttt'}
})
// 中间服务器
const express = require('express');
const cors = require('cors');
const PORT = 8080;
const app = express();
app.use(cors({
origin:['http://localhost:5173','http://example1.com', /\.example2\.com$/],
methods:['PUT', 'GET', 'A'],
allowedHeaders:['Content-Type', 'Authorization','abc'],
}));
app.use('/api',(request,response)=>{
// 1. 根据request获取必要信息
// 2. 向真正的服务端发送请求
// 2. 拿到响应数据后再响应给客户端
})
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
})
3. JSONP
- 为什么可以解决
浏览器对ajax的跨域限制很严格,但是对script、link、img等标签的跨域限制很宽容,所以可以使用script加载异源脚本实现解决跨域,缺点是script标签只能发送get请求。 - 如何实现
- jquery实现
- 手写
// 1. jquery实现
$.getJSON('协议://主机:端口/test?callback=?',(data)=>{
console.log(data)
})
// 2. 手写
const scriptJSON = (url) => {
const script = document.createElement('script');
script.onload = () => script.remove();
script.src = url;
document.body.appendChild(script);
}
const handleTest= (data) => {
consloe.log(data)
};
scriptJSON('协议://主机:端口/test?callback=handleTest');
5.什么是CORS(跨域资源共享)
1. 概念
cors是浏览器用于校验跨域请求的一套规范
服务器可以依照CORS规范添加特定响应头,从而达到控制浏览器校验结果的目的。
规则如下:
- 服务器明确表示允许跨域请求,则浏览器校验通过
- 服务器明确表示拒绝跨域请求或没有表示,则浏览器校验不通过
2. 请求分类
跨域请求可以分为简单请求和复杂请求
-
简单请求(不会触发CORS 预检请求的跨域请求)
-
流程
js指挥浏览器发送跨域请求,浏览器直接发送该请求
浏览器收到响应数据后,开始进行校验
若校验通过,则正常交付数据
若校验不通过,则js抛出异常 -
控制校验结果的关键响应头
Access-Control-Allow-Origin:* | <origin> | null- *通配符的意思,任何来源的简单请求都会校验通过
- <origin>源(协议://主机名:端口),只能指定一个,如果来源与该源相等,则校验通过,否则校验不通过
- null如果来源是null则校验通过,否则校验不通过(如:使用非分级协议(如 data: 或 file:)的资源和沙盒文件的 Origin 就是null)
注:若没有设置响应头Access-Control-Allow-Origin,则代表服务器没有表示允许哪些源可以访问资源,此时校验不通过
-
-
复杂请求(会触发CORS 预检请求的跨域请求)
-
流程
js指挥浏览器发送ajax跨域请求,浏览器先发送一个预检请求(OPTIONS请求)
浏览器收到预检请求的响应数据后,开始进行校验
若校验通过,则发送实际请求
若校验不通过,则js抛出异常
-
(浏览器自动发送的)预检请求携带的关键请求头
- Origin
- Access-Control-Request-Method
- Access-Control-Request-Headers
它们的含义分别是:请求源、请求方法、自定义请求头
注:如果没有自定义标头,浏览器就不会携带Access-Control-Request-Headers
-
控制校验结果的关键响应头
- Access-Control-Allow-Origin
- Access-Control-Allow-Methods
- Access-Control-Allow-Headers
它们的含义分别是:允许的请求源、允许的请求方法、允许的自定义请求头
请求方法和自定义请求头可以指定多个,以英文逗号隔开注:有一个辅助的响应头Access-Control-Max-Age,表示能将预检请求的结果缓存多长时间,单位:秒
-
3. 请求类型判断
- 简单请求
- 请求方法必须是GET、HEAD、POST三种之一
- 除了自动设置的标头字段,只有CORS安全表头字段可以人为设置
- 媒体类型必须是text/plain、multipart/form-data、application/x-www-form-urlencoded三种之一
- 不能在XMLHttpRequest实例对象的upload属性上注册任何事件监听器
- 不能使用 ReadableStream 对象
- 复杂请求
- 只要不是简单请求,便是复杂请求
- 简单请求必须满足5个条件,分别用来限制:请求方法、请求头、媒体类型、XMLHttpRequest、ReadableStream对象
- 在跨域请求中,除了简单请求,其余的都是复杂请求
- 简单请求具体条件是:
- 请求方法必须是GET、HEAD、POST三种之一
- 请求头字段必须满足CORS安全规范,一般只要不手动修改请求头便可以
- Content-Type的值必须是text/plain、multipart/form-data、application/x-www-form-urlencoded三种之一
- 不能在XMLHttpRequest实例对象的upload属性上注册任何事件监听器
- 不能使用 ReadableStream 对象
6. 简单示例
前端开发常用代理解决跨域问题
- 前端
- 后端
- 结果