@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
相关产品推荐
相关产品推荐

