C#调用AWS SIGV4签名保护的API时连接被远程主机关闭问题
问题背景
要在C#应用中调用AWS托管、需SIGV4签名校验的API,修改了AWS官方S3 SIGV4示例适配execute-api服务后,请求时报错:无法从传输连接读取数据:现有连接被远程主机强制关闭(英文原报错:Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host.)
原有实现代码
public class GetExecuteApiSample { static readonly string AWSAccessKey = ConfigurationManager.AppSettings["AWSAccessKey"]; static readonly string AWSSecretKey = ConfigurationManager.AppSettings["AWSSecretKey"]; public static void Run() { var endpointUri = string.Format("https://XXXXXX.execute-api.eu-west-1.amazonaws.com"); var uri = new Uri(endpointUri + "/api/v1/list-my-results"); // 简单GET请求无请求体,使用预计算的空哈希值 var headers = new Dictionary<string, string> { {AWS4SignerBase.X_Amz_Content_SHA256, AWS4SignerBase.EMPTY_BODY_SHA256}, {"content-type", "text/plain"} }; var signer = new AWS4SignerForAuthorizationHeader { EndpointUri = uri, HttpMethod = "GET", Service = "execute-api", Region = "eu-west-1" }; var authorization = signer.ComputeSignature(headers, "", // 无查询参数 AWS4SignerBase.EMPTY_BODY_SHA256, AWSAccessKey, AWSSecretKey); // 将计算得到的签名放入格式化的Authorization头中 headers.Add("Authorization", authorization); HttpHelpers.InvokeHttpRequest(uri, "GET", headers, null); } }
原有HttpHelpers工具类代码
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Text; namespace AWSSignatureV4_S3_Sample.Util { /// <summary> /// 各类Http辅助方法 /// </summary> public static class HttpHelpers { /// <summary> /// 向指定端点发起http请求 /// </summary> /// <param name="endpointUri"></param> /// <param name="httpMethod"></param> /// <param name="headers"></param> /// <param name="requestBody"></param> public static void InvokeHttpRequest(Uri endpointUri, string httpMethod, IDictionary<string, string> headers, string requestBody) { try { var request = ConstructWebRequest(endpointUri, httpMethod, headers); if (!string.IsNullOrEmpty(requestBody)) { var buffer = new byte[8192]; // 任意缓冲大小 var requestStream = request.GetRequestStream(); using (var inputStream = new MemoryStream(Encoding.UTF8.GetBytes(requestBody))) { var bytesRead = 0; while ((bytesRead = inputStream.Read(buffer, 0, buffer.Length)) > 0) { requestStream.Write(buffer, 0, bytesRead); } } } CheckResponse(request); } catch (WebException ex) { using (var response = ex.Response as HttpWebResponse) { if (response != null) { var errorMsg = ReadResponseBody(response); Console.WriteLine("\n-- HTTP调用失败,异常信息:'{0}',状态码:'{1}'", errorMsg, response.StatusCode); } } } } /// <summary> /// 构造指向指定端点的HttpWebRequest并填充请求头 /// </summary> /// <param name="endpointUri">待调用的端点</param> /// <param name="httpMethod">GET、PUT等请求方法</param> /// <param name="headers">应用到请求的头集合</param> /// <returns>初始化完成的HttpWebRequest实例</returns> public static HttpWebRequest ConstructWebRequest(Uri endpointUri, string httpMethod, IDictionary<string, string> headers) { var request = (HttpWebRequest)WebRequest.Create(endpointUri); request.Method = httpMethod; foreach (var header in headers.Keys) { // 并非所有头都可以通过字典直接设置 if (header.Equals("host", StringComparison.OrdinalIgnoreCase)) request.Host = headers[header]; else if (header.Equals("content-length", StringComparison.OrdinalIgnoreCase)) request.ContentLength = long.Parse(headers[header]); else if (header.Equals("content-type", StringComparison.OrdinalIgnoreCase)) request.ContentType = headers[header]; else request.Headers.Add(header, headers[header]); } return request; } public static void CheckResponse(HttpWebRequest request) { // 获取响应,读取所有响应体内容为字符串后输出 using (var response = (HttpWebResponse)request.GetResponse()) { if (response.StatusCode == HttpStatusCode.OK) { Console.WriteLine("\n-- HTTP调用成功"); var responseBody = ReadResponseBody(response); if (!string.IsNullOrEmpty(responseBody)) { Console.WriteLine("\n-- 响应体内容:"); Console.WriteLine(responseBody); } } else Console.WriteLine("\n-- HTTP调用失败,状态码:{0}", response.StatusCode); } } /// <summary> /// 读取服务调用的响应数据(如果有) /// </summary> /// <param name="response"> /// 前序请求返回的响应实例 /// </param> /// <returns>响应的正文内容</returns> public static string ReadResponseBody(HttpWebResponse response) { if (response == null) throw new ArgumentNullException("response", "值不能为空"); // 打开响应的读取器,将内容读取为字符串后返回给调用方 string responseBody = string.Empty; using (var responseStream = response.GetResponseStream()) { if (responseStream != null) { using (var reader = new StreamReader(responseStream)) { responseBody = reader.ReadToEnd(); } } } return responseBody; } /// <summary> /// 对规范化的头名称和值进行URL编码的辅助方法,确保可以安全地包含在预签名URL中 /// </summary> /// <param name="data">待编码的字符串</param> /// <param name="isPath">字符串是否为URL路径</param> /// <returns>编码后的字符串</returns> public static string UrlEncode(string data, bool isPath = false) { // RFC3986规定的合法URL字符集,该集合外的字符将被编码 const string validUrlCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~"; var encoded = new StringBuilder(data.Length * 2); string unreservedChars = String.Concat(validUrlCharacters, (isPath ? "/:" : "")); foreach (char symbol in System.Text.Encoding.UTF8.GetBytes(data)) { if (unreservedChars.IndexOf(symbol) != -1) encoded.Append(symbol); else encoded.Append("%").Append(String.Format("{0:X2}", (int)symbol)); } return encoded.ToString(); } } }
解决方案
该报错是TLS协议版本不兼容导致,AWS API Gateway默认要求使用TLS 1.2及以上版本,旧版.NET Framework默认的安全协议配置不包含TLS 1.2,触发远端强制断开连接,按以下步骤修改即可:
- 在构造
HttpWebRequest前指定安全协议版本,修改ConstructWebRequest方法:
public static HttpWebRequest ConstructWebRequest(Uri endpointUri, string httpMethod, IDictionary<string, string> headers) { // 新增指定安全协议为TLS 1.2,适配AWS API要求 ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; var request = (HttpWebRequest)WebRequest.Create(endpointUri); request.Method = httpMethod; // 原有逻辑保持不变 foreach (var header in headers.Keys) { if (header.Equals("host", StringComparison.OrdinalIgnoreCase)) request.Host = headers[header]; else if (header.Equals("content-length", StringComparison.OrdinalIgnoreCase)) request.ContentLength = long.Parse(headers[header]); else if (header.Equals("content-type", StringComparison.OrdinalIgnoreCase)) request.ContentType = headers[header]; else request.Headers.Add(header, headers[header]); } return request; }
如果项目是.NET Framework 4.5以下版本,无法直接使用Tls12枚举,可替换为数值(SecurityProtocolType)3072即可,建议优先升级到.NET Framework 4.5及以上版本。
2. 可选优化:GET请求不需要设置content-type头,可以移除对应的头配置,避免不必要的校验问题。
3. 权限校验:确认使用的AK/SK有对应API Gateway的execute-api:Invoke权限。
内容的提问来源于stack exchange,提问作者Giox
相关产品推荐
相关产品推荐

