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

GCS预签名URL上传Postman正常但Flutter调用返回403错误如何解决

问题根因

你当前的错误核心原因是实际请求的Content-Type和预签名URL生成时约定的Content-Type不一致,从GCS返回的错误信息里的CanonicalRequest段可以看到,你发出的请求实际Content-Type为multipart/form-data; boundary=dart-http-boundary-6w1yq6BQN3EkGBrhHZnwidOXZsBecsgSwTT3nBjB9vQCToHt0cg,和你要求的application/octet-stream不符,因此签名校验失败返回403。

出现这个问题的原因是你使用了MultipartRequest发送请求:这类请求专门用于提交表单格式的上传内容,会自动覆盖Content-Type头,添加multipart格式声明和分隔边界,你手动设置的Content-Type不会生效。而GCS预签名PUT上传二进制文件不需要使用multipart格式,直接传递文件二进制流即可。

解决代码

替换你的上传实现,直接用普通PUT请求传递文件二进制流:

import 'dart:io';
import 'package:http/http.dart' as http;

Future<http.StreamedResponse> uploadVideo(
      {required String uploadURL, required String filePath}) async {
    final request = http.Request('PUT', Uri.parse(uploadURL));
    // 直接读取文件二进制作为请求体
    request.bodyBytes = await File(filePath).readAsBytes();
    // 设置约定的Content-Type,不会被覆盖
    request.headers['Content-Type'] = 'application/octet-stream';

    final response = await request.send();

    if (response.statusCode == 200) {
      print(await response.stream.bytesToString());
    } else {
      print('请求失败,状态码:${response.statusCode},原因:${response.reasonPhrase}');
      // 出错时打印完整错误信息方便后续排查
      print('错误详情:${await response.stream.bytesToString()}');
    }
    return response;
  }

后续同类问题排查要点

  • 遇到GCS返回SignatureDoesNotMatch错误时,优先对比错误信息中CanonicalRequest部分的所有参数,和你生成预签名URL时使用的参数是否完全一致:包括请求方法、资源路径、查询参数、X-Goog-SignedHeaders中列出的所有请求头的值,任何一项不匹配都会导致签名失败。
  • 预签名URL的所有签名规则在生成时就已经固定,实际调用时不能修改任何参与签名的参数,包括Content-Type、请求方法、过期时间等。
  • 预签名PUT上传二进制文件到GCS不需要使用multipart格式,仅在使用表单提交上传的特殊场景才需要。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.09 11:54:05