【Azure Developer】解決Azure Key Vault管理Storage的示例程式碼在中國區Azure遇見的各種認證/授權問題 - C# Example Code

路邊兩盞燈發表於2021-01-19

問題描述

使用Azure金鑰保管庫(Key Vault)來託管儲存賬號(Storage Account)金鑰的示例中,從Github中下載的示例程式碼在中國區Azure執行時候會遇見各種認證和授權問題,以下列舉出執行程式碼中遇見的各種異常:

  1. "AADSTS90002: Tenant 'xxxxxxxx-66d7-xxxx-8f9f-xxxxxxxxxxxx' not found. This may happen if there are no active subscriptions for the tenant. Check to make sure you have the correct tenant ID. Check with your subscription administrator.
  2. Microsoft.Rest.Azure.CloudException |  HResult=0x80131500 |  Message=The subscription 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' could not be found. |  Source=Microsoft.Azure.Management.KeyVault
  3. The client 'xxxxxxxx-e256-xxxx-8ef8-xxxxxxxxxxxx' with object id 'xxxxxxxx-e256-xxxx-xxxxxxxxxxxx' does not have authorization to perform action 'Microsoft.KeyVault/vaults/read' over scope '/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/dev-service-rg/providers/Microsoft.KeyVault/vaults/<youkeyvaultname>' or the scope is invalid. If access was recently granted, please refresh your credentials.

  4. Unexpected exception encountered: AADSTS700016: Application with identifier '54d5b1e9-5f5c-48f1-8483-d72471cbe7e7' was not found in the directory 'xxxxxxxx-66d7-xxxx-8f9f-xxxxxxxxxxxx'. This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You may have sent your authentication request to the wrong tenant.

  5. {"AADSTS7000218: The request body must contain the following parameter: 'client_assertion' or 'client_secret'.\r\nTrace ID: 57169df7-d54d-4533-b6cf-fc269ee93f00\r\nCorrelation ID: 33fb61c4-7266-4690-bb8d-4d4ebb5614f5\r\nTimestamp: 2021-01-19 02:44:50Z"}
  6. AADSTS54005: OAuth2 Authorization code was already redeemed, please retry with a new valid code or use an existing refresh token. |Trace ID: cbfb3d00-a3e5-445e-96b3-918a94054100 |Correlation ID: 40964a5f-e267-43da-988a-00bf33fa7ad4 |Timestamp: 2021-01-19 03:16:38Z

 

