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


138

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

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

使用.NET Framework是否有更好的方法呢?

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


1
你可以再详细一点吗?开箱即用的模拟方式有很多。
Esteban Araya

Answers:


60

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

基本上,您将利用.NET框架中现成的这些类:

但是,代码通常会很冗长,这就是为什么您会看到许多示例(例如您引用的示例)试图简化过程的原因。


4
请注意,模拟不是灵丹妙药,某些API并非专门设计用于模拟。
Lex Li

荷兰程序员博客中的链接非常棒。比其他呈现技术更直观的模拟方法。
code4life

295

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

假冒

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

  • 较新的代码(.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中仍然存在,但通常应避免使用该API,并且在.NET Core或.NET Standard中不可用。

访问用户帐户

在Windows中,使用用户名和密码来访问用户帐户的LogonUserAPI 是-这是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的时候可以通过用户的击键安全地收集一个。

编写代码来说明所有这些的代码量超出了StackOverflow答案恕我直言。

组合且更简单的方法

与其亲自编写所有内容,不如考虑使用我的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.RunImpersonatedAPI 非常相似,但是不需要您对令牌句柄有所了解。

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


14
这与msdn.microsoft.com/en-us/library/…上的代码非常相似,但是在此处列出所有代码真是太好了。简单明了,易于集成到我的解决方案中。非常感谢您的辛勤工作!
McArthey 2012年

1
感谢您发布此信息。但是,在using语句中,我尝试了这行代码System.Security.Principal.WindowsIdentity.GetCurrent()。Name,结果只是我登录的用户名,而不是我传递给Impersonation构造函数的用户名。
克里斯,

3
@Chris-您需要使用其他登录类型之一。类型9仅提供对出站网络凭据的模拟。我从WinForms应用程序测试了类型2、3和8,它们确实正确更新了当前主体。对于服务或批处理应用程序,人们会假设类型4和5也是如此。请参阅我在文章中引用的链接。
马特·约翰逊·品脱


4
@Sophit- 这里参考源代码 清楚地显示了Undo在处置期间被调用。
Matt Johnson-Pint 2014年

20

这可能是您想要的:

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

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

另外,如果我们要假冒模拟称为特定服务(或Web应用程序)的客户端,则需要正确配置客户端,以使其传递适当的令牌。

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

编辑:
请看这里以了解如何模拟其他用户,以及更多文档。


2
此代码看起来只能模拟当前Windows身份。有没有办法获取另一个用户的WindowsIdentity对象?
ashwnacharya

您编辑中的链接(msdn.microsoft.com/en-us/library/chf6fbt4.aspx-转到此处的示例)确实是我想要的!
马特

哇!您以正确的方向指导了我,只需几个字就可以使用配置文件进行魔术模拟谢谢埃斯特万,秘鲁人致敬
AjFmO

6

这是我对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语句一起使用,以包含一些模拟运行的代码。


3

查看上一个答案的更多详细信息,我创建了一个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");
               }
           }

Vieuw完整代码:

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
}

2

我知道我参加聚会已经很晚了,但是我认为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#)模拟与网络凭据

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


1
您的方法似乎更通用,同时更具体地针对参数+1
Kerry Perret

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.