You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Windows身份认证替代Microsoft Graph身份认证的可行性及架构适配咨询

Windows身份认证替代Microsoft Graph身份认证的可行性及架构适配咨询

看起来你现在正面临把依赖Microsoft Graph的Azure AD认证体系切换到Windows认证的需求,先给你一个明确的结论:Windows认证没法直接作为「开箱即用的替代方案」,因为两者的认证逻辑、用户数据获取方式完全不同,你需要从认证流程到数据获取层都做针对性调整。下面我结合你的分层ASP.NET Core架构,详细拆解差异点、适配方案和替代选项:


一、核心差异点(为什么不能直接替换)

先把最关键的不同点理清楚,帮你理解调整的必要性:

1. 认证机制本质完全不同

你当前用的是OAuth2.0/OpenID Connect 协议的Azure AD云认证:靠MSAL库获取访问令牌,再用令牌调用Microsoft Graph API拉取Azure AD的用户数据;
而Windows认证是基于NTLM/Kerberos的域内本地认证:依赖企业Windows域控制器验证用户身份,没有「令牌」这一套机制——身份验证是由应用服务器(IIS/Kestrel)直接和域控制器交互完成的,认证通过后直接拿到当前用户的Windows身份标识。

2. 用户数据获取路径天差地别

  • 之前你通过GraphServiceClient调用Microsoft Graph的REST API,跨网络拉取Azure AD的用户数据(包括全量用户列表);
  • Windows认证本身只负责「验证身份」,不会主动提供用户目录数据。要获取域用户的详细信息或全量列表,你得用Active Directory Services (AD DS) 的.NET类库(比如System.DirectoryServices.AccountManagement)直接连接企业域控制器,而不是调用云API。

3. 权限控制逻辑不同

  • Microsoft Graph靠「权限范围(Scope)」(比如User.Read.All)控制对用户列表的访问,权限由Azure AD的应用注册配置;
  • Windows认证下访问域用户数据,靠的是运行应用的服务账号的域权限:比如你的API服务要读取域用户列表,必须确保运行API的账号(域账号)被域控制器授予了「读取用户对象」的权限。

4. 分层架构中的认证流程变化

你当前的流程是:Client层通过MSAL拿令牌 → 带令牌调用API → API消费Graph数据
Windows认证下,流程会变成:

  • 如果是Blazor Server/MVC客户端:直接通过Windows集成认证传递身份,Client层不需要处理令牌;
  • 如果是Blazor WebAssembly客户端:WASM本身无法直接使用Kerberos/NTLM,必须通过API层代理身份——Client调用API时,由API层完成Windows认证,再返回用户数据。

二、适配Windows认证的具体方案(针对你的分层架构)

结合你的TestApp.Client/API/Domain分层结构,给你一套可落地的调整步骤:

1. 替换Client层的认证配置

情况1:Client是Blazor Server/MVC/Razor Pages

移除所有MSAL和Graph相关的配置,替换为Windows认证中间件:

// TestApp.Client/Program.cs
// 移除原有的AddMsalAuthentication、AddGraphClient代码
builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
    .AddNegotiate();

builder.Services.AddAuthorization(options =>
{
    // 设置默认授权策略,确保所有请求都需要认证
    options.FallbackPolicy = options.DefaultPolicy;
});

// 中间件顺序要对
app.UseAuthentication();
app.UseAuthorization();

情况2:Client是Blazor WebAssembly

WASM无法直接使用Windows认证,需要让API层负责认证,Client通过API代理获取数据:

  • Client层可以用Cookie认证(如果是托管在同一域下),或者保持匿名但所有数据请求都转发到API,由API验证Windows身份。

2. 重构用户数据获取服务(替换GraphServiceClient)

把原来依赖GraphServiceClientAdUserService拆分为「API层的AD访问服务」和「Client层的API调用服务」:

第一步:在API层实现AD域用户访问逻辑

先安装NuGet包:System.DirectoryServices.AccountManagement

// TestApp.API/Services/AdUserService.cs
using System.DirectoryServices.AccountManagement;
using TestApp.Application.Repositories;
using TestApp.Shared.DTOs;

namespace TestApp.API.Services;

public class AdUserService : IAdUserService
{
    private readonly PrincipalContext _domainContext;

    // 从配置文件读取域信息和服务账号
    public AdUserService(IConfiguration configuration)
    {
        var domainName = configuration["ActiveDirectory:DomainName"];
        var serviceUser = configuration["ActiveDirectory:ServiceAccount"];
        var servicePwd = configuration["ActiveDirectory:ServicePassword"];
        
        // 连接域控制器
        _domainContext = new PrincipalContext(ContextType.Domain, domainName, serviceUser, servicePwd);
    }

