146

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

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

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

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

4

7 回答 7

320

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

冒充

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

  • 较新的代码(.NET 4.6+、.NET Core 等)通常应该使用WindowsIdentity.RunImpersonated,它接受用户帐户令牌的句柄,然后是要执行的代码的Action或。Func<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用户击键安全地收集一个时通过。

为说明所有这些而编写的代码量超出了 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;
});

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

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

于 2011-08-30T21:39:17.017 回答
62

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

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

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

于 2008-09-24T04:01:16.917 回答
21

这可能是你想要的:

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

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

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

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

编辑:
查看此处以了解如何模拟不同的用户,并获取更多文档。

于 2008-09-24T04:04:15.740 回答
7

这是我的马特约翰逊答案的 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

您需要使用 withUsing语句来包含一些模拟运行的代码。

于 2014-11-11T19:11:52.530 回答
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
}
于 2016-02-16T07:51:40.717 回答
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#) 模拟网络凭据

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

于 2017-11-07T16:11:05.100 回答
0

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

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

于 2015-09-04T09:19:08.813 回答