本文参考阮一峰老师的 浏览器同源政策及其规避方法
什么是跨域?
在实际的开发工作中,我们经常会有跨域请求服务器数据的情况,经常碰到跨域问题需要处理,那么什么是跨域呢?接下来我们好好聊一聊。
浏览器的同源策略
跨域是因为浏览器的同源策略。浏览器安全的基石是"同源政策",所谓"同源"指的是"三个相同":协议 - 域名 - 端口
都相同才算同源,比如说有个地址 clerverSnnail.cn:3000 ;https就是协议号,://clerverSnnail就是域名,:3000端口号,其中有一个不相同那就是跨域
这里我们先做个简单的demo领略一下浏览器的同源策略,首先前端代码和后端代码都创建一份
client文件夹里的index.html写前端,引入jquery,使用ajax方法朝http://localhost:3000 这个地址发接口请求获取数据,代码如下:
js
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<body>
<button id="btn">获取数据</button>
<script>
let btn = document.getElementById('btn');
btn.addEventListener('click', () => {
$.ajax({
url: 'http://localhost:3000',
method: 'get', //请求的方式get
data:{ //向后端传递的参数
name: '二蛋'
},
success(res){
console.log(res); //请求到东西便会打印
}
})
})
</script>
</body>
server文件夹,使用终端打开,输入 npm init -y 初始化文件夹,npm i koa 安装koa,借助node框架koa在index.js中写后端,代码如下:
js
const Koa = require('koa');
const app = new Koa()
const main = (ctx) => {
ctx.body = 'hello world' //向前端输出的内容
}
app.use(main)
app.listen(3000, () => {
console.log('项目已启动');
})
运行前后端代码,当我们点击获取数据按钮,前端就会朝http://localhost:3000 发接口请求获取数据,那么这时候我们能拿到后端给的'hello word'吗?让我们看看
好的报了个经典错误,浏览器的同源策略不允许我在://loacalhost:5500的域名去请求://loacalhost:3000的数据,这就出现了跨域问题。
那么我们怎么解决这个问题呢?接下来我们来聊一聊
跨域的解决方案:
1. JSONP
jsonp(JSON with Padding),是JSON的一种 "使用模式",可以让网页跨域读取数据,其本质是利用script标签的开放策略。
(1)前端创建一个srcipt标签, 借助该标签的src属性朝后端发送请求,且前端在window上声明一个函数(callback),并将该函数名拼接在src属性的路径后面作为参数传递给后端
(2)后端接收到前端的请求且获取到前端传递过来的函数名,将需要响应给前端的数据拼接在该函数的调用中(相当于作为实参传递)
(3)前端接收到后端的响应,相当于在执行window上声明的函数(callback),遂该函数的参数就是后端响应回来的数据
缺点:
(1)需要后端配合
(2)只适用于GET请求(因为浏览器加载script的src属性默认就是GET请求,无法修改)
下面我们用代码实现一下,前端在上面dome的基础上index.html中做个简单的封装,使用Promise包裹一层,我希望在jsonp函数调用的时候后面能接.then
js
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<body>
<button id="btn">获取数据</button>
<script>
function jsonp(url, cb) {
return new Promise((resolve, reject) => {
const script = document.createElement('script') //创建script标签
script.src = `${url}?cb=${cb}` // http://localhost:3000?cb=xxx
document.body.appendChild(script)
// 拿到了后端返回的一个函数
window[cb] = (data) => {
// 操作后端携带过来的数据
resolve(data)
}
})
}
//当我们点击获取数据按钮的时候,就会调用jsonp(),朝地址发起请求
let btn = document.getElementById('btn')
btn.addEventListener('click', () => {
jsonp('http://localhost:3000', 'callback').then(res => {
console.log(res);
})
})
</script>
</body>
那么前端借助script标签请求回来的数据怎么拿到页面上用呢?这时候就需要后端配合了
js
const Koa = require('koa')
const app = new Koa()
//假设data是需要给前端的数据
const data = {
name: '二蛋',
age: 18
}
const main = (ctx) => {
const { cb } = ctx.query
// 创建一个字符串,就是callback函数的调用
const str = `${cb}(${JSON.stringify(data)})` //'callback({name: '二蛋',age: 18})'
ctx.body = str //浏览器读到这个字符串会直接读成代码的格式
}
app.use(main)
app.listen(3000, () => {
console.log('jsonp项目已启动');
})
运行起来,点击按钮,这样就可以不触发同源策略拿到后端给的数据了
2. CORS
跨域资源共享(Cross-origin resource sharing),相比JSONP只能发GET请求,CORS允许任何类型的请求,通过设置响应头,告诉浏览器不需要走同源策略的保护机制。
回到demo中的代码,回到最初的样子,前端代码不变,后端在server文件夹打开终端,npm i @koa/cors 安装cors
js
const Koa = require('koa');
const app = new Koa()
const cors = require('@koa/cors') //引入安装好的cors
app.use(cors()) //
const main = (ctx) => {
ctx.body = 'hello world' //向前端输出的内容
}
app.use(main)
app.listen(3000, () => {
console.log('项目已启动');
})
运行前后端,点击获取数据按钮,这样我们就直接获取到了后端传过来的数据
哇这是不是简直不要太简单,仅仅是增加了两行代码就解决了跨域问题,但是这个方法并不优雅,如果在线上环境就这样使用的话,那么是不是谁都可以访问这个数据了?所以这样只适合在开发环境下使用,但是也有弥补措施:
js
app.use(cors({
origin: 'http://127.0.0.1:5500' //设置只允许这个源访问
}))
上面这个是借助别人写的插件来解决的,那么我们看看原生node怎么用cors,需要服务器配置Access-Control-Allow-Origin
头信息,下面看看是如何实现的:
js
const http = require('http')
const server = http.createServer((req, res) => {
res.writeHead(200, {
"Access-Control-Allow-Origin": '*' // 被允许的源 *号就是所有的源
})
res.end('hello cors')
})
server.listen(3000)
关于CORS更详细的介绍可参考阮一峰老师的 跨域资源共享CORS详解
方法先介绍到这里,后续方法还会持续更新...