跨域的几种解决方法

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

什么是跨域?

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

浏览器的同源策略

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

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

相关推荐
郑大乾66634 分钟前
vuex - 第一天
javascript·vue.js·node.js
阿卡基YUAN37 分钟前
JavaScript 箭头函数
前端·javascript
湛海不过深蓝42 分钟前
【js】记录预览pdf文件
开发语言·javascript·pdf
兮动人1 小时前
vue之axios基本使用
前端·javascript·vue.js
嵌入式小强工作室1 小时前
STM32 Flash DB的使用方法
前端·javascript·stm32
简单的东西为什么越来越复杂2 小时前
Java Set的理解
面试
C182981825752 小时前
面试241228
面试·职场和发展
啥都想学的又啥都不会的研究生2 小时前
redis相关问题
java·数据库·redis·笔记·学习·缓存·面试
qlj2242 小时前
react-native键盘遮盖底部输入框问题修复
javascript·react native·react.js
GISer_Jing2 小时前
Javascript数据结构常见面试题目(全)
javascript·数据结构·面试