您如何在 .NET 中进行模拟?

Posted

技术标签:

【中文标题】您如何在 .NET 中进行模拟?【英文标题】:How do you do Impersonation in .NET? 【发布时间】:2008-09-24 03:55:09 【问题描述】:

在 .NET 中是否有一种开箱即用的简单方式来模拟用户?

到目前为止,我一直在使用 this class from code project 来满足我的所有模拟要求。

使用 .NET Framework 有更好的方法吗?

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

【问题讨论】:

你能说得更具体点吗?开箱即用的模拟方式有很多。 【参考方案1】:

.NET 空间中的“模拟”通常意味着在特定用户帐户下运行代码。与通过用户名和密码访问该用户帐户相比,这是一个有点不同的概念,尽管这两个想法经常结合在一起。我将描述它们,然后解释如何使用我的 SimpleImpersonation 库,它在内部使用它们。

假冒

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

较新的代码(.NET 4.6+、.NET Core 等)通常应使用WindowsIdentity.RunImpersonated,它接受用户帐户令牌的句柄,然后是ActionFunc<T>要执行的代码。

WindowsIdentity.RunImpersonated(tokenHandle, () =>

    // do whatever you want as this user.
);

var result = WindowsIdentity.RunImpersonated(tokenHandle, () =>

    // do whatever you want as this user.
    return result;
);

旧代码使用WindowsIdentity.Impersonate 方法检索WindowsImpersonationContext 对象。该对象实现了IDisposable,因此一般应从using 块中调用。

using (WindowsImpersonationContext context = WindowsIdentity.Impersonate(tokenHandle))

    // do whatever you want as this user.

虽然此 API 在 .NET Framework 中仍然存在,但通常应避免使用,并且在 .NET Core 或 .NET Standard 中不可用。

访问用户帐户

在 Windows 中使用用户名和密码访问用户帐户的 API 是 LogonUser - 这是一个 Win32 本机 API。目前没有用于调用它的内置 .NET API,因此必须求助于 P/Invoke。

[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 库,它将模拟和用户访问结合到一个 API 中。它在现代和旧代码库中都能很好地工作,使用相同的简单 API:

var credentials = new UserCredentials(domain, username, password);
Impersonation.RunAsUser(credentials, logonType, () =>

    // do whatever you want as this user.
); 

var credentials = new UserCredentials(domain, username, password);
var result = Impersonation.RunAsUser(credentials, logonType, () =>

    // do whatever you want as this user.
    return something;
);

请注意,它与WindowsIdentity.RunImpersonated API 非常相似,但不需要您了解任何有关令牌句柄的知识。

这是 3.0.0 版的 API。有关更多详细信息,请参阅项目自述文件。另请注意,该库的早期版本使用IDisposable 模式的API,类似于WindowsIdentity.Impersonate。较新的版本更安全,而且两者仍在内部使用。

【讨论】:

这与msdn.microsoft.com/en-us/library/… 提供的代码非常相似,但看到这里列出的所有内容真是太棒了。直截了当且易于整合到我的解决方案中。非常感谢您所做的所有辛勤工作! 感谢您发布此信息。但是,在 using 语句中,我尝试了这行代码 System.Security.Principal.WindowsIdentity.GetCurrent().Name,结果只是我登录的用户名,而不是我传递给 Impersonation 构造函数的用户名。 @Chris - 您需要使用其他登录类型之一。类型 9 仅提供对出站网络凭据的模拟。我从 WinForms 应用程序测试了类型 2、3 和 8,它们确实正确更新了当前主体。有人会假设类型 4 和 5 也适用于服务或批处理应用程序。请参阅我在帖子中引用的链接。 @Sophit - It already does. @Sophit - reference source code here 清楚地表明 Undo 在处理过程中被调用。【参考方案2】:

这里是 .NET 模拟概念的一些很好的概述。

Michiel van Otegem: WindowsImpersonationContext made easy WindowsIdentity.Impersonate Method (check out the code samples)

基本上,您将利用 .NET 框架中开箱即用的这些类:

WindowsImpersonationContext WindowsIdentity

代码通常会变得很长,这就是为什么您会看到许多示例,例如您引用的示例,它们试图简化流程。

【讨论】:

请注意,模拟不是灵丹妙药,有些 API 根本不适合模拟。 来自荷兰程序员博客的链接非常好。比其他技术更直观的模拟方法。【参考方案3】:

这可能就是你想要的:

using System.Security.Principal;
using(WindowsIdentity.GetCurrent().Impersonate())

     //your code goes here

但我真的需要更多细节来帮助你。您可以使用配置文件进行模拟(如果您尝试在网站上执行此操作),或者如果它是 WCF 服务,则可以通过方法装饰器(属性)进行模拟,或者通过......您明白了。

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

最后,如果您真正想要做的是委托,您还需要正确设置 AD,以便信任用户和机器进行委托。

编辑: 查看here,了解如何模拟其他用户,并获取更多文档。

【讨论】:

此代码看起来只能模拟当前 Windows 身份。有没有办法获取另一个用户的 WindowsIdentity 对象? 编辑中的链接(msdn.microsoft.com/en-us/library/chf6fbt4.aspx - 转到 Examples 那里)真的是我想要的! 哇!您引导我朝着正确的方向前进,只需要几句话就可以做到使用配置文件模拟谢谢Esteban,saludos desde Peru【参考方案4】:

这是我的马特约翰逊答案的 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 语句来包含一些要模拟运行的代码。

【讨论】:

【参考方案5】:

查看我之前回答的更多细节 我创建了一个 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

【讨论】:

【参考方案6】:

我知道我参加聚会已经很晚了,但我认为来自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#) Impersonation with Network Credentials

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

【讨论】:

您的方法似乎更通用,而参数 +1 更具体【参考方案7】:

您可以使用此解决方案。 (使用nuget包) 源代码可在:Github 上获得: https://github.com/michelcedric/UserImpersonation

更多细节 https://michelcedric.wordpress.com/2015/09/03/usurpation-didentite-dun-user-c-user-impersonation/

【讨论】:

以上是关于您如何在 .NET 中进行模拟?的主要内容,如果未能解决你的问题,请参考以下文章

您如何在子弹物理引擎中模拟传送带?

如何模拟IE8环境?

如何使用 Moq 在 .NET Core 2.1 中模拟新的 HttpClientFactory

如何在 Ocaml 中使用多核进行蒙特卡罗模拟?

如何在 Spring.Net 中更改配置

如何模拟Android系统杀死的服务