跨域的几种解决方法

本文参考阮一峰老师的 浏览器同源政策及其规避方法

什么是跨域?

在实际的开发工作中,我们经常会有跨域请求服务器数据的情况,经常碰到跨域问题需要处理,那么什么是跨域呢?接下来我们好好聊一聊。

浏览器的同源策略

跨域是因为浏览器的同源策略。浏览器安全的基石是"同源政策",所谓"同源"指的是"三个相同":协议 - 域名 - 端口 都相同才算同源,比如说有个地址 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详解

方法先介绍到这里,后续方法还会持续更新...

相关推荐
Pandaconda10 分钟前
【计算机网络 - 基础问题】每日 3 题(十)
开发语言·经验分享·笔记·后端·计算机网络·面试·职场和发展
尸僵打怪兽1 小时前
后台数据管理系统 - 项目架构设计-Vue3+axios+Element-plus(0920)
前端·javascript·vue.js·elementui·axios·博客·后台管理系统
ggome1 小时前
Uniapp低版本的安卓不能用解决办法
前端·javascript·uni-app
Ylucius1 小时前
JavaScript 与 Java 的继承有何区别?-----原型继承,单继承有何联系?
java·开发语言·前端·javascript·后端·学习
前端初见1 小时前
双token无感刷新
前端·javascript
bin91532 小时前
前端JavaScript导出excel,并用excel分析数据,使用SheetJS导出excel
前端·javascript·excel
.生产的驴2 小时前
SpringBoot 消息队列RabbitMQ 消息确认机制确保消息发送成功和失败 生产者确认
java·javascript·spring boot·后端·rabbitmq·负载均衡·java-rabbitmq
测试老哥2 小时前
功能测试干了三年,快要废了。。。
自动化测试·软件测试·python·功能测试·面试·职场和发展·压力测试
打野赵怀真2 小时前
你有看过vue的nextTick源码吗?
前端·javascript