You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Blazor Server应用通过Graph API获取组织用户列表问题求助

解决Microsoft登录后获取组织所有用户的问题

问题根源

你当前代码里使用了客户端凭据流(AcquireTokenForClient),这是应用权限的认证方式,但你拥有的是委托权限(User.Read.All),需要使用代表当前登录用户的令牌调用Graph API。另外代码还存在几个错误:

  • GetAccessToken方法中请求的Scope是Application.Read.All,不是所需的User.Read.All
  • 方法最后返回result.AccessToken,但实际变量是result22
  • 手动创建HttpClient调用Graph的方式不规范,应该利用Microsoft.Identity.Web的集成能力简化流程

修复步骤

1. 配置Program.cs集成Microsoft Graph

修改Program.cs,添加Microsoft Graph服务注册,让框架自动处理令牌获取:

using Microsoft.Identity.Web.UI;
using Microsoft.Identity.Web;
using blazor.UI.Helper;
using AntDesign;
using Blazored.SessionStorage;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Authorization;

namespace blazorserver.UI
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            // 定义Graph所需权限
            var graphScopes = new[] { "User.Read.All" };

            // 添加认证并集成Microsoft Graph
            builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
                .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
                .AddMicrosoftGraph(builder.Configuration.GetSection("MicrosoftGraph"), graphScopes);

            builder.Services.AddControllersWithViews()
               .AddMicrosoftIdentityUI();

            builder.Services.AddServerSideBlazor()
                .AddMicrosoftIdentityConsentHandler();

            // 其他服务注册保持不变
            builder.Services.AddRazorPages();
            builder.Services.AddServerSideBlazor();
            builder.Services.AddHttpClient();
            builder.Services.AddAntDesign();
            builder.Services.AddBlazoredSessionStorage();
            builder.Services.AddHttpContextAccessor();
            builder.Services.AddScoped<NotificationService>();
            builder.Services.AddScoped<IStorage, LocalStorage>();

            builder.Services.AddAuthorization(options =>
            {
                options.FallbackPolicy = options.DefaultPolicy;
            });

            var app = builder.Build();

            app.UseCookiePolicy(new CookiePolicyOptions
            {
                Secure = CookieSecurePolicy.Always
            });

            if (!app.Environment.IsDevelopment())
            {
                app.UseExceptionHandler("/Error");
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseRouting();
            app.UseAuthentication();
            app.UseAuthorization();

            app.MapControllers();
            app.MapBlazorHub();
            app.MapFallbackToPage("/_Host");

            app.Run();
        }
    }
}

同时确保appsettings.json中添加MicrosoftGraph配置节点:

"MicrosoftGraph": {
  "BaseUrl": "https://graph.microsoft.com/v1.0",
  "Scopes": "User.Read.All"
}

2. 修改登录页获取组织用户并存入Session

删除手动获取令牌的GetAccessToken方法,直接注入GraphServiceClient调用API:

@page "/"
@page "/login"
@layout LoginLayout
@using Azure.Core;
@using blazorServer.Shared.Models
@using System;
@using System.Text.Json;
@using System.Net.Http.Json
@using System.Collections.Generic;
@using System.Collections.ObjectModel;
@using BlazorServer.UI.Helper;
@using Microsoft.Extensions.Configuration;
@using System.Security.Claims
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.WebUtilities;
@using Microsoft.Extensions.Primitives;
@using Microsoft.Graph;
@using Azure.Identity;
@using Microsoft.Identity.Web;
@using System.Net.Http.Headers;
@using Microsoft.Identity.Client;
@using System.IdentityModel.Tokens.Jwt;
@using System.Threading;

@inject HttpClient Http
@inject NavigationManager NavigationManager
@inject IConfiguration configuration
@inject IHttpClientFactory ClientFactory
@inject Blazored.SessionStorage.ISessionStorageService sessionStorage
@inject AuthenticationStateProvider AuthenticationStateProvider
@inject IStorage LocalStorage
@inject GraphServiceClient GraphClient

<Spin Spinning=_loading Tip="Processing...">
<GridRow Align="center" Style="minHeight:100vh;height:100vh;overflow:
&quot;">
    <GridCol Span="18">
        <Image Preview=false Src="/loginscreen.jpg" Height="100%" Width="100%" />
    </GridCol>
    <GridCol Xs="6" Style="background-color:#004a87;&quot;">
        <GridRow Align="center">
            <GridCol Span="24" Style="text-align:center;&quot;">
                <Image Preview=false Src="/dd.svg" Alt="logo" Height="150px" Width="150px" />
                <br />
                <Button Loading="true">
                    <span style="color:black;font-size:larger">user authenticating...</span>
                </Button>
            </GridCol>
           
        </GridRow>
        <br />
        <br />
        
        <br />
    </GridCol>
