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

Flutter真机上传图片至DigitalOcean Spaces失败,报SignatureDoesNotMatch错误

问题:Flutter真机上传图片到DigitalOcean Spaces失败,返回SignatureDoesNotMatch错误

代码在模拟器运行正常,但真机执行时返回以下错误:

<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>SignatureDoesNotMatch</Code><Message></Message><RequestId>tx0000000d23c29d18ee2fc-00655e7f39-20443a-blr1a</RequestId><HostId>20443a-blr1a-blr1-zg01</HostId>
</Error>

相关上传代码:

import 'dart:convert';
import 'dart:io';

import 'package:aws_common/aws_common.dart';
import 'package:aws_signature_v4/aws_signature_v4.dart';
import 'package:mime/mime.dart';
Future<String> imageUpload(File xfile, String uid) async {
  const awsAccessKeyId = 'xxxxxx';
  const awsSecretAccessKey = 'xxxxxxxxxxxxxxxxx';
  const region = 'blr1';
  const host = 'bucketname.$region.digitaloceanspaces.com';
  const folder = 'business_assets';
  final mimeType = lookupMimeType(xfile.path) ?? 'application/octet-stream';
  final ext = xfile.path.split('.').lastOrNull;
  final fileExt = ext != null ? '.$ext' : '';
  final filename = '${uid}_${DateTime.now().millisecondsSinceEpoch}$fileExt';
  final path = '$folder/$filename';

  final serviceConfiguration = S3ServiceConfiguration();
  const awsCredentials = AWSCredentials(awsAccessKeyId, awsSecretAccessKey);
  const signer = AWSSigV4Signer(
    credentialsProvider: AWSCredentialsProvider(awsCredentials),
  );
  final scope = AWSCredentialScope(region: region, service: AWSService.s3);

  final file = File(xfile.path).openRead();
  final uploadRequest = AWSStreamedHttpRequest.put(
    Uri.https(host, path),
    body: file,
    headers: {
      AWSHeaders.host: host,
      AWSHeaders.contentType: mimeType,
      'x-amz-acl': 'public-read',
    },
  );

  final signedUploadRequest = await signer.sign(
    uploadRequest,
    credentialScope: scope,
    serviceConfiguration: serviceConfiguration,
  );
  final headers = signedUploadRequest.headers;
  final uri = signedUploadRequest.uri;
  final uploadResponse = await signedUploadRequest.send().response;
  final uploadStatus = uploadResponse.statusCode;
  final resCodes = await uploadResponse.bodyBytes;
  final resdata = utf8.decode(resCodes);
  if (uploadStatus != 200) {
    throw Exception('Could not upload file');
  }
  return 'https://$host/$path';
}
解决方案

以下是针对真机SignatureDoesNotMatch错误的排查和修复步骤:

1. 修复时间同步问题

真机系统时间与NTP服务器偏差超过5分钟时,会直接导致签名验证失败:

  • 开启设备的自动时间同步功能,确保系统时间和时区与网络时间一致;
  • 若手动设置时间,调整到当前准确时间。

2. 修正文件流的签名计算逻辑

模拟器和真机的文件流读取机制存在差异,可能导致签名计算时的请求体哈希不匹配:

  • 替换原文件流读取方式,先读取完整文件字节再生成请求体,确保签名计算能获取准确的文件哈希:
    // 替换原文件流读取代码
    final fileBytes = await xfile.readAsBytes();
    final uploadRequest = AWSStreamedHttpRequest.put(
      Uri.https(host, path),
      body: Stream.value(fileBytes),
      headers: {
        AWSHeaders.host: host,
        AWSHeaders.contentType: mimeType,
        'x-amz-acl': 'public-read',
      },
    );
    

3. 指定DigitalOcean Spaces专属服务配置

DigitalOcean Spaces虽兼容S3,但需明确配置端点避免默认AWS S3端点干扰:

  • 修改S3ServiceConfiguration,添加端点覆盖参数:
    final serviceConfiguration = S3ServiceConfiguration(
      endpointOverride: Uri.parse('https://$region.digitaloceanspaces.com'),
    );
    

4. 验证请求头一致性

真机环境可能存在代理或网络层修改请求头的情况,导致签名验证不通过:

  • 签名后打印所有请求头,对比模拟器和真机的请求头差异,重点检查Host、Content-Type、x-amz-*等字段;
  • 移除可能引发冲突的自定义头,或确保这些头被包含在签名计算范围内。

5. 确认密钥与权限

虽然模拟器能正常运行,但真机可能存在密钥加载异常:

  • 检查awsAccessKeyId和awsSecretAccessKey在真机中是否完整加载,无混淆或截断;
  • 验证该密钥对是否拥有DigitalOcean Spaces的写入权限,确认策略配置正确。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 20:57:10