无法在 Azure 中使用系统分配的托管标识来使用 blob 存储操作

Not able to use blob storage operations using System Assigned Managed Identity in Azure

尝试使用 C# 语言在 Azure 中使用系统分配的托管标识时出现 "Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature." 错误。

遵循的步骤

Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.
   at Microsoft.Azure.Storage.Core.Executor.Executor.<ExecuteAsync>d__1`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.Azure.Storage.Blob.CloudBlockBlob.<DownloadTextAsync>d__72.MoveNext()

计划Class

    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                var blob = new AzureCloudBlob();
                Console.WriteLine(blob.ReadBlob());
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                Console.WriteLine(ex.StackTrace);
            }
        }
    }

AzureCloudBlob class,用于使用系统分配的托管标识访问令牌连接和读取 blob

    class AzureCloudBlob
    {
        public CloudBlockBlob CreateConnection()
        {
            var token = GetToken();
            var tokenCredentials = new TokenCredential(token);
            var storageCredentials = new StorageCredentials(tokenCredentials);
            var serviceUri = new Uri("https://mystorageacc.blob.core.windows.net/practice/blob.txt");

            return new CloudBlockBlob(serviceUri, storageCredentials);
        }

        public string ReadBlob()
        {
            var client = CreateConnection();
            var blobClient = client.DownloadTextAsync();
            var data = blobClient.Result;
            return data;
        }

        public string GetToken()
        {
            var request = (HttpWebRequest)WebRequest.Create("http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/");
            request.Headers["Metadata"] = "true";
            request.Method = "GET";

            try
            {
                var response = (HttpWebResponse)request.GetResponse();
                var streamResponse = new StreamReader(response.GetResponseStream());
                string stringResponse = streamResponse.ReadToEnd();
                var list = (Dictionary<string, string>)JsonConvert.DeserializeObject(stringResponse, typeof(Dictionary<string, string>));
                string accessToken = list["access_token"];
                Console.WriteLine(accessToken);
                return accessToken;
            }
            catch (Exception e)
            {
                string errorText = String.Format("{0} \n\n{1}", e.Message, e.InnerException != null ? e.InnerException.Message : "Acquire token failed");
                Console.WriteLine(errorText);
                return errorText;
            }
        }
    }

如果您想使用 Azure AD 身份验证访问 Azure 存储,我们应该使用 resouce=https://storage.azure.com/ 来获取 Azure AD 访问令牌。但是你用resource=https://management.azure.com/。请更换它。此外,请注意您需要为 MSI 分配正确的角色。角色应该是 Storage Blob Data ReaderStorage Blob Data ContributorStorage Blob Data Owner.

详细步骤如下

  1. 在 Azure VM 上启用系统分配的托管标识

    $vm = Get-AzVM -ResourceGroupName myResourceGroup -Name myVM
    Update-AzVM -ResourceGroupName myResourceGroup -VM $vm -AssignIdentity:$SystemAssigned
    
  2. 在存储帐户范围内将角色分配给 MSI

    $sp =Get-AzADServicePrincipal -displayname "<your VM name>"
    New-AzRoleAssignment -ObjectId $sp.id `
        -RoleDefinitionName "Storage Blob Data Reader" `
        -Scope  "/subscriptions/<subscription>/resourceGroups/sample-resource-group/providers/Microsoft.Storage/storageAccounts/<storage-account>"
    
  3. 代码

    class Program
    {
        static void Main(string[] args)
        {
            //get token
            string accessToken = GetMSIToken("https://storage.azure.com/");
    
            //create token credential
            TokenCredential tokenCredential = new TokenCredential(accessToken);
    
            //create storage credentials
            StorageCredentials storageCredentials = new StorageCredentials(tokenCredential);
    
            Uri blobAddress = new Uri("<URI to blob file>");
    
            //create block blob using storage credentials
            CloudBlockBlob blob = new CloudBlockBlob(blobAddress, storageCredentials);
    
            //retrieve blob contents
            Console.WriteLine(blob.DownloadText());
            Console.ReadLine();
        }
    
        static string GetMSIToken(string resourceID)
        {
            string accessToken = string.Empty;
            // Build request to acquire MSI token
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=" + resourceID);
            request.Headers["Metadata"] = "true";
            request.Method = "GET";
    
            try
            {
                // Call /token endpoint
                HttpWebResponse response = (HttpWebResponse)request.GetResponse();
    
                // Pipe response Stream to a StreamReader, and extract access token
                StreamReader streamResponse = new StreamReader(response.GetResponseStream());
                string stringResponse = streamResponse.ReadToEnd();
                JavaScriptSerializer j = new JavaScriptSerializer();
                Dictionary<string, string> list = (Dictionary<string, string>)j.Deserialize(stringResponse, typeof(Dictionary<string, string>));
                accessToken = list["access_token"];
                return accessToken;
            }
            catch (Exception e)
            {
                string errorText = String.Format("{0} \n\n{1}", e.Message, e.InnerException != null ? e.InnerException.Message : "Acquire token failed");
                return accessToken;
            }
        }
    }