執行自動化整合測試

身為開發人員,您想要對自己開發的應用程式,執行自動化整合測試。 在自動化整合測試中呼叫受 Microsoft 身分識別平台保護的 API (或其他受保護的 API,例如 Microsoft Graph) 是一項挑戰。 Microsoft Entra ID 通常需要互動式的使用者登入提示,這很難自動化。 本文描述如何使用非互動式流程 (稱為 資源擁有者密碼認證授與 (ROPC)),讓使用者自動登入以進行測試。

若要備妥自動化整合測試,請建立一些測試使用者、建立並設定一個應用程式註冊,然後可能要對您的租用戶進行某些設定變更。 其中某些步驟需要系統管理員權限。 此外,Microsoft 建議您不要在生產環境中使用 ROPC 流程。 建立另一個測試租用戶 (您為系統管理員),讓您可以安全且有效地執行自動化整合測試。

警告

Microsoft 建議您不要在生產環境中使用 ROPC 流程。 在大部分的產品案例中,提供有更安全的替代方案,也建議使用這些方案。 此 ROPC 流程在應用程式中需要非常高的信任度,而且帶有在其他驗證流程中不會出現的風險。 您應該只有在另一個測試租用戶中使用此流程進行測試,並且僅使用測試使用者。

重要

  • Microsoft 身分識別平台僅支援 Microsoft Entra 租用戶內的 ROPC,而不是個人帳戶。 因此您必須使用租用戶特定端點 (https://login.microsoftonline.com/{TenantId_or_Name}) 或 organizations 端點。
  • 受邀加入 Microsoft Entra 租用戶的個人帳戶無法使用 ROPC。
  • 沒有密碼的帳戶無法使用 ROPC 登入,這表示像是 SMS 登入、FIDO 與 Authenticator 應用程式等功能將無法使用該流程。
  • 如果使用者必須使用多重要素驗證 (MFA) 來登入應用程式,則會遭到封鎖。
  • 混合式身分識別同盟 案例不支援 ROPC (例如用來驗證內部部署帳戶的 Microsoft Entra ID 和 Active Directory 同盟服務 (AD FS))。 如果以整頁方式將使用者重新導向至內部部署身分識別提供者,則 Microsoft Entra ID 無法測試該身分識別提供者的使用者名稱和密碼。 不過,ROPC 支援傳遞驗證
  • 混合式識別身分同盟案例的例外狀況如下:將 AllowCloudPasswordValidation 設定為 TRUE 的首頁領域探索原則,可讓 ROPC 流程在內部部署密碼同步至雲端時,供同盟使用者使用。 如需詳細資訊,請參閱為舊版應用程式啟用同盟使用者的直接 ROPC 驗證

建立另一個測試租用戶

在生產環境中使用 ROPC 驗證流程具有風險,因此請建立另一個租用戶,測試您的應用程式。 您可以使用現有的測試租用戶,但您必須是租用戶中的管理員,因為下列幾個步驟都需要管理員權限。

建立和設定金鑰保存庫

建議您在 Azure Key Vault 中安全地儲存作為祕密的測試使用者名稱與密碼。 當您稍後執行測試時,其會在安全性主體的內容中執行。 如果您是在本機執行測試,則安全性主體是Microsoft Entra 使用者(例如,在Visual Studio 或 Visual Studio Code 中),如果您是在 Azure Pipelines 或其他 Azure 資源中執行測試,則為服務主體或受控識別。 安全性主體必須具有讀取列出祕密權限,讓測試執行器可以從您的金鑰保存庫,取得測試使用者名稱與密碼。 如需詳細資訊,請參閱 Azure Key Vault 中的驗證

  1. 若尚未有金鑰保存庫,請建立新的金鑰保存庫
  2. 請記下本文稍後範例測試中會使用的保存庫 URI 屬性值 (類似於 https://<your-unique-keyvault-name>.vault.azure.net/)。
  3. 為執行測試的安全性主體指派存取原則。 在金鑰保存庫中授與使用者、服務主體或受控識別取得列出祕密權限。

建立測試使用者

提示

本文中的步驟可能會依據您開始的入口網站而稍有不同。

在您的租用戶中建立一些測試使用者,以進行測試。 因為測試使用者並非實際的真人,所以建議您指派複雜的密碼,並將這些密碼安全地儲存為 Azure Key Vault 中的祕密

  1. 以至少 雲端應用程式系統管理員 的身分登入 Microsoft Entra 系統管理中心
  2. 瀏覽至 [身分識別]>[使用者]>[所有使用者]。
  3. 選取 [新增使用者],然後在您的目錄中建立一或多個測試使用者帳戶。
  4. 本文稍後的範例測試,會使用單一測試使用者。 在先前建立的金鑰保存庫中,將測試使用者名稱與密碼新增為祕密。 將使用者名稱新增為名為 "TestUserName" 的祕密,並將密碼新增為名為 "TestPassword" 的祕密。

建立並設定應用程式註冊

註冊在測試期間呼叫 API 時,身為用戶端應用程式的應用程式。 這應該是您在生產環境中可能已具備的相同應用程式。 您應該要有另一個僅供測試之用的應用程式。

註冊應用程式

建立應用程式註冊。 您可以遵循應用程式註冊快速入門中的步驟。 您不需要新增重新導向 URI 或新增認證,因此可以略過這些區段。

請記下應用程式 (用戶端) 識別碼,在本文稍後的範例測試中將會用到。

為應用程式啟用公開用戶端流程

ROPC 是公開用戶端流程,因此您必須為公開用戶端流程啟用您的應用程式。 從 Microsoft Entra 系統管理中心中的應用程式註冊,前往 [驗證]>[進階設定]>[允許公用用戶端流程]。 將切換設定為 [是]

因為 ROPC 不是互動式流程,所以執行階段並不會出現提示您要同意這些流程的同意畫面。 預先同意權限,可避免在取得權杖時發生錯誤。

將權限新增至您的應用程式。 請勿將任何敏感性或高等級權限新增至應用程式,建議您將測試案例的範圍設定為與 Microsoft Entra ID 整合的基本整合案例。

Microsoft Entra 系統管理中心 中的應用程式註冊,前往 [API 權限]>[新增權限]。 新增呼叫將會使用之 API 所需的權限。 本文中進一步的測試範例會使用 https://graph.microsoft.com/User.Readhttps://graph.microsoft.com/User.ReadBasic.All 權限。

新增權限之後,必須予以同意。 您同意權限的方式,取決於您的測試應用程式是否位於和應用程式註冊相同的租用戶中,以及您是否為該租用戶的系統管理員。

應用程式與應用程式註冊位於相同的租用戶中,且您是系統管理員

如果您打算在註冊應用程式的相同租用戶中測試應用程式,而您是該租用戶中的系統管理員,則可以同意 Microsoft Entra 系統管理中心 的權限。 在 Azure 入口網站的應用程式註冊中,前往 [API 權限],並在 [新增權限] 按鈕旁選取 [您的租用戶名稱]> 的 [授與管理員同意]< 按鈕,然後按一下 [是] 加以確認。

應用程式與應用程式註冊位於不同的租用戶中,或者您並非系統管理員

如果您不打算在註冊應用程式的相同租用戶中測試您的應用程式,或您不是租用戶中的系統管理員,則無法同意來自 Microsoft Entra 系統管理中心 的權限。 但您仍然可以在網頁瀏覽器中觸發登入提示,藉此同意某些權限。

Microsoft Entra 系統管理中心 的應用程式註冊中,前往 [驗證]>[平台組態]>[新增平台]>[Web]。 新增重新導向 URI "https://localhost";然後選取 [設定]

非系統管理員使用者無法透過 Azure 入口網站預先同意,因此請在瀏覽器中傳送下列要求。 收到登入畫面的提示時,請以上一個步驟中所建立的測試帳戶登入。 同意提示您的權限。 您需要針對想要呼叫的每個 API,重複此步驟,並測試您要使用的使用者。

// Line breaks for legibility only

https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize?
client_id={your_client_ID}
&response_type=code
&redirect_uri=https://localhost
&response_mode=query
&scope={resource_you_want_to_call}/.default
&state=12345

請以您的租用戶識別碼取代 {租用戶},以您應用程式的用戶端識別碼取代 {您的用戶端識別碼},並以識別碼 URI (例如,"https://graph.microsoft.com") 或嘗試存取的 API 應用程式識別碼,取代 {要呼叫的資源}

從 MFA 原則中排除測試應用程式與使用者

您的租用戶可能會條件式存取原則,要求所有使用者進行多重要素驗證 (MFA),如Microsoft 所建議。 MFA 無法搭配 ROPC 一起使用,因此您將需要豁免此需求的測試應用程式與測試使用者。

若要排除使用者帳戶:

  1. 以至少 雲端應用程式系統管理員 的身分登入 Microsoft Entra 系統管理中心
  2. 瀏覽至左側瀏覽窗格中的 [身分識別]>[資訊安全中心],然後選取 [條件式存取]
  3. [原則]中,選取需要 MFA 的條件式存取原則。
  4. 選取 [使用者或工作負載身分識別]
  5. 選取 [排除] 索引標籤,然後選取 [使用者與群組] 核取方塊。
  6. 選取要在 [選取排除的使用者] 中排除的使用者帳戶。
  7. 選取 [選取] 按鈕,然後選取 [儲存]

若要排除測試應用程式:

  1. [原則]中,選取需要 MFA 的條件式存取原則。
  2. 選取 [雲端應用程式或動作]
  3. 選取 [排除] 索引標籤,然後選取 [選取排除的雲端應用程式]
  4. 選取您要在 [選取排除的雲端應用程式] 中排除的應用程式。
  5. 選取 [選取] 按鈕,然後選取 [儲存]

撰寫您的應用程式測試

現在即已設定妥當,可以撰寫自動化測試。 以下是各種測試:

  1. .NET 範例程式碼會使用 Microsoft 驗證程式庫 (MSAL)xUnit,這是常見的測試架構。
  2. JavaScript 範例程式碼使用 Microsoft 驗證程式庫 (MSAL)Playwright,這是常見的測試架構。

設定您的 appsettings.json 檔案

將先前建立之測試應用程式的用戶端識別碼、必要範圍與金鑰保存庫 URI,新增至測試專案的 appsettings.json 檔案。

{
  "Authentication": {
    "AzureCloudInstance": "AzurePublic", //Will be different for different Azure clouds, like US Gov
    "AadAuthorityAudience": "AzureAdMultipleOrgs",
    "ClientId": <your_client_ID>
  },

  "WebAPI": {
    "Scopes": [
      //For this Microsoft Graph example.  Your value(s) will be different depending on the API you're calling
      "https://graph.microsoft.com/User.Read",
      //For this Microsoft Graph example.  Your value(s) will be different depending on the API you're calling
      "https://graph.microsoft.com/User.ReadBasic.All"
    ]
  },

  "KeyVault": {
    "KeyVaultUri": "https://<your-unique-keyvault-name>.vault.azure.net//"
  }
}

設定您的用戶端,用於所有測試類別中

使用 SecretClient(),從 Azure Key Vault 中取得測試使用者名稱與密碼祕密。 若 Key Vault 正在進行節流,則程式碼會使用指數輪詢加以重試。

DefaultAzureCredential() 會從環境變數或受控識別所設定的服務主體,取得存取權杖,利用 Azure Key Vault 進行驗證 (若程式碼在具有受控識別的 Azure 資源上執行)。 若程式碼正在本機執行,則 DefaultAzureCredential 會使用本機使用者的認證。 如需詳細資訊,請參閱 Azure 身分識別用戶端程式庫內容。

使用 Microsoft 驗證程式庫 (MSAL),利用 ROPC 流程進行驗證並取得存取權杖。 該存取權杖會在 HTTP 要求中以持有人權杖的方式傳遞。

using Xunit;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
using System.Security;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using Microsoft.Extensions.Configuration;
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
using Azure.Core;
using System;

public class ClientFixture : IAsyncLifetime
{
    public HttpClient httpClient;

    public async Task InitializeAsync()
    {
        var builder = new ConfigurationBuilder().AddJsonFile("<path-to-json-file>");

        IConfigurationRoot Configuration = builder.Build();

        var PublicClientApplicationOptions = new PublicClientApplicationOptions();
        Configuration.Bind("Authentication", PublicClientApplicationOptions);
        var app = PublicClientApplicationBuilder.CreateWithApplicationOptions(PublicClientApplicationOptions)
            .Build();

        SecretClientOptions options = new SecretClientOptions()
        {
            Retry =
                {
                    Delay= TimeSpan.FromSeconds(2),
                    MaxDelay = TimeSpan.FromSeconds(16),
                    MaxRetries = 5,
                    Mode = RetryMode.Exponential
                 }
        };

        string keyVaultUri = Configuration.GetValue<string>("KeyVault:KeyVaultUri");
        var client = new SecretClient(new Uri(keyVaultUri), new DefaultAzureCredential(), options);

        KeyVaultSecret userNameSecret = client.GetSecret("TestUserName");
        KeyVaultSecret passwordSecret = client.GetSecret("TestPassword");

        string password = passwordSecret.Value;
        string username = userNameSecret.Value;
        string[] scopes = Configuration.GetSection("WebAPI:Scopes").Get<string[]>();
        SecureString securePassword = new NetworkCredential("", password).SecurePassword;

        AuthenticationResult result = null;
        httpClient = new HttpClient();

        try
        {
            result = await app.AcquireTokenByUsernamePassword(scopes, username, securePassword)
                .ExecuteAsync();
        }
        catch (MsalException) { }

        string accessToken = result.AccessToken;
        httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
    }

    public Task DisposeAsync() => Task.CompletedTask;
}

用於您的測試類別

下列範例是呼叫 Microsoft Graph 的測試。 以您想要在自己的應用程式或 API 上測試的內容,取代此測試。

public class ApiTests : IClassFixture<ClientFixture>
{
    ClientFixture clientFixture;

    public ApiTests(ClientFixture clientFixture)
    {
        this.clientFixture = clientFixture;
    }

    [Fact]
    public async Task GetRequestTest()
    {
        var testClient = clientFixture.httpClient;
        HttpResponseMessage response = await testClient.GetAsync("https://graph.microsoft.com/v1.0/me");
        var responseCode = response.StatusCode.ToString();
        Assert.Equal("OK", responseCode);
    }
}