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

禁用本地DC的Azure AD环境中,C#客户端获取UserPrincipalName的问题

解决Azure AD环境下获取登录用户UPN的.NET代码异常问题

问题分析

  • 你遇到的InvalidCastException,是因为在纯Azure AD(本地域控混合模式禁用)环境下,UserPrincipal.Current的底层实现存在兼容性问题,会误将用户主体识别为组主体,导致类型转换失败。
  • whoami.exe /upn能正常返回UPN,是因为它直接调用Windows原生的LSA(本地安全机构)API:先获取用户的安全标识符(SID),再通过SID查询对应的UPN,完全绕开了DirectoryServices.AccountManagement对本地域依赖的逻辑。

替代实现方案(模拟whoami逻辑)

可以直接调用Windows底层API实现相同效果,无需依赖DirectoryServices.AccountManagement,代码示例如下:

C# 代码实现

using System;
using System.Runtime.InteropServices;
using System.Security.Principal;

public static string GetCurrentUserUpn()
{
    IntPtr tokenHandle = IntPtr.Zero;
    try
    {
        // 获取当前进程的访问令牌
        if (!OpenProcessToken(System.Diagnostics.Process.GetCurrentProcess().Handle, 0x0008 /*TOKEN_QUERY*/, out tokenHandle))
        {
            throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
        }

        // 获取令牌信息所需内存大小
        uint tokenInfoSize = 0;
        GetTokenInformation(tokenHandle, TOKEN_INFORMATION_CLASS.TokenUser, IntPtr.Zero, 0, out tokenInfoSize);
        if (Marshal.GetLastWin32Error() != 0x000000EA /*ERROR_INSUFFICIENT_BUFFER*/)
        {
            throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
        }

        // 分配内存存储令牌信息
        IntPtr tokenInfoPtr = Marshal.AllocHGlobal((int)tokenInfoSize);
        try
        {
            if (!GetTokenInformation(tokenHandle, TOKEN_INFORMATION_CLASS.TokenUser, tokenInfoPtr, tokenInfoSize, out tokenInfoSize))
            {
                throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
            }

            // 解析出用户SID
            TOKEN_USER tokenUser = Marshal.PtrToStructure<TOKEN_USER>(tokenInfoPtr);
            SecurityIdentifier sid = new SecurityIdentifier(tokenUser.User.Sid);

            // 通过SID转换为NT账户格式
            NTAccount ntAccount = (NTAccount)sid.Translate(typeof(NTAccount));
            
            // 调用LookupAccountName获取UPN
            uint nameSize = 0;
            uint domainSize = 0;
            SID_NAME_USE sidNameUse = SID_NAME_USE.SidTypeUser;
            LookupAccountName(null, ntAccount.Value, IntPtr.Zero, ref nameSize, IntPtr.Zero, ref domainSize, out sidNameUse);
            if (Marshal.GetLastWin32Error() != 0x000000EA)
            {
                throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
            }

            IntPtr namePtr = Marshal.AllocHGlobal((int)nameSize);
            IntPtr domainPtr = Marshal.AllocHGlobal((int)domainSize);
            try
            {
                if (!LookupAccountName(null, ntAccount.Value, namePtr, ref nameSize, domainPtr, ref domainSize, out sidNameUse))
                {
                    throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
                }

                return Marshal.PtrToStringUni(namePtr);
            }
            finally
            {
                Marshal.FreeHGlobal(namePtr);
                Marshal.FreeHGlobal(domainPtr);
            }
        }
        finally
        {
            Marshal.FreeHGlobal(tokenInfoPtr);
        }
    }
    finally
    {
        if (tokenHandle != IntPtr.Zero)
        {
            CloseHandle(tokenHandle);
        }
    }
}

// 定义Windows API相关结构体与枚举
[StructLayout(LayoutKind.Sequential)]
private struct TOKEN_USER
{
    public SID_AND_ATTRIBUTES User;
}

[StructLayout(LayoutKind.Sequential)]
private struct SID_AND_ATTRIBUTES
{
    public IntPtr Sid;
    public uint Attributes;
}

private enum TOKEN_INFORMATION_CLASS
{
    TokenUser = 1
}

private enum SID_NAME_USE
{
    SidTypeUser = 1
}

// 导入Windows API函数
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool OpenProcessToken(IntPtr processHandle, uint desiredAccess, out IntPtr tokenHandle);

[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool GetTokenInformation(IntPtr tokenHandle, TOKEN_INFORMATION_CLASS tokenInfoClass, IntPtr tokenInfo, uint tokenInfoLength, out uint returnLength);

[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool LookupAccountName(string lpSystemName, string lpAccountName, IntPtr lpSid, ref uint cchSid, IntPtr lpReferencedDomainName, ref uint cchReferencedDomainName, out SID_NAME_USE peUse);

[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hObject);

代码说明

  • 这段代码完全复刻whoami.exe /upn的执行逻辑:先获取当前进程的访问令牌,提取用户SID,再通过LookupAccountName API查询该SID对应的UPN。
  • 彻底规避了DirectoryServices.AccountManagement在纯Azure AD环境下的兼容性问题,因为它直接调用Windows底层API,和whoami使用同一套机制。

为什么UserPrincipal.Current会报错?

DirectoryServices.AccountManagement命名空间最初是为本地Active Directory设计的,在纯Azure AD(无本地域控)的场景下,其内部逻辑无法正确解析Azure AD用户的主体类型,误将用户识别为组,从而抛出类型转换异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 07:17:17