You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Android端使用AWS签名调用Amazon S3 GET API遭遇签名不匹配403错误求助

解决Android调用Amazon S3时的403签名不匹配问题

你碰到的这个The request signature we calculated does not match signature you provided错误,是AWS签名流程里最常见的问题之一,基本都是因为签名生成时的参数和实际发送请求的参数对不上导致的。我帮你梳理下代码里的几个关键问题:

1. Endpoint和ResourcePath配置完全错了

这是最核心的问题:

  • requestAws.endpoint应该只设S3服务的根域名,比如URI.create("https://s3.us-west-2.amazonaws.com"),不能带后面的对象路径。你现在把完整的对象URL赋值给它,签名计算的时候会把整个URL当成服务地址,自然算出来的签名就不对。
  • requestAws.resourcePath需要是对象的相对路径(从服务根域名开始的部分),比如你的对象在my-bucket/folder/sample,那这里应该写/my-bucket/folder/sample,而不是完整的HTTPS链接。

你之前把同一个完整URL同时给了endpoint和resourcePath,这直接导致签名的基础计算逻辑出错了。

2. 手动加X-Amz-Content-Sha256画蛇添足

AWS4Signer在签名时会自动生成这个头(GET请求的空内容哈希就是你写的那个值),你手动再添一次,虽然值是对的,但可能会导致请求头重复,或者签名计算时的头和实际发送的头顺序/数量不一致,反而触发签名验证失败。直接删掉这行代码就行。

3. 只取前三个Header会丢关键信息

AWS签名对请求头的完整性和顺序要求很严,你只循环取前三个header加到OkHttp请求里,会漏掉像X-Amz-Date这种签名必需的头,或者头的顺序不对,导致AWS那边验证签名时不匹配。正确的做法是把所有签名后的header都加进去:

val requestBuilder = okhttp3.Request.Builder()
    .url("https://s3.us-west-2.amazonaws.com/..../../sample")
// 遍历所有签名后的header,逐个添加
requestAws.headers.forEach { (key, value) ->
    requestBuilder.addHeader(key, value)
}
val request = requestBuilder.build()

修复后的完整代码参考

我把你的代码调整好了,你可以直接参考:

val secretkey = "E+t5/nDf6/NKNJBjbsdjv"
val accesskey = "DJKSBDKSBNKFGNBFG"
val credentials: AWSCredentials = BasicAWSCredentials(accesskey, secretkey)
val API_GATEWAY_SERVICE_NAME = "s3"
val requestAws: Request<*> = DefaultRequest<Any?>(API_GATEWAY_SERVICE_NAME)

// 正确设置S3服务的根域名作为endpoint
val serviceEndpoint = URI.create("https://s3.us-west-2.amazonaws.com")
requestAws.endpoint = serviceEndpoint
// 替换成你实际的bucket和对象路径,注意开头要加/
requestAws.resourcePath = "/your-bucket-name/your-folder/sample"
requestAws.httpMethod = HttpMethodName.GET

val signer = AWS4Signer()
signer.setServiceName(API_GATEWAY_SERVICE_NAME)
signer.setRegionName("us-west-2")
signer.sign(requestAws, credentials)

val httpClient = OkHttpClient()
val requestBuilder = okhttp3.Request.Builder()
    // 这里用完整的对象URL就行
    .url("https://s3.us-west-2.amazonaws.com/your-bucket-name/your-folder/sample")
// 把所有签名生成的header都加上
requestAws.headers.forEach { (key, value) ->
    requestBuilder.addHeader(key, value)
}

val request = requestBuilder.build()
val response: okhttp3.Response = httpClient.newCall(request).execute()
// 记得用string()而不是toString(),不然会打印对象地址
Log.i("LOG", response.body?.string() ?: "Response body is empty")

额外要检查的点

  • 确认你的Access Key和Secret Key是对的,并且这个IAM用户拥有该S3对象的s3:GetObject权限。
  • 检查你手机的系统时间是不是准确,AWS签名对时间要求很严,如果和服务器时间差超过5分钟,直接就403了。
  • 确认S3对象的路径拼写完全正确,包括bucket名、文件夹和文件名的大小写(有些区域的S3 bucket大小写是敏感的)。

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

火山引擎 最新活动