Android 版 ADAL 遷移至 MSAL 遷移指南

本文重點說明,將使用 Azure Active Directory 認證庫(ADAL)的應用程式遷移至 Microsoft 驗證資源庫(MSAL)時,你需要做的變更。

差異亮點

ADAL 可搭配 Azure AD v1.0 端點運作。 Microsoft 驗證資源庫(MSAL)與 Microsoft 身分識別平台 相容,該平台前身為 Azure AD v2.0 端點。 Microsoft 身分識別平台 與 Azure AD v1.0 的不同之處在於:

支援:

  • 組織身份(Microsoft Entra ID)

  • 非組織身份,如 Outlook.com、Xbox Live 等

  • (僅限 Azure AD B2C 使用)與 Google、Facebook、X 和 Amazon 的聯邦登入

  • 標準是否相容於:

    • OAuth v2.0
    • OpenID Connect(OIDC)

MSAL 公開 API 引入了重要變更,包括:

  • 一種新的代幣存取模式:
    • ADAL 透過 AuthenticationContext代表伺服器的 ,提供對標記的存取。 MSAL 透過代表用戶端的 PublicClientApplication 來存取權杖。 客戶端開發者不需要為每個需要互動的權威建立新 PublicClientApplication 實例。 只需要一個 PublicClientApplication 設定即可。
    • 支援除了資源識別碼之外,也可使用範圍來要求存取權杖。
    • 支持漸進式同意。 開發者可以隨著使用者在應用程式中存取越來越多功能,而要求更多權限範圍,包括應用程式註冊時未納入的功能所需權限範圍。
    • 權限不再在運行時被驗證。 開發商在開發過程中會公布一份「已知權威」名單。
  • 代幣 API 變更:
    • 在 ADAL 中, AcquireToken() 首先提出無聲請求。 若無法做到,系統會發出互動式要求。 這種行為導致部分開發者僅 AcquireToken依賴 ,導致使用者有時會意外被要求輸入憑證。 MSAL 要求開發者有意識地決定使用者何時收到 UI 提示。
      • AcquireTokenSilent 一律會發出靜默要求,結果不是成功,就是失敗。
      • AcquireToken 總是會導致系統透過 UI 提示使用者的要求。
  • MSAL 支援從預設瀏覽器或嵌入式網頁檢視登入:
    • 預設情況下,裝置會使用預設瀏覽器。 這讓 MSAL 可以使用認證狀態(cookie),而這些狀態可能已經存在於一個或多個已登入帳號。 如果不存在任何驗證狀態,則在授權過程中透過 MSAL 進行驗證,會建立驗證狀態(Cookie),供其他也會在同一瀏覽器中使用的 Web 應用程式使用。
  • 新例外模型:
    • 例外更清楚定義了發生的錯誤類型,以及開發者需要做什麼來解決。
  • MSAL 支援在 AcquireToken 和 AcquireTokenSilent 呼叫中使用參數物件。
  • MSAL 支援下列項目的宣告式設定:
    • 客戶 ID,重定向 URI。
    • 嵌入式瀏覽器與預設瀏覽器
    • 當局
    • HTTP 設定如讀取與連線逾時

你的應用程式註冊與遷移到 MSAL

你不需要更改現有的應用程式註冊就能使用 MSAL。 如果你想利用漸進式/漸進式同意,可能需要檢視註冊表,以確定你想要逐步申請的具體範圍。 以下將提供有關權限範圍和逐步同意的更多資訊。

在你的應用程式註冊中,你會看到一個 API 權限 標籤。這會列出你應用程式目前設定以請求存取權限的 API 和權限(範圍)。 它也會顯示與每個 API 權限相關的範圍名稱清單。

在 ADAL 和 Azure AD v1.0 端,使用者對自己擁有資源的同意是在首次使用時就被授予。 使用 MSAL 與 Microsoft 身分識別平台,可以逐步請求同意。 對於某些權限,漸進式同意很有用,尤其是當使用者可能將其視為高敏感度權限,或是在未清楚說明為何需要這些權限時,可能會提出疑問的情況。 在 ADAL 中,這些權限可能導致使用者放棄登入你的應用程式。

Tip

使用漸進同意,為使用者提供更多背景說明為何你的應用程式需要權限。

