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

如何在C#中生成AWS ElastiCache IAM认证的正确签名?

.NET8连接ElastiCache IAM认证失败问题

报错信息

未处理的异常。StackExchange.Redis.RedisConnectionException: 无法连接到Redis服务器。认证失败;请检查密码(或客户端证书)配置是否正确:(RedisServerException) 错误:WRONGPASS 用户名-密码对无效或用户已禁用。

问题背景

  • 已通过Java示例成功连接目标ElastiCache集群,但基于sigv4示例自行编写的.NET8代码始终认证失败
  • 核心矛盾:使用相同凭证和时间戳生成IAM认证令牌时,.NET代码输出与Java代码结果不一致,怀疑是查询字符串处理或哈希机制存在问题
  • 排查发现原代码误用请求头格式处理认证信息,不符合ElastiCache IAM认证要求的查询字符串格式

测试代码

using Amazon.Runtime;
using Amazon.Runtime.CredentialManagement;
using StackExchange.Redis;
using System.Security.Cryptography;
using System.Text;
using System.Web;

var profile = Environment.GetEnvironmentVariable("AWS_PROFILE");
if (string.IsNullOrEmpty(profile) || !new CredentialProfileStoreChain().TryGetAWSCredentials(profile, out var credentials))
{
    credentials = new InstanceProfileAWSCredentials();
}

var username = Environment.GetEnvironmentVariable("USERNAME");
var cacheName = Environment.GetEnvironmentVariable("CACHE_NAME");
var region = Environment.GetEnvironmentVariable("AWS_REGION");
var cacheEndpoint = Environment.GetEnvironmentVariable("CACHE_ENDPOINT");
var cacheChannel = Environment.GetEnvironmentVariable("CACHE_CHANNEL");

var iamAuthTokenRequest = new IAMAuthTokenRequest(username!, cacheName!, region!);
var authToken = iamAuthTokenRequest.Get(credentials);
Console.WriteLine(authToken);

var options = new ConfigurationOptions()
{
    EndPoints =
    {
        { cacheEndpoint!, 6379 }
    },
    ChannelPrefix = RedisChannel.Literal(cacheChannel!),
    User = username,
    Password = authToken,
    Ssl = true,
};
var redis = ConnectionMultiplexer.Connect(options);
IDatabase db = redis.GetDatabase();
string value = "abcdefg";
db.StringSet("mykey", value);

public class IAMAuthTokenRequest
{
    private static readonly HttpMethod _requestMethod = HttpMethod.Get;
    private static readonly string _requestProtocol = "http://",
                                    _paramAction = "Action",
                                    _paramUser = "User",
                                    _actionName = "connect",
                                    _amzSecurityToken = "X-Amz-Security-Token",
                                    _amzAlgorithm = "X-Amz-Algorithm",
                                    _algorithm = "AWS4-HMAC-SHA256",
                                    _amzSignedHeaders = "X-Amz-SignedHeaders",
                                    _amzExpires = "X-Amz-Expires", 
                                    _amzCredential = "X-Amz-Credential",
                                    _amzDate = "X-Amz-Date",
                                    _amzSignature = "X-Amz-Signature",
                                    _serviceName = "elasticache";
    private static readonly int _expirySeconds = 900;

    private readonly string _userId, 
                            _cacheName, 
                            _region;

    public IAMAuthTokenRequest(string userId, string cacheName, string region)
    {
        _userId = userId;
        _cacheName = cacheName;
        _region = region;
    }

    public string Get(AWSCredentials credentials)
    {
        var now = DateTime.UtcNow;
        var headers = new Dictionary<string, string>()
        {
            { "host", _cacheName }
        };
        var immutableCredentials = credentials.GetCredentials();
        var uri = GetSignableRequest();
        var authorizationrequest = GetAuthorizationRequest(
            uri,
            now,
            _algorithm,
            headers,
            CalculateHash(""),
            immutableCredentials);

        return authorizationrequest;
    }

    private Uri GetSignableRequest() => new($"{_requestProtocol}{_cacheName}");

