在.NET中如何进行模拟身份?

156

有没有一种简单的、开箱即用的方法在 .NET 中模拟用户身份?

到目前为止,我一直在使用来自代码项目的这个类来满足我的所有模拟要求。

有没有更好的方法可以利用 .NET Framework 来实现它?

我有一个用户凭据集合(用户名、密码、域名),代表着我需要模拟的身份。


1
你能具体一些吗?有很多开箱即用的模拟方式。 - Esteban Araya
7个回答

335

.NET中的"模拟用户身份"通常是指在特定用户帐户下运行代码。这是一个比通过用户名和密码获取该用户帐户访问权限要稍微独立一些的概念,但这两个想法经常一起使用。

模拟用户身份

模拟用户身份的API通过System.Security.Principal命名空间在.NET中提供:

  • 较新的代码通常应使用WindowsIdentity.RunImpersonated,它接受用户帐户令牌的句柄,然后执行一个ActionFunc<T>来执行代码。

WindowsIdentity.RunImpersonated(userHandle, () =>
{
    // do whatever you want as this user.
});
或者
var result = WindowsIdentity.RunImpersonated(userHandle, () =>
{
    // do whatever you want as this user.
    return result;
});

此外,.NET 5+提供了异步任务的 WindowsIdentity.RunImpersonatedAsync 方法。如果您使用旧版本,可以通过引入System.Security.Principal.Windows Nuget包来使用该方法。