以上錯誤就是在除錯Key vault dotnet managed storage程式碼的過程(https://github.com/Azure-Samples/key-vault-dotnet-managed-storage)中遇見的錯誤。下面我們一一的解決以上錯誤並使得程式成功執行:

【Azure Developer】解決Azure Key Vault管理Storage的示例程式碼在中國區Azure遇見的各種認證/授權問題 - C# Example Code

 

除錯程式碼

首先通過Github下載程式碼並在Azure環境中準備好AAD,Key Vault,Storage Account。

【Azure Developer】解決Azure Key Vault管理Storage的示例程式碼在中國區Azure遇見的各種認證/授權問題 - C# Example Code

 PS: 獲取AAD中註冊應用的相應配置值,可以參考博文

【Azure Developer】使用Postman獲取Azure AD中註冊應用程式的授權Token,及為Azure REST API設定Authorization 

【Azure Developer】Python程式碼通過AAD認證訪問微軟Azure金鑰保管庫(Azure Key Vault)中機密資訊(Secret)

 

第一個錯誤:"AADSTS90002: Tenant 'xxxxxxxx-66d7-xxxx-8f9f-xxxxxxxxxxxx' not found. This may happen if there are no active subscriptions for the tenant. Check to make sure you have the correct tenant ID. Check with your subscription administrator.

這是因為程式碼預設是連線到Global Azure的AAD環境,所以認證的時候會把app.config中的tenant值在Global Azure AAD中查詢。而我們在專案中配置的Tenant是中國區Azure的,所以出現not found的提示。 只需要在程式碼中指定AAD的環境中Azure China即可解決該問題。

  • ClientContext.cs檔案中 修改GetServiceCredentialsAsync方法ActiveDirectoryServiceSettings.AzureActiveDirectoryServiceSettings.AzureChina
        /// <summary>
        /// Returns a task representing the attempt to log in to Azure public as the specified
        /// service principal, with the specified credential.
        /// </summary>
        /// <param name="certificateThumbprint"></param>
        /// <returns></returns>
        public static Task<ServiceClientCredentials> GetServiceCredentialsAsync(string tenantId, string applicationId, string appSecret)
        {
            if (_servicePrincipalCredential == null)
            {
                _servicePrincipalCredential = new ClientCredential(applicationId, appSecret);
            }

            //Update the Azure to Azure China
            return ApplicationTokenProvider.LoginSilentAsync(
                tenantId,
                _servicePrincipalCredential,
                ActiveDirectoryServiceSettings.AzureChina,
                TokenCache.DefaultShared);
        }

 

第二個錯誤:Microsoft.Rest.Azure.CloudException |  HResult=0x80131500 |  Message=The subscription 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' could not be found. |  Source=Microsoft.Azure.Management.KeyVault

這個錯誤的原因為在Azure Management KeyVault物件中找不到我們在專案中配置的訂閱資訊。在Debug程式碼時候才發現,KeyVaultManagementClient物件預設的URL也是指向Global Azure。中國區的Key Vault Management的URL為https://management.chinacloudapi.cn, 與Global不同。需要在KeyVaultSampleBase.cs程式碼中設定ManagementClient.BaseUri = new Uri("https://management.chinacloudapi.cn"); 即可。

        private void InstantiateSample(string tenantId, string appId, string appSecret, string subscriptionId, string resourceGroupName, string vaultLocation, string vaultName, string storageAccountName, string storageAccountResourceId)
        {
            context = ClientContext.Build(tenantId, appId, appSecret, subscriptionId, resourceGroupName, vaultLocation, vaultName, storageAccountName, storageAccountResourceId);

            // log in with as the specified service principal for vault management operations
            var serviceCredentials = Task.Run(() => ClientContext.GetServiceCredentialsAsync(tenantId, appId, appSecret)).ConfigureAwait(true).GetAwaiter().GetResult();
// instantiate the management client
            ManagementClient = new KeyVaultManagementClient(serviceCredentials);
            ManagementClient.BaseUri = new Uri("https://management.chinacloudapi.cn");
            ManagementClient.SubscriptionId = subscriptionId;

            // instantiate the data client, specifying the user-based access token retrieval callback
            DataClient = new KeyVaultClient(ClientContext.AcquireUserAccessTokenAsync);
        }

 

第三個錯誤:The client 'xxxxxxxx-e256-xxxx-8ef8-xxxxxxxxxxxx' with object id 'xxxxxxxx-e256-xxxx-xxxxxxxxxxxx' does not have authorization to perform action 'Microsoft.KeyVault/vaults/read' over scope '/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/dev-service-rg/providers/Microsoft.KeyVault/vaults/<youkeyvaultname>' or the scope is invalid. If access was recently granted, please refresh your credentials.

 【Azure Developer】解決Azure Key Vault管理Storage的示例程式碼在中國區Azure遇見的各種認證/授權問題 - C# Example Code

 

在通過程式碼獲取Key Vault Management物件時候,由於程式當前使用的AAD註冊應用沒有被授予Key Vault的操作許可權,所以出現does not have authorization to perform action 'Microsoft.KeyVault/vaults/read' 。通過到Key Vault的門戶中為AAD應用分配許可權即可解決此問題。

  • Key Vault Portal -> Access Control(IAM) -> Add Role Assignment.

 【Azure Developer】解決Azure Key Vault管理Storage的示例程式碼在中國區Azure遇見的各種認證/授權問題 - C# Example Code

 

第四個錯誤:Unexpected exception encountered: AADSTS700016: Application with identifier '54d5b1e9-5f5c-48f1-8483-d72471cbe7e7' was not found in the directory 'xxxxxxxx-66d7-xxxx-8f9f-xxxxxxxxxxxx'. This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You may have sent your authentication request to the wrong tenant.

這個錯誤很迷惑,因為identifier “54d5b1e9-5f5c-48f1-8483-d72471cbe7e7”並不包含在配置中,它是如何產生的呢? 在全域性檢視專案檔案後,發現它是程式碼中hardcode的一個值。需要在使用時候把替換為app.config中的application id。

  • SampleConstants.cs檔案中的WellKnownClientId
        public static string WellKnownClientId
        {
            // Native AD app id with permissions in the subscription
            // Consider fetching it from configuration.
            get
            {
                return "54d5b1e9-5f5c-48f1-8483-d72471cbe7e7";
            }
        }
  • ClientContext.cs檔案中使用ConfigurationManager.AppSettings[SampleConstants.ConfigKeys.VaultMgmtAppId]替換WellKnownClientId
        public static async Task<string> AcquireUserAccessTokenAsync(string authority, string resource, string scope)
        {
            var context = new AuthenticationContext(authority, TokenCache.DefaultShared);
            if (_deviceCodeResponse == null)
            {
                //_deviceCodeResponse = await context.AcquireDeviceCodeAsync(resource, SampleConstants.WellKnownClientId).ConfigureAwait(false);
                _deviceCodeResponse = await context.AcquireDeviceCodeAsync(resource, ConfigurationManager.AppSettings[SampleConstants.ConfigKeys.VaultMgmtAppId]).ConfigureAwait(false);

                Console.WriteLine("############################################################################################");
                Console.WriteLine("To continue with the test run, please follow these instructions: {0}", _deviceCodeResponse.Message);
                Console.WriteLine("############################################################################################");
            }

            //context.AcquireTokenAsync()

            var result = await context.AcquireTokenByDeviceCodeAsync(_deviceCodeResponse).ConfigureAwait(false);
            return result.AccessToken;
        }

 

第五個錯誤:{"AADSTS7000218: The request body must contain the following parameter: 'client_assertion' or 'client_secret'.\r\nTrace ID: 57169df7-d54d-4533-b6cf-fc269ee93f00\r\nCorrelation ID: 33fb61c4-7266-4690-bb8d-4d4ebb5614f5\r\nTimestamp: 2021-01-19 02:44:50Z"}

這個錯誤是在 context.AcquireTokenByDeviceCodeAsync時,由於配置的AAD應用中沒有開啟移動應用或客戶端應用的高階設定。詳細的分析可以參考部落格:https://blogs.aaddevsup.xyz/2019/08/receiving-error-aadsts7000218-the-request-body-must-contain-the-following-parameter-client_assertion-or-client_secret/

【Azure Developer】解決Azure Key Vault管理Storage的示例程式碼在中國區Azure遇見的各種認證/授權問題 - C# Example Code

 

 

第六個錯誤:AADSTS54005: OAuth2 Authorization code was already redeemed, please retry with a new valid code or use an existing refresh token. |Trace ID: cbfb3d00-a3e5-445e-96b3-918a94054100 |Correlation ID: 40964a5f-e267-43da-988a-00bf33fa7ad4 |Timestamp: 2021-01-19 03:16:38Z

這個錯誤發生在 retrievedMsaResponse = await sample.DataClient.GetStorageAccountWithHttpMessagesAsync(vaultUri, managedStorageName).ConfigureAwait(false)的部分,由於通過AcquireTokenByDeviceCodeAsync獲取到的token只能完成一次認證。所以再一次呼叫KeyVaultClient的Get-xxxxx-WithHttpMessageAsync時就會出現OAuth2 Authorization code was already redeemed錯誤。 解決方法為修改建立 DataClient = new KeyVaultClient(ClientContext.AcquireUserAccessTokenAsync)的認證方式或者是AcquireUserAccessTokenAsync中的獲取token方式。

private void InstantiateSample(string tenantId, string appId, string appSecret, string subscriptionId, string resourceGroupName, string vaultLocation, string vaultName, string storageAccountName, string storageAccountResourceId)
        {
            context = ClientContext.Build(tenantId, appId, appSecret, subscriptionId, resourceGroupName, vaultLocation, vaultName, storageAccountName, storageAccountResourceId);

            // log in with as the specified service principal for vault management operations
            var serviceCredentials = Task.Run(() => ClientContext.GetServiceCredentialsAsync(tenantId, appId, appSecret)).ConfigureAwait(true).GetAwaiter().GetResult();
            //var serviceCredentials =ClientContext.GetServiceCredentialsAsync(tenantId, appId, appSecret).Result;

            // instantiate the management client
            ManagementClient = new KeyVaultManagementClient(serviceCredentials);
            ManagementClient.BaseUri = new Uri("https://management.chinacloudapi.cn");
            ManagementClient.SubscriptionId = subscriptionId;

            // instantiate the data client, specifying the user-based access token retrieval callback
            DataClient = new KeyVaultClient(ClientContext.AcquireUserAccessTokenAsync);
       //DataClient = new KeyVaultClient(serviceCredentials); }

或者

        public static async Task<string> AcquireUserAccessTokenAsync(string authority, string resource, string scope)
        {
            var context = new AuthenticationContext(authority, TokenCache.DefaultShared);
            if (_deviceCodeResponse == null)
            {
                //_deviceCodeResponse = await context.AcquireDeviceCodeAsync(resource, SampleConstants.WellKnownClientId).ConfigureAwait(false);
                _deviceCodeResponse = await context.AcquireDeviceCodeAsync(resource, ConfigurationManager.AppSettings[SampleConstants.ConfigKeys.VaultMgmtAppId]).ConfigureAwait(false);

                Console.WriteLine("############################################################################################");
                Console.WriteLine("To continue with the test run, please follow these instructions: {0}", _deviceCodeResponse.Message);
                Console.WriteLine("############################################################################################");
            }

            //context.AcquireTokenAsync()

            var result = await context.AcquireTokenByDeviceCodeAsync(_deviceCodeResponse).ConfigureAwait(false);
       return result.AccessToken; }

(PS: 以上第六個錯誤還沒有完全解決。)

 

參考資料:

建立 SAS 定義,並通過編寫程式碼提取共享訪問簽名令牌:https://docs.azure.cn/zh-cn/key-vault/secrets/storage-keys-sas-tokens-code

Azure Sample:https://github.com/Azure-Samples

RECEIVING ERROR AADSTS7000218: THE REQUEST BODY MUST CONTAIN THE FOLLOWING PARAMETER: ‘CLIENT_ASSERTION’ OR ‘CLIENT_SECRET’ :https://blogs.aaddevsup.xyz/2019/08/receiving-error-aadsts7000218-the-request-body-must-contain-the-following-parameter-client_assertion-or-client_secret/

Python程式碼通過AAD認證訪問微軟Azure金鑰保管庫(Azure Key Vault)中機密資訊(Secret)https://www.cnblogs.com/lulight/p/14286396.html

相關文章