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

使用S3 Signed URL上传文件时出现SignatureDoesNotMatch错误

问题根因

这套稳定运行3年的代码突发签名错误,直接触发原因是2022年7月AWS对S3预签名URL的校验逻辑做了静默收紧,之前不做强制校验的规则全部改为强校验,结合贴出的错误响应和代码,有三个明确的触发点:

  • 后端生成预签名URL时错误使用了GET方法,但前端上传实际发的是PUT请求,错误内容里<CanonicalRequest>开头第一行就是GET /tmp/xxx,这是旧版Laravel Vapor的已知bug,之前校验宽松时不会触发报错。
  • 前端处理请求头的逻辑有漏洞:仅删除了首字母大写的Host头,没有覆盖小写变体;同时axios/浏览器会自动追加Accept、User-Agent、X-Requested-With等额外请求头,这些头不在签名时的SignedHeaders列表内,直接导致签名计算值不匹配。
  • 签名生成时x-amz-acl参数被拼在查询串中,但前端实际把这个参数放在请求头发送,参数位置不匹配也会触发校验失败。
排查顺序(按修复效率从高到低)
  • 先抓浏览器发往S3的真实请求,核对三个核心信息:
    • 请求方法是否和预签名时指定的方法一致,当前错误已经明确存在GET/PUT方法不匹配问题,这是最高优先级排查项
    • 实际发送的请求头列表,是否比后端返回的签名headers多了额外字段
    • 查询串中X-Amz-SignedHeaders列明的头,是否和实际发送的头完全一致,大小写、数量、值有任何差异都会报错
  • 核对Laravel Vapor核心依赖版本:2022年7月AWS更新校验规则后,Laravel官方在vapor-core 1.30.0版本专门修复了预签名URL请求方法错误的问题,低于这个版本的代码都会踩坑
  • 核对AWS PHP SDK版本:当前使用的3.148.3是规则更新前的旧版本,对新的header排序、参数校验逻辑兼容存在缺陷
落地修复方案
  1. 升级依赖修复后端签名逻辑
    直接把laravel/vapor-core升级到1.30.0以上版本,从根源修复签名时请求方法错误的问题;如果暂时无法升级依赖,手动在生成预签名URL的逻辑中强制指定请求方法为PUT。
    同步把AWS PHP SDK升级到3.200以上版本,兼容新的签名校验规则。
  2. 修正前端上传的请求头处理逻辑
    原有仅删除大写Host头的逻辑完全不满足新的校验要求,做三个改动即可:
    • 拿到后端返回的headers后,统一删除所有大小写形式的Host头
    • 显式设置上传时的Content-Type和后端签名时传入的content_type完全一致,禁止浏览器自动猜测文件类型追加头
    • 屏蔽axios自动追加的非签名列表内的公共头,禁止发送不在SignedHeaders列表内的字段
      修正后的核心上传代码参考:
    async store(file, options = {}, fileAcl) {
        const response = await Vue.axios.post(
            "/vapor/signed-storage-url", {
                bucket: options.bucket || "",
                content_type: options.contentType || file.type,
                expires: options.expires || "",
                visibility: fileAcl || "",
                ignore_web_middleware: true
            }, {
                baseURL: options.baseURL || null,
                headers: options.headers || {}
            }
        );
    
        let headers = response.data.headers;
        // 移除所有大小写变体的Host头
        Object.keys(headers).forEach(key => {
            if (key.toLowerCase() === "host") delete headers[key];
        });
        // 强制指定Content-Type和签名值一致
        headers["Content-Type"] = options.contentType || file.type;
    
        if (typeof options.progress === "undefined") {
            options.progress = () => {};
        }
    
        const newInstance = axios.create();
        // 清空axios默认公共头,避免自动追加鉴权、UA等无关头
        newInstance.defaults.headers.common = {};
        await newInstance.put(response.data.url, file, {
            headers: headers,
            transformRequest: [(data, reqHeaders) => {
                // 仅保留签名列表内的头和Content-Type,其余全部删除
                Object.keys(reqHeaders).forEach(key => {
                    const lowerKey = key.toLowerCase();
                    const isSignedHeader = Object.keys(response.data.headers).some(signKey => signKey.toLowerCase() === lowerKey);
                    if (!isSignedHeader && lowerKey !== "content-type") {
                        delete reqHeaders[key];
                    }
                });
                return data;
            }],
            onUploadProgress: progressEvent => {
                options.progress(progressEvent.loaded / progressEvent.total);
            }
        });
    
        response.data.extension = file.name.split(".").pop();
        return response.data;
    }
    
  3. 紧急兜底方案
    如果需要立刻恢复业务来不及升级依赖,先在S3桶的CORS配置中放开所有允许的头,同时在后端生成预签名URL时,把前端实际上传会带的Content-Type、x-amz-acl等头全部加入签名的SignedHeaders列表,确保签名计算覆盖所有实际发送的字段。

内容的提问来源于stack exchange,提问作者Ali Gajani

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 10:18:22