2023-06-20 从根本理解浏览器的同源策略及跨域方案
前言
目前我们很多前端开发程序员在处理跨域问题的时候,大部分都不清楚跨域是怎么发生的,以及到底什么是跨域,本文将会一步一步的解开这些迷惑
跨域产生的前提原因
跨域这一问题其实都是浏览器搞出来的,脱离了浏览器的限制,根本不存在跨域问题。要知道,只有浏览器发出请求才会产生跨域问题,而两个服务端通信发出请求根本不会发生跨域,这是因为浏览器为了安全问题才搞出了同源策略
那什么是同源策略呢?
同源策略
浏览器出于安全考虑,对同源请求放行,简单的说,就是自己人;对异源请求限制即不是自己人的请求,这些限制规则统称为同源策略。因此限制造成的开发问题,称之为跨域(异源)问题
何为同源?
协议、域名和端口三个都一样的为同源,否则是异源
源1 | 源2 | 是否同源 |
---|---|---|
http://a.com:81/a | https://a.com/a | ❌ |
http://a.com:81/a | http://www.a.com:81/a | ❌ |
http://a.com:81/a | http://a.com:82/a | ❌ |
http://a.com:81/a | http://a.com:81/a/b | ✔ |
上述第一行是两个源的协议不同,是为异源;
第二行是域名不同,也为异源;
第三行是端口不同,异源;
最后一行是协议、域名和端口都相同,路径不同是为同源。
何为同源请求?
同源请求不止针对Ajax,对其他类型的如请求图片、JS文件、CSS等,只不过对Ajax的限制较为厉害而已
浏览器如何限制?
对标签元素发出的跨域请求轻微限制
对Ajax发出的跨域请求严厉限制
对Ajax的限制如图:
这里跨域的发生地方在收到响应时;浏览器是发出请求了的,同时服务端也接收到了请求,而且返回了响应,只不过这个响应被浏览器拦截了,因为有安全因素考虑,会对响应做出校验,通过了就没有跨域问题了,没有校验通过就会引发错误产生跨域问题了
比如在浏览器控制台发出一个异源请求,就会发生错误
跨域的三大基本解决方案
一是CORS(Cross-Origin-Resource Sharing)-跨域资源共享
如上述图所示,,跨域发生的原因就是在这个校验规则上,校验通过了就不会发生跨域问题,校验不通过就会引发跨域问题,跨域的校验规则就是CORS规则,也是跨域产生的最本质的地方
跨域的解决方法有很多种,但是CORS一定是最根本、最正统的方法
CORS是一套机制,用于浏览器校验跨域请求
它的基本理念是:
只要服务器明确表示允许,则校验通过
服务器明确拒绝或没有表示,则校验不通过
所以这里的解决主要靠服务器,还得保证服务器是自己人,能访问修改
CORS将请求分为两类:简单请求和预检请求
简单请求
-
请求方法为:GET、HEAD、POST
-
头部字段满足CORS安全规范,详见:MDN
-
请求头的Content-Type为:
- text/plain(表示请求体中是纯文本数据,可以用普通的纯文本编辑器查看与编辑)
- multipart/form-data(表示请求体中包含多种数据类型,如文件上传等,通常用于表单提交)
- applocation/x-www-form-urlencoded(表示请求体中包含键值对数据,以键值对的形式拼接在URL后面发送请求,也可放在请求体中)
这里注意只要不去改动请求头部字段,则基本都是满足安全规范
预检请求
既是非简单请求
举两个例子:
fetch('https://douyin.com')
这是个简单请求
fetch('https://douyin.com', {headers:{a:1}})
这是预检请求,因为改了头部字段
知道了简单请求和预检请求后,那么浏览器遇到这两种请求是如何校验的呢?
针对简单请求和预检请求的校验
简单请求:
这里浏览器发现是简单请求后,会自动在请求头headers中添加Origin这个字段,表示是来自哪个源的请求,然后服务器一般会在返回响应头中添加Access-Control-Allow-Origin字段,如果值和请求源一样,浏览器一看表示允许这个源通过,则校验通过;或者值为*
号,表示所有源都可以通过,那浏览器一看不管了,都校验通过
预检请求:
当浏览器发现是预检请求后,会先发出一个OPTIONS
的预检请求,请求头中带有Origin-请求源地址,Access-Control-Request-Method-请求方法和Access-Control-Request-Headers-这次请求改动的头部字段等,服务器收到后会返回响应头字段有允许的源地址、允许的方法、允许的头部headers和最大缓存时间,下次这个时间范围内直接使用缓存
当这个OPTIONS请求通过后才会发出真实的请求和简单请求处理一模一样
二是古老的JSONP方案
在没有CORS方法的年代,JSONP就是最佳方法
在同源策略中,对标签的跨域请求限制较小,JSONP就利用了这一点
与Ajax毫无关系,要求服务器响应一段JS代码,举个例子如下:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<title>跨域方法之JSONP</title>
</head>
<body>
<button>点击获取用户</button>
<script>
function callback(resp) {
console.log(resp);
}
function request(url) {
const script = document.createElement("script");
script.src = url;
script.onload = function () {
script.remove();
};
document.body.appendChild(script);
}
document.querySelector("button").onclick = function () {
request("http://localhost:8001/jsonp?callback=callback");
};
</script>
</body>
</html>
用node写一个简单的服务器
const express = require("express");
const app = express();
app.get("/jsonp", (req, res) => {
const { callback } = req.query;
res.send(`${callback}("这是后台响好数据")`);
});
app.listen(8001, () => {
console.log("欢迎访问http://localhost:8001");
});
点击按钮请求不是Ajax而是一个js
注意:JSONP有个致命的缺陷,因为其是标签发出请求,只能是get请求,而如post等方式的请求处理不了
三是跨域方案之代理
上述的两个方案CORS和JSONP均对服务器有要求
简单来说就是要求服务器是自己人,一旦不是自己人,无法修改服务器则这两种方案都失效了
这时候就需要用到代理方案了,找一个中间代理人
这里浏览器要想请求不是自己人的服务器,则首先把请求地址发送到自己人的服务器上,自己人的服务器再把这个请求转发到目标服务器上,而两个服务器之间的请求是没有跨域的,脱离了浏览器的范畴了,所以自己人服务器正常接收到目标服务器的响应,再把这个响应转发给浏览器,这时候的跨域问题就非常简单明了了用CORS或者JSONP都可以
下面是一个例子:本地请求王者荣耀的接口
肯定是跨域的了,但是其服务器不是自己人,不能改,这时候前端可以用node写一个代理服务器
首先写一个node服务器来验证服务器之间的请求是不存在跨域问题的
然后只要把这段代码运用自己人的服务器上并且用CORS方案即可
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>跨域方案之代理</title>
</head>
<body>
<button>点击请求代理服务器</button>
<script>
const btn = document.querySelector("button");
btn.onclick = function () {
fetch("http://localhost:9100/hero")
.then(resp => resp.json())
.then(res => {
console.log(res);
});
};
</script>
</body>
</html>
const axios = require("axios");
const express = require("express");
const app = express();
// get请求
app.get("/hero", async (req, res) => {
// 请求王者荣耀的接口
const resp = await axios.get("https://pvp.qq.com/web201605/js/herolist.json");
// 使用CORS解决代理服务器的跨域
res.header("Access-Control-Allow-Origin", "*");
// 转发响应
res.send(resp.data);
});
// 监听
app.listen(9100, () => {
console.log("代理服务器启动了, http://localhost:9100");
});
跨域方案的总结
有如下决策图:
当遇到服务器不是自己人不能更改服务器的时候,就只能用代理方法了;
当是自己人但是古老的浏览器不支持CORS的话就选择JSNOP方法,剩下的最佳方法是CORS。
一个经典的开发场景:
公司中开发项目,服务端的部署图:
上图中的nginx服务器一般不提供CORS和JSONP
生产环境下:
`http://a.com/index.html` 请求的HTML页面
`http://a.com/api/movie` 请求的接口
二者之间不存在跨域问题,同源
开发环境下:
`http://localhost:8080/index.html` HTML页面
`http://api.a.com.api/movie` 接口
异源请求,跨域了
这时候要用代理了,如dev-server启动本地服务器用作转发代理,原理同CORS
`http://localhost:8080/api/movie` 接口
让代理服务器与nginx服务器交互即可
完结撒花~~
转载自:https://juejin.cn/post/7248109163850104889