You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

@nuxtjs/cloudinary对接Media Optimizer报403权限不足问题

@nuxtjs/cloudinary 搭配Media Optimizer返回403权限不足解决方案

问题说明

按照@nuxtjs/cloudinary模块指南及教学视频配置使用时,接口持续返回报错:
Status 403, message:You don't have sufficient permissions to access this endpoint

当前使用Cloudinary Media Optimizer产品,cloud name、API Key、API Secret均已正确配置。已知该模块默认基于Programmable Media产品开发,默认API端点与Media Optimizer不通用,但参考教程中同样使用Media Optimizer搭配该模块可正常运行,多次调整配置未解决。

涉及代码如下:

ImageUploader.vue

<template>
  <div>
    <input type="file" accept=".jpeg,.gpj,image/jpeg" @change="uploadFile" />
  </div>
</template>

<script>
import { unWrap } from "~/utils/fetchUtils";
export default {
  methods: {
    async uploadFile(e) {
      const file = e.target.files[0];

      /* 校验文件存在 */
      if (!file) return;

      /* 构造cloudinary配置项 */
      const filename = file.name.split(".").slice(0, -1).join(".") + Date.now();
      const options = { timestamp: Date.now(), public_id: filename };

      /* 生成签名 */
      const response = await unWrap(
        await fetch("/api/cloudinary/signature", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify(options),
        })
      );
      const signature = response.data.signature;

      /* 初始化文件读取器 */
      const readData = (fileObj) =>
        new Promise((resolve) => {
          const reader = new FileReader();
          reader.onloadend = () => resolve(reader.result);
          reader.readAsDataURL(fileObj);
        });
      const data = await readData(file);

      /* 上传转换后的数据 */
      console.log(signature); // 测试用
      const asset = await this.$cloudinary.upload(data, {
        ...options,
        apiKey: this.$config.cloudinary.apiKey,
        signature,
      });
    },
  },
};
</script>

nuxt.config

modules: [
  "@nuxtjs/firebase",
  "~/modules/cloudinary",
]

publicRuntimeConfig: {
  cloudinary: {
    apiKey: "399741578529427",
  },
},

privateRuntimeConfig: {
  cloudinary: {
    apiSecret: "<My-Secret-API-Key>",
  },
},

cloudinary: {
  cloudName: "nuxtbnb-m98",
},

modules/cloudinary.js(原文件名拼写错误为cloudinray.js)

import { createHash } from "crypto";

export default function () {
  const config = this.options.privateRuntimeConfig.cloudinary;

  this.nuxt.hook("render:setupMiddleware", (app) => {
    app.use("/api/cloudinary/signature", setSignature);
  });

  function setSignature(req, res) {
    try {
      const sha1 = createHash("sha1");
      const payload = [];

      Object.keys(req.body).forEach((key) => {
        payload.push(`${key}=${req.body[key]}`);
      });

      sha1.update(payload.sort().join("&") + config.apiSecret);
      const signature = sha1.digest("hex");

      res.end(
        JSON.stringify({
          signature,
        })
      );
    } catch (error) {
      console.log(error);
    }
  }
}

问题根因

  • 模块默认调用Programmable Media的上传端点,Media Optimizer产品需要显式指定专属上传端点,否则会直接命中无权限的接口路径
  • 签名逻辑存在缺陷:模块默认上传时会自动追加type=upload参数,该参数未参与签名计算,导致签名校验失败
  • 自定义模块文件名拼写错误(cloudinray → cloudinary),若nuxt.config中路径未对应修正会出现模块加载异常
  • Media Optimizer账户默认未开启API签名上传权限,需要手动在后台开启
  • 原签名生成接口未设置正确的响应头,极端场景下会出现前端解析响应失败的问题

修复步骤

  • 登录Cloudinary后台,进入「设置-安全」页面,开启「认证上传」权限,确认当前API Key有Media Optimizer的API调用权限
  • 修正自定义模块文件名为cloudinary.js,和nuxt.config中引用的路径保持一致
  • 修改nuxt.config中的cloudinary配置,显式指定Media Optimizer的上传端点,开启HTTPS传输:
cloudinary: {
  cloudName: "nuxtbnb-m98",
  uploadURL: "https://api.cloudinary.com/v1_1/nuxtbnb-m98/image/upload",
  secure: true,
  useComponent: true
},
  • 修改签名生成逻辑,固定追加type=upload参数参与签名,过滤不参与签名的字段,补充响应头和错误处理,修正后的modules/cloudinary.js代码:
import { createHash } from "crypto";

export default function () {
  const config = this.options.privateRuntimeConfig.cloudinary;

  this.nuxt.hook("render:setupMiddleware", (app) => {
    app.use("/api/cloudinary/signature", setSignature);
  });

  function setSignature(req, res) {
    try {
      const sha1 = createHash("sha1");
      const payload = [];
      // 固定加入type参数,和上传时传参保持一致
      const signParams = { ...req.body, type: 'upload' }
      // 排除不参与签名的字段
      const excludeKeys = ['api_key', 'file', 'resource_type']
      Object.keys(signParams).forEach((key) => {
        if (!excludeKeys.includes(key)) {
          payload.push(`${key}=${signParams[key]}`);
        }
      });

      sha1.update(payload.sort().join("&") + config.apiSecret);
      const signature = sha1.digest("hex");

      res.setHeader('Content-Type', 'application/json')
      res.end(
        JSON.stringify({
          signature,
        })
      );
    } catch (error) {
      console.log(error);
      res.statusCode = 500
      res.end(JSON.stringify({ error: 'signature generate failed' }))
    }
  }
}
  • 修改ImageUploader.vue中的上传调用逻辑,显式传入type参数,和签名参数保持一致:
const asset = await this.$cloudinary.upload(data, {
  ...options,
  type: 'upload',
  apiKey: this.$config.cloudinary.apiKey,
  signature,
});

验证

修改完成后重启Nuxt服务,重新选择文件上传即可正常调用接口,不会再返回403权限错误。

内容的提问来源于stack exchange,提问作者Muhammad Mahmoud

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.02 02:28:04