微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Azure Blob 存储授权

如何解决Azure Blob 存储授权

编辑 2:以下是授权错误输出

<?xml version=\"1.0\" encoding=\"utf-8\"?>
<Error>
  <Code>AuthenticationFailed</Code>
  <Message>Server Failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.\nRequestId:34d738a5-101e-000d-5a14-ed5956000000\nTime:2021-01-17T21:07:38.6231913Z
  </Message>
  <AuthenticationErrorDetail>Signature did not match. String to sign used was cw\n2021-01-17T19:06:42Z\n2021-01-18T21:06:42Z\n/blob/example-account/example-container/example-blob.json\n\n\nhttps\n2019-02-02\nb\n\n\n\n\n\n
  </AuthenticationErrorDetail>
</Error>

我不太明白...我更新了下面的 C# 代码以打印出带有 \n 字符的 string_to_sign,它与上面输出中的 string_to_sign 完全相同。

注意:从 Azure 存储生成的可工作的 SAS 令牌是帐户 SAS,而我生成的令牌是服务 SAS。在 Azure 存储中是否可以限制 Service SAS?

编辑:我尝试直接从 Azure 存储生成 SAS 令牌,这似乎确实有效。它似乎是一个帐户 SAS,而不是我在下面尝试使用的服务 SAS。

?sv=2019-12-12&ss=b&srt=o&sp=wac&se=2021-01-18T01:15:13Z&st=2021-01-17T17:15:13Z&spr=https&sig=<signature>

我希望能够使用其 REST API 将文件上传到 Azure 存储。但是,我在将其授权时遇到了一些麻烦。我发现文档有点矛盾,在某些地方它说我可以在 URI 中包含一个 SAS 令牌,在其他地方它在 Authorize 标头中。对于上下文,我尝试直接从 APIM 执行此操作,因此在下面的示例代码中,它是使用其有限的 API 编写的。这只是我用来生成授权字符串的一般概念,但我在使用它时不断收到 403(我不确定是否需要从 Azure 存储端执行某些操作)。

/**
 Based on https://docs.microsoft.com/en-us/rest/api/storageservices/create-service-sas
*/
using System;
using System.Collections.Generic;

namespace sas_token
{
  class Program
  {
    static void Main(string[] args)
    {
      string key = args[0];
      Console.WriteLine(generate_blob_sas_token(key));
    }

