通过共享密钥授权调用 REST API 操作Call REST API operations with Shared Key authorization

本文介绍如何调用 Azure 存储 REST API,包括如何构建授权标头。This article shows you how to call the Azure Storage REST APIs, including how to form the Authorization header. 本文内容是从对 REST 无甚了解、而且也不知道如何进行 REST 调用的开发人员角度编写的。It's written from the point of view of a developer who knows nothing about REST and no idea how to make a REST call. 了解如何调用 REST 操作后,即可利用这一知识使用任何其他的 Azure 存储 REST 操作。After you learn how to call a REST operation, you can leverage this knowledge to use any other Azure Storage REST operations.

先决条件Prerequisites

示例应用程序列出了存储帐户的 blob 容器。The sample application lists the blob containers for a storage account. 若要尝试本文中的代码,需准备以下各项:To try out the code in this article, you need the following items:

  • 安装 Visual Studio 2019(包含 Azure 开发 工作负荷)。Install Visual Studio 2019 with the Azure development workload.

  • Azure 订阅。An Azure subscription. 如果没有 Azure 订阅,可在开始前创建一个试用帐户If you don't have an Azure subscription, create a trial account before you begin.

  • 通用存储帐户。A general-purpose storage account. 如果还没有存储帐户,请参阅创建存储帐户If you don't yet have a storage account, see Create a storage account.

  • 本文中将举例说明如何列出存储帐户中的容器。The example in this article shows how to list the containers in a storage account. 若要查看输出,请在开始之前,将一些容器添加到存储帐户中的 blob 存储。To see output, add some containers to blob storage in the storage account before you start.

下载示例应用程序Download the sample application

该示例应用程序是以 C# 编写的控制台应用程序。The sample application is a console application written in C#.

使用 git 可将应用程序的副本下载到开发环境。Use git to download a copy of the application to your development environment.

git clone https://github.com/Azure-Samples/storage-dotnet-rest-api-with-auth.git

此命令会将存储库克隆到本地 git 文件夹。This command clones the repository to your local git folder. 若要打开 Visual Studio 解决方案,请找到 storage-dotnet-rest-api-with-auth 文件夹并打开,然后双击 StorageRestApiAuth.sln。To open the Visual Studio solution, look for the storage-dotnet-rest-api-with-auth folder, open it, and double-click on StorageRestApiAuth.sln.

关于 RESTAbout REST

REST 表示 representational state transfer(表述性状态转移)。REST stands for representational state transfer. 有关具体定义,请参阅 WikipediaFor a specific definition, check out Wikipedia.

REST 是一种体系结构,用于通过 Internet 协议(例如 HTTP/HTTPS)与服务交互。REST is an architecture that enables you to interact with a service over an internet protocol, such as HTTP/HTTPS. REST 独立于在服务器或客户端上运行的软件。REST is independent of the software running on the server or the client. 可以从任何支持 HTTP/HTTPS 的平台调用 REST API。The REST API can be called from any platform that supports HTTP/HTTPS. 你可以编写一个在 Mac、Windows、Linux、Android 手机或平板电脑、iPhone、iPod 或网站上运行的应用程序,并为所有这些平台使用相同的 REST API。You can write an application that runs on a Mac, Windows, Linux, an Android phone or tablet, iPhone, iPod, or web site, and use the same REST API for all of those platforms.

调用 REST API 的操作包含由客户端发出请求,以及由服务返回响应。A call to the REST API consists of a request, which is made by the client, and a response, which is returned by the service. 在请求中,你发送一个 URL,其中说明了你要调用哪个操作、要在其上执行操作的资源、任何查询参数和标头,以及数据的有效负载(取决于已调用的操作)。In the request, you send a URL with information about which operation you want to call, the resource to act upon, any query parameters and headers, and depending on the operation that was called, a payload of data. 服务的响应包括状态代码、一组响应标头,以及数据的有效负载(取决于已调用的操作)。The response from the service includes a status code, a set of response headers, and depending on the operation that was called, a payload of data.

关于示例应用程序About the sample application

示例应用程序列出了存储帐户中的容器。The sample application lists the containers in a storage account. 一旦了解 REST API 文档中的信息如何关联到实际代码后,其他 REST 调用将更容易理解。Once you understand how the information in the REST API documentation correlates to your actual code, other REST calls are easier to figure out.

若参阅 Blob 服务 REST API,你将会了解到所有可以在 blob 存储中执行的操作。If you look at the Blob Service REST API, you see all of the operations you can perform on blob storage. 存储客户端库是 REST API 的包装器 – 它们可使你轻松访问存储而无需直接使用 REST API。The storage client libraries are wrappers around the REST APIs – they make it easy for you to access storage without using the REST APIs directly. 但如上所述,有时你会想要使用 REST API 而不是存储客户端库。But as noted above, sometimes you want to use the REST API instead of a storage client library.

