马上要放假了,需要抓紧时间测试对接一个三方接口,对方是使用Amazon服务的,国内不多见,能查的资(代)料(码),时间紧比较紧,也没有时间去啃Amazon的文档,主要我的英文水平也不行,于是粗略过了两遍文档后下载了Amazon的示例后填入AKSK进行测试,结果返回403 Forbidden,WTF,官网示例都不行?
然后找接口方要了一个示例,用JAVA写的很简单,直接调用了Amazon的sdk签名然后使用HttpClient进行请求,看起来很Easy啊,于是我也安装了AmazonSDK的nuget准备进行dotnet版本的开发。。。然后发现dotnet版本SDK是各种抽象类,而且方法名都差不多,跟Java版本的差很多,同名方法调用不了,在不啃文档花时间以我的能力直接用官方SDK还是太难了。之前不是下载了官方提供的示例吗,我这次把里面的签名算法拷贝了出来,自己调用自己写,仍旧报错。。。
没办法了,找到同事他用python写了一个,调用成功了,WTF?为啥啊
后来总结了一下,开始用的HttpClient,怕有什么问题,于是使用了RestSharp再次尝试,好了!!!
于是我又开始用了HttpClient,签名算法什么的一概不变,还是不行。。。
省略中间各种挣扎,最后使用抓包工具一点点比较两个请求,终于HttpClient成功了。
附上两版代码,另外还有一个坑,HttpClient在header中用Add添加Authorization会报错,需要用
csharp
request.Headers.Authorization = new AuthenticationHeaderValue("AWS4-HMAC-SHA256", authorizationHeader);
RestSharp版本的
csharp
public class ApiClient
{
private const string AccessKey = "AccessKey ";
private const string SecretKey = "SecretKey ";
private const string Region = "cn-northwest-1";
private const string Service = "execute-api";
private const string Url = "https://test.execute-api.cn-northwest-1.amazonaws.com.cn/api/test";
private const string ApiKey = "ApiKey";
public async Task CallApiAsync()
{
var client = new RestClient(Url);
// 准备请求payload
var payload = new
{
key="value"
};
var request = new RestRequest("", Method.Post);
string jsonPayload = JsonSerializer.Serialize(payload);
// 计算payload hash
string payloadHash;
using (var sha256 = SHA256.Create())
{
var bytes = Encoding.UTF8.GetBytes(jsonPayload);
var hash = sha256.ComputeHash(bytes);
payloadHash = BitConverter.ToString(hash).Replace("-", "").ToLower();
}
// 获取当前UTC时间
var now = DateTime.UtcNow;
var amzDate = now.ToString("yyyyMMddTHHmmssZ");
// 设置请求头
request.AddStringBody(jsonPayload, DataFormat.Json);
request.AddHeader("Content-Type", "application/json");
request.AddHeader("Host", "test.execute-api.cn-northwest-1.amazonaws.com.cn");
request.AddHeader("x-amz-content-sha256", payloadHash);
request.AddHeader("x-amz-date", amzDate); // 使用当前UTC时间
request.AddHeader("x-api-key", ApiKey);
// 计算签名
var signer = new AWS4RequestSigner(AccessKey, SecretKey);
await signer.Sign(request, Service, Region, payloadHash);
try
{
var response = await client.ExecuteAsync(request);
Console.WriteLine($"Status Code: {response.StatusCode}");
Console.WriteLine($"Response: {response.Content}");
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
}
public class AWS4RequestSigner
{
private readonly string _accessKey;
private readonly string _secretKey;
public AWS4RequestSigner(string accessKey, string secretKey)
{
_accessKey = accessKey;
_secretKey = secretKey;
}
public async Task Sign(RestRequest request, string service, string region, string payloadHash)
{
var amzDate = request.Parameters
.First(p => p.Name == "x-amz-date").Value.ToString();
var dateStamp = amzDate.Substring(0, 8);
// 准备签名所需的字符串
var credentialScope = $"{dateStamp}/{region}/{service}/aws4_request";
// 计算签名
var stringToSign = CreateStringToSign(request, credentialScope, amzDate, payloadHash);
var signingKey = GetSigningKey(dateStamp, region, service);
var signature = CalculateSignature(signingKey, stringToSign);
// 构造授权头
var authorizationHeader = $"AWS4-HMAC-SHA256 " +
$"Credential={_accessKey}/{credentialScope}, " +
$"SignedHeaders=content-type;host;x-amz-content-sha256;x-amz-date;x-api-key, " +
$"Signature={signature}";
Console.WriteLine($"Authorization header: {authorizationHeader}");
request.AddHeader("Authorization", authorizationHeader);
}
private string CreateStringToSign(RestRequest request, string credentialScope, string amzDate, string payloadHash)
{
var canonicalRequest = CreateCanonicalRequest(request, payloadHash);
return $"AWS4-HMAC-SHA256\n{amzDate}\n{credentialScope}\n" +
CalculateHash(canonicalRequest);
}
private string CreateCanonicalRequest(RestRequest request, string payloadHash)
{
var canonicalUrl = "/api/test";
var canonicalQueryString = "";
// 按照错误消息中的顺序构建规范头部
var contentType = request.Parameters.First(p => p.Name == "Content-Type").Value.ToString();
var host = request.Parameters.First(p => p.Name == "Host").Value.ToString();
var xAmzContentSha256 = request.Parameters.First(p => p.Name == "x-amz-content-sha256").Value.ToString();
var xAmzDate = request.Parameters.First(p => p.Name == "x-amz-date").Value.ToString();
var xApiKey = request.Parameters.First(p => p.Name == "x-api-key").Value.ToString();
var canonicalHeaders =
$"content-type:{contentType}\n" +
$"host:{host}\n" +
$"x-amz-content-sha256:{xAmzContentSha256}\n" +
$"x-amz-date:{xAmzDate}\n" +
$"x-api-key:{xApiKey}\n";
var signedHeaders = "content-type;host;x-amz-content-sha256;x-amz-date;x-api-key";
return $"POST\n{canonicalUrl}\n{canonicalQueryString}\n{canonicalHeaders}\n{signedHeaders}\n{payloadHash}";
}
private byte[] GetSigningKey(string dateStamp, string region, string service)
{
var kSecret = Encoding.UTF8.GetBytes($"AWS4{_secretKey}");
var kDate = Sign(dateStamp, kSecret);
var kRegion = Sign(region, kDate);
var kService = Sign(service, kRegion);
return Sign("aws4_request", kService);
}
private byte[] Sign(string data, byte[] key)
{
using (var hmac = new HMACSHA256(key))
{
return hmac.ComputeHash(Encoding.UTF8.GetBytes(data));
}
}
private string CalculateSignature(byte[] signingKey, string stringToSign)
{
using (var hmac = new HMACSHA256(signingKey))
{
var signature = hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign));
return BitConverter.ToString(signature).Replace("-", "").ToLower();
}
}
private string CalculateHash(string text)
{
using (var sha256 = SHA256.Create())
{
var bytes = Encoding.UTF8.GetBytes(text);
var hash = sha256.ComputeHash(bytes);
return BitConverter.ToString(hash).Replace("-", "").ToLower();
}
}
}
以下是HttpClient的
csharp
public class ApiClientHttpClient
{
private const string AccessKey = "AccessKey";
private const string SecretKey = "SecretKey";
private const string Region = "cn-northwest-1";
private const string Service = "execute-api";
private const string Url = "https://test.execute-api.cn-northwest-1.amazonaws.com.cn/api/test";
private const string ApiKey = "ApiKey";
public async Task CallApiAsync()
{
using (var client = new HttpClient())
{
// 准备请求payload
var payload = new
{
Key="value"
};
string jsonPayload = JsonSerializer.Serialize(payload);
// 计算payload hash
string payloadHash = CalculateSha256(jsonPayload);
// 获取当前UTC时间
var now = DateTime.UtcNow;
var amzDate = now.ToString("yyyyMMddTHHmmssZ");
var request = new HttpRequestMessage(HttpMethod.Post, Url)
{
Content = new StringContent(jsonPayload, Encoding.UTF8, "application/json") // 使用UTF8编码和"application/json"内容类型
};
// 移除默认添加的charset参数
request.Content.Headers.ContentType.CharSet = null;
//request.Headers.Accept.ParseAdd("application/json");
//request.Headers.Accept.ParseAdd("text/json");
//request.Headers.Accept.ParseAdd("text/x-json");
//request.Headers.Accept.ParseAdd("text/javascript");
//request.Headers.Accept.ParseAdd("application/xml");
//request.Headers.Accept.ParseAdd("text/xml");
//request.Headers.AcceptEncoding.ParseAdd("gzip");
//request.Headers.AcceptEncoding.ParseAdd("deflate");
//request.Headers.AcceptEncoding.ParseAdd("br");
request.Headers.Host = "test.execute-api.cn-northwest-1.amazonaws.com.cn";
request.Headers.Add("x-amz-content-sha256", payloadHash);
request.Headers.Add("x-amz-date", amzDate); // 使用当前UTC时间
request.Headers.Add("x-api-key", ApiKey);
request.Headers.UserAgent.ParseAdd("test/1.0"); // 替换为你的应用名称和版本
// 计算签名
var signer = new AWS4RequestSignerHttpClient(AccessKey, SecretKey);
await signer.Sign(request, Service, Region, payloadHash, amzDate);
try
{
var response = await client.SendAsync(request);
Console.WriteLine($"Status Code: {response.StatusCode}");
Console.WriteLine($"Response: {await response.Content.ReadAsStringAsync()}");
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
}
private static string CalculateSha256(string input)
{
using (SHA256 sha256 = SHA256.Create())
{
byte[] bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(input));
return BitConverter.ToString(bytes).Replace("-", "").ToLower();
}
}
}
public class AWS4RequestSignerHttpClient
{
private readonly string _accessKey;
private readonly string _secretKey;
public AWS4RequestSignerHttpClient(string accessKey, string secretKey)
{
_accessKey = accessKey;
_secretKey = secretKey;
}
public async Task Sign(HttpRequestMessage request, string service, string region, string payloadHash, string amzDate)
{
var dateStamp = amzDate.Substring(0, 8);
var credentialScope = $"{dateStamp}/{region}/{service}/aws4_request";
// 计算签名
var stringToSign = CreateStringToSign(request, credentialScope, amzDate, payloadHash);
var signingKey = GetSigningKey(dateStamp, region, service);
var signature = CalculateSignature(signingKey, stringToSign);
// 构造授权头
var authorizationHeader = $"Credential={_accessKey}/{credentialScope}, " +
$"SignedHeaders=content-type;host;x-amz-content-sha256;x-amz-date;x-api-key, " +
$"Signature={signature}";
request.Headers.Authorization = new AuthenticationHeaderValue("AWS4-HMAC-SHA256", authorizationHeader);
}
private string CreateStringToSign(HttpRequestMessage request, string credentialScope, string amzDate, string payloadHash)
{
var canonicalRequest = CreateCanonicalRequest(request, payloadHash);
return $"AWS4-HMAC-SHA256\n{amzDate}\n{credentialScope}\n" + CalculateHash(canonicalRequest);
}
private string CreateCanonicalRequest(HttpRequestMessage request, string payloadHash)
{
var canonicalUrl = "/api/test";
var canonicalQueryString = "";
// 按照错误消息中的顺序构建规范头部
var contentType = request.Content.Headers.ContentType?.ToString().Split(';').First();
var host = request.Headers.Host;
var xAmzContentSha256 = request.Headers.FirstOrDefault(h => h.Key == "x-amz-content-sha256").Value.First();
var xAmzDate = request.Headers.FirstOrDefault(h => h.Key == "x-amz-date").Value.First();
var xApiKey = request.Headers.FirstOrDefault(h => h.Key == "x-api-key").Value.First();
var canonicalHeaders =
$"content-type:{contentType}\n" +
$"host:{host}\n" +
$"x-amz-content-sha256:{xAmzContentSha256}\n" +
$"x-amz-date:{xAmzDate}\n" +
$"x-api-key:{xApiKey}\n";
var signedHeaders = "content-type;host;x-amz-content-sha256;x-amz-date;x-api-key";
return $"POST\n{canonicalUrl}\n{canonicalQueryString}\n{canonicalHeaders}\n{signedHeaders}\n{payloadHash}";
}
private byte[] GetSigningKey(string dateStamp, string region, string service)
{
var kSecret = Encoding.UTF8.GetBytes($"AWS4{_secretKey}");
var kDate = Sign(dateStamp, kSecret);
var kRegion = Sign(region, kDate);
var kService = Sign(service, kRegion);
return Sign("aws4_request", kService);
}
private byte[] Sign(string data, byte[] key)
{
using (var hmac = new HMACSHA256(key))
{
return hmac.ComputeHash(Encoding.UTF8.GetBytes(data));
}
}
private string CalculateSignature(byte[] signingKey, string stringToSign)
{
using (var hmac = new HMACSHA256(signingKey))
{
var signature = hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign));
return BitConverter.ToString(signature).Replace("-", "").ToLower();
}
}
private string CalculateHash(string text)
{
using (var sha256 = SHA256.Create())
{
var bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(text));
return BitConverter.ToString(bytes).Replace("-", "").ToLower();
}
}
}
主要的问题是Content-Type: application/json不要有charset=utf-8;第二个事需要一个User-Agent ,其他的算法对了并且正确的添加到请求中就好了
另外,x-api-key只是我的接口要求字段