分享方式:


在 Microsoft 身分識別平台中簽署金鑰變換

本文討論您對於 Microsoft 身分識別平台用來簽署安全性權杖的公開金鑰需了解哪些事項。 請務必注意這些金鑰會定期變換,且在緊急狀況下可以立即變換。 所有使用 Microsoft 身分識別平台的應用程式,都應能以程式設計的方式處理金鑰變換程序。 您將了解金鑰的運作方式、如何評估變換對應用程式的影響,以及必要時如何更新應用程式或建立定期手動變換程序來處理金鑰變換。

在 Microsoft 身分識別平台中簽署金鑰的總覽

Microsoft 身分識別平台會使用根據業界標準所建置的公開金鑰加密技術,建立 Azure AD 本身和使用 Azure AD 的應用程式之間的信任。 實際上,其運作方式如下:Microsoft 身分識別平台使用由公開和私密金鑰組所組成的簽署金鑰。 當使用者登入使用 Microsoft 身分識別平台進行驗證的應用程式時,Microsoft 身分識別平台會建立包含使用者相關資訊的安全性權杖。 此權杖會由 Microsoft 身分識別平台使用其私密金鑰進行簽署,然後再傳送回應用程式。 若要確認該權杖有效且來自 Azure AD,應用程式必須使用由 Microsoft 身分識別平台公開且包含在租用戶 OpenID Connect 探索文件或 SAML/WS-Fed 同盟中繼資料文件中的公開金鑰來驗證權杖簽章。

基於安全考量,Microsoft 身分識別平台的簽署金鑰會定期變換,且在緊急情況下可以立即變換。 金鑰的變換之間沒有任何固定或保證時間 - 與 Microsoft 身分識別平台整合的任何應用程式都應該準備好處理金鑰變換事件,無論發生的頻率為何。 如果您的應用程式無法處理突然的重新整理,並嘗試使用過期的金鑰來驗證權杖上的簽章,則您的應用程式就會錯誤地拒絕權杖。 每隔24小時檢查一次更新是最佳作法,節流 (最多每隔五分鐘一次) 如果發現權杖不會以應用程式快取中的金鑰進行驗證,則會立即重新整理金鑰檔。

OpenID Connect 探索文件和同盟中繼資料文件中永遠有一個以上的有效金鑰可用。 應用程式應該要已準備好使用文件中指定的任何和全部金鑰,因為某個金鑰可能很快就得要替換,而可能由另一個金鑰,依此類推。 當我們支援新的平台、雲端或驗證通訊協定時,出現的金鑰數目可能會隨著時間而改變,視 Microsoft 身分識別平台的內部架構而定。 JSON 回應中的金鑰順序以及公開順序,都應該視為對您應用程式有其意義。

僅支援單一簽署金鑰的應用程式,或是需要手動更新簽署金鑰的應用程式,在本質上並不安全且較不可靠。 這些應用程式應該更新為使用標準程式庫 (部分機器翻譯),以確保其一律會使用最新的簽署金鑰,還有其他最佳做法。

如何評估您的應用程式是否會受到影響,以及如何因應

應用程式處理金鑰變換的方式取決於各種變數,例如應用程式類型或使用的身分識別通訊協定和程式庫。 下列各節會評估最常見的應用程式類型是否會受到金鑰變換所影響,並提供如何更新應用程式以支援自動變換或手動更新金鑰的指引。

本指南 適用於︰

  • 從 Microsoft Entra 應用程式資源庫 (包括自訂) 新增的應用程式有個別的簽署金鑰指引。 更多資訊
  • 透過應用程式 proxy 發佈的內部部署應用程式不需要擔心簽署金鑰。

存取資源的原生用戶端應用程式

只存取資源的應用程式 (例如 Microsoft Graph、KeyVault、Outlook API 和其他 Microsoft API) 只會取得權杖,並將權杖傳遞給資源擁有者。 假設它們並未保護任何資源,它們就不會檢查權杖,因此不需要確定已正確簽署。

原生用戶端應用程式 (不論是桌上型或行動) 屬於此分類,因此不受變換影響。

存取資源的 Web 應用程式 / API

只存取資源的應用程式 (例如 Microsoft Graph、KeyVault、Outlook API 和其他 Microsoft API) 只會取得權杖,並將權杖傳遞給資源擁有者。 假設它們並未保護任何資源,它們就不會檢查權杖,因此不需要確定已正確簽署。