“列出容器”操作List Containers operation

查看 ListContainers 操作的参考。Review the reference for the ListContainers operation. 该信息可以让你了解请求中某些字段的出处并进行响应。This information will help you understand where some of the fields come from in the request and response.

请求方法:GET。Request Method: GET. 此谓词是你指定为请求对象属性的 HTTP 方法。This verb is the HTTP method you specify as a property of the request object. 此谓词的其他值包括 HEAD、PUT 和 DELETE,具体将取决于正在调用的 API。Other values for this verb include HEAD, PUT, and DELETE, depending on the API you are calling.

请求 URIhttps://myaccount.blob.core.chinacloudapi.cn/?comp=listRequest URI: https://myaccount.blob.core.chinacloudapi.cn/?comp=list.   请求 URI 是从 blob 存储帐户终结点 https://myaccount.blob.core.chinacloudapi.cn 和资源字符串 /?comp=list 创建的。The request URI is created from the blob storage account endpoint https://myaccount.blob.core.chinacloudapi.cn and the resource string /?comp=list.

URI 参数:调用 ListContainers 时还可以使用其他查询参数。URI parameters: There are additional query parameters you can use when calling ListContainers. 其中有些参数为调用超时 (以秒计)和前缀 ,后者用于筛选。A couple of these parameters are timeout for the call (in seconds) and prefix, which is used for filtering.

另一个有用参数是 maxresults: ,如果可用容器超过此值,则响应正文将包含一个 NextMarker 元素,指示要在下一个请求中返回的下一个容器。Another helpful parameter is maxresults: if more containers are available than this value, the response body will contain a NextMarker element that indicates the next container to return on the next request. 若要使用此功能,可提供 NextMarker 值,作为发出下一个请求时 URI 中的 marker 参数。To use this feature, you provide the NextMarker value as the marker parameter in the URI when you make the next request. 使用此功能时,它类似于通过结果进行分页。When using this feature, it is analogous to paging through the results.

若要使用其他参数,请将它们追加到带有值的资源字符串,如下例所示:To use additional parameters, append them to the resource string with the value, like this example:

/?comp=list&timeout=60&maxresults=100

请求标头: 本部分列出了必需和可选的请求标头。Request Headers: This section lists the required and optional request headers. 至少需要三个标头:Authorization 标头、x-ms-date (包含请求的 UTC 时间)和 x-ms-version (指定要使用的 REST API 版本)。Three of the headers are required: an Authorization header, x-ms-date (contains the UTC time for the request), and x-ms-version (specifies the version of the REST API to use). 可以选择将 x-ms-client-request-id 包含在标头中 – 可以将此字段的值设置为任何内容;该值将在启用日志记录时写入存储分析日志。Including x-ms-client-request-id in the headers is optional – you can set the value for this field to anything; it is written to the storage analytics logs when logging is enabled.

请求正文: ListContainers 没有请求正文。Request Body: There is no request body for ListContainers. 上传 blob 时,会在所有 PUT 操作上使用请求正文,以及 SetContainerAccessPolicy,以允许在要应用的存储访问策略的 XML 列表中发送 blob。Request Body is used on all of the PUT operations when uploading blobs, as well as SetContainerAccessPolicy, which allows you to send in an XML list of stored access policies to apply. 有关存储访问策略,将在使用共享访问签名 (SAS) 一文中展开讨论。Stored access policies are discussed in the article Using Shared Access Signatures (SAS).

响应状态代码: 告知你需要知道的任何状态代码。Response Status Code: Tells of any status codes you need to know. 在此示例中,HTTP 状态代码可以是 200。In this example, an HTTP status code of 200 is ok. 有关 HTTP 状态代码的完整列表,请参阅状态代码定义For a complete list of HTTP status codes, check out Status Code Definitions. 若要查看特定于存储 REST API 的错误代码,请参阅常见的 REST API 错误代码To see error codes specific to the Storage REST APIs, see Common REST API error codes

响应标头: 其中包括 Content Type ;x-ms-request-id (传入的请求 ID);x-ms-version (指示所使用的 Blob 服务的版本)和 Date (UTC,告知发出请求的时间)。Response Headers: These include Content Type; x-ms-request-id, which is the request ID you passed in; x-ms-version, which indicates the version of the Blob service used; and the Date, which is in UTC and tells what time the request was made.