    private string GetAuthorizationRequest(
        Uri uri,
        DateTime now,
        string algorithm,
        Dictionary<string, string> headers,
        string payloadHash,
        ImmutableCredentials credentials)
    {
        var amzDate = ToAmzDate(now);
        var datestamp = now.ToString("yyyyMMdd");

        var credentialScope = $"{datestamp}/{_region}/{_serviceName}/aws4_request";
        var queryParams = QueryParams(
            Encode(credentials.Token),
            Encode($"{credentials.AccessKey}/{credentialScope}"),
            amzDate);

        // Create the string to sign
        var hashedCanonicalRequest = BuildCanonicalRequest(uri.AbsolutePath, queryParams, headers, payloadHash);
        var stringToSign = CanonicalRequest(algorithm, amzDate, credentialScope, hashedCanonicalRequest);

        // Sign the string
        var signingKey = GetSignatureKey(credentials.SecretKey, datestamp, _region, _serviceName);
        var signature = CalculateHmacHex(signingKey, stringToSign);

        var query = BuildQueryString(
            queryParams,
            signature);

        // return signing information
        return $"{uri.ToString().Replace("http://", "")}?{query}";
    }

    private static string Encode(string input) => HttpUtility.UrlEncode(input, Encoding.UTF8);

    private Dictionary<string, string> QueryParams(string securityToken, string credentials, string amzDate) =>
        new()
        {
            { _paramAction, _actionName },
            { _paramUser, _userId },
            { _amzAlgorithm, _algorithm },
            { _amzCredential, credentials },
            { _amzDate, amzDate },
            { _amzExpires, _expirySeconds.ToString("d") },
            { _amzSecurityToken, securityToken },
            { _amzSignedHeaders, "host" },
        };

    private static string BuildQueryString(Dictionary<string, string> queryParams, string signature)
    {
        queryParams.Add(_amzSignature, signature);
        return string.Join("&", queryParams.Select(pair => $"{pair.Key}={pair.Value}"));
    }

    private static string BuildCanonicalRequest(string uriPath, Dictionary<string, string> query, Dictionary<string, string> headers, string payloadHash)
    {
        var canonicalQuerystring = CanonicalDictionaryToString(query, "&", pair => $"{pair.Key.ToLowerInvariant()}={pair.Value.ToLowerInvariant()}");
        var canonicalHeaders = CanonicalDictionaryToString(headers, "\n", header => $"{header.Key.ToLowerInvariant()}:{header.Value.Trim().ToLowerInvariant()}") + "\n";
        var signedHeaders = CanonicalDictionaryToString(headers, ";", header => header.Key.ToLowerInvariant());

        var canonicalRequest = CanonicalRequest(
            _requestMethod.Method,
            uriPath,
            canonicalQuerystring,
            canonicalHeaders,
            signedHeaders,
            payloadHash);
        return CalculateHash(canonicalRequest);
    }

    private static string CanonicalRequest(params string[] parts) => string.Join("\n", parts);

    private static string ToAmzDate(DateTime date) => date.ToString("yyyyMMddTHHmmssZ");

    private static string CanonicalDictionaryToString(IDictionary<string, string> input, string join, Func<KeyValuePair<string, string>, string> selector) =>
        string.Join(join, new SortedDictionary<string, string>(input, StringComparer.OrdinalIgnoreCase).Select(selector));

    private static byte[] GetSignatureKey(string key, string dateStamp, string regionName, string serviceName)
    {
        var kSecret = Encoding.UTF8.GetBytes($"AWS4{key}");
        var kDate = HmacSha256(kSecret, dateStamp);
        var kRegion = HmacSha256(kDate, regionName);
        var kService = HmacSha256(kRegion, serviceName);
        return HmacSha256(kService, "aws4_request");
    }

    private static string CalculateHmacHex(byte[] key, string data)
    {
        var hash = HmacSha256(key, data);
        return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
    }

    private static byte[] HmacSha256(byte[] key, string data)
    {
        using var hmac = new HMACSHA256(key);
        return hmac.ComputeHash(Encoding.UTF8.GetBytes(data));
    }

    private static string CalculateHash(string data)
    {
        var hash = SHA256.HashData(Encoding.UTF8.GetBytes(data));
        return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
    }
}

内容的提问来源于stack exchange,提问作者APW

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 03:45:01