Android使用OkHttp签名AWS API Gateway请求时遇「Missing Authentication Token」
解决Android中OkHttp签名AWS API Gateway报「Missing Authentication Token」的问题
嘿,我之前也碰到过一模一样的坑!Postman能用但自己写的Android代码就报这个错,大概率是签名过程中几个容易忽略的细节没处理到位,给你列几个排查和修复的方向:
1. 务必处理临时凭证的Session Token
Cognito返回的是临时安全凭证,除了AWSAccessKeyId和AWSSecretKey,你必须把SessionToken加到请求头的X-Amz-Security-Token里,而且这个头要被纳入签名计算的范围:
- 生成签名时,
SignedHeaders要包含x-amz-security-token(注意小写,AWS签名对大小写敏感) - 最终请求的头里必须带上这个Token,Postman会自动帮你处理,但手动写代码很容易漏
2. 核对请求路径和API Gateway配置完全匹配
这个是最容易踩的坑!API Gateway的路径是大小写敏感的,而且如果你的API配置了阶段前缀(比如/prod、/dev),代码里必须用完整路径:
- 比如Postman里用的是
https://xxx.execute-api.us-east-1.amazonaws.com/prod/my-endpoint,那代码里的请求路径不能只写/my-endpoint,必须带上/prod/my-endpoint - 哪怕路径差一个斜杠或者大小写不对,API Gateway都会直接返回「Missing Authentication Token」,因为它找不到对应的资源
3. 检查OkHttp拦截器的执行顺序和请求修改逻辑
如果用OkHttp的Interceptor处理签名,要注意:
- 拦截器要在所有请求体、路径修改之后添加,否则签名计算的内容和实际请求不一致
- 确保拦截器没有意外修改HTTP方法(比如把POST改成GET)、路径或者请求体内容
- 打印拦截器处理前后的请求头和路径,对比Postman的请求细节
4. 确认时间同步和X-Amz-Date格式
AWS签名v4要求请求的X-Amz-Date和服务器UTC时间误差不能超过5分钟:
- 必须用
YYYYMMDD'T'HHMMSS'Z'格式的UTC时间(比如20240520T123456Z) - 不要用本地时间,否则时间差过大直接被拒绝
5. 对比Postman和Android请求的签名细节
把Postman导出的请求头(所有AWS相关头)和你代码里生成的做对比:
- 检查
Authorization头的格式:是否包含Credential、SignedHeaders、Signature三个部分 - 核对
SignedHeaders里的头列表是否和实际请求的头完全一致(要按字母排序) - 确认请求体的SHA256哈希计算正确:如果是POST请求,要计算请求体的哈希;如果是空请求体,要用固定的空哈希值
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
给你一个简化的OkHttp签名拦截器示例
核心逻辑要确保覆盖所有必要的签名步骤:
public class AwsApiSigningInterceptor implements Interceptor { private final AwsCredentials credentials; private final String service = "execute-api"; private final String region = "us-east-1"; // 替换成你的API区域 public AwsApiSigningInterceptor(AwsCredentials credentials) { this.credentials = credentials; } @Override public Response intercept(Chain chain) throws IOException { Request original = chain.request(); ZonedDateTime utcNow = ZonedDateTime.now(ZoneOffset.UTC); String amzDate = utcNow.format(DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'")); String dateStamp = utcNow.format(DateTimeFormatter.ofPattern("yyyyMMdd")); // 1. 构建规范请求 String canonicalRequest = buildCanonicalRequest(original, amzDate); // 2. 构建待签名字符串 String credentialScope = dateStamp + "/" + region + "/" + service + "/aws4_request"; String stringToSign = "AWS4-HMAC-SHA256\n" + amzDate + "\n" + credentialScope + "\n" + hashSha256(canonicalRequest); // 3. 计算签名 String signature = calculateSignature(stringToSign, credentials.getAWSSecretKey(), dateStamp, region, service); // 4. 构建授权头 String authHeader = String.format( "AWS4-HMAC-SHA256 Credential=%s/%s, SignedHeaders=%s, Signature=%s", credentials.getAWSAccessKeyId(), credentialScope, getSignedHeaders(original), signature ); // 5. 构建带签名的请求 Request signedRequest = original.newBuilder() .header("X-Amz-Date", amzDate) .header("X-Amz-Security-Token", credentials.getSessionToken()) .header("Authorization", authHeader) .build(); return chain.proceed(signedRequest); } // 辅助方法:构建规范请求(省略实现,需处理方法、路径、查询参数、头、请求体哈希) private String buildCanonicalRequest(Request request, String amzDate) { /* ... */ } // 辅助方法:计算SHA256哈希 private String hashSha256(String input) { /* ... */ } // 辅助方法:获取排序后的签名头列表 private String getSignedHeaders(Request request) { /* ... */ } // 辅助方法:计算HMAC-SHA256签名 private String calculateSignature(String stringToSign, String secretKey, String dateStamp, String region, String service) { /* ... */ } }
我当时是因为代码里漏加了API的阶段前缀,补上之后就立刻正常了,你可以先排查这个点,再逐步检查其他细节!
内容的提问来源于stack exchange,提问作者Clive Sargeant
相关产品推荐
相关产品推荐