响应正文:此字段是提供请求数据的 XML 结构。Response Body: This field is an XML structure providing the data requested. 在此示例中,响应是容器及其属性的列表。In this example, the response is a list of containers and their properties.

创建 REST 请求Creating the REST request

为了确保在生产中运行时的安全,请始终使用 HTTPS 而不是 HTTP。For security when running in production, always use HTTPS rather than HTTP. 出于本次练习的目的,应使用 HTTP 以便查看请求和响应数据。For the purposes of this exercise, you should use HTTP so you can view the request and response data. 若要查看实际 REST 调用中的请求和响应信息,可以下载 Fiddler 或类似应用。To view the request and response information in the actual REST calls, you can download Fiddler or a similar application. 在 Visual Studio 解决方案中,存储帐户名称和密钥是在类中硬编码的。In the Visual Studio solution, the storage account name and key are hardcoded in the class. ListContainersAsyncREST 方法会将存储帐户名称和存储帐户密钥传递给用于创建 REST 请求各个组件的方法。The ListContainersAsyncREST method passes the storage account name and storage account key to the methods that are used to create the various components of the REST request. 在实际应用中,存储帐户名称和密钥将驻留在配置文件、环境变量中,或从 Azure Key Vault 中检索。In a real world application, the storage account name and key would reside in a configuration file, environment variables, or be retrieved from an Azure Key Vault.

在我们的示例项目中,用于创建授权标头的代码位于单独的类中。In our sample project, the code for creating the Authorization header is in a separate class. 这样做是为了让你可以获取整个类并将其添加到你自己的解决方案中,然后“按原样”使用。The idea is that you could take the whole class and add it to your own solution and use it "as is." 授权标头代码适用于 Azure 存储的大多数 REST API 调用。The Authorization header code works for most REST API calls to Azure Storage.

要生成请求(这是一个 HttpRequestMessage 对象),请转到 Program.cs 中的 ListContainersAsyncREST。To build the request, which is an HttpRequestMessage object, go to ListContainersAsyncREST in Program.cs. 用于生成请求的步骤如下:The steps for building the request are:

  • 创建要用于调用服务的 URI。Create the URI to be used for calling the service.
  • 创建 HttpRequestMessage 对象并设置有效负载。Create the HttpRequestMessage object and set the payload. 有效负载对于 ListContainersAsyncREST 为 null,因为我们未传入任何内容。The payload is null for ListContainersAsyncREST because we're not passing anything in.
  • 添加 x-ms-date 和 x-ms-version 的请求标头。Add the request headers for x-ms-date and x-ms-version.
  • 获取授权标头并添加。Get the authorization header and add it.

你需要一些基本信息:Some basic information you need:

  • 对于 ListContainers,方法 是 GETFor ListContainers, the method is GET. 在实例化请求时设置此值。This value is set when instantiating the request.
  • 资源 是指示正在调用的 API 的 URI 查询部分,因此,值为 /?comp=listThe resource is the query portion of the URI that indicates which API is being called, so the value is /?comp=list. 如前文所述,该资源位于显示有关 ListContainers API 信息的参考文档页上。As noted earlier, the resource is on the reference documentation page that shows the information about the ListContainers API.
  • URI 是通过为该存储帐户创建 Blob 服务终结点并连结该资源而构建的。The URI is constructed by creating the Blob service endpoint for that storage account and concatenating the resource. 请求 URI 的值最终为 http://contosorest.blob.core.chinacloudapi.cn/?comp=listThe value for request URI ends up being http://contosorest.blob.core.chinacloudapi.cn/?comp=list.
  • 对于 ListContainers,requestBody 为 null 并且没有任何额外标头 。For ListContainers, requestBody is null and there are no extra headers.

不同 API 可能有其他参数传入,如 ifMatch 。Different APIs may have other parameters to pass in such as ifMatch. 你可能使用 ifMatch 的一个示例是调用 PutBlob 时。An example of where you might use ifMatch is when calling PutBlob. 在这种情况下,将 ifMatch 设置为 eTag,如果你提供的 eTag 与 blob 上的当前 eTag 匹配,那么它只更新 blob。In that case, you set ifMatch to an eTag, and it only updates the blob if the eTag you provide matches the current eTag on the blob. 如果其他人自检索 eTag 后已更新 blob,则其更改不会被重写。If someone else has updated the blob since retrieving the eTag, their change won't be overridden.

首先,设置 uripayloadFirst, set the uri and the payload.

// Construct the URI. It will look like this:
//   https://myaccount.blob.core.chinacloudapi.cn/resource
String uri = string.Format("http://{0}.blob.core.chinacloudapi.cn?comp=list", storageAccountName);

// Provide the appropriate payload, in this case null.
//   we're not passing anything in.
Byte[] requestPayload = null;

