C# 实现AWS API接口安全验证详解

概述

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

C# 实现AWS API接口安全验证详解

核心组件

主要类结构

  • ApiClient: 负责构造和发送HTTP请求
  • AWS4RequestSigner: 处理AWS签名计算的核心类

关键参数

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

var payload = new
{
    cnoc = "xxxxx",
    cname = "青岛xxx有限公司",
    bscope = "一般项目:工业自动控制系统装置销售...",
    branch = "Sales Channels"
};

计算payload的SHA256哈希

string payloadHash;
using (var sha256 = SHA256.Create())
{
    var bytes = Encoding.UTF8.GetBytes(jsonPayload);
    var hash = sha256.ComputeHash(bytes);
    payloadHash = BitConverter.ToString(hash).Replace("-", "").ToLower();
}

添加必要的请求头

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)

private string CreateCanonicalRequest(RestRequest request, string payloadHash)
{
    // 按照指定格式组合HTTP方法、URI、查询字符串、请求头和payload hash
    return $"POST
{canonicalUrl}
{canonicalQueryString}
{canonicalHeaders}
{signedHeaders}
{payloadHash}";
}

创建待签名字符串

private string CreateStringToSign(RestRequest request, string credentialScope, string amzDate, string payloadHash)
{
    var canonicalRequest = CreateCanonicalRequest(request, payloadHash);
    return $"AWS4-HMAC-SHA256
{amzDate}
{credentialScope}
" +
           CalculateHash(canonicalRequest);
}

计算签名密钥

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

认证头构造

最终的授权头格式如下:

var authorizationHeader = $"AWS4-HMAC-SHA256 " +
    $"Credential={_accessKey}/{credentialScope}, " +
    $"SignedHeaders=content-type;host;x-amz-content-sha256;x-amz-date;x-api-key, " +
    $"Signature={signature}";

完整代码

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
{amzDate}
{credentialScope}
" +
                   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}
" +
                $"host:{host}
" +
                $"x-amz-content-sha256:{xAmzContentSha256}
" +
                $"x-amz-date:{xAmzDate}
" +
                $"x-api-key:{xApiKey}
";

            var signedHeaders = "content-type;host;x-amz-content-sha256;x-amz-date;x-api-key";

            return $"POST
{canonicalUrl}
{canonicalQueryString}
{canonicalHeaders}
{signedHeaders}
{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)
  • 注意请求头的大小写敏感性

常见问题排查

签名不匹配

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

请求被拒绝

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

小结

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

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

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
恋杻癖和棒子舔犭句莫挨我嫌晦气的头像 - 鹿快
评论 抢沙发

请登录后发表评论

    暂无评论内容