使用應用程式專用流程 (用戶端認證 / 用戶端憑證) 要求權杖的 Web 應用程式和 Web API 屬於此類型,因此不受變換影響。

保護資源且使用 Azure App Service 建置的 Web 應用程式 / API

Azure App Service 的驗證/授權 (EasyAuth) 功能已經具有必要的邏輯可自動處理金鑰變換。

使用 ASP.NET OWIN OpenID Connect、WS-Fed 或 WindowsAzureActiveDirectoryBearerAuthentication 中介軟體保護資源的 Web 應用程式 / API

如果您的應用程式使用 ASP.NET OWIN OpenID Connect、WS-Fed 或 WindowsAzureActiveDirectoryBearerAuthentication 中介軟體,則它已經具有必要的邏輯可自動處理金鑰變換。

您可在應用程式的 Startup.cs 或 Startup.Auth.cs 檔案中尋找下列任何程式碼片段,以確認您的應用程式使用任何這些項目。

app.UseOpenIdConnectAuthentication(
    new OpenIdConnectAuthenticationOptions
    {
        // ...
    });
app.UseWsFederationAuthentication(
    new WsFederationAuthenticationOptions
    {
        // ...
    });
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
    new WindowsAzureActiveDirectoryBearerAuthenticationOptions
    {
        // ...
    });

使用 .NET Core OpenID Connect 或 JwtBearerAuthentication 中介軟體保護資源的 Web 應用程式/API

如果您的應用程式使用 ASP.NET OWIN OpenID Connect 或 JwtBearerAuthentication 中介軟體,則它已經具有自動處理金鑰變換的必要邏輯。

您可以應用程式的 Startup.cs 或 Startup.Auth.cs 中尋找下列任何程式碼片段,以確認您的應用程式使用任何這些項目

app.UseOpenIdConnectAuthentication(
     new OpenIdConnectAuthenticationOptions
     {
         // ...
     });
app.UseJwtBearerAuthentication(
    new JwtBearerAuthenticationOptions
    {
     // ...
     });

使用 Node.js passport-azure-ad 模組保護資源的 Web 應用程式 / API

如果您的應用程式使用 Node.js passport-ad 模組,則它已經具有必要的邏輯可自動處理金鑰變換。

您可以在應用程式的 app.js 中搜尋下列程式碼片段,以確認您的應用程式使用 passport-ad

var OIDCStrategy = require('passport-azure-ad').OIDCStrategy;