await WindowsIdentity.RunImpersonatedAsync(userHandle, async () =>
{
    // do whatever you want as this user.
});
或者
var result = await WindowsIdentity.RunImpersonated(userHandle, async () =>
{
    // do whatever you want as this user.
    return result;
});
  • 旧代码使用WindowsIdentity.Impersonate 方法来检索一个 WindowsImpersonationContext 对象。该对象实现了 IDisposable,所以通常应该在 using 块中调用。

  • using (WindowsImpersonationContext context = WindowsIdentity.Impersonate(userHandle))
    {
        // do whatever you want as this user.
    }
    

    虽然这个API在.NET Framework中仍然存在,但通常应该避免使用。

    访问用户帐户

    使用用户名和密码访问Windows用户帐户的API是Win32本地APILogonUser。目前没有内置的托管.NET API可以调用它。

    [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    internal static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, out IntPtr phToken);
    

    这是基本的调用定义,但实际使用它在生产中需要考虑更多:

    • 使用“安全”访问模式获取句柄。
    • 适当关闭本机句柄。
    • 代码访问安全性 (CAS) 信任级别 (.NET Framework中仅适用)。
    • 如果可以通过用户击键安全地收集,请传递SecureString

    不要自己编写该代码,考虑使用我的SimpleImpersonation库,它提供了一个托管包装器来获取用户句柄的LogonUser API:

    using System.Security.Principal;
    using Microsoft.Win32.SafeHandles;
    using SimpleImpersonation;
    
    var credentials = new UserCredentials(domain, username, password);
    using SafeAccessTokenHandle userHandle = credentials.LogonUser(LogonType.Interactive);  // or another LogonType
    

    现在,您可以使用上面第一节中提到的任何方法来使用userHandle。这是自SimpleImpersonation库4.0.0版本以来的首选API。有关更多详细信息,请参阅项目自述文件。

    远程计算机访问

    重要的是要认识到模拟是一个本地计算机概念。无法使用仅已知于远程计算机的用户进行模拟。如果您想要访问远程计算机上的资源,则本地计算机和远程计算机必须连接到同一个域,或者两台计算机的域之间需要建立信任关系。如果任一计算机没有域,则无法使用LogonUser或SimpleImpersonation连接到该计算机。


    16
    这与http://msdn.microsoft.com/en-us/library/system.security.principal.windowsimpersonationcontext.aspx上提供的代码非常相似,但在这里看到所有内容列出来真是太棒了。简单明了,很容易整合到我的解决方案中。非常感谢你所做的一切辛苦工作! - McArthey
    3
    @Chris - 你需要使用其他的登录类型。类型9仅提供对出站网络凭据的模拟。我测试了WinForms应用程序中的类型2、3和8,它们确实可以正确地更新当前主体。人们可以假设类型4和5也是如此,适用于服务或批处理应用程序。请参阅我在帖子中引用的链接。 - Matt Johnson-Pint
    1
    @synergetic - 这取决于你使用它做什么。例如,如果你正在访问数据库,则由于在此处讨论的问题,连接可能会保留模拟用户上下文。如果是其他事情,请提出一个新问题 - 或者如果是针对我的库的特定问题,请在此处提出问题。谢谢。 - Matt Johnson-Pint
    2
    @Sophit - 它已经实现了. - Matt Johnson-Pint
    4
    @Sophit - 此处引用源代码 明确显示在处理过程中调用了 Undo - Matt Johnson-Pint
    显示剩余20条评论

    63

    4
    请注意,冒充并不是万能的解决方案,有些API本身就没有设计为支持冒充。 - Lex Li
    那个来自荷兰程序员博客的链接非常棒。比其他介绍的技术更直观的模拟方法。 - code4life

    20

    这很可能是你想要的:

    using System.Security.Principal;
    using(WindowsIdentity.GetCurrent().Impersonate())
    {
         //your code goes here
    }
    

    但我需要更多细节来帮助你解决问题。如果你正在尝试在网站上进行模拟,则可以使用配置文件进行模拟,如果是WCF服务,则可以通过方法修饰符(属性)进行模拟,或者......你懂的。

    此外,如果我们要讨论模拟调用特定服务(或Web应用程序)的客户端,则需要正确配置客户端,以便它传递适当的令牌。

    最后,如果您真正想要做的是委派,则还需要正确设置AD,以使用户和计算机受信任而可供委派。

    编辑:
    在这里查看如何模拟不同的用户,并获取进一步的文档。


    2
    这段代码看起来只能模拟当前 Windows 身份。有没有办法获取另一个用户的 WindowsIdentity 对象? - ashwnacharya
    你编辑中的链接(https://msdn.microsoft.com/en-us/library/chf6fbt4.aspx - 转到示例)确实是我正在寻找的! - Matt
    哇!你指引了我正确的方向,只需要几个词就可以完成神奇的使用配置文件进行模拟。谢谢Esteban,来自秘鲁的问候。 - AjFmO

    7

    以下是我对Matt Johnson答案的VB.Net版本,我添加了一个登陆类型的枚举。其中LOGON32_LOGON_INTERACTIVE是第一个适用于SQL Server的枚举值。我的连接字符串是可信任的,没有在连接字符串中包含用户名/密码。

      <PermissionSet(SecurityAction.Demand, Name:="FullTrust")> _
      Public Class Impersonation
        Implements IDisposable
    
        Public Enum LogonTypes
          ''' <summary>
          ''' This logon type is intended for users who will be interactively using the computer, such as a user being logged on  
          ''' by a terminal server, remote shell, or similar process.
          ''' This logon type has the additional expense of caching logon information for disconnected operations; 
          ''' therefore, it is inappropriate for some client/server applications,
          ''' such as a mail server.
          ''' </summary>
          LOGON32_LOGON_INTERACTIVE = 2
    
          ''' <summary>
          ''' This logon type is intended for high performance servers to authenticate plaintext passwords.
          ''' The LogonUser function does not cache credentials for this logon type.
          ''' </summary>
          LOGON32_LOGON_NETWORK = 3
    
          ''' <summary>
          ''' This logon type is intended for batch servers, where processes may be executing on behalf of a user without 
          ''' their direct intervention. This type is also for higher performance servers that process many plaintext
          ''' authentication attempts at a time, such as mail or Web servers. 
          ''' The LogonUser function does not cache credentials for this logon type.
          ''' </summary>
          LOGON32_LOGON_BATCH = 4
    
          ''' <summary>
          ''' Indicates a service-type logon. The account provided must have the service privilege enabled. 
          ''' </summary>
          LOGON32_LOGON_SERVICE = 5
    
          ''' <summary>
          ''' This logon type is for GINA DLLs that log on users who will be interactively using the computer. 
          ''' This logon type can generate a unique audit record that shows when the workstation was unlocked. 
          ''' </summary>
          LOGON32_LOGON_UNLOCK = 7
    
          ''' <summary>
          ''' This logon type preserves the name and password in the authentication package, which allows the server to make 
          ''' connections to other network servers while impersonating the client. A server can accept plaintext credentials 
          ''' from a client, call LogonUser, verify that the user can access the system across the network, and still 
          ''' communicate with other servers.
          ''' NOTE: Windows NT:  This value is not supported. 
          ''' </summary>
          LOGON32_LOGON_NETWORK_CLEARTEXT = 8
    
          ''' <summary>
          ''' This logon type allows the caller to clone its current token and specify new credentials for outbound connections.
          ''' The new logon session has the same local identifier but uses different credentials for other network connections. 
          ''' NOTE: This logon type is supported only by the LOGON32_PROVIDER_WINNT50 logon provider.
          ''' NOTE: Windows NT:  This value is not supported. 
          ''' </summary>
          LOGON32_LOGON_NEW_CREDENTIALS = 9
        End Enum
    
        <DllImport("advapi32.dll", SetLastError:=True, CharSet:=CharSet.Unicode)> _
        Private Shared Function LogonUser(lpszUsername As [String], lpszDomain As [String], lpszPassword As [String], dwLogonType As Integer, dwLogonProvider As Integer, ByRef phToken As SafeTokenHandle) As Boolean
        End Function
    
        Public Sub New(Domain As String, UserName As String, Password As String, Optional LogonType As LogonTypes = LogonTypes.LOGON32_LOGON_INTERACTIVE)
          Dim ok = LogonUser(UserName, Domain, Password, LogonType, 0, _SafeTokenHandle)
          If Not ok Then
            Dim errorCode = Marshal.GetLastWin32Error()
            Throw New ApplicationException(String.Format("Could not impersonate the elevated user.  LogonUser returned error code {0}.", errorCode))
          End If
    
          WindowsImpersonationContext = WindowsIdentity.Impersonate(_SafeTokenHandle.DangerousGetHandle())
        End Sub
    
        Private ReadOnly _SafeTokenHandle As New SafeTokenHandle
        Private ReadOnly WindowsImpersonationContext As WindowsImpersonationContext
    
        Public Sub Dispose() Implements System.IDisposable.Dispose
          Me.WindowsImpersonationContext.Dispose()
          Me._SafeTokenHandle.Dispose()
        End Sub
    
        Public NotInheritable Class SafeTokenHandle
          Inherits SafeHandleZeroOrMinusOneIsInvalid
    
          <DllImport("kernel32.dll")> _
          <ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)> _
          <SuppressUnmanagedCodeSecurity()> _
          Private Shared Function CloseHandle(handle As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
          End Function
    
          Public Sub New()
            MyBase.New(True)
          End Sub
    
          Protected Overrides Function ReleaseHandle() As Boolean
            Return CloseHandle(handle)
          End Function
        End Class
    
      End Class
    

    你需要使用 Using 语句来容纳需要以模拟用户身份运行的一些代码。

    6

    请查看我之前的回答以获取更多详细信息。

    我已经创建了一个Nuget包(Nuget)

    Github上的代码

    示例:您可以使用:

    string login = "";
    string domain = "";
    string password = "";
    
    using (UserImpersonation user = new UserImpersonation(login, domain, password))
    {
       if (user.ImpersonateValidUser())
       {
           File.WriteAllText("test.txt", "your text");
           Console.WriteLine("File writed");
       }
       else
       {
           Console.WriteLine("User not connected");
       }
    }
    

    查看完整代码:
    using System;
    using System.Runtime.InteropServices;
    using System.Security.Principal;
    
    
    /// <summary>
    /// Object to change the user authticated
    /// </summary>
    public class UserImpersonation : IDisposable
    {
        /// <summary>
        /// Logon method (check athetification) from advapi32.dll
        /// </summary>
        /// <param name="lpszUserName"></param>
        /// <param name="lpszDomain"></param>
        /// <param name="lpszPassword"></param>
        /// <param name="dwLogonType"></param>
        /// <param name="dwLogonProvider"></param>
        /// <param name="phToken"></param>
        /// <returns></returns>
        [DllImport("advapi32.dll")]
        private static extern bool LogonUser(String lpszUserName,
            String lpszDomain,
            String lpszPassword,
            int dwLogonType,
            int dwLogonProvider,
            ref IntPtr phToken);
    
        /// <summary>
        /// Close
        /// </summary>
        /// <param name="handle"></param>
        /// <returns></returns>
        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        public static extern bool CloseHandle(IntPtr handle);
    
        private WindowsImpersonationContext _windowsImpersonationContext;
        private IntPtr _tokenHandle;
        private string _userName;
        private string _domain;
        private string _passWord;
    
        const int LOGON32_PROVIDER_DEFAULT = 0;
        const int LOGON32_LOGON_INTERACTIVE = 2;
    
        /// <summary>
        /// Initialize a UserImpersonation
        /// </summary>
        /// <param name="userName"></param>
        /// <param name="domain"></param>
        /// <param name="passWord"></param>
        public UserImpersonation(string userName, string domain, string passWord)
        {
            _userName = userName;
            _domain = domain;
            _passWord = passWord;
        }
    
        /// <summary>
        /// Valiate the user inforamtion
        /// </summary>
        /// <returns></returns>
        public bool ImpersonateValidUser()
        {
            bool returnValue = LogonUser(_userName, _domain, _passWord,
                    LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
                    ref _tokenHandle);
    
            if (false == returnValue)
            {
                return false;
            }
    
            WindowsIdentity newId = new WindowsIdentity(_tokenHandle);
            _windowsImpersonationContext = newId.Impersonate();
            return true;
        }
    
        #region IDisposable Members
    
        /// <summary>
        /// Dispose the UserImpersonation connection
        /// </summary>
        public void Dispose()
        {
            if (_windowsImpersonationContext != null)
                _windowsImpersonationContext.Undo();
            if (_tokenHandle != IntPtr.Zero)
                CloseHandle(_tokenHandle);
        }
    
        #endregion
    }
    

    4
    我知道我来晚了,但我认为Phillip Allan-Harding的库对于这种情况和类似情况是最好的选择。
    你只需要像这样一小段代码:
    private const string LOGIN = "mamy";
    private const string DOMAIN = "mongo";
    private const string PASSWORD = "HelloMongo2017";
    
    private void DBConnection()
    {
        using (Impersonator user = new Impersonator(LOGIN, DOMAIN, PASSWORD, LogonType.LOGON32_LOGON_NEW_CREDENTIALS, LogonProvider.LOGON32_PROVIDER_WINNT50))
        {
        }
    }
    

    并添加他的类:

    .NET(C#)使用网络凭据进行模拟

    如果您需要模拟登录具有网络凭据,可以使用我的示例,但它具有更多选项。


    2
    你的方法似乎更通用,同时在参数上更具体化 +1 - Natalie Perret

    0

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接