调用 Web API 的桌面应用 - 使用 WAM 获取令牌

Microsoft 身份验证库 (MSAL) 可调用 Web 帐户管理器 (WAM),后者是一个充当身份验证代理的 Windows 10+ 组件。 此中转站使应用的用户可以受益于与 Windows 已知帐户(例如在 Windows 会话中登录的帐户)的集成。

WAM 价值主张

使用 WAM 等身份验证代理可带来诸多好处:

  • 安全性增强。 请参阅令牌保护
  • 支持 Windows Hello、条件访问和 FIDO 密钥。
  • 与 Windows 的“电子邮件和帐户”视图集成
  • 快速单一登录。
  • 能够使用当前 Windows 帐户以无提示方式登录。
  • Windows 附带的 Bug 修复和增强功能。

WAM 限制

  • WAM 适用于 Windows 10 及更高版本,以及 Windows Server 2019 及更高版本。 在 Mac、Linux 和早期版本的 Windows 上,MSAL 将自动回退到浏览器。
  • 不支持 Azure Active Directory B2C (Azure AD B2C) 和 Active Directory 联合身份验证服务 (AD FS) 机构。 MSAL 将回退到浏览器。

WAM 集成包

大多数应用都需要引用 Microsoft.Identity.Client.Broker 包才能使用此集成。 .NET MAUI 应用不必执行此操作,因为当目标为 net6-windows 及更高版本时,该功能位于 MSAL 内部。

WAM 调用模式

可以对 WAM 使用以下模式:

    // 1. Configuration - read below about redirect URI
    var pca = PublicClientApplicationBuilder.Create("client_id")
                    .WithBroker(new BrokerOptions(BrokerOptions.OperatingSystems.Windows))
                    .Build();

    // Add a token cache; see https://learn.microsoft.com/azure/active-directory/develop/msal-net-token-cache-serialization?tabs=desktop

    // 2. Find an account for silent login

    // Is there an account in the cache?
    IAccount accountToLogin = (await pca.GetAccountsAsync()).FirstOrDefault();
    if (accountToLogin == null)
    {
        // 3. No account in the cache; try to log in with the OS account
        accountToLogin = PublicClientApplication.OperatingSystemAccount;
    }

    try
    {
        // 4. Silent authentication 
        var authResult = await pca.AcquireTokenSilent(new[] { "User.Read" }, accountToLogin)
                                    .ExecuteAsync();
    }
    // Cannot log in silently - most likely Azure AD would show a consent dialog or the user needs to re-enter credentials
    catch (MsalUiRequiredException) 
    {
        // 5. Interactive authentication
        var authResult = await pca.AcquireTokenInteractive(new[] { "User.Read" })
                                    .WithAccount(accountToLogin)
                                    // This is mandatory so that WAM is correctly parented to your app; read on for more guidance
                                    .WithParentActivityOrWindow(myWindowHandle) 
                                    .ExecuteAsync();
                                    
        // Consider allowing the user to re-authenticate with a different account, by calling AcquireTokenInteractive again                                  
    }

如果代理不存在(例如 Windows 8.1、Mac 或 Linux),MSAL 将回退到浏览器,其中将应用重定向 URI 规则。

重定向 URI

无需在 MSAL 中配置 WAM 重定向 URI,但需要在应用注册中配置它们:

ms-appx-web://microsoft.aad.brokerplugin/{client_id}

令牌缓存持久性

保留 MSAL 的令牌缓存非常重要,因为 MSAL 会继续在那里存储 ID 令牌和帐户元数据。 有关详细信息,请参阅 MSAL.NET 中的令牌缓存序列化

用于无提示登录的帐户

若要查找用于无提示登录的帐户,建议采用以下模式:

  • 如果用户以前登录过,请使用该帐户。 如果未登录过,请为当前 Windows 帐户使用 PublicClientApplication.OperatingSystemAccount
  • 允许用户通过交互方式登录来更改为其他帐户。

父窗口句柄

必须使用 WithParentActivityOrWindow API 配置具有交互式体验父级窗口的 MSAL。

UI 应用程序

对于 Windows 窗体 (WinForms)、Windows Presentation Foundation (WPF) 或 Windows UI 库版本 3 (WinUI3) 等 UI 应用,请参阅检索窗口句柄

控制台应用程序

对于控制台应用程序,由于终端窗口及其选项卡,配置涉及更多内容。 使用以下代码:

enum GetAncestorFlags
{   
    GetParent = 1,
    GetRoot = 2,
    /// <summary>
    /// Retrieves the owned root window by walking the chain of parent and owner windows returned by GetParent.
    /// </summary>
    GetRootOwner = 3
}

/// <summary>
/// Retrieves the handle to the ancestor of the specified window.
/// </summary>
/// <param name="hwnd">A handle to the window whose ancestor will be retrieved.
/// If this parameter is the desktop window, the function returns NULL. </param>
/// <param name="flags">The ancestor to be retrieved.</param>
/// <returns>The return value is the handle to the ancestor window.</returns>
[DllImport("user32.dll", ExactSpelling = true)]
static extern IntPtr GetAncestor(IntPtr hwnd, GetAncestorFlags flags);

[DllImport("kernel32.dll")]
static extern IntPtr GetConsoleWindow();

// This is your window handle!
public IntPtr GetConsoleOrTerminalWindow()
{
   IntPtr consoleHandle = GetConsoleWindow();
   IntPtr handle = GetAncestor(consoleHandle, GetAncestorFlags.GetRootOwner );
  
   return handle;
}

故障排除

“WAM 帐户选取器未返回帐户”错误消息

“WAM 帐户选取器未返回帐户”消息表明,应用程序用户关闭了显示帐户的对话框,或者该对话框本身崩溃了。 如果 Windows 控件 AccountsControl 在 Windows 中注册不正确,则可能会发生崩溃。 若要解决此问题,请执行以下操作:

  1. 在任务栏上,右键单击“开始”,然后选择“Windows PowerShell(管理员)”。

  2. 如果“用户帐户控制”对话框出现提示,请选择“是”以启动 PowerShell。

  3. 复制以下脚本,然后运行它:

    if (-not (Get-AppxPackage Microsoft.AccountsControl)) { Add-AppxPackage -Register "$env:windir\SystemApps\Microsoft.AccountsControl_cw5n1h2txyewy\AppxManifest.xml" -DisableDevelopmentMode -ForceApplicationShutdown } Get-AppxPackage Microsoft.AccountsControl
    

部署单个文件期间出现“MsalClientException: ErrorCode: wam_runtime_init_failed”错误消息

将应用程序打包到单个文件捆绑包时,可能会出现以下错误:

MsalClientException: wam_runtime_init_failed: The type initializer for 'Microsoft.Identity.Client.NativeInterop.API' threw an exception. See https://aka.ms/msal-net-wam#troubleshooting

此错误指示 Microsoft.Identity.Client.NativeInterop 中的本机二进制文件未打包到单个文件捆绑包。 若要嵌入这些文件以进行提取并获取一个输出文件,请将属性 IncludeNativeLibrariesForSelfExtract 设置为 true详细了解如何将本机二进制文件打包到单个文件

连接问题

如果应用程序用户经常看到类似于“请检查你的连接并重试”的错误消息,请参阅 Office 故障排除指南。 该故障排除指南也使用代理。

示例

可以在 GitHub 上找到使用 WAM 的 WPF 示例。

后续步骤

转到此方案中的下一篇文章:从桌面应用调用 Web API