passport.use(new OIDCStrategy({
    //...
));

保護資源且使用 Visual Studio 2015 或更新版本建立的 Web 應用程式/API

如果應用程式是使用 Visual Studio 2015 或更新版本中的 Web 應用程式範本所建置,而且您已從 [變更驗證] 功能表中選取 [辦公或學校帳戶],則其中已經具有自動處理金鑰變換的必要邏輯。 這個內嵌在 OWIN OpenID Connect 中介軟體中的邏輯會從 OpenID Connect 探索文件擷取並快取金鑰,還會定期重新整理金鑰。

如果您是以手動方式在方案中加入驗證,應用程式可能不會有所需的金鑰變換邏輯。 您可以自行撰寫,或依照 使用任何其他程式庫或手動實作任何支援的通訊協定的 Web 應用程式 / API (部分機器翻譯) 中的步驟進行。

保護資源且使用 Visual Studio 2013 建立的 Web 應用程式

如果應用程式是使用 Visual Studio 2013 中的 Web 應用程式範本所建置,而且您已從 [變更驗證] 功能表中選取 [組織帳戶],則它已經具有自動處理金鑰變換的必要邏輯。 此邏輯會將組織的唯一識別碼和簽署金鑰資訊儲存在兩個與專案相關聯的資料庫資料表中。 您可以在專案的 Web.config 檔案中找到資料庫的連接字串。

如果您是以手動方式在方案中加入驗證,應用程式可能不會有所需的金鑰變換邏輯。 您必須自行撰寫,或依照 使用任何其他程式庫或手動實作任何支援的通訊協定的 Web 應用程式 / API (部分機器翻譯) 中的步驟進行。

下列步驟可協助您確認應用程式中的邏輯能正常運作。

  1. 在 Visual Studio 2013 中開啟方案,然後選取右側視窗上的 [伺服器總管] 索引標籤。
  2. 依序展開 [資料連線]、[DefaultConnection]、[資料表]。 找出 [IssuingAuthorityKeys] 資料表並以滑鼠右鍵按一下,然後選取 [顯示資料表資料]
  3. [IssuingAuthorityKeys] 資料表中會有至少一個對應至金鑰指紋值的資料列。 刪除資料表中的任何資料列。
  4. 在 [租用戶] 資料表上按一下滑鼠右鍵,然後選取 [顯示資料表資料]
  5. [租用戶] 資料表中會有至少一個對應至唯一目錄租用戶識別碼的資料列。 刪除資料表中的任何資料列。 如果您未刪除 [租用戶] 資料表和 [IssuingAuthorityKeys] 資料表中的資料列,您就會在執行階段收到錯誤。
  6. 建置並執行應用程式。 在登入帳戶之後,即可停止應用程式。
  7. 返回 [伺服器總管],然後查看 [IssuingAuthorityKeys] 和 [租用戶] 資料表中的值。 您將會發現,它們已自動重新填入同盟中繼資料文件中的適當資訊。

保護資源且使用 Visual Studio 2013 建立的 Web API

如果您使用 Web API 範本在 Visual Studio 2013 中建置了 Web API 應用程式,然後從 [變更驗證] 功能表中選取了 [組織帳戶],則應用程式已經具有所需的邏輯。

如果您以手動方式設定了驗證,請依照下列指示來了解如何設定 Web API 以自動更新其金鑰資訊。

下列程式碼片段示範如何從同盟中繼資料文件取得最新的金鑰,然後使用 JWT 權杖處理常式 (英文) 來驗證權杖。 此程式碼片段假設您將會使用自己的快取機制來保留金鑰,以驗證日後來自 Microsoft 身分識別平台的權杖,不論它位於資料庫、組態檔或其他位置。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IdentityModel.Tokens;
using System.Configuration;
using System.Security.Cryptography.X509Certificates;
using System.Xml;
using System.IdentityModel.Metadata;
using System.ServiceModel.Security;
using System.Threading;

namespace JWTValidation
{
    public class JWTValidator
    {
        private string MetadataAddress = "[Your Federation Metadata document address goes here]";

        // Validates the JWT Token that's part of the Authorization header in an HTTP request.
        public void ValidateJwtToken(string token)
        {
            JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler()
            {
                // Do not disable for production code
                CertificateValidator = X509CertificateValidator.None
            };

            TokenValidationParameters validationParams = new TokenValidationParameters()
            {
                AllowedAudience = "[Your App ID URI goes here]",
                ValidIssuer = "[The issuer for the token goes here, such as https://sts.windows.net/aaaabbbb-0000-cccc-1111-dddd2222eeee/]",
                SigningTokens = GetSigningCertificates(MetadataAddress)

                // Cache the signing tokens by your desired mechanism
            };

            Thread.CurrentPrincipal = tokenHandler.ValidateToken(token, validationParams);
        }

        // Returns a list of certificates from the specified metadata document.
        public List<X509SecurityToken> GetSigningCertificates(string metadataAddress)
        {
            List<X509SecurityToken> tokens = new List<X509SecurityToken>();

            if (metadataAddress == null)
            {
                throw new ArgumentNullException(metadataAddress);
            }

            using (XmlReader metadataReader = XmlReader.Create(metadataAddress))
            {
                MetadataSerializer serializer = new MetadataSerializer()
                {
                    // Do not disable for production code
                    CertificateValidationMode = X509CertificateValidationMode.None
                };

                EntityDescriptor metadata = serializer.ReadMetadata(metadataReader) as EntityDescriptor;

                if (metadata != null)
                {
                    SecurityTokenServiceDescriptor stsd = metadata.RoleDescriptors.OfType<SecurityTokenServiceDescriptor>().First();

                    if (stsd != null)
                    {
                        IEnumerable<X509RawDataKeyIdentifierClause> x509DataClauses = stsd.Keys.Where(key => key.KeyInfo != null && (key.Use == KeyType.Signing || key.Use == KeyType.Unspecified)).
                                                             Select(key => key.KeyInfo.OfType<X509RawDataKeyIdentifierClause>().First());

                        tokens.AddRange(x509DataClauses.Select(token => new X509SecurityToken(new X509Certificate2(token.GetX509RawData()))));
                    }
                    else
                    {
                        throw new InvalidOperationException("There is no RoleDescriptor of type SecurityTokenServiceType in the metadata");
                    }
                }
                else
                {
                    throw new Exception("Invalid Federation Metadata document");
                }
            }
            return tokens;
        }
    }
}

保護資源且使用 Visual Studio 2012 建立的 Web 應用程式

如果應用程式是在 Visual Studio 2012 中建置的,您大概是使用身分識別與存取工具來設定應用程式。 您也可能是使用 驗證簽發者名稱登錄 (VINR)。 VINR 負責維護信任的識別提供者 (Microsoft 身分識別平台) 相關資訊,以及用來驗證其所簽發權杖的金鑰。 VINR 也可透過下載與目錄相關聯的最新同盟中繼資料文件、使用最新文件檢查組態是否過期,以及視需要讓應用程式更新為使用新金鑰,讓您輕鬆地自動更新 Web.config 檔案中儲存的金鑰資訊。

如果您是使用 Microsoft 所提供的任何程式碼範例或逐步解說文件建立應用程式,則專案中已含有金鑰變換邏輯。 您會發現專案中已存在下列程式碼。 如果應用程式還沒有此邏輯,請遵循下列步驟,以新增此邏輯並確認它能正常運作。

  1. 在 [方案總管] 中,針對適當的專案新增對 System.IdentityModel 組件的參考。
  2. 開啟 Global.asax.cs 檔案,並新增下列 using 指示詞:
    using System.Configuration;
    using System.IdentityModel.Tokens;
    
  3. Global.asax.cs 檔案中新增下列方法:
    protected void RefreshValidationSettings()
    {
     string configPath = AppDomain.CurrentDomain.BaseDirectory + "\\" + "Web.config";
     string metadataAddress =
                   ConfigurationManager.AppSettings["ida:FederationMetadataLocation"];
     ValidatingIssuerNameRegistry.WriteToConfig(metadataAddress, configPath);
    }
    
  4. Global.asax.csApplication_Start() 方法中,叫用 RefreshValidationSettings() 方法,如下所示︰
    protected void Application_Start()
    {
     AreaRegistration.RegisterAllAreas();
     ...
     RefreshValidationSettings();
    }
    

在遵循這些步驟之後,應用程式的 Web.config 將會以同盟中繼資料文件中的最新資訊進行更新,其中也包括最新的金鑰。 此更新會在每次 IIS 回收應用程式集區時進行;依預設,IIS 設定為每隔 29 小時回收應用程式一次。

請遵循下列步驟來確認金鑰變換邏輯是否能運作。

  1. 在確認應用程式是使用上述程式碼之後,開啟 Web.config 檔案並瀏覽至 <issuerNameRegistry> 區塊,具體而言,請尋找下列幾行︰
    <issuerNameRegistry type="System.IdentityModel.Tokens.ValidatingIssuerNameRegistry, System.IdentityModel.Tokens.ValidatingIssuerNameRegistry">
         <authority name="https://sts.windows.net/aaaabbbb-0000-cccc-1111-dddd2222eeee/">
           <keys>
             <add thumbprint="AA11BB22CC33DD44EE55FF66AA77BB88CC99DD00" />
           </keys>
    
  2. <add thumbprint=""> 設定中,將任何字元替換為其他字元以變更指紋值。 儲存 Web.config 檔案。
  3. 建置應用程式,然後加以執行。 如果您可以完成登入程序,則應用程式已從目錄的同盟中繼資料文件下載所需資訊,而成功地更新金鑰。 如果您在登入時發生問題,請閱讀使用 Microsoft 身分識別平台為 Web 應用程式新增登入 (英文) 一文,或是下載並檢查下列程式碼範例︰Microsoft Entra ID 的多租用戶雲端應用程式,以確定應用程式中的變更是否正確。

保護資源且使用任何其他程式庫或手動實作任何支援的通訊協定的 Web 應用程式 / API

如果您使用其他程式庫,或手動實作任何支援的通訊協定,您必須檢閱文件庫或您的實作,以確保從 OpenID Connect 探索文件或同盟中繼資料文件擷取金鑰。 檢查的方法之一是在您的程式碼或程式庫的程式碼中,搜尋是否有任何呼叫 OpenID 探索文件或同盟中繼資料文件的情況。

如果金鑰儲存在某處或硬式編碼在您的應用程式中,您可以手動擷取金鑰並根據本指導文件結尾的指示執行手動變換來更新金鑰。 強烈建議您增強應用程式來支援自動變換時使用本文描述的任何方法,以免未來如果 Microsoft 身分識別平台提高變換頻率或臨時需要緊急變換時,造成運作中斷和超出負荷。

如何測試您的應用程式,以判斷其是否會受影響

您可以藉由使用下列 PowerShell 指令碼,驗證您的應用程式是否支援自動金鑰變換。

若要使用 PowerShell 檢查和更新簽署金鑰,您需要 MSIdentityTools (英文) PowerShell 模組。

  1. 安裝 MSIdentityTools (英文) PowerShell 模組:

    Install-Module -Name MSIdentityTools
    
  2. 搭配系統管理員帳戶使用 Connect-MgGraph 命令來登入,以同意所需的範圍:

     Connect-MgGraph -Scope "Application.ReadWrite.All"
    
  3. 取得可用簽署金鑰指紋的清單:

    Get-MsIdSigningKeyThumbprint
    
  4. 挑選任何金鑰指紋,並設定 Microsoft Entra ID 以將該金鑰與您的應用程式搭配使用 (從 Microsoft Entra 系統管理中心取得應用程式識別碼):

    Update-MsIdApplicationSigningKeyThumbprint -ApplicationId <ApplicationId> -KeyThumbprint <Thumbprint>
    
  5. 登入以取得新的權杖來測試 Web 應用程式。 金鑰更新變更是即時的,但是請確定您使用新的瀏覽器工作階段 (例如使用 Internet Explorer 的「InPrivate」,使用 Chrome 的「Incognito」,或使用 Firefox 的「Private」模式) 以確保您發出新權杖。

  6. 針對每個傳回的簽署金鑰指紋,執行 Update-MsIdApplicationSigningKeyThumbprint Cmdlet 並測試您的 Web 應用程式登入程序。

  7. 如果 Web 應用程式正確登入,則支援自動變換。 如果未正確登入,請修改您的應用程式以支援手動變換。 如需詳細資訊,請參閱建立手動變換程序

  8. 執行下列指令碼以還原為正常行為:

    Update-MsIdApplicationSigningKeyThumbprint -ApplicationId <ApplicationId> -Default
    

如果應用程式不支援自動變換,如何執行手動變換

如果您的應用程式不支援自動變換,您必須先建立程序,該程序會定期監視 Microsoft 身分識別平台的簽署金鑰,並據此執行手動變換。

若要使用 PowerShell 檢查和更新簽署金鑰,您需要 MSIdentityTools (英文) PowerShell 模組。

  1. 安裝 MSIdentityTools (英文) PowerShell 模組:

    Install-Module -Name MSIdentityTools
    
  2. 取得最新的簽署金鑰 (從 Microsoft Entra 系統管理中心取得租用戶識別碼):

    Get-MsIdSigningKeyThumbprint -Tenant <tenandId> -Latest
    
  3. 比較此金鑰與應用程式目前已硬式編碼或設定為使用的金鑰。

  4. 如果最新的金鑰與應用程式所使用的金鑰不同,請下載最新的簽署金鑰:

    Get-MsIdSigningKeyThumbprint -Latest -DownloadPath <DownloadFolderPath>
    
  5. 更新應用程式的程式碼或組態,以使用新的金鑰。

  6. 設定 Microsoft Entra ID 以將該最新金鑰與您的應用程式搭配使用 (從 Microsoft Entra 系統管理中心取得應用程式識別碼):

    Get-MsIdSigningKeyThumbprint -Latest | Update-MsIdApplicationSigningKeyThumbprint -ApplicationId <ApplicationId>
    
  7. 登入以取得新的權杖來測試 Web 應用程式。 金鑰更新變更是即時的,但是請確定您使用新的瀏覽器工作階段 (例如使用 Internet Explorer 的「InPrivate」,使用 Chrome 的「Incognito」,或使用 Firefox 的「Private」模式) 以確保您發出新權杖。

  8. 如果您遇到任何問題,請還原為先前使用的金鑰,並連絡 Azure 支援:

    Update-MsIdApplicationSigningKeyThumbprint -ApplicationId <ApplicationId> -KeyThumbprint <PreviousKeyThumbprint>
    
  9. 更新應用程式以支援手動變換之後,請還原為正常行為:

    Update-MsIdApplicationSigningKeyThumbprint -ApplicationId <ApplicationId> -Default