</GridRow>
</Spin>

@code {
    string style = "background: #0092ff; padding: 0 0 0 0;";

    public class Model
    {
        public string Username { get; set; }
        public string Password { get; set; }
        public bool WindowsLogin { get; set; } = true;
    }

    private Model model = new Model();
    AntDesign.Form<Model> form;
    private bool auto = false;
    private string apiURL;
    private UserSession userSession;
    private bool _loading = false;
    private string returnUrl;
    private List<User> organizationUsers;

    protected override void OnInitialized()
    {
        apiURL = configuration.GetValue<string>("WebApiUrl");
        var uri = NavigationManager.ToAbsoluteUri(NavigationManager.Uri);
        StringValues values;

        if (QueryHelpers.ParseQuery(uri.Query).TryGetValue("returnUrl", out values))
        {
            returnUrl = Convert.ToString(values);
        }
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        try
        {
            _loading = true;
            var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
            var user = authState.User;

            if (user.Identity.IsAuthenticated)
            {
                // 获取组织所有用户
                try
                {
                    var userPage = await GraphClient.Users.Request().GetAsync();
                    organizationUsers = new List<User>(userPage.CurrentPage);
                    // 处理分页获取剩余用户
                    while (userPage.NextPageRequest != null)
                    {
                        userPage = await userPage.NextPageRequest.GetAsync();
                        organizationUsers.AddRange(userPage.CurrentPage);
                    }
                    // 存入Session
                    await sessionStorage.SetItemAsync("OrganizationUsers", organizationUsers);
                }
                catch (Exception graphEx)
                {
                    Console.WriteLine($"获取组织用户失败: {graphEx.Message}");
                }

                var userName = user.Identity.Name;
                var userEmail = user.FindFirst(ClaimTypes.Email)?.Value;
                var userCredentials = await Http.GetFromJsonAsync<List<UserModel>>(apiURL + "api/admin/getuserbyemail?email=" + user.Identity.Name);

                if (userCredentials != null && userCredentials.Count > 0)
                {
                    userSession = new()
                        {
                            Name = userCredentials.First().FirstName + " " + userCredentials.First().LastName,
                            Email = userCredentials.First().Email,
                            UserId = userCredentials.First().FirstName + " " + userCredentials.First().LastName,
                            FirstName = userCredentials.First().FirstName,
                            UserRoles = userCredentials.Select(x => x.Role).ToList()

                        };

                    if (userSession.UserId == null || userSession.UserId.Length <= 0)
                    {
                        userSession.UserId = userSession.Name.Contains("\\") ? userSession.Name.Split("\\")[1] : userSession.Name;
                    }
                    await sessionStorage.SetItemAsync("UserSession", userSession);
                    NavigationManager.NavigateTo(string.IsNullOrWhiteSpace(returnUrl) ? "/WorkList" : $"/{returnUrl}", true);
                }
                else
                {
                    userSession = new()
                        {
                            Name = user.Identity.Name,
                            Email = user.FindFirst(c => c.Type == ClaimTypes.Email)?.Value,
                            UserId = user.FindFirst(c => c.Type == ClaimTypes.WindowsAccountName)?.Value,
                            FirstName = user.FindFirst(c => c.Type == ClaimTypes.Surname)?.Value
                        };

                    if (userSession.UserId == null || userSession.UserId.Length <= 0)
                    {
                        userSession.UserId = userSession.Name.Contains("\\") ? userSession.Name.Split("\\")[1] : userSession.Name;
                    }

                    await sessionStorage.SetItemAsync("UserSession", userSession);
                    NavigationManager.NavigateTo(string.IsNullOrWhiteSpace(returnUrl) ? "/dd" : $"/{returnUrl}", true);
                }
            }
            else
            {
                await DisplayNotification(NotificationType.Error, "Error", "The user is NOT authenticated");
            }

            _loading = false;
        }
        catch(Exception ex)
        {
            Console.WriteLine(ex);
            throw ex;
        }
    }
}

3. 权限验证

确保Azure AD应用注册中:

  • 已添加User.Read.All委托权限并完成管理员同意
  • 重定向URI配置正确
  • 客户端凭据(若需)配置无误

后续使用

在需要展示下拉框的组件中,从Session取出用户列表:

@inject Blazored.SessionStorage.ISessionStorageService sessionStorage

private List<User> users;

protected override async Task OnInitializedAsync()
{
    users = await sessionStorage.GetItemAsync<List<User>>("OrganizationUsers");
}

内容的提问来源于stack exchange,提问作者Srinidhi S

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 05:14:56