C#AWS signatureV4对接Amazon接口

马上要放假了,需要抓紧时间测试对接一个三方接口,对方是使用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会报错,需要用

 request.Headers.Authorization = new AuthenticationHeaderValue("AWS4-HMAC-SHA256", authorizationHeader);

RestSharp版本的

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的

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只是我的接口要求字段

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/959894.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CDN、源站与边缘网络

什么是“源站” 源服务器 源服务器的目的是处理和响应来自互联网客户端的传入请求。源服务器的概念通常与边缘服务器或缓存服务器的概念结合使用。源服务器的核心是一台运行一个或多个程序的计算机,这些程序旨在侦听和处理传入的客户端请求。源服务器可以承担为网…

数据库、数据仓库、数据湖有什么不同

数据库、数据仓库和数据湖是三种不同的数据存储和管理技术,它们在用途、设计目标、数据处理方式以及适用场景上存在显著差异。以下将从多个角度详细说明它们之间的区别: 1. 数据结构与存储方式 数据库: 数据库主要用于存储结构化的数据&…

Java Swing 基础组件详解 [论文投稿-第四届智能系统、通信与计算机网络]

大会官网:www.icisccn.net Java Swing 是一个功能强大的 GUI 工具包,提供了丰富的组件库用于构建跨平台的桌面应用程序。本文将详细讲解 Swing 的基础组件,包括其作用、使用方法以及示例代码,帮助你快速掌握 Swing 的核心知识。 一…

Mac m1,m2,m3芯片使用nvm安装node14报错

使用nvm安装了node 12/16/18都没有问题,到14就报错了。第一次看到这个报错有点懵,查询资料发现是Mac芯片的问题。 Issue上提供了两个方案: 1、为了在arm64的Mac上安装node 14,需要使用Rosseta,可以通过以下命令安装 …

多模态论文笔记——ViViT

大家好,这里是好评笔记,公主号:Goodnote,专栏文章私信限时Free。本文详细解读多模态论文《ViViT: A Video Vision Transformer》,2021由google 提出用于视频处理的视觉 Transformer 模型,在视频多模态领域有…

网络安全 | F5-Attack Signatures-Set详解

关注:CodingTechWork 创建和分配攻击签名集 可以通过两种方式创建攻击签名集:使用过滤器或手动选择要包含的签名。  基于过滤器的签名集仅基于在签名过滤器中定义的标准。基于过滤器的签名集的优点在于,可以专注于定义用户感兴趣的攻击签名…

【C语言系列】深入理解指针(4)

深入理解指针(4) 一、回调函数是什么?二、qsort使用举例2.1使用qsort函数排序整型数据2.2使用qsort排序结构数据 三、qsort函数的模拟实现四、总结 一、回调函数是什么? 回调函数就是一个通过函数指针调用的函数。 如果你把函数的…

零售业革命:改变行业的顶级物联网用例

mpro5 产品负责人Ruby Whipp表示,技术进步持续重塑零售业,其中物联网(IoT)正引领这一变革潮流。 研究表明,零售商们正在采用物联网解决方案,以提升运营效率并改善顾客体验。这些技术能够监控运营的各个方面…

macos的图标过大,这是因为有自己的设计规范

苹果官方链接:App 图标 | Apple Developer Documentation 这个在官方文档里有说明,并且提供了sketch 和 ps 的模板。 figma还提供了模板: Figma

【从零到一,C++项目实战】CineShare++(基于C++的视频点播系统)

🌈个人主页: 南桥几晴秋 🌈C专栏: 南桥谈C 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据…

【leetcode100】从前序与中序遍历序列构造二叉树

1、题目描述 给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。 示例 1: 输入: preorder [3,9,20,15,7], inorder [9,3,15,20,7] 输出: [3,9,20,nul…

免费GPU算力,不花钱部署DeepSeek-R1

在人工智能和大模型技术飞速发展的今天,越来越多的开发者和研究者希望能够亲自体验和微调大模型,以便更好地理解和应用这些先进的技术。然而,高昂的GPU算力成本往往成为了阻碍大家探索的瓶颈。幸运的是,腾讯云Cloud Studio提供了免…

window保存好看的桌面壁纸

1、按下【WINR】快捷键调出“运行”窗口,输入以下命令后回车。 %localappdata%\Packages\Microsoft.Windows.ContentDeliveryManager_cw5n1h2txyewy\LocalState\Assets 2、依次点击【查看】【显示】,勾选【隐藏的项目】,然后按【CtrlA】全部…

android 的aab包

什么是 AAB (Android App Bundle)? AAB (Android App Bundle) 是 Google 推出的新一代 Android 应用发布格式,用于取代传统的 APK 格式。AAB 的全称是 Android App Bundle,扩展名为 .aab,它并不是直接可以安装的文件,…

【25考研】中科院软件考研复试难度分析!

中科院软件复试不需要上机!且对专业综合能力要求较高!提醒同学一定要认真复习! 一、复试内容 二、参考书目 官方并未明确给出,建议同学参考初试书目: 1)《数据结构(C语言版)》严蔚…

2014年蓝桥杯第五届CC++大学B组真题及代码

目录 1A:啤酒和饮料(填空)(枚举) 2B:切面条(填空) 3C:李白打酒(填空)(dfs) 4D:史丰收速算(代码…

目标跟踪之sort算法(3)

这里写目录标题 1 流程1 预处理2 跟踪 2 代码 参考:sort代码 https://github.com/abewley/sort 1 流程 1 预处理 1.1 获取离线检测数据。1.2 实例化跟踪器。2 跟踪 2.1 轨迹处理。根据上一帧的轨迹预测当前帧的轨迹,剔除到当前轨迹中为空的轨迹得到当前…

单片机基础模块学习——DS18B20温度传感器芯片

不知道该往哪走的时候,就往前走。 一、DS18B20芯片原理图 该芯片共有三个引脚,分别为 GND——接地引脚DQ——数据通信引脚VDD——正电源 数据通信用到的是1-Wier协议 优点:占用端口少,电路设计方便 同时该协议要求通过上拉电阻…

【精选】基于数据挖掘的招聘信息分析与市场需求预测系统 职位分析、求职者趋势分析 职位匹配、人才趋势、市场需求分析数据挖掘技术 职位需求分析、人才市场趋势预测

博主介绍: ✌我是阿龙,一名专注于Java技术领域的程序员,全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师,我在计算机毕业设计开发方面积累了丰富的经验。同时,我也是掘金、华为云、阿里云、InfoQ等平台…

文献阅读 250125-Accurate predictions on small data with a tabular foundation model

Accurate predictions on small data with a tabular foundation model Accurate predictions on small data with a tabular foundation model | Nature 使用一种基于表格的模型来对小型数据实现准确预测 ## Abstract: 基于其他列来填充标签列中缺失值的基本预测任务对于各种应…