Як зробити себе за себе в .NET?


139

Чи існує простий нестандартний спосіб представити себе користувачем у .NET?

Поки що я використовував цей клас з кодового проекту для всіх моїх вимог до себе.

Чи є кращий спосіб зробити це за допомогою .NET Framework?

У мене є набір облікових даних користувачів (ім’я користувача, пароль, доменне ім’я), який представляє особу, яку мені потрібно представити.


1
Чи можете ви бути більш конкретними? Є безліч способів зробити себе за неповносправним.
Естебан Арая

Відповіді:


60

Ось хороший огляд понять, що представляє себе .NET.

В основному ви будете використовувати ці класи, які знаходяться поза рамками в .NET-рамках:

Код часто може бути тривалим, і саме тому ви бачите багато прикладів, як той, на який ви посилаєтесь, які намагаються спростити процес.


4
Лише зауважте, що видавання себе не є срібною кулею, а деякі API просто не розроблені для роботи з видаванням себе.
Лекс Лі

Це посилання з блогу голландського програміста було чудовим. Набагато інтуїтивніший підхід до себе, ніж інші представлені методи.
code4life

296

"Подання себе" в просторі .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.

Доступ до облікового запису користувача

API для використання імені користувача та пароля для отримання доступу до облікового запису користувача в Windows LogonUser- це рідний API Win32. Наразі не існує вбудованого API .NET для його виклику, тому потрібно вдатися до 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, IMHO.

Комбінований та простіший підхід

Замість того, щоб писати все це самостійно, подумайте про використання моєї бібліотеки 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, але не вимагає нічого знати про ручки маркера.

Це API за версією 3.0.0. Детальнішу інформацію див. У розділі readme проекту. Також зауважте, що в попередній версії бібліотеки використовувався API з IDisposableмалюнком, подібним до WindowsIdentity.Impersonate. Новіша версія набагато безпечніша, і обидві досі використовуються всередині країни.


14
Це дуже схоже на код, доступний на веб- сайті msdn.microsoft.com/en-us/library/…, але надзвичайно чудово бачити все це, перераховане тут. Просто та легко включити у своє рішення. Дякую за те, що виконали всю важку роботу!
МакАртей

1
Дякуємо, що опублікували це. Проте в операторі використання я спробував цей рядок коду System.Security.Principal.WindowsIdentity.GetCurrent (). Ім'я та результат отримав саме ім’я користувача, з яким я увійшов, а не той, з якого я перейшов до виконавця імперсонації.
Кріс

3
@Chris - Вам потрібно буде використовувати один з інших типів входу. Тип 9 надає лише себе за особою на вихідних мережевих облікових даних. Я перевірив типи 2, 3 і 8 з програми WinForms, і вони належним чином оновлюють поточний принцип. Можна припустити, що типи 4 і 5 є також для службових або пакетних додатків. Дивіться посилання, на яке я посилався у публікації.
Метт Джонсон-Пінт


4
@Sophit - Тут посилальний вихідний код чітко показує, Undoщо викликається під час утилізації.
Метт Джонсон-Пінт

20

Це, мабуть, те, що ви хочете:

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

Але мені справді потрібно більше деталей, щоб допомогти тобі. Ви можете зробити себе за себе з конфігураційним файлом (якщо ви намагаєтеся це зробити на веб-сайті), або через декоратори методів (атрибути), якщо це служба WCF, або через ... ви отримуєте ідею.

Крім того, якщо ми говоримо про видання себе за клієнта, який викликав певну послугу (або веб-додаток), потрібно правильно налаштувати клієнта, щоб він передав відповідні лексеми.

Нарешті, якщо ви дійсно хочете зробити Делегацію, вам також потрібно правильно настроїти AD, щоб користувачі та машини довіряли делегуванню.

Редагувати:
Погляньте тут, щоб дізнатись, як себе представити іншому користувачеві та подальша документація.


2
Цей код виглядає так, що він може представляти себе лише поточною особою Windows. Чи є спосіб отримати об’єкт WindowsIdentity іншого користувача?
ashwnacharya

Посилання у вашій редакції ( msdn.microsoft.com/en-us/library/chf6fbt4.aspx - перейдіть до Прикладів там) - це те, що я шукав!
Метт

Оце Так! ви скеровували мене в правильному напрямку, потрібно було зробити кілька слів, щоб зробити магічне втілення файлу конфігурації Дякую Естебану, saludos desde Перу
AjFmO

6

Ось мій порт vb.net відповіді Метта Джонсона. Я додав перерахунок для типів входу. LOGON32_LOGON_INTERACTIVEбуло першим значенням enum, яке працювало на сервері 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оператором, щоб містити якийсь код, щоб запустити себе.


3

Перегляньте більш детальну інформацію з попередньої відповіді, я створив цілий пакет 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
}

2

Я знаю, що я вже запізнююся на вечірку, але вважаю, що бібліотека від Філліпа Аллана-Хардінга , найкраща для цієї справи та подібних.

Вам потрібен лише невеликий фрагмент коду, як цей:

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
Керрі Перрет,

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.