你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn。
本文介绍如何使用 C# 创建授权的 REST 请求来调用 Azure 存储 REST API作。 在学习了如何为 Blob 存储调用 REST API 操作之后,您可以使用类似的步骤对任何其他 Azure 存储 REST 操作进行操作。
先决条件
示例应用程序列出了存储帐户的 Blob 容器。 若要试用本文中的代码,需要以下项:
安装 Visual Studio 并包括 Azure 开发 工作负载。 此示例是使用 Visual Studio 2019 生成的。 如果使用其他版本,则指南可能会略有不同。
一份 Azure 订阅。 如果没有 Azure 订阅,请在开始之前创建一个免费帐户。
常规用途存储帐户。 如果还没有存储帐户,请参阅 “创建存储帐户”。
本文中的示例演示如何列出存储帐户中的容器。 若要查看输出,请在开始之前将一些 Blob 容器添加到存储帐户。
下载示例应用程序
示例应用程序是用 C# 编写的控制台应用程序。
使用 git 将应用程序的副本下载到开发环境。
git clone https://github.com/Azure-Samples/storage-dotnet-rest-api-with-auth.git
此命令将存储库克隆到本地 git 文件夹。 若要打开 Visual Studio 解决方案,请导航到 storage-dotnet-rest-api-with-auth 文件夹并打开StorageRestApiAuth.sln。
关于 REST
表示性状态传输(REST)是一种体系结构,可用于通过 Internet 协议(如 HTTP/HTTPS)与服务交互。 REST 独立于服务器或客户端上运行的软件。 可以从支持 HTTP/HTTPS 的任何平台调用 REST API。 可以编写在 Mac、Windows、Linux、Android 手机或平板电脑、iPhone、iPod 或网站上运行的应用程序,并为所有这些平台使用相同的 REST API。
对 REST API 的调用包括客户端发出的请求以及服务返回的响应。 在请求中,你发送一个 URL,其中说明了你要调用哪个操作、要在其上执行操作的资源、任何查询参数和标头,以及数据的有效负载(取决于已调用的操作)。 来自服务的响应包括状态代码、一组响应标头,以及根据调用的操作所产生的数据有效负载。
关于示例应用程序
示例应用程序列出了存储帐户中的容器。 了解 REST API 文档中的信息如何与实际代码相关联后,其他 REST 调用更易于理解。
如果查看 Blob 服务 REST API,则会看到可以在 Blob 存储上执行的所有操作。 存储客户端库是 REST API 的包装器,因此无需直接使用 REST API 即可轻松访问存储资源。 但是,有时可能需要使用 REST API 而不是存储客户端库。
“列出容器”操作
本文重点介绍 列出容器 操作。 以下信息可帮助你了解请求和响应中的一些字段。
请求方法:GET。 此动词是您指定为请求对象属性的HTTP方法。 此谓词的其他值包括 HEAD、PUT 和 DELETE,具体取决于要调用的 API。
请求 URI: https://myaccount.blob.core.windows.net/?comp=list。 请求 URI 是从 Blob 存储帐户终结点 https://myaccount.blob.core.windows.net 和资源字符串 /?comp=list创建的。
URI 参数:调用 ListContainers 时可以使用其他查询参数。 其中有些参数为调用超时(以秒计)和前缀,后者用于筛选。
另一个有用的参数是 maxresults:如果可用容器数超过此值,响应正文将包含一个 NextMarker 元素,该元素指示下一个请求返回的下一个容器。 若要使用此功能,请在发出下一个请求时,在 URI 中提供 NextMarker 值作为 标记 参数。 使用此功能时,它类似于通过结果进行分页。
若要使用其他参数,请将这些参数追加到具有值的资源字符串,如以下示例所示:
/?comp=list&timeout=60&maxresults=100
请求标头: 本部分列出了必需和可选的请求标头。 需要三个标头: 授权 标头 x-ms-date (包含请求的 UTC 时间)和 x-ms-version (指定要使用的 REST API 的版本)。 在标头中包含 x-ms-client-request-id 是可选的。 可以将此字段的值设置为任何内容,并在启用日志记录时将其写入存储分析日志。
请求正文:ListContainers 没有请求正文。 上传 Blob 时,请求正文用于所有 PUT 操作,包括 SetContainerAccessPolicy。 请求体允许您发送一个包含存储访问策略的 XML 列表以应用。 有关存储访问策略,将在使用共享访问签名 (SAS) 一文中展开讨论。
响应状态代码: 告知需要知道的任何状态代码。 在此示例中,HTTP 状态代码为 200 正常。 有关 HTTP 状态代码的完整列表,请查看 状态代码定义。 若要查看特定于存储 REST API 的错误代码,请参阅 常见的 REST API 错误代码
响应标头: 其中包括 内容类型; x-ms-request-id,即传入的请求 ID; x-ms-version,指示使用的 Blob 服务的版本;和 日期,采用 UTC 格式,并告知发出请求的时间。
响应正文:此字段是提供所请求数据的 XML 结构。 在此示例中,响应是容器及其属性的列表。
创建 REST 请求
为确保在生产环境中的安全性,请始终使用 HTTPS 而非 HTTP。 在本练习中,我们使用 HTTP,以便可以查看请求和响应数据。 若要查看实际 REST 调用中的请求和响应信息,可以下载 Fiddler 或类似的应用程序。 在 Visual Studio 解决方案中,存储帐户名称和密钥在类中硬编码。 ListContainersAsyncREST 方法将存储帐户名称和存储帐户密钥传递给用于创建 REST 请求的各个组件的方法。 在实际应用程序中,存储帐户名称和密钥将驻留在配置文件、环境变量中,或从 Azure Key Vault 中检索。
在我们的示例项目中,用于创建 Authorization 标头的代码位于单独的类中。 其想法是,你可以采用整个类并将其添加到自己的解决方案中,并“按原样”使用它。授权标头代码适用于对 Azure 存储的大多数 REST API 调用。
若要生成请求(即 HttpRequestMessage 对象),请转到 Program.cs 中的 ListContainersAsyncREST。 生成请求的步骤如下:
- 创建用于调用服务的 URI。
- 创建 HttpRequestMessage 对象并设置有效负载。 ListContainersAsyncREST 的有效负载为 null,因为我们未传入任何内容。
- 添加 x-ms-date 和 x-ms-version 的请求标头。
- 获取授权标头并添加它。
你需要的一些基本信息:
- 对于 ListContainers, 该方法 为
GET. 实例化请求时会设置此值。 -
资源是 URI 的查询部分,指示正在调用哪个 API,因此该值为
/?comp=list。 如前所述,资源位于参考文档页上,其中显示了 有关 ListContainers API 的信息。 - URI 是通过为该存储帐户创建 Blob 服务终结点并连接资源来构造的。
请求 URI 的值最终为
http://contosorest.blob.core.windows.net/?comp=list。 - 对于 ListContainers,requestBody 为 null,并且没有额外的标头。
不同的 API 可能有其他参数传入,例如 ifMatch。 你可能使用 ifMatch 的一个示例是调用 PutBlob 时。 在这种情况下,将 ifMatch 设置为 eTag,并且仅当提供的 eTag 与 Blob 上的当前 eTag 匹配时,它才会更新 Blob。 如果其他人在检索 eTag 后更新了 Blob,则他们的更改不会被覆盖。
首先,设置 uri 和 requestPayload。
// Construct the URI. It will look like this:
// https://myaccount.blob.core.windows.net/resource
String uri = string.Format("http://{0}.blob.core.windows.net?comp=list", storageAccountName);
// Provide the appropriate payload, in this case null.
// we're not passing anything in.
Byte[] requestPayload = null;
接下来,实例化请求,将方法设置为 GET,并提供 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-date和x-ms-version添加请求标头。 此代码中的这个位置也是你在其中添加调用所需的任何其他请求标头的位置。 在此示例中,没有其他标头。 传递额外标头的 API 示例是“设置容器 ACL”操作。 此 API 调用添加名为“x-ms-blob-public-access”的标头和访问级别的值。
// 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.
调用创建授权标头并将其添加到请求标头的方法。 授权标头将在文章的后部创建。 方法名称为 GetAuthorizationHeader,可在以下代码片段中看到:
// Get the authorization header and add it.
httpRequestMessage.Headers.Authorization = AzureStorageAuthenticationHelper.GetAuthorizationHeader(
storageAccountName, storageAccountKey, now, httpRequestMessage);
此时, httpRequestMessage 包含与授权标头一起完成的 REST 请求。
发送请求
构造请求后,可以调用 SendAsync 方法将其发送到 Azure 存储。 检查响应状态代码的值是否为 200,这意味着作已成功。 接下来,分析响应。 在这种情况下,你将获取容器的 XML 列表。 让我们看看用于调用 GetRESTRequest 方法以创建请求、执行请求,然后检查容器列表的响应的代码。
// 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 ),可以查看请求和响应信息。 让我们看看一下。 存储帐户的名称为 contosorest。
请求:
GET /?comp=list HTTP/1.1
请求标头:
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.windows.net
Connection: Keep-Alive
执行后返回的状态代码和响应标头:
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): 对于“列表容器”作,这会显示容器及其属性的列表。
<?xml version="1.0" encoding="utf-8"?>
<EnumerationResults
ServiceEndpoint="http://contosorest.blob.core.windows.net/">
<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>
了解如何创建请求、调用服务并分析结果后,让我们了解如何创建授权标头。
创建授权标头
小窍门
Azure 存储支持 Blob 和队列的 Microsoft Entra 集成。 Microsoft Entra ID 提供了更简单的方式来授权对 Azure 存储的请求。 有关使用 Microsoft Entra ID 授权 REST 操作的详细信息,请参阅 使用 Microsoft Entra ID 授权文档。 有关 Microsoft Entra 与 Azure 存储的集成概述,请参阅 使用 Microsoft Entra ID 对 Azure 存储的访问进行身份验证。
若要详细了解授权概念,请参阅 授权对 Azure 存储的请求。
让我们将文章提取到所需的确切内容并显示代码。
首先,使用共享密钥授权。 授权标头格式如下所示:
Authorization="SharedKey <storage account name>:<signature>"
签名字段是由请求生成的基于哈希的消息认证码(HMAC),通过 SHA256 算法计算,然后用 Base64 编码。
此代码片段显示共享密钥签名字符串的格式:
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;
对于 Blob 存储,可以指定 VERB、md5、内容长度、规范化标头和规范化资源。 可以在本示例中将其他选项留空,但请插入 \n 以指定这些选项为空。
规范化是标准化具有多个可能表示形式的数据的过程。 在本例中,你正在标准化标头和资源。 规范化标头是以“x-ms-”开头的标头。 规范化资源是资源的 URI,包括存储帐户名称和所有查询参数(例如 ?comp=list)。 规范化资源还包括可能添加的任何其他查询参数,例如 timeout=60。
让我们从两个规范化字段开始,因为它们是创建授权标头所必需的。
规范化标头
若要创建此值,请检索以“x-ms-”开头的标头并对其进行排序,然后将其格式化为实例字符串 [key:value\n] ,并串联成一个字符串。 对于此示例,规范化标头如下所示:
x-ms-date:Fri, 17 Nov 2017 00:44:48 GMT\nx-ms-version:2017-07-29\n
下面是用于创建该输出的代码:
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();
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();
}
规范化资源
签名字符串的这一部分表示请求面向的存储帐户。 请记住,请求 URI 是 http://contosorest.blob.core.windows.net/?comp=list实际帐户名称(contosorest 在本例中)。 在此示例中,将返回:
/contosorest/\ncomp:list
如果你有查询参数,此示例也包括这些参数。 下面是代码,该代码还处理具有多个值的附加查询参数和查询参数。 请记住,要生成此代码以适用于所有 REST API。 你希望包括所有可能性,即使 ListContainers 方法不需要所有这些可能性。
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();
}
设置规范化字符串后,让我们看看如何创建授权标头本身。 首先,创建一个如前文所述的 StringToSign 格式的消息签名字符串。 此概念更易于解释在代码中使用注释,因此下面是返回授权标头的最终方法:
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 如以下示例所示:
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 的最终值:
SharedKey contosorest:Ms5sfwkA8nqTRw7Uury4MPHqM6Rj2nfgbYNvUKOa67w=
AuthorizationHeader 是在发布响应之前放置在请求标头中的最后一个标头。
这涵盖了需要了解的一切,以便将类组合在一起,可以使用该类创建调用存储服务 REST API 的请求。
示例:列出数据块
让我们看一下如何更改代码,以便对容器 container-1 调用“列出 Blob”操作。 此代码与列出容器的代码几乎完全相同,唯一的区别是 URI 以及分析响应的方式。
如果查看 ListBlobs 的参考文档,你会发现该方法为 GET,RequestURI 为:
https://myaccount.blob.core.windows.net/container-1?restype=container&comp=list
在 ListContainersAsyncREST 中,更改将 URI 设置为 ListBlobs API 的代码。 容器名称为 container-1。
String uri =
string.Format("http://{0}.blob.core.windows.net/container-1?restype=container&comp=list",
storageAccountName);
然后,在你处理响应时,更改代码以查找 blob 而不是容器。
foreach (XElement container in x.Element("Blobs").Elements("Blob"))
{
Console.WriteLine("Blob name = {0}", container.Element("Name").Value);
}
运行此示例时,将获得如下所示的结果:
规范化标头:
x-ms-date:Fri, 17 Nov 2017 05:16:48 GMT\nx-ms-version:2017-07-29\n
规范化资源:
/contosorest/container-1\ncomp:list\nrestype:container
消息签名:
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
授权标头:
SharedKey contosorest:uzvWZN1WUIv2LYC6e3En10/7EIQJ5X9KtFQqrZkxi6s=
以下值来自 Fiddler:
请求:
GET http://contosorest.blob.core.windows.net/container-1?restype=container&comp=list HTTP/1.1
请求标头:
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.windows.net
Connection: Keep-Alive
执行后返回的状态代码和响应标头:
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 列表及其属性。
<?xml version="1.0" encoding="utf-8"?>
<EnumerationResults
ServiceEndpoint="http://contosorest.blob.core.windows.net/" 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>
概要
本文介绍了如何向 Blob 存储 REST API 发出请求。 通过请求,可以检索容器列表或容器中的 Blob 列表。 你了解了如何为 REST API 调用创建授权签名以及如何在 REST 请求中使用它。 最后,你学习了如何检查响应。