跨域的几种解决方法

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

什么是跨域?

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

浏览器的同源策略

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

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

相关推荐
前端大波8 小时前
vue3的自动化路由(unplugin-vue-router)
javascript·vue.js·自动化
美团测试工程师9 小时前
软件测试面试题总结【含答案】
软件测试·面试
戌中横9 小时前
JavaScript 对象
java·开发语言·javascript
梵高的代码色盘9 小时前
互联网大厂Java求职面试实录与技术深度解析
java·spring·缓存·微服务·面试·互联网大厂·技术深度
击败不可能10 小时前
vue做任务工具方法的实现
前端·javascript·vue.js
a程序小傲10 小时前
高并发下如何防止重复下单?
java·开发语言·算法·面试·职场和发展·状态模式
爱上妖精的尾巴11 小时前
7-13 WPS JS宏 this 用构造函数自定义类-2,调用内部对象必须用this
开发语言·javascript·wps·jsa
bin915311 小时前
(文后附完整代码)html+css+javascript 弓箭射击游戏项目分析
前端·javascript·css·游戏·html·前端开发
像素猎人12 小时前
力扣:面试题16.01.交换数字
c++·算法·leetcode·面试
qq_4061761412 小时前
深入剖析JS中的XSS与CSRF漏洞:原理、攻击与防御全指南
服务器·开发语言·前端·javascript