    // 获取当前认证用户的详细信息
    public async Task<AdUserDto> GetCurrentDomainUserAsync(string windowsUsername)
    {
        // Windows用户名格式为:DOMAIN\Username
        var userPrincipal = UserPrincipal.FindByIdentity(_domainContext, windowsUsername);
        if (userPrincipal == null) return null;

        return new AdUserDto
        {
            Id = userPrincipal.Sid.Value,
            DisplayName = userPrincipal.DisplayName,
            JobTitle = userPrincipal.Description, // 映射AD的JobTitle属性(根据你的AD配置调整)
            MobilePhone = userPrincipal.VoiceTelephoneNumber,
            Email = userPrincipal.EmailAddress,
            GivenName = userPrincipal.GivenName,
            Surname = userPrincipal.Surname
        };
    }

    // 获取符合条件的域用户列表(匹配邮箱后缀+启用状态)
    public async Task<List<AdUserDto>> GetDomainUsersAsync()
    {
        var userList = new List<AdUserDto>();
        
        // 构建AD查询条件
        var searchFilter = new UserPrincipal(_domainContext)
        {
            Enabled = true,
            EmailAddress = "*@pasar.com.ph"
        };

        using var searcher = new PrincipalSearcher(searchFilter);
        foreach (var result in searcher.FindAll())
        {
            if (result is UserPrincipal user)
            {
                userList.Add(new AdUserDto
                {
                    Id = user.Sid.Value,
                    DisplayName = user.DisplayName,
                    Email = user.EmailAddress,
                    GivenName = user.GivenName,
                    Surname = user.Surname
                });
            }
        }

        return userList;
    }
}

第二步:在API层暴露用户数据接口

// TestApp.API/Controllers/AdUserController.cs
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using TestApp.Shared.DTOs;

namespace TestApp.API.Controllers;

[ApiController]
[Route("api/[controller]")]
[Authorize] // 只允许已认证的域用户访问
public class AdUserController : ControllerBase
{
    private readonly IAdUserService _adUserService;

    public AdUserController(IAdUserService adUserService)
    {
        _adUserService = adUserService;
    }

    [HttpGet("current")]
    public async Task<IActionResult> GetCurrentUser()
    {
        // User.Identity.Name 就是当前域用户的身份标识(DOMAIN\Username)
        var user = await _adUserService.GetCurrentDomainUserAsync(User.Identity.Name);
        return Ok(user);
    }

    [HttpGet("list")]
    public async Task<IActionResult> GetUserList()
    {
        var users = await _adUserService.GetDomainUsersAsync();
        return Ok(users);
    }
}

第三步:重构Client层的AdUserService

移除GraphServiceClient依赖,改为调用API接口:

// TestApp.Client/Services/AdUserService.cs
using TestApp.Client.Services.Interfaces;
using TestApp.Shared.DTOs;
using System.Net.Http;

namespace TestApp.Client.Services;

public class AdUserService(HttpClient httpClient, INotificationService notificationService) : IAdUserService
{
    private readonly HttpClient _httpClient = httpClient;

    public async Task<AdUserDto> GetCurrentUserAsync()
    {
        try
        {
            return await _httpClient.GetFromJsonAsync<AdUserDto>("api/aduser/current");
        }
        catch (Exception ex)
        {
            notificationService.ShowError($"获取当前用户失败:{ex.Message}");
            return null;
        }
    }

    public async Task<List<AdUserDto>> GetAdUsersAsync2()
    {
        try
        {
            return await _httpClient.GetFromJsonAsync<List<AdUserDto>>("api/aduser/list");
        }
        catch (Exception ex)
        {
            notificationService.ShowError($"获取用户列表失败:{ex.Message}");
            return new List<AdUserDto>();
        }
    }
}

3. 配置API层的Windows认证

// TestApp.API/Program.cs
builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
    .AddNegotiate();

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

// 中间件顺序不能错
app.UseAuthentication();
app.UseAuthorization();

三、如果Windows认证不适用,还有什么替代方案?

如果你的应用不是部署在Windows域环境(比如纯云部署),或者没法访问企业AD DS,可以考虑这些选项:

  1. Azure AD OIDC认证 + 自定义用户存储:继续用Azure AD认证(不用Microsoft Graph),只通过OIDC端点获取当前用户的基本信息,其他用户列表数据同步到自己的数据库(比如SQL Server)维护;
  2. Azure AD Domain Services (Azure AD DS):如果是云部署,用Azure AD DS把Azure AD的用户数据同步到云域控制器,然后用Windows认证或AD DS类库访问数据;
  3. 自定义ASP.NET Core Identity认证:完全脱离Azure AD,用自己的数据库存储用户数据,实现独立的认证体系——但需要维护用户数据的同步或录入。

四、关键注意事项

  1. 部署环境限制:Windows认证只能在Windows域环境下工作,如果你把应用部署在非Windows服务器(比如Linux),或者用户不是域用户,这个方案就不适用;
  2. Blazor WASM的特殊处理:WASM客户端无法直接使用Windows认证,必须通过API层代理所有用户数据请求;
  3. 权限配置:访问AD DS的服务账号必须有域的读取权限,否则会出现「无法访问域控制器」或「权限不足」的错误。

如果还有具体的场景细节(比如部署环境、Client类型),可以补充出来,我再帮你细化方案~

火山引擎 最新活动