接下来,实例化请求,将方法设置为 GET 并提供 URI。Next, instantiate the request, setting the method to GET and providing the URI.

// Instantiate the request message with a null payload.
using (var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, uri)
{ Content = (requestPayload == null) ? null : new ByteArrayContent(requestPayload) })
{

添加 x-ms-datex-ms-version 的请求标头。Add the request headers for x-ms-date and x-ms-version. 此代码中的这个位置也是你在其中添加调用所需的任何其他请求标头的位置。This place in the code is also where you add any additional request headers required for the call. 在此示例中,没有其他标头。In this example, there are no additional headers. 例如,“设置容器 ACL”操作是一个 API,它传入额外的标头。An example of an API that passes in extra headers is the Set Container ACL operation. 此 API 调用会添加名为“x-ms-blob-public-acces”的标头和访问级别的值。This API call adds a header called "x-ms-blob-public-access" and the value for the access level.

// Add the request headers for x-ms-date and x-ms-version.
DateTime now = DateTime.UtcNow;
httpRequestMessage.Headers.Add("x-ms-date", now.ToString("R", CultureInfo.InvariantCulture));
httpRequestMessage.Headers.Add("x-ms-version", "2017-07-29");
// If you need any additional headers, add them here before creating
//   the authorization header.

调用创建授权标头的方法,并将其添加到请求标头。Call the method that creates the authorization header and add it to the request headers. 你将在本文的后面部分了解如何创建授权标头。You'll see how to create the authorization header later in the article. 方法名称为 GetAuthorizationHeader,你可以在此代码段中看到:The method name is GetAuthorizationHeader, which you can see in this code snippet:

// Get the authorization header and add it.
httpRequestMessage.Headers.Authorization = AzureStorageAuthenticationHelper.GetAuthorizationHeader(
    storageAccountName, storageAccountKey, now, httpRequestMessage);

此时,httpRequestMessage 包含 REST 请求,并带有授权标头。At this point, httpRequestMessage contains the REST request complete with the authorization headers.

发送请求Send the request

构造请求后,可以调用 SendAsync 方法将其发送到 Azure 存储。Now that you have constructed the request, you can call the SendAsync method to send it to Azure Storage. 检查响应状态代码的值是否为 200,该代码意味着操作已成功。Check that the value of the response status code is 200, meaning that the operation has succeeded. 接下来,分析响应。Next, parse the response. 在这种情况下,你将获取到一个容器的 XML 列表。In this case, you get an XML list of containers. 让我们看一下调用 GetRESTRequest 方法以创建请求、执行请求的代码,然后检查对容器列表的响应。Let's look at the code for calling the GetRESTRequest method to create the request, execute the request, and then examine the response for the list of containers.

    // Send the request.
    using (HttpResponseMessage httpResponseMessage =
      await new HttpClient().SendAsync(httpRequestMessage, cancellationToken))
    {
        // If successful (status code = 200),
        //   parse the XML response for the container names.
        if (httpResponseMessage.StatusCode == HttpStatusCode.OK)
        {
            String xmlString = await httpResponseMessage.Content.ReadAsStringAsync();
            XElement x = XElement.Parse(xmlString);
            foreach (XElement container in x.Element("Containers").Elements("Container"))
            {
                Console.WriteLine("Container name = {0}", container.Element("Name").Value);
            }
        }
    }
}

如果在调用 SendAsync 时运行网络探测器(如 Fiddler),则会看到请求和响应信息。If you run a network sniffer such as Fiddler when making the call to SendAsync, you can see the request and response information. 让我们一起看一下。Let's take a look. 存储帐户的名称是 contosorest 。The name of the storage account is contosorest.

请求:Request:

GET /?comp=list HTTP/1.1

请求标头: Request Headers:

x-ms-date: Thu, 16 Nov 2017 23:34:04 GMT
x-ms-version: 2014-02-14
Authorization: SharedKey contosorest:1dVlYJWWJAOSHTCPGiwdX1rOS8B4fenYP/VrU0LfzQk=
Host: contosorest.blob.core.chinacloudapi.cn
Connection: Keep-Alive

执行后返回的状态代码和响应标头: Status code and response headers returned after execution:

HTTP/1.1 200 OK
Content-Type: application/xml
Server: Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0
x-ms-request-id: 3e889876-001e-0039-6a3a-5f4396000000
x-ms-version: 2017-07-29
Date: Fri, 17 Nov 2017 00:23:42 GMT
Content-Length: 1511

响应正文 (XML): 对于“列出容器”操作,此项会显示容器及其属性的列表。Response body (XML): For the List Containers operation, this shows the list of containers and their properties.

