编辑
2025-10-14
C#
00

目录

概述
核心组件
主要类结构
关键参数
签名验证流程
请求准备
添加必要的请求头
签名计算过程
认证头构造
完整代码
最佳实践和注意事项
时间同步
安全考虑
错误处理
性能优化
常见问题排查
签名不匹配
请求被拒绝
小结

概述

AWS API Gateway的安全验证是保护API接口不被非法访问的重要机制。本文将详细介绍如何使用C#实现AWS API的签名验证过程(AWS Signature Version 4)。

image.png

核心组件

主要类结构

  • 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(); } } } }

最佳实践和注意事项

时间同步

  • 确保服务器时间准确,与AWS服务器时间偏差不超过15分钟
  • 使用UTC时间进行签名计算

安全考虑

  • 妥善保管AccessKey和SecretKey
  • 建议使用环境变量或配置文件存储敏感信息
  • 定期轮换密钥

错误处理

  • 实现完整的错误处理机制
  • 记录详细的日志信息便于调试

性能优化

  • 可以缓存签名密钥(signing key)
  • 注意请求头的大小写敏感性

常见问题排查

签名不匹配

  • 检查请求头的排序是否正确
  • 确认换行符使用\n而不是\r\n
  • 验证时间戳格式

请求被拒绝

  • 检查API密钥是否正确
  • 确认账号权限配置
  • 验证区域设置是否正确

小结

AWS API的签名验证虽然流程较为复杂,但通过正确实现这些步骤,可以确保API调用的安全性。本文提供的代码实现了完整的签名验证流程,可以作为实际项目开发的参考。建议在使用时根据具体需求进行适当调整和优化。

特别提醒:示例代码中的密钥和API端点已经过脱敏处理,实际使用时请替换为自己的配置信息。同时,建议遵循AWS的安全最佳实践,合理保护密钥信息。

本文作者:技术老小子

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!