AWS API Gateway的安全验证是保护API接口不被非法访问的重要机制。本文将详细介绍如何使用C#实现AWS API的签名验证过程(AWS Signature Version 4)。
ApiClient
: 负责构造和发送HTTP请求AWS4RequestSigner
: 处理AWS签名计算的核心类C#private const string AccessKey = "YOUR_ACCESS_KEY";
private const string SecretKey = "YOUR_SECRET_KEY";
private const string Region = "cn-northwest-1";
private const string Service = "execute-api";
构造请求payload
C#var payload = new
{
cnoc = "xxxxx",
cname = "青岛xxx有限公司",
bscope = "一般项目:工业自动控制系统装置销售...",
branch = "Sales Channels"
};
计算payload的SHA256哈希
C#string payloadHash;
using (var sha256 = SHA256.Create())
{
var bytes = Encoding.UTF8.GetBytes(jsonPayload);
var hash = sha256.ComputeHash(bytes);
payloadHash = BitConverter.ToString(hash).Replace("-", "").ToLower();
}
C#request.AddHeader("Content-Type", "application/json");
request.AddHeader("Host", "your-api-endpoint");
request.AddHeader("x-amz-content-sha256", payloadHash);
request.AddHeader("x-amz-date", amzDate);
request.AddHeader("x-api-key", ApiKey);
创建规范请求(Canonical Request)
C#private string CreateCanonicalRequest(RestRequest request, string payloadHash)
{
// 按照指定格式组合HTTP方法、URI、查询字符串、请求头和payload hash
return $"POST\n{canonicalUrl}\n{canonicalQueryString}\n{canonicalHeaders}\n{signedHeaders}\n{payloadHash}";
}
创建待签名字符串
C#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);
}
计算签名密钥
C#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);
}
生成最终签名
C#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();
}
}
最终的授权头格式如下:
C#var authorizationHeader = $"AWS4-HMAC-SHA256 " +
$"Credential={_accessKey}/{credentialScope}, " +
$"SignedHeaders=content-type;host;x-amz-content-sha256;x-amz-date;x-api-key, " +
$"Signature={signature}";
C#using System;
using System.Text;
using System.Text.Json;
using RestSharp;
using System.Security.Cryptography;
namespace AppAws
{
public class ApiClient
{
private const string AccessKey = "x";
private const string SecretKey = "xxx";
private const string Region = "cn-northwest-1";
private const string Service = "execute-api";
private const string Url = "https://xxx";
private const string ApiKey = "xxx";
public async Task CallApiAsync()
{
var client = new RestClient(Url);
// 准备请求payload
var payload = new
{
cnoc = "xx",
cname = "青岛xxx限公司",
bscope = "一般项目:xxx",
branch = "Sales Channels"
};
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", "ug6otu1t66.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}");
// 输出当前使用的时间戳,用于调试
Console.WriteLine($"Used timestamp: {amzDate}");
}
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}";
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/company-role-pred2";
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();
}
}
}
}
\n
而不是\r\n
AWS API的签名验证虽然流程较为复杂,但通过正确实现这些步骤,可以确保API调用的安全性。本文提供的代码实现了完整的签名验证流程,可以作为实际项目开发的参考。建议在使用时根据具体需求进行适当调整和优化。
特别提醒:示例代码中的密钥和API端点已经过脱敏处理,实际使用时请替换为自己的配置信息。同时,建议遵循AWS的安全最佳实践,合理保护密钥信息。
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!