<?xml version="1.0" encoding="utf-8"?>
<EnumerationResults
  ServiceEndpoint="http://contosorest.blob.core.chinacloudapi.cn/">
  <Containers>
    <Container>
      <Name>container-1</Name>
      <Properties>
        <Last-Modified>Thu, 16 Mar 2017 22:39:48 GMT</Last-Modified>
        <Etag>"0x8D46CBD5A7C301D"</Etag>
        <LeaseStatus>unlocked</LeaseStatus>
        <LeaseState>available</LeaseState>
      </Properties>
    </Container>
    <Container>
      <Name>container-2</Name>
      <Properties>
        <Last-Modified>Thu, 16 Mar 2017 22:40:50 GMT</Last-Modified>
        <Etag>"0x8D46CBD7F49E9BD"</Etag>
        <LeaseStatus>unlocked</LeaseStatus>
        <LeaseState>available</LeaseState>
      </Properties>
    </Container>
    <Container>
      <Name>container-3</Name>
      <Properties>
        <Last-Modified>Thu, 16 Mar 2017 22:41:10 GMT</Last-Modified>
        <Etag>"0x8D46CBD8B243D68"</Etag>
        <LeaseStatus>unlocked</LeaseStatus>
        <LeaseState>available</LeaseState>
      </Properties>
    </Container>
    <Container>
      <Name>container-4</Name>
      <Properties>
        <Last-Modified>Thu, 16 Mar 2017 22:41:25 GMT</Last-Modified>
        <Etag>"0x8D46CBD93FED46F"</Etag>
        <LeaseStatus>unlocked</LeaseStatus>
        <LeaseState>available</LeaseState>
        </Properties>
      </Container>
      <Container>
        <Name>container-5</Name>
        <Properties>
          <Last-Modified>Thu, 16 Mar 2017 22:41:39 GMT</Last-Modified>
          <Etag>"0x8D46CBD9C762815"</Etag>
          <LeaseStatus>unlocked</LeaseStatus>
          <LeaseState>available</LeaseState>
        </Properties>
      </Container>
  </Containers>
  <NextMarker />
</EnumerationResults>

现在,你已了解如何创建请求、调用服务和分析结果,接下来我们来看下如何创建授权标头。Now that you understand how to create the request, call the service, and parse the results, let's see how to create the authorization header. 创建标头比较复杂,但好消息是,代码一旦运行成功,它将适用于所有存储服务 REST API。Creating that header is complicated, but the good news is that once you have the code working, it works for all of the Storage Service REST APIs.

创建授权标头Creating the authorization header

提示

现在,Azure 存储支持将 Azure Active Directory (Azure AD) 集成用于 blob 和队列。Azure Storage now supports Azure Active Directory (Azure AD) integration for blobs and queues. Azure AD 提供更简单的 Azure 存储请求授权体验。Azure AD offers a much simpler experience for authorizing a request to Azure Storage. 有关如何使用 Azure AD 授权 REST 操作的详细信息,请参阅使用 Azure Active Directory 进行授权For more information on using Azure AD to authorize REST operations, see Authorize with Azure Active Directory. 有关 Azure AD 与 Azure 存储集成的概述,请参阅使用 Azure Active Directory 对 Azure 存储的访问权限进行身份验证For an overview of Azure AD integration with Azure Storage, see Authenticate access to Azure Storage using Azure Active Directory.

有一篇文章从概念上(无代码)说明了如何授权对 Azure 存储的请求There is an article that explains conceptually (no code) how to Authorize requests to Azure Storage.

让我们就基于此篇文章准确提取所需的内容并显示代码。Let's distill that article down to exactly is needed and show the code.

首先,使用共享密钥授权。First, use Shared Key authorization. 授权标头格式如下所示:The authorization header format looks like this:

Authorization="SharedKey <storage account name>:<signature>"  

