Azure 存储队列通过 REST API c# 使用共享密钥身份验证

问题描述

我正在尝试使用 REST API 调用 Azure 存储队列,但出现错误

在 HTTP 请求中找到的 MAC 签名 'UCiypkoySXueF4scXt+EqQESf5VXmAVLJUA93+3W10M=' 与任何不一样 计算签名。服务器使用以下字符串进行签名:'POST 文本/纯文本

我的 C# 代码

  var Client = new HttpClient();
        var RequestDateString = DateTime.UtcNow.ToString("R",CultureInfo.InvariantCulture);

        if (Client.DefaultRequestHeaders.Contains("x-ms-date"))
            Client.DefaultRequestHeaders.Remove("x-ms-date");
        Client.DefaultRequestHeaders.Add("x-ms-date",RequestDateString);


        var StorageAccountName = "storaxxxxxxxsnd";
        var StorageKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx==";
        String urlPath = String.Format("{0}/messages","splitator");
        Uri uri = new Uri(string.Format("https://{0}.queue.core.windows.net/",StorageAccountName) + urlPath);

        if (Client.DefaultRequestHeaders.Contains("Authorization"))
            Client.DefaultRequestHeaders.Remove("Authorization");

        var canonicalizedStringToBuild = string.Format("{0}\n{1}",RequestDateString,$"/{StorageAccountName}/{uri.AbsolutePath.Trimstart('/')}");
        string signature;
        using (var hmac = new HMACSHA256(Convert.FromBase64String(StorageKey)))
        {
            byte[] dataToHmac = Encoding.UTF8.GetBytes(canonicalizedStringToBuild);
            signature = Convert.ToBase64String(hmac.ComputeHash(dataToHmac));
        }

        string authorizationHeader = string.Format($"{StorageAccountName}:" + signature);
        Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("SharedKey",authorizationHeader);
        Client.DefaultRequestHeaders.Accept.Clear();
        
        Client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/plain"));
        if (Client.DefaultRequestHeaders.Contains("x-ms-version"))
            Client.DefaultRequestHeaders.Remove("x-ms-version");

        Client.DefaultRequestHeaders.Add("x-ms-version","2015-12-11");

        // if (httpMethod == HttpMethod.Delete || httpMethod == HttpMethod.Put)
        //  {
        //    if (Client.DefaultRequestHeaders.Contains("If-Match"))
        //       Client.DefaultRequestHeaders.Remove("If-Match");
        // Currently I'm not using optimistic concurrency :-(
        try
        {
            //Client.DefaultRequestHeaders.Add("If-Match","*");
            var stringContent = new StringContent("TESTAUTH",Encoding.UTF8,"text/plain");
           var response= Client.PostAsync(uri,stringContent);
            var resu=response.Result;
            
        }
        catch(Exception ex)
        {

        }

我不确定我错过了什么。我尝试了各种组合,但都失败了。

我也尝试过微软推荐的 stringToSign 公式

I tried  using canonical headers too


        string signature;
     
        var stringTosign =  "POST\n" + "\n" + "\n" + "1024" + "\n" + "\n" + "text/plain\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + dateInRfc1123Format + "/xxxxxx/splitator/messages";

       var hmac = new HMACSHA256(Convert.FromBase64String(accountKey));
       var headerval= accountName + ":" + Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringTosign)));
        Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("SharedKey",headerval);
        Client.DefaultRequestHeaders.Accept.Clear();
    

解决方法

我修复了您代码中的问题,现在可以正常工作了。请试一试:

namespace ConsoleApp25
{
    class Program
    {
        static void Main(string[] args)
        {
            var Client = new HttpClient();
           
            var StorageAccountName = "yy1";
            var StorageKey = "xxxx";
            var apiversion = "2020-02-10";
            var queue_name = "myqueue2";
            String urlPath = String.Format("{0}/messages",queue_name);
            Uri uri = new Uri(string.Format("https://{0}.queue.core.windows.net/{1}",StorageAccountName,urlPath));

            //define a message to send
            string raw_message = "TESTAUTH is ok";

            //to send the message to the queue storage,the raw message must be formatted as below
            string queue_message = $"<QueueMessage><MessageText>{raw_message}</MessageText></QueueMessage>";

            //define the content type
            string content_type = "text/plain; charset=utf-8";

            //define date
            var RequestDateString = DateTime.UtcNow.ToString("R",CultureInfo.InvariantCulture);

            string StringToSign = String.Format("POST\n"
                + "\n" // content encoding
                + "\n" // content language
                + queue_message.Length + "\n" // content length
                + "\n" // content md5
                + content_type +"\n" // content type
                + "\n" // date
                + "\n" // if modified since
                + "\n" // if match
                + "\n" // if none match
                + "\n" // if unmodified since
                + "\n" // range
                + "x-ms-date:" + RequestDateString + "\nx-ms-version:" + apiversion + "\n" // headers
                + "/{0}/{1}/{2}",queue_name,"messages"); //url

            string auth = SignThis(StringToSign,StorageKey,StorageAccountName);

            //define authorization header
            if (Client.DefaultRequestHeaders.Contains("Authorization"))
                Client.DefaultRequestHeaders.Remove("Authorization");

            Client.DefaultRequestHeaders.Add("Authorization",auth);

            Client.DefaultRequestHeaders.Accept.Clear();

            //define x-ms-version header
            Client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/plain"));
            if (Client.DefaultRequestHeaders.Contains("x-ms-version"))
                Client.DefaultRequestHeaders.Remove("x-ms-version");

            Client.DefaultRequestHeaders.Add("x-ms-version",apiversion);

            //define the x-ms-date header
            if (Client.DefaultRequestHeaders.Contains("x-ms-date"))
                Client.DefaultRequestHeaders.Remove("x-ms-date");
            Client.DefaultRequestHeaders.Add("x-ms-date",RequestDateString);

            try
            {
                var stringContent = new StringContent(queue_message,Encoding.UTF8,"text/plain");
                var response = Client.PostAsync(uri,stringContent);
                var resu = response.Result;
               
            }
            catch (Exception ex)
            {

            }

            Console.WriteLine("**completed**");
            Console.ReadLine();
        }

        private static String SignThis(String StringToSign,string Key,string Account)
        {
            String signature = string.Empty;
            byte[] unicodeKey = Convert.FromBase64String(Key);
            using (HMACSHA256 hmacSha256 = new HMACSHA256(unicodeKey))
            {
                Byte[] dataToHmac = System.Text.Encoding.UTF8.GetBytes(StringToSign);
                signature = Convert.ToBase64String(hmacSha256.ComputeHash(dataToHmac));
            }

            String authorizationHeader = String.Format(
                  CultureInfo.InvariantCulture,"{0} {1}:{2}","SharedKey",Account,signature);

            return authorizationHeader;
        }

    }
}

如果您不想生成shared key,因为它并不容易,您可以在rest api中使用sas token进行身份验证。