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

NextJS集成Cloudinary指定folder签名时触发Unauthorized错误

问题:NextJS集成Cloudinary时添加folder参数签名后返回Unauthorized错误

我在使用NextJS集成Cloudinary服务时发现,当在签名生成文件sign.js中指定folder参数后,上传接口返回“Unauthorized”错误,但不指定folder参数时可以正常工作。

原有代码

sign.js - 签名生成文件

const cloudinary = require("cloudinary").v2;

const sign = async (req, res) => {
  const timestamp = Math.round((new Date()).getTime() / 1000);

  const signature = cloudinary.utils.api_sign_request({
    timestamp: timestamp,
    folder: 'product'
  }, process.env.NEXT_PUBLIC_CLOUDINARY_SECRET);

  res.statusCode = 200;
  res.json({ signature, timestamp });
};

export default sign;

index.js - 主业务文件

import Head from 'next/head'
import Image from 'next/image'
import styles from '../styles/Home.module.css'
import { useState } from 'react';

export default function Home() {

    const [selectedFile, setSelectedFile] = useState();
    const [isFilePicked, setIsFilePicked] = useState(false);
    const [file, setFile] = useState();

    const changeHandler = (event) => {
        console.log(event.target.files[0]);
        setSelectedFile(event.target.files[0]);
        setFile(URL.createObjectURL(event.target.files[0]))
        setIsFilePicked(true);
    };

    const handleSubmission = async () => {
        const { signature, timestamp } = await getSignature();
        const formData = new FormData();
        const url = `https://api.cloudinary.com/v1_1/${process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME}/upload`
        formData.append('file', selectedFile)
        formData.append("signature", signature);
        formData.append("timestamp", timestamp);
        formData.append("api_key", process.env.NEXT_PUBLIC_CLOUDINARY_KEY);
        fetch(url, {
            method: 'POST',
            body: formData
        }).then((response) => response.json())
            .then((result) => {
                console.log(result);
            })
            .catch((error) => {
                console.error('Error:', error);
            });
    }

    return (
        <div className={styles.container}>
            <Head>
                <title>Tailwind-Next</title>
                <meta name="description" content="Generated by create next app" />
                <link rel="icon" href="/favicon.ico" />
            </Head>
            <div className={styles.main}>
                <div className="flex items-center justify-center w-[50%] mb-5">
                    {selectedFile == null ?
                        <label htmlFor="dropzone-file" className={`flex flex-col items-center justify-center w-full h-64 border-2 border-gray-300 border-dashed rounded-lg cursor-pointer bg-gray-50 dark:hover:bg-bray-800  hover:bg-gray-100 ${styles.dropzone}`}>
                            <div className="flex flex-col items-center justify-center pt-5 pb-6">
                                <svg aria-hidden="true" className="w-10 h-10 mb-3 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"></path></svg>
                                <p className="mb-2 text-sm text-gray-500 dark:text-gray-400"><span className="font-semibold">Click to upload</span> or drag and drop</p>
                                <p className="text-xs text-gray-500 dark:text-gray-400">SVG, PNG, JPG or GIF (MAX. 800x400px)</p>
                            </div>
                            <input id="dropzone-file" type="file" className="hidden" accept="image/*" onChange={changeHandler} />
                        </label> :
                        <div className='flex flex-col items-center justify-center w-full h-64 relative'>
                            <div className={styles.image_wrapper}>
                                <img src={file} className={styles.img_preview} alt="" />
                            </div>
                        </div>}

                </div>
                <button className="w-[50%] bg-blue-600 hover:bg-blue-500 text-white font-bold py-2 px-4 border-b-4 border-blue-700 hover:border-blue-600 rounded" onClick={handleSubmission}>
                    Upload
                </button>
            </div>

        </div>
    )

}

async function getSignature() {
    const response = await fetch("/api/sign");
    const data = await response.json();
    const { signature, timestamp } = data;
    return { signature, timestamp };
}

解决方案

问题出在签名参数与上传请求参数不匹配:Cloudinary的签名验证要求,所有参与签名生成的参数(除api_key、file等特殊参数外),必须同时出现在上传请求的FormData中,且值完全一致。你在签名时加入了folder: 'product',但上传请求里没有携带这个参数,导致签名验证失败,返回Unauthorized。

只需要修改index.js中的handleSubmission函数,在FormData中添加folder参数即可:

const handleSubmission = async () => {
    const { signature, timestamp } = await getSignature();
    const formData = new FormData();
    const url = `https://api.cloudinary.com/v1_1/${process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME}/upload`
    formData.append('file', selectedFile)
    formData.append("signature", signature);
    formData.append("timestamp", timestamp);
    formData.append("api_key", process.env.NEXT_PUBLIC_CLOUDINARY_KEY);
    // 新增:添加与签名一致的folder参数
    formData.append("folder", "product");
    fetch(url, {
        method: 'POST',
        body: formData
    }).then((response) => response.json())
        .then((result) => {
            console.log(result);
        })
        .catch((error) => {
            console.error('Error:', error);
        });
}

这样签名参数和上传请求参数完全匹配,验证就能通过,文件会正常上传到指定的product文件夹中。

内容的提问来源于stack exchange,提问作者Harshbardhan Singh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 07:01:09