签名字段是基于哈希的消息身份验证代码 (HMAC),该代码通过请求创建并使用 SHA256 算法计算而得,然后使用 Base64 编码进行编码。The signature field is a Hash-based Message Authentication Code (HMAC) created from the request and calculated using the SHA256 algorithm, then encoded using Base64 encoding. 是否明白了?Got that? (不要急,你还没有听说过“规范化” 一词。)(Hang in there, you haven't even heard the word canonicalized yet.)

此代码段演示了共享密钥签名字符串的格式:This code snippet shows the format of the Shared Key signature string:

StringToSign = VERB + "\n" +  
               Content-Encoding + "\n" +  
               Content-Language + "\n" +  
               Content-Length + "\n" +  
               Content-MD5 + "\n" +  
               Content-Type + "\n" +  
               Date + "\n" +  
               If-Modified-Since + "\n" +  
               If-Match + "\n" +  
               If-None-Match + "\n" +  
               If-Unmodified-Since + "\n" +  
               Range + "\n" +  
               CanonicalizedHeaders +  
               CanonicalizedResource;  

其中大部分字段都很少用到。Most of these fields are rarely used. 对于 Blob 存储,你可以指定谓词、md5、内容长度、规范化标头和规范化资源。For Blob storage, you specify VERB, md5, content length, Canonicalized Headers, and Canonicalized Resource. 可以将其他内容留空(但将其置于 \n 中,使其知道它们为空)。You can leave the others blank (but put in the \n so it knows they are blank).

规范化标头和规范化资源有哪些?What are CanonicalizedHeaders and CanonicalizedResource? 问得好。Good question. 事实上,规范化究竟是什么意思?In fact, what does canonicalized mean? Microsoft Word 甚至无法将其作为一个词识别。Microsoft Word doesn't even recognize it as a word. 下面是 Wikipedia 有关标准化的介绍:在计算机科学中,标准化(有时称为规范化或正则化)是将有多种可能表示形式的数据转换为“标准”、“常规”,或规范格式的过程 。Here's what Wikipedia says about canonicalization: In computer science, canonicalization (sometimes standardization or normalization) is a process for converting data that has more than one possible representation into a "standard", "normal", or canonical form. 在正常情况下,这意味着获取项目列表(例如,在规范化标头情况下的标头),并将它们标准化为所需的格式。In normal-speak, this means to take the list of items (such as headers in the case of Canonicalized Headers) and standardize them into a required format. 总的来说,Azure 决定使用一种格式,而你需要匹配它。Basically, Azure decided on a format and you need to match it.

让我们从这两个规范化字段开始,因为需要它们来创建授权标头。Let's start with those two canonicalized fields, because they are required to create the Authorization header.

规范化标头Canonicalized headers

若要创建此值,请检索以“x-ms-”开头的标头并对其进行排序,然后将它们格式化为 [key:value\n] 字符串实例,并将其连结到一个字符串中。To create this value, retrieve the headers that start with "x-ms-" and sort them, then format them into a string of [key:value\n] instances, concatenated into one string. 在此示例中,规范化标头如下所示:For this example, the canonicalized headers look like this:

x-ms-date:Fri, 17 Nov 2017 00:44:48 GMT\nx-ms-version:2017-07-29\n

以下是用于创建该输出的代码:Here's the code used to create that output:

private static string GetCanonicalizedHeaders(HttpRequestMessage httpRequestMessage)
{
    var headers = from kvp in httpRequestMessage.Headers
        where kvp.Key.StartsWith("x-ms-", StringComparison.OrdinalIgnoreCase)
        orderby kvp.Key
        select new { Key = kvp.Key.ToLowerInvariant(), kvp.Value };

    StringBuilder headersBuilder = new StringBuilder();

    // Create the string in the right format; this is what makes the headers "canonicalized" --
    //   it means put in a standard format. https://en.wikipedia.org/wiki/Canonicalization
    foreach (var kvp in headers)
    {
        headersBuilder.Append(kvp.Key);
        char separator = ':';

        // Get the value for each header, strip out \r\n if found, then append it with the key.
        foreach (string headerValue in kvp.Value)
        {
            string trimmedValue = headerValue.TrimStart().Replace("\r\n", string.Empty);
            headersBuilder.Append(separator).Append(trimmedValue);

            // Set this to a comma; this will only be used
            // if there are multiple values for one of the headers.
            separator = ',';
        }

        headersBuilder.Append("\n");
    }

    return headersBuilder.ToString();
}

规范化资源Canonicalized resource

此部分签名字符串表示请求指向的存储帐户。This part of the signature string represents the storage account targeted by the request. 请记住,请求 URI 是 <http://contosorest.blob.core.chinacloudapi.cn/?comp=list>,使用实际帐户名(在此情况下为 contosorest)。Remember that the Request URI is <http://contosorest.blob.core.chinacloudapi.cn/?comp=list>, with the actual account name (contosorest in this case). 在此示例中,将返回:In this example, this is returned:

/contosorest/\ncomp:list

如果你有查询参数,此示例也包括这些参数。If you have query parameters, this example includes those parameters as well. 以下是代码,该代码还处理其他查询参数和具有多个值的查询参数。Here's the code, which also handles additional query parameters and query parameters with multiple values. 请记住,你正在生成此代码以使其适用于所有 REST API。Remember that you're building this code to work for all of the REST APIs. 你需要包括所有可能性,即使 ListContainers 方法不需要所有这些参数。You want to include all possibilities, even if the ListContainers method doesn't need all of them.

private static string GetCanonicalizedResource(Uri address, string storageAccountName)
{
    // The absolute path will be "/" because for we're getting a list of containers.
    StringBuilder sb = new StringBuilder("/").Append(storageAccountName).Append(address.AbsolutePath);

    // Address.Query is the resource, such as "?comp=list".
    // This ends up with a NameValueCollection with 1 entry having key=comp, value=list.
    // It will have more entries if you have more query parameters.
    NameValueCollection values = HttpUtility.ParseQueryString(address.Query);

    foreach (var item in values.AllKeys.OrderBy(k => k))
    {
        sb.Append('\n').Append(item.ToLower()).Append(':').Append(values[item]);
    }

    return sb.ToString();
}

现在,已设置规范化字符串,我们接着来看下如何创建授权标头本身。Now that the canonicalized strings are set, let's look at how to create the authorization header itself. 首先,创建一个如前文所述的 StringToSign 格式的消息签名字符串。You start by creating a string of the message signature in the format of StringToSign previously displayed in this article. 在代码中使用注释会更容易解释这一概念,因此,下面提供了返回授权标头的最后一种方法:This concept is easier to explain using comments in the code, so here it is, the final method that returns the Authorization Header:

internal static AuthenticationHeaderValue GetAuthorizationHeader(
    string storageAccountName, string storageAccountKey, DateTime now,
    HttpRequestMessage httpRequestMessage, string ifMatch = "", string md5 = "")
{
    // This is the raw representation of the message signature.
    HttpMethod method = httpRequestMessage.Method;
    String MessageSignature = String.Format("{0}\n\n\n{1}\n{5}\n\n\n\n{2}\n\n\n\n{3}{4}",
                method.ToString(),
                (method == HttpMethod.Get || method == HttpMethod.Head) ? String.Empty
                  : httpRequestMessage.Content.Headers.ContentLength.ToString(),
                ifMatch,
                GetCanonicalizedHeaders(httpRequestMessage),
                GetCanonicalizedResource(httpRequestMessage.RequestUri, storageAccountName),
                md5);

    // Now turn it into a byte array.
    byte[] SignatureBytes = Encoding.UTF8.GetBytes(MessageSignature);

    // Create the HMACSHA256 version of the storage key.
    HMACSHA256 SHA256 = new HMACSHA256(Convert.FromBase64String(storageAccountKey));

    // Compute the hash of the SignatureBytes and convert it to a base64 string.
    string signature = Convert.ToBase64String(SHA256.ComputeHash(SignatureBytes));

    // This is the actual header that will be added to the list of request headers.
    AuthenticationHeaderValue authHV = new AuthenticationHeaderValue("SharedKey",
        storageAccountName + ":" + signature);
    return authHV;
}

运行此代码时,生成的 MessageSignature 如以下示例所示:When you run this code, the resulting MessageSignature looks like this example:

GET\n\n\n\n\n\n\n\n\n\n\n\nx-ms-date:Fri, 17 Nov 2017 01:07:37 GMT\nx-ms-version:2017-07-29\n/contosorest/\ncomp:list

下面是 AuthorizationHeader 的最终值:Here's the final value for AuthorizationHeader:

SharedKey contosorest:Ms5sfwkA8nqTRw7Uury4MPHqM6Rj2nfgbYNvUKOa67w=

AuthorizationHeader 是发出响应前放置在请求标头中的最后一个标头。The AuthorizationHeader is the last header placed in the request headers before posting the response.

这涵盖了你需要知道的所有信息,可以将一个类组合在一起,创建一个用于调用存储服务 REST API 的请求。That covers everything you need to know to put together a class with which you can create a request to call the Storage Services REST APIs.

示例:列出 BlobExample: List blobs

让我们看一下如何更改代码,以便对容器 container-1 调用“列出 Blob”操作。Let's look at how to change the code to call the List Blobs operation for container container-1. 此代码与清单容器的代码几乎完全相同,唯一的区别在于 URI 以及解析响应的方式。This code is almost identical to the code for listing containers, the only differences being the URI and how you parse the response.

如果查看 ListBlobs 的参考文档,将发现该方法是 GET ,RequestURI 为:If you look at the reference documentation for ListBlobs, you find that the method is GET and the RequestURI is:

https://myaccount.blob.core.chinacloudapi.cn/container-1?restype=container&comp=list

在 ListContainersAsyncREST 中,更改将 URI 设置为 ListBlobs API 的代码。In ListContainersAsyncREST, change the code that sets the URI to the API for ListBlobs. 容器名称为 container-1 。The container name is container-1.

String uri =
    string.Format("http://{0}.blob.core.chinacloudapi.cn/container-1?restype=container&comp=list",
      storageAccountName);

然后,在你处理响应时,更改代码以查找 blob 而不是容器。Then where you handle the response, change the code to look for blobs instead of containers.

foreach (XElement container in x.Element("Blobs").Elements("Blob"))
{
    Console.WriteLine("Blob name = {0}", container.Element("Name").Value);
}

在运行此示例时,将获得如下结果:When you run this sample, you get results like the following:

规范化标头:Canonicalized headers:

x-ms-date:Fri, 17 Nov 2017 05:16:48 GMT\nx-ms-version:2017-07-29\n

规范化资源:Canonicalized resource:

/contosorest/container-1\ncomp:list\nrestype:container

消息签名:Message signature:

GET\n\n\n\n\n\n\n\n\n\n\n\nx-ms-date:Fri, 17 Nov 2017 05:16:48 GMT
  \nx-ms-version:2017-07-29\n/contosorest/container-1\ncomp:list\nrestype:container

授权标头:Authorization header:

SharedKey contosorest:uzvWZN1WUIv2LYC6e3En10/7EIQJ5X9KtFQqrZkxi6s=

以下值来自 FiddlerThe following values are from Fiddler:

请求:Request:

GET http://contosorest.blob.core.chinacloudapi.cn/container-1?restype=container&comp=list HTTP/1.1

请求标头: Request Headers:

x-ms-date: Fri, 17 Nov 2017 05:16:48 GMT
x-ms-version: 2017-07-29
Authorization: SharedKey contosorest:uzvWZN1WUIv2LYC6e3En10/7EIQJ5X9KtFQqrZkxi6s=
Host: contosorest.blob.core.chinacloudapi.cn
Connection: Keep-Alive

执行后返回的状态代码和响应标头: Status code and response headers returned after execution:

HTTP/1.1 200 OK
Content-Type: application/xml
Server: Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0
x-ms-request-id: 7e9316da-001e-0037-4063-5faf9d000000
x-ms-version: 2017-07-29
Date: Fri, 17 Nov 2017 05:20:21 GMT
Content-Length: 1135

响应正文 (XML): 此 XML 响应显示 blob 及其属性列表。Response body (XML): This XML response shows the list of blobs and their properties.

<?xml version="1.0" encoding="utf-8"?>
<EnumerationResults
    ServiceEndpoint="http://contosorest.blob.core.chinacloudapi.cn/" ContainerName="container-1">
    <Blobs>
        <Blob>
            <Name>DogInCatTree.png</Name>
            <Properties><Last-Modified>Fri, 17 Nov 2017 01:41:14 GMT</Last-Modified>
            <Etag>0x8D52D5C4A4C96B0</Etag>
            <Content-Length>419416</Content-Length>
            <Content-Type>image/png</Content-Type>
            <Content-Encoding />
            <Content-Language />
            <Content-MD5 />
            <Cache-Control />
            <Content-Disposition />
            <BlobType>BlockBlob</BlobType>
            <LeaseStatus>unlocked</LeaseStatus>
            <LeaseState>available</LeaseState>
            <ServerEncrypted>true</ServerEncrypted>
            </Properties>
        </Blob>
        <Blob>
            <Name>GuyEyeingOreos.png</Name>
            <Properties>
                <Last-Modified>Fri, 17 Nov 2017 01:41:14 GMT</Last-Modified>
                <Etag>0x8D52D5C4A25A6F6</Etag>
                <Content-Length>167464</Content-Length>
                <Content-Type>image/png</Content-Type>
                <Content-Encoding />
                <Content-Language />
                <Content-MD5 />
                <Cache-Control />
                <Content-Disposition />
                <BlobType>BlockBlob</BlobType>
                <LeaseStatus>unlocked</LeaseStatus>
                <LeaseState>available</LeaseState>
                <ServerEncrypted>true</ServerEncrypted>
            </Properties>
            </Blob>
        </Blobs>
    <NextMarker />
</EnumerationResults>

摘要Summary

在本文中,你学习了如何向 Blob 存储 REST API 发出请求。In this article, you learned how to make a request to the blob storage REST API. 可以通过该请求检索容器列表或容器中 Blob 的列表。With the request, you can retrieve a list of containers or a list of blobs in a container. 此外,你还学习了如何创建 REST API 调用的授权签名,以及如何在 REST 请求中使用它。You learned how to create the authorization signature for the REST API call and how to use it in the REST request. 最后,你学习了如何检查该响应。Finally, you learned how to examine the response.

后续步骤Next steps