You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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,触发远端强制断开连接,按以下步骤修改即可:

  1. 在构造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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 01:27:03