如何在.NET 中进行模拟?

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

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

有没有更好的方法通过使用.NETFramework 来实现呢?

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

294990 次浏览

下面是对.NET 模拟概念的一些很好的概述。

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

但是代码通常会很长,这就是为什么您会看到许多类似于您引用的例子,这些例子试图简化过程。

这可能就是你想要的:

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

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

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

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

编辑:
查看 这里以了解如何模拟不同的用户,以及进一步的文档。

“模仿”在。NET 空间通常意味着在特定用户帐户下运行代码。与通过用户名和密码访问该用户帐户相比,这是一个有些独立的概念,尽管这两个概念经常成对出现。

模仿

模拟的 API 是在.NET 中通过 System.Security.Principal名称空间提供的:

  • 较新的代码通常应该使用 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;
    });
    

    还有用于异步任务的 WindowsIdentity.RunImpersonatedAsync,可在。NET 5 + ,或者更老的版本,如果你拉在 系统,安全,主体,窗口 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 仍然存在于.NETFramework 中,但通常应该避免使用它。

访问用户帐户

在 Windows 中使用用户名和密码访问用户帐户的 API 是 LogonUser-这是一个 Win32原生 API。目前没有内置的托管。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)信任级别(仅在.NETFramework 中)
  • 通过 SecureString时,可以通过用户击键安全地收集一个。

与其自己编写代码,不如考虑使用我的 简单模仿库,它为 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 连接到该计算机。

这里是我的 vb.net 端口的马特约翰逊的答案。我为登录类型添加了一个枚举。LOGON32_LOGON_INTERACTIVE是第一个可用于 sql 服务器的枚举值。我的连接字符串是可信的。连接字符串中没有用户名/密码。

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

从我以前的答案中查看更多细节 我已经创建了一个 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
}

我知道我已经迟到了,但是我认为 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 #)

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