組織管理員可以代表所有成員同意您的應用程式所需的權限。 有些組織只允許管理員同意申請。 管理員同意要求你在應用程式註冊時包含所有應用程式使用的 API 權限和範圍。

Tip

即使你可以用 MSAL 申請範圍,針對應用程式註冊中未包含的內容,我們建議你更新應用程式註冊,包含使用者可能授權的所有資源與範圍。

從資源 ID 移轉至範圍

首次使用時驗證並請求所有權限授權

如果你目前使用 ADAL,且不需要使用增量同意,最簡單的使用 MSAL 方法是用新acquireToken物件提出AcquireTokenParameter請求並設定資源 ID 值。

注意事項

無法同時設定範圍和資源 ID。嘗試同時設定兩者會得到 IllegalArgumentException。

這會產生與你已經習慣的相同 v1 行為。 你在應用程式註冊時要求的所有權限,都是在使用者第一次互動時向他們請求的。

僅在必要時驗證並請求權限

為了善用增量同意,請列出你的應用程式從應用程式註冊中使用的權限(範圍),並根據以下條件將它們組織成兩個清單:

  • 在使用者首次於登入期間與你的應用程式互動時,你想要請求哪些權限範圍?
  • 與應用程式重要功能相關的權限,你也需要向使用者說明。

當你組織好範圍後,依照你想申請令牌的資源(API)來組織每個清單。 以及你希望使用者同時授權的任何其他權限範圍。

用來向 MSAL 請求的參數物件支援:

  • Scope: 你想要申請授權並獲得存取權杖的範圍清單。
  • ExtraScopesToConsent: 一份額外的範圍清單,當您在為其他資源申請存取權杖時,想要申請授權。 這份範圍清單能讓你減少申請使用者授權的次數。 這代表用戶授權或同意提示會減少。

從 AuthenticationContext 遷移到 PublicClientApplications

建立 PublicClientApplication

當你使用 MSAL 時,你會實例化一個 PublicClientApplication. 這個物件會模擬你的應用程式身份,並用來向一個或多個權威機構提出請求。 使用這個物件,你可以設定客戶端身份、重定向 URI、預設權限、要使用裝置瀏覽器還是嵌入式網頁檢視、日誌層級等等。

你可以用 JSON 宣告式配置這個物件,你可以把它當成檔案提供,或是把它當作資源儲存在你的 APK 裡。

雖然此物件並非單例,但在內部它同時使用 shared Executors 來處理互動式與靜默式請求。

企業對企業

在 ADAL 中,你向其請求存取權杖的每個組織都需要一個獨立的 AuthenticationContext。 在 MSAL 中,這已不再是必要條件。 你可以指定你想從哪個權限請求代幣,作為無聲或互動請求的一部分。

從權威驗證遷移到已知權威

MSAL 沒有啟用或停用權威驗證的旗標。 權威驗證是 ADAL 中的一項功能,在 MSAL 早期版本中,能防止你的程式碼向潛在惡意權威請求權杖。 MSAL 現在會取得 Microsoft 已知的權威清單,並將該清單與你設定中的權威合併。

Tip

如果你是 Azure 企業對消費者(B2C)使用者,這表示你不必再停用權威驗證。 相反地,請將你支援的每一項 Azure AD B2C 政策都納入 MSAL 設定中的權威。 請注意,自 2025 年 5 月 1 日起,Azure AD B2C 將不再對新客戶開放購買。 欲了解更多,請參閱常見問題中的Azure AD B2C是否仍可購買?。

如果您嘗試使用 Microsoft 不認識,且未包含在您的設定中的授權單位,您會收到UnknownAuthorityException。

Logging

你現在可以宣告式地將日誌設定為設定的一部分,就像這樣:

"logging": {
  "pii_enabled": false,
  "log_level": "WARNING",
  "logcat_enabled": true
}

從 UserInfo 遷移到 Account。

在 ADAL 中,AuthenticationResult 會提供 UserInfo 物件,用來擷取已驗證帳戶的相關資訊。 「使用者」一詞原本可指人類或軟體代理,但對這個詞的使用方式,讓人難以清楚傳達:某些應用程式支援單一使用者(無論是人類或軟體代理)擁有多個帳號。

