如何在WPF应用中获取Azure AD用户姓名列表并解决认证报错
问题分析与解决方案
你遇到的AADSTS50058错误核心矛盾是认证流不匹配:
- Web API中的
GetAccessTokenForUserAsync是基于用户委派权限的逻辑,需要当前存在已登录的用户上下文,才能代表用户获取访问Microsoft Graph的令牌。 - 但WPF中使用的
AcquireTokenForClient属于客户端凭证流,是服务对服务的无用户上下文认证,导致API尝试获取用户令牌时,因无用户登录抛出错误。
以下是两种针对性解决方案:
方案一:修改WPF使用用户交互登录流(推荐,匹配原API设计)
该方案让WPF先引导用户登录,获取用户上下文的访问令牌后再调用API,符合原API的用户委派权限逻辑。
修改后的WPF代码
public partial class MainWindow : Window { private static readonly HttpClient client = new HttpClient(); private static IPublicClientApplication publicClientApp; public MainWindow() { InitializeComponent(); // 桌面应用属于公共客户端,使用PublicClientApplication而非ConfidentialClientApplication publicClientApp = PublicClientApplicationBuilder.Create("47......") .WithAuthority(AzureCloudInstance.AzurePublic, "d81e......") // 替换为你的租户ID .WithRedirectUri("http://localhost") // 桌面应用默认重定向URI .Build(); } private async void FetchDataButton_Click(object sender, RoutedEventArgs e) { try { // 替换为你API中定义的用户委派作用域 string[] scopes = new string[] { "api://47....../access_as_user" }; AuthenticationResult authResult = null; var accounts = await publicClientApp.GetAccountsAsync(); // 先尝试静默获取令牌(若用户之前登录过) try { authResult = await publicClientApp.AcquireTokenSilent(scopes, accounts.FirstOrDefault()).ExecuteAsync(); } catch (MsalUiRequiredException) { // 静默失败,弹出登录界面 authResult = await publicClientApp.AcquireTokenInteractive(scopes) .WithAccount(accounts.FirstOrDefault()) .WithParentActivityOrWindow(new WindowInteropHelper(this).Handle) // 绑定WPF窗口,让登录弹窗置顶 .ExecuteAsync(); } // 设置请求头令牌 client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken); // 调用API并处理结果 string apiUrl = "https://localhost:7159/api/Users"; var response = await client.GetAsync(apiUrl); if (response.IsSuccessStatusCode) { var userNames = await response.Content.ReadFromJsonAsync<List<string>>(); // 可将结果绑定到下拉框等控件,示例: // UserComboBox.ItemsSource = userNames; MessageBox.Show(string.Join(Environment.NewLine, userNames), "用户列表", MessageBoxButton.OK, MessageBoxImage.Information); } else { var errorDetail = await response.Content.ReadAsStringAsync(); MessageBox.Show($"错误: {response.ReasonPhrase}\n详情: {errorDetail}", "请求失败", MessageBoxButton.OK, MessageBoxImage.Error); } } catch (MsalException msalEx) { MessageBox.Show($"认证错误: {msalEx.Message}", "认证失败", MessageBoxButton.OK, MessageBoxImage.Error); } catch (Exception ex) { MessageBox.Show($"异常: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } } }
配套配置要求
- 在Azure AD中,为你的API应用暴露用户委派作用域(如
access_as_user),并给WPF应用授予该作用域的权限。 - 确保Web API的认证中间件配置正确(如使用
AddMicrosoftIdentityWebApi),能正常验证用户的访问令牌。
方案二:修改Web API使用客户端凭证流(无用户上下文场景)
若你的业务不需要用户上下文,仅需服务端直接获取所有用户,可修改API使用客户端凭证流调用Graph。
修改后的Web API代码
[Route("api/[controller]")] [ApiController] public class UsersController : ControllerBase { private readonly ILogger<UsersController> _logger; private readonly IConfiguration _configuration; public UsersController(ILogger<UsersController> logger, IConfiguration configuration) { _logger = logger; _configuration = configuration; } [HttpGet] public async Task<IActionResult> GetUsers() { try { // 从配置读取Azure AD参数 var clientId = _configuration["AzureAd:ClientId"]; var clientSecret = _configuration["AzureAd:ClientSecret"]; var tenantId = _configuration["AzureAd:TenantId"]; // 客户端凭证流获取Graph令牌 var scopes = new[] { "https://graph.microsoft.com/.default" }; var authority = $"https://login.microsoftonline.com/{tenantId}"; var confidentialClient = ConfidentialClientApplicationBuilder .Create(clientId) .WithClientSecret(clientSecret) .WithAuthority(authority) .Build(); var authResult = await confidentialClient.AcquireTokenForClient(scopes).ExecuteAsync(); // 初始化Graph客户端 var graphClient = new GraphServiceClient(new DelegateAuthenticationProvider(async (requestMessage) => { requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken); })); // 分页获取用户姓名 var usersList = new List<string>(); var usersPage = await graphClient.Users .Request() .Select(u => new { u.DisplayName }) .GetAsync(); usersList.AddRange(usersPage.Select(u => u.DisplayName)); while (usersPage.NextPageRequest != null) { usersPage = await usersPage.NextPageRequest.GetAsync(); usersList.AddRange(usersPage.Select(u => u.DisplayName)); } return Ok(usersList); } catch (Exception ex) { _logger.LogError(ex, "获取用户列表时发生错误。"); return StatusCode(StatusCodes.Status500InternalServerError, "获取用户列表时发生错误。"); } } }
配套配置要求
- 在Azure AD中,为Web API应用授予应用权限(如
User.Read.All),并由管理员完成权限同意。 - WPF应用保持原
AcquireTokenForClient逻辑,确保已被授予调用API的应用权限。
内容的提问来源于stack exchange,提问作者Madhura D
相关产品推荐
相关产品推荐