    public static string generate_blob_sas_token(string key)
    {
      const string canonicalizedResource = "canonicalizedResource";

      // NOTE: this only works for Blob type files,Tables have a different
      // structure
      // NOTE: use a List instead of Dictionary since the order of keys in
      // Dictionaries is undefined and the signature string requires a very
      // specific order
      List<keyvaluePair<string,string>> sas_token_properties = new List<keyvaluePair<string,string>>(){


    // signedPermissions,select 1..* from [racwdxltmeop],MUST be in that order
    new keyvaluePair<string,string>("sp","cw"),// signedStart time,date from when the token is valid
    // NOTE: because of clock skew between services,even setting the time to
    // Now may not create an immediately usable token
    new keyvaluePair<string,string>("st",DateTime.UtcNow.AddMinutes(-120).ToString("yyyy-MM-ddTHH:mm:ssZ")),// signedExpiry time,date until the token is valid
    new keyvaluePair<string,string>("se",DateTime.UtcNow.AddDays(1).ToString("yyyy-MM-ddTHH:mm:ssZ")),// canonicalizedResource,must be prefixed with /blob in recent versions
    // NOTE: this is NOT included as a query parameter,but is in the signature
    // URL = https://myaccount.blob.core.windows.net/music/intro.mp3
    // canonicalizedResource = "/blob/myaccount/music/intro.mp3"
    new keyvaluePair<string,string>(canonicalizedResource,"/blob/example-account/example-container"),// signedIdentifier,can be used to identify a Stored Access Policy
    new keyvaluePair<string,string>("si",""),// signedIP,single or range of allowed IP addresses
    new keyvaluePair<string,string>("sip",// signedProtocol
    // [http,https]
    new keyvaluePair<string,string>("spr","https"),// signedVersion,the version of SAS used (defines which keys are
    // required/available)
    new keyvaluePair<string,string>("sv","2019-02-02"),// signedResource,the type of resource the token is allowed to access
    // [b = blob,d = directory,c = container,bv,bs]
    new keyvaluePair<string,string>("sr","b"),// signedSnapshottime
    new keyvaluePair<string,string>("sst",// the following specify how the response should be formatted

    // Cache-Control
    new keyvaluePair<string,string>("rscc",// Content-disposition
    new keyvaluePair<string,string>("rscd",// content-encoding
    new keyvaluePair<string,string>("rsce",// Content-Language
    new keyvaluePair<string,string>("rscl",// Content-Type
    new keyvaluePair<string,string>("rsct","")
};

      // the format is a very specific text string,where values are delimited by new
      // lines,and the order of the properties in the string is important!
      List<string> values = new List<string>();
      foreach (keyvaluePair<string,string> entry in sas_token_properties)
      {
        values.Add(entry.Value);
      }
      string string_to_sign = string.Join("\n",new List<string>(values));
      Console.WriteLine(string_to_sign.Replace("\n","\\n"));
      System.Security.Cryptography.HMACSHA256 hmac = new System.Security.Cryptography.HMACSHA256(System.Text.Encoding.UTF8.GetBytes(key));
      var signature = System.Convert.ToBase64String(hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(string_to_sign)));

      // create the query parameters of any set values + the signature
      // NOTE: all properties that contribute to the signature must be added
      // as query params EXCEPT canonicalizedResource
      List<string> parameters = new List<string>();

      foreach (keyvaluePair<string,string> entry in sas_token_properties)
      {
        if (!string.IsNullOrEmpty(entry.Value) && entry.Key != canonicalizedResource)
        {
          parameters.Add(entry.Key + "=" + System.Net.WebUtility.UrlEncode(entry.Value));
        }
      }

      parameters.Add("sig=" + System.Net.WebUtility.UrlEncode(signature));

      string sas_token_querystring = string.Join("&",parameters);

      return sas_token_querystring;
    }
  }
}

我使用以下(简化的)APIM 策略中的输出(我将“sas_token”变量设置为函数输出以测试流程):

<set-variable name="x-request-body" value="@(context.Request.Body.As<string>())" />
<send-request mode="new" response-variable-name="tokenstate" timeout="20" ignore-error="true">
   <set-url>@("https://example-account.blob.core.windows.net/example-container/test.json")</set-url>
   <set-method>PUT</set-method>
   <set-header name="x-ms-date" exists-action="override">
     <value>@(DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"))</value>
   </set-header>
   <set-header name="x-ms-version" exists-action="override">
     <value>2019-02-02</value>
   </set-header>
   <set-header name="x-ms-blob-type" exists-action="override">
     <value>BlockBlob</value>
   </set-header>
   <set-header name="Authorization" exists-action="override">
     <value>@("SharedAccessSignature " + (string)context.Variables["sas_token"])</value>
   </set-header>
   <set-body>@((string)context.Variables["x-request-body"])</set-body>
</send-request>

为了完整起见,以下是我使用 {"hello": "then"} 跟踪测试请求时 APIM 的结果:

{
    "message": "Request is being forwarded to the backend service. Timeout set to 20 seconds","request": {
        "method": "PUT","url": "https://example-account.blob.core.windows.net/example-container/test.json","headers": [
            {
                "name": "Host","value": "example-account.blob.core.windows.net"
            },{
                "name": "Content-Length","value": 17
            },{
                "name": "x-ms-date","value": "2021-01-17T16:53:28Z"
            },{
                "name": "x-ms-version","value": "2019-02-02"
            },{
                "name": "x-ms-blob-type","value": "BlockBlob"
            },{
                "name": "Authorization","value": "SharedAccessSignature sp=cw&st=2021-01-17T13%3A42%3A02Z&se=2021-01-18T15%3A42%3A02Z&spr=https&sv=2019-02-02&sr=b&sig=<signature>"
            },{
                "name": "X-Forwarded-For","value": "205.193.94.40"
            }
        ]
    }
}
send-request (92.315 ms)
{
    "response": {
        "status": {
            "code": 403,"reason": "Server Failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature."
        },"headers": [
            {
                "name": "x-ms-request-id","value": "185d86f5-601e-0038-5cf1-ec3542000000"
            },"value": "321"
            },{
                "name": "Content-Type","value": "application/xml"
            },{
                "name": "Date","value": "Sun,17 Jan 2021 16:53:28 GMT"
            },{
                "name": "Server","value": "Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0"
            }
        ]
    }
}

另外,对 C# 来说仍然是新手,所以如果可以做得更好,请告诉我。

解决方法

我认为您不能在 Authorization 标头中放置 SAS 令牌。我找不到任何相关的样本,所以我使用了 使用来自 NuGet 的 Azure.Storage.Blob C# 客户端库来执行此操作

var data = System.Text.Encoding.UTF8.GetBytes("Hello Azure Storage");

var keyCred = new StorageSharedKeyCredential(account,key);
var sasBuilder = new AccountSasBuilder()
{
    Services = AccountSasServices.Blobs,ResourceTypes = AccountSasResourceTypes.Object,ExpiresOn = DateTimeOffset.UtcNow.AddHours(1),Protocol = SasProtocol.Https
};
sasBuilder.SetPermissions(AccountSasPermissions.All);
var sasToken = sasBuilder.ToSasQueryParameters(keyCred).ToString();

var blobClient = new BlobServiceClient(new Uri($"https://{account}.blob.core.windows.net/?{sasToken}"),null);

var containter = blobClient.GetBlobContainerClient("test");
containter.UploadBlob("test.txt",new MemoryStream(data));

像这样生成一个 HTTP 请求:

PUT https://xxxxxx.blob.core.windows.net/test/test.txt?sv=2020-04-08&ss=b&srt=o&spr=https&se=2021-01-17T18%3A13%3A55Z&sp=rwdxlacuptf&sig=RI9It3O6mcmw********S%2B1r91%2Bj5zGbk%3D HTTP/1.1
Host: xxxxxx.blob.core.windows.net
x-ms-blob-type: BlockBlob
x-ms-version: 2020-04-08
If-None-Match: *
x-ms-client-request-id: c6e93312-af95-4a04-a207-2e2062b1dd26
x-ms-return-client-request-id: true
User-Agent: azsdk-net-Storage.Blobs/12.8.0 (.NET Core 3.1.10; Microsoft Windows 10.0.19042)
Request-Id: |ffa2da23-45c79d128da40651.
Content-Length: 19

Hello Azure Storage

然后在 WebClient 中直接使用 SAS 令牌,

var wc = new WebClient();
wc.Headers.Add("x-ms-blob-type: BlockBlob");
wc.UploadData($"https://{account}.blob.core.windows.net/test/test2.txt?{sasToken}","PUT",data);

也可以,这应该是最低要求:

PUT https://xxxxx.blob.core.windows.net/test/test2.txt?sv=2020-04-08&ss=b&srt=o&spr=https&se=2021-01-17T18%3A50%3A01Z&sp=rwdxlacuptf&sig=Fj4QVfwIfjXP10G%xxxxxxxx%2FF%2FcjikizKggY%3D HTTP/1.1
Host: xxxx.blob.core.windows.net
x-ms-blob-type: BlockBlob
Connection: Keep-Alive
Content-Length: 19

Hello Azure Storage

删除 x-ms-blob-type 标头失败:

远程服务器返回错误:(400) 一个 HTTP 标头是 未指定此请求的必填项..

您可以在 GitHub 上随意浏览源代码以了解更多详细信息。

,

感谢 David 的帮助确认这是我的错误,我错误地转换了密钥以生成 HMAC。下面是正确的代码,注意 Base64 解码,而最初我只是获取字节数组:

string string_to_sign = string.Join("\n",new List<string>(values));
Console.WriteLine(string_to_sign.Replace("\n","\\n"));
System.Security.Cryptography.HMACSHA256 hmac = new System.Security.Cryptography.HMACSHA256(System.Convert.FromBase64String(key));
var signature = System.Convert.ToBase64String(hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(string_to_sign)));

然后我可以像在 APIM 政策中那样使用它:

<set-variable name="x-request-body" value="@(context.Request.Body.As<string>())" />
<send-request mode="new" response-variable-name="tokenstate" timeout="20" ignore-error="true">
   <set-url>@(string.Format("https://example-account.blob.core.windows.net/example-container/test.json?{0}",context.Variables["sas_token"]))</set-url>
   <set-method>PUT</set-method>
   <set-header name="x-ms-date" exists-action="override">
     <value>@(DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"))</value>
   </set-header>
   <set-header name="x-ms-version" exists-action="override">
     <value>2019-02-02</value>
   </set-header>
   <set-header name="x-ms-blob-type" exists-action="override">
     <value>BlockBlob</value>
   </set-header>
   <set-body>@((string)context.Variables["x-request-body"])</set-body>
</send-request>
,

Azure Storage 支持以下授权方式:

enter image description here

但是 SAS 令牌不能作为 REST API 的授权头。

https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-requests-to-azure-storage

我封装了几种认证方式:

final options1 = [
'Option 1','Option 2','Option 3'
]

虽然很多软件包和azure的交互都是基于REST API的,但是对于上传blob之类的操作,我不建议你使用rest api来完成。 Azure官方提供了很多打包好的可以直接使用的包,比如:

https://docs.microsoft.com/en-us/dotnet/api/azure.storage.blobs?view=azure-dotnet

.Net 示例:

https://docs.microsoft.com/en-us/azure/storage/blobs/storage-quickstart-blobs-dotnet

在上述 SDK 中,您可以使用 sas 令牌进行身份验证。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。