考慮設立銀行帳戶。 你可能在多家金融機構擁有多個帳戶。 當你開戶時,你(使用者)會被發放憑證,例如提款卡和密碼,用來查詢你的餘額、帳單支付等,適用於每個帳戶。 這些憑證只能在核發憑證的金融機構使用。

類比地,就像金融機構的帳戶一樣,Microsoft 身分識別平台 中的帳戶是透過憑證存取的。 這些憑證要麼是Microsoft註冊的,要麼由核發。 或者由 Microsoft 代表某個組織。

在這個類比中,Microsoft 身分識別平台 與金融機構的不同之處在於,Microsoft 身分識別平台 提供了一個框架,允許使用者使用一個帳號及其相關的憑證,存取屬於多個個人和組織的資源。 這就像能在另一家金融機構使用一張銀行發行的卡一樣。 這之所以有效,是因為所有相關組織都使用 Microsoft 身分識別平台,允許一個帳號跨多個組織使用。 以下為範例:

Sam 為 Contoso.com 工作,但管理Azure Fabrikam.com 的虛擬機。 Sam 要管理 Fabrikam 的虛擬機器,必須獲得授權才能存取它們。 此存取權限可透過將 Sam 的帳號加入 Fabrikam.com,並賦予他的帳號一個角色,讓他能操作虛擬機來獲得。 這會透過 Azure 入口網站來完成。

將 Sam 的 Contoso.com 帳號加入 Fabrikam.com 會讓 Fabrikam.com 的Microsoft Entra ID中為 Sam 建立新紀錄。 Sam 在 Microsoft Entra ID 中的紀錄被稱為使用者物件。 在這種情況下,該使用者物件會指向 Contoso.com 中 Sam 的使用者物件。 Sam 的 Fabrikam 使用者物件是 Sam 的本機表示,會用來儲存在 Fabrikam.com 情境下與 Sam 相關聯的帳戶資訊。 在 Contoso.com,Sam 的職稱是資深 DevOps 顧問。 在 Fabrikam,Sam 的職稱是 Contractor-虛擬機器。 在 Contoso.com 中,Sam 既無需負責管理虛擬機器,也未獲授權管理虛擬機器。 Fabrikam.com,這是他唯一的工作職能。 然而山姆仍然只有一套憑證需要追蹤,那就是 Contoso.com 頒發的憑證。

成功發出 acquireToken 呼叫後,你會看到可在後續 acquireTokenSilent 請求中使用的 IAccount 物件參考。

IMultiTenantAccount

如果你有一個應用程式能存取每個帳號所屬租戶的申訴,你可以將物件投射 IAccount 到 IMultiTenantAccount。 這個介面提供一張以租戶 ID 為鍵的 映射 ITenantProfiles,讓你能存取你申請令牌的每個租戶帳戶相對於目前帳戶所屬的理賠。

位於 IAccount 和 IMultiTenantAccount 根層級的宣告一律包含主租用戶的宣告。 如果你尚未在該房客內提出代幣申請,這個集合將會是空的。

其他變更

使用新的 AuthenticationCallback

// Existing ADAL Interface
public interface AuthenticationCallback<T> {

    /**
     * This will have the token info.
     *
     * @param result returns <T>
     */
    void onSuccess(T result);

    /**
     * Sends error information. This can be user related error or server error.
     * Cancellation error is AuthenticationCancelError.
     *
     * @param exc return {@link Exception}
     */
    void onError(Exception exc);
}
// New Interface for Interactive AcquireToken
public interface AuthenticationCallback {

    /**
     * Authentication finishes successfully.
     *
     * @param authenticationResult {@link IAuthenticationResult} that contains the success response.
     */
    void onSuccess(final IAuthenticationResult authenticationResult);

    /**
     * Error occurs during the authentication.
     *
     * @param exception The {@link MsalException} contains the error code, error message and cause if applicable. The exception
     *                  returned in the callback could be {@link MsalClientException}, {@link MsalServiceException}
     */
    void onError(final MsalException exception);

    /**
     * Will be called if user cancels the flow.
     */
    void onCancel();
}

// New Interface for Silent AcquireToken
public interface SilentAuthenticationCallback {

    /**
     * Authentication finishes successfully.
     *
     * @param authenticationResult {@link IAuthenticationResult} that contains the success response.
     */
    void onSuccess(final IAuthenticationResult authenticationResult);

    /**
     * Error occurs during the authentication.
     *
     * @param exception The {@link MsalException} contains the error code, error message and cause if applicable. The exception
     *                  returned in the callback could be {@link MsalClientException}, {@link MsalServiceException} or
     *                  {@link MsalUiRequiredException}.
     */
    void onError(final MsalException exception);
}

移轉到新的例外狀況

在 ADAL 中,有一種例外類型 AuthenticationException,其中包含一個可用來取得 ADALError 列舉值的方法。 在 MSAL 中,有一系列例外的階層,每個例外都有自己對應的特定錯誤碼。

例外狀況 Description
MsalArgumentException 當一個或多個輸入參數無效時,會拋出。
MsalClientException 如果錯誤發生在用戶端,就會丟棄。
MsalDeclinedScopeException 如果伺服器拒絕一個或多個請求的範圍,則會丟棄。
MsalException MSAL 擲回的預設受檢查例外。
MsalIntuneAppProtectionPolicyRequiredException 如果該資源啟用了 MAMCA 保護政策,則會丟棄。
MsalServiceException 如果錯誤發生在伺服器端,則會丟棄。
MsalUiRequiredException 如果標記無法無聲刷新,則會丟棄。
MsalUserCancelException 如果使用者取消了認證流程,就會被丟棄。

ADALErrror 到 MsalException 的翻譯

如果你在 ADAL 裡發現這些錯誤...... ...注意以下 MSAL 例外:
沒有相應的 ADALError MsalArgumentException
  • ADALError.ANDROIDKEYSTORE_FAILED
  • ADALError.AUTH_FAILED_USER_MISMATCH
  • ADALError.DECRYPTION_FAILED
  • ADALError.DEVELOPER_AUTHORITY_CAN_NOT_BE_VALIDED
  • ADALError.DEVELOPER_AUTHORITY_IS_NOT_VALID_INSTANCE
  • ADALError.DEVELOPER_AUTHORITY_IS_NOT_VALID_URL
  • ADALError.DEVICE_CONNECTION_IS_NOT_AVAILABLE
  • ADALError.DEVICE_NO_SUCH_ALGORITHM
  • ADALError.ENCODING_IS_NOT_SUPPORTED
  • ADALError.ENCRYPTION_ERROR
  • ADALError.IO_EXCEPTION
  • ADALError.JSON_PARSE_ERROR
  • ADALError.NO_NETWORK_CONNECTION_POWER_OPTIMIZATION
  • ADALError.SOCKET_TIMEOUT_EXCEPTION
MsalClientException
沒有相應的 ADALError MsalDeclinedScopeException
  • ADALError.APP_PACKAGE_NAME_NOT_FOUND
  • ADALError.BROKER_APP_VERIFICATION_FAILED
  • ADALError.PACKAGE_NAME_NOT_FOUND
MsalException
沒有相應的 ADALError MsalIntuneAppProtectionPolicyRequiredException
  • ADALError.SERVER_ERROR
  • ADALError.SERVER_INVALID_REQUEST
MsalServiceException
  • ADALError.AUTH_REFRESH_FAILED_PROMPT_NOT_ALLOWED
MsalUiRequiredException
沒有相應的 ADALError MsalUserCancelException

從 ADAL 記錄到 MSAL 記錄

// Legacy Interface
    StringBuilder logs = new StringBuilder();
    Logger.getInstance().setExternalLogger(new ILogger() {
            @Override
            public void Log(String tag, String message, String additionalMessage, LogLevel logLevel, ADALError errorCode) {
                logs.append(message).append('\n');
            }
        });
// New interface
  StringBuilder logs = new StringBuilder();
  Logger.getInstance().setExternalLogger(new ILoggerCallback() {
      @Override
      public void log(String tag, Logger.LogLevel logLevel, String message, boolean containsPII) {
          logs.append(message).append('\n');
      }
  });

// New Log Levels:
public enum LogLevel
{
    /**
     * Error level logging.
     */
    ERROR,
    /**
     * Warning level logging.
     */
    WARNING,
    /**
     * Info level logging.
     */
    INFO,
    /**
     * Verbose level logging.
     */
    VERBOSE
}