Cómo proporcionar el nombre de usuario y la contraseña cuando se conecta a un recurso compartido de red

Cuando se conecta a un recurso compartido de red para el cual el usuario actual (en mi caso, un usuario de servicio habilitado para la red) no tiene derechos, debe proporcionarse el nombre y la contraseña.

Sé cómo hacer esto con las funciones de Win32 (la familia WNet* de mpr.dll ), pero me gustaría hacerlo con la funcionalidad .Net (2.0).

¿Qué opciones hay disponibles?

Tal vez alguna información más ayuda:

  • El caso de uso es un servicio de Windows, no una aplicación de Asp.Net.
  • El servicio se ejecuta bajo una cuenta que no tiene derechos sobre el recurso compartido.
  • La cuenta de usuario necesaria para el recurso compartido no se conoce del lado del cliente.
  • El cliente y el servidor no son miembros del mismo dominio.

Puede cambiar la identidad del hilo o P / Invocar WNetAddConnection2. Prefiero lo último, ya que a veces necesito mantener credenciales múltiples para diferentes ubicaciones. Lo envuelvo en un IDisposable y llamo a WNetCancelConnection2 para eliminar los creditos luego (evitando el error de múltiples usuarios):

 using (new NetworkConnection(@"\\server\read", readCredentials)) using (new NetworkConnection(@"\\server2\write", writeCredentials)) { File.Copy(@"\\server\read\file", @"\\server2\write\file"); } 

Me gustó tanto la respuesta de Mark Brackett que hice mi propia implementación rápida. Aquí está si alguien más lo necesita a toda prisa:

 public class NetworkConnection : IDisposable { string _networkName; public NetworkConnection(string networkName, NetworkCredential credentials) { _networkName = networkName; var netResource = new NetResource() { Scope = ResourceScope.GlobalNetwork, ResourceType = ResourceType.Disk, DisplayType = ResourceDisplaytype.Share, RemoteName = networkName }; var userName = string.IsNullOrEmpty(credentials.Domain) ? credentials.UserName : string.Format(@"{0}\{1}", credentials.Domain, credentials.UserName); var result = WNetAddConnection2( netResource, credentials.Password, userName, 0); if (result != 0) { throw new Win32Exception(result, "Error connecting to remote share"); } } ~NetworkConnection() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { WNetCancelConnection2(_networkName, 0, true); } [DllImport("mpr.dll")] private static extern int WNetAddConnection2(NetResource netResource, string password, string username, int flags); [DllImport("mpr.dll")] private static extern int WNetCancelConnection2(string name, int flags, bool force); } [StructLayout(LayoutKind.Sequential)] public class NetResource { public ResourceScope Scope; public ResourceType ResourceType; public ResourceDisplaytype DisplayType; public int Usage; public string LocalName; public string RemoteName; public string Comment; public string Provider; } public enum ResourceScope : int { Connected = 1, GlobalNetwork, Remembered, Recent, Context }; public enum ResourceType : int { Any = 0, Disk = 1, Print = 2, Reserved = 8, } public enum ResourceDisplaytype : int { Generic = 0x0, Domain = 0x01, Server = 0x02, Share = 0x03, File = 0x04, Group = 0x05, Network = 0x06, Root = 0x07, Shareadmin = 0x08, Directory = 0x09, Tree = 0x0a, Ndscontainer = 0x0b } 

Hoy, 7 años después, estoy enfrentando el mismo problema y me gustaría compartir mi versión de la solución.

Está listo para copiar y pegar 🙂 Aquí está:

Paso 1

En tu código (siempre que necesites hacer algo con permisos)

 ImpersonationHelper.Impersonate(domain, userName, userPassword, delegate { //Your code here //Let's say file copy: if (!File.Exists(to)) { File.Copy(from, to); } }); 

Paso 2

El archivo de ayuda que hace una magia

 using System; using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; using System.Security; using System.Security.Permissions; using System.Security.Principal; using Microsoft.Win32.SafeHandles; namespace BlaBla { public sealed class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid { private SafeTokenHandle() : base(true) { } [DllImport("kernel32.dll")] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] [SuppressUnmanagedCodeSecurity] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool CloseHandle(IntPtr handle); protected override bool ReleaseHandle() { return CloseHandle(handle); } } public class ImpersonationHelper { [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] private static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, out SafeTokenHandle phToken); [DllImport("kernel32.dll", CharSet = CharSet.Auto)] private extern static bool CloseHandle(IntPtr handle); [PermissionSet(SecurityAction.Demand, Name = "FullTrust")] public static void Impersonate(string domainName, string userName, string userPassword, Action actionToExecute) { SafeTokenHandle safeTokenHandle; try { const int LOGON32_PROVIDER_DEFAULT = 0; //This parameter causes LogonUser to create a primary token. const int LOGON32_LOGON_INTERACTIVE = 2; // Call LogonUser to obtain a handle to an access token. bool returnValue = LogonUser(userName, domainName, userPassword, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, out safeTokenHandle); //Facade.Instance.Trace("LogonUser called."); if (returnValue == false) { int ret = Marshal.GetLastWin32Error(); //Facade.Instance.Trace($"LogonUser failed with error code : {ret}"); throw new System.ComponentModel.Win32Exception(ret); } using (safeTokenHandle) { //Facade.Instance.Trace($"Value of Windows NT token: {safeTokenHandle}"); //Facade.Instance.Trace($"Before impersonation: {WindowsIdentity.GetCurrent().Name}"); // Use the token handle returned by LogonUser. using (WindowsIdentity newId = new WindowsIdentity(safeTokenHandle.DangerousGetHandle())) { using (WindowsImpersonationContext impersonatedUser = newId.Impersonate()) { //Facade.Instance.Trace($"After impersonation: {WindowsIdentity.GetCurrent().Name}"); //Facade.Instance.Trace("Start executing an action"); actionToExecute(); //Facade.Instance.Trace("Finished executing an action"); } } //Facade.Instance.Trace($"After closing the context: {WindowsIdentity.GetCurrent().Name}"); } } catch (Exception ex) { //Facade.Instance.Trace("Oh no! Impersonate method failed."); //ex.HandleException(); //On purpose: we want to notify a caller about the issue /Pavel Kovalev 9/16/2016 2:15:23 PM)/ throw; } } } } 

Busqué muchos métodos y lo hice a mi manera. Debe abrir una conexión entre dos máquinas a través del símbolo del sistema NET USE y después de terminar su trabajo borre la conexión con el símbolo del sistema NET USE “myconnection” / delete.

Debe utilizar el proceso de símbolo del sistema desde el código detrás de esta manera:

 var savePath = @"\\servername\foldername\myfilename.jpg"; var filePath = @"C:\\temp\myfileTosave.jpg"; 

El uso es simple:

 SaveACopyfileToServer(filePath, savePath); 

Aquí hay funciones:

 using System.IO using System.Diagnostics; public static void SaveACopyfileToServer(string filePath, string savePath) { var directory = Path.GetDirectoryName(savePath).Trim(); var username = "loginusername"; var password = "loginpassword"; var filenameToSave = Path.GetFileName(savePath); if (!directory.EndsWith("\\")) filenameToSave = "\\" + filenameToSave; var command = "NET USE " + directory + " /delete"; ExecuteCommand(command, 5000); command = "NET USE " + directory + " /user:" + username + " " + password; ExecuteCommand(command, 5000); command = " copy \"" + filePath + "\" \"" + directory + filenameToSave + "\""; ExecuteCommand(command, 5000); command = "NET USE " + directory + " /delete"; ExecuteCommand(command, 5000); } 

Y también la función ExecuteCommand es:

 public static int ExecuteCommand(string command, int timeout) { var processInfo = new ProcessStartInfo("cmd.exe", "/C " + command) { CreateNoWindow = true, UseShellExecute = false, WorkingDirectory = "C:\\", }; var process = Process.Start(processInfo); process.WaitForExit(timeout); var exitCode = process.ExitCode; process.Close(); return exitCode; } 

Estas funciones funcionaron muy rápido y estable para mí.

La solución de Luke Quinane se ve bien, pero funcionó solo parcialmente en mi aplicación ASP.NET MVC. Al tener dos recursos compartidos en el mismo servidor con diferentes credenciales, podría usar la suplantación solo para la primera.

El problema con WNetAddConnection2 también es que se comporta de manera diferente en diferentes versiones de Windows. Es por eso que busqué alternativas y encontré la función LogonUser . Aquí está mi código que también funciona en ASP.NET:

 public sealed class WrappedImpersonationContext { public enum LogonType : int { Interactive = 2, Network = 3, Batch = 4, Service = 5, Unlock = 7, NetworkClearText = 8, NewCredentials = 9 } public enum LogonProvider : int { Default = 0, // LOGON32_PROVIDER_DEFAULT WinNT35 = 1, WinNT40 = 2, // Use the NTLM logon provider. WinNT50 = 3 // Use the negotiate logon provider. } [DllImport("advapi32.dll", EntryPoint = "LogonUserW", SetLastError = true, CharSet = CharSet.Unicode)] public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword, LogonType dwLogonType, LogonProvider dwLogonProvider, ref IntPtr phToken); [DllImport("kernel32.dll")] public extern static bool CloseHandle(IntPtr handle); private string _domain, _password, _username; private IntPtr _token; private WindowsImpersonationContext _context; private bool IsInContext { get { return _context != null; } } public WrappedImpersonationContext(string domain, string username, string password) { _domain = String.IsNullOrEmpty(domain) ? "." : domain; _username = username; _password = password; } // Changes the Windows identity of this thread. Make sure to always call Leave() at the end. [PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")] public void Enter() { if (IsInContext) return; _token = IntPtr.Zero; bool logonSuccessfull = LogonUser(_username, _domain, _password, LogonType.NewCredentials, LogonProvider.WinNT50, ref _token); if (!logonSuccessfull) { throw new Win32Exception(Marshal.GetLastWin32Error()); } WindowsIdentity identity = new WindowsIdentity(_token); _context = identity.Impersonate(); Debug.WriteLine(WindowsIdentity.GetCurrent().Name); } [PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")] public void Leave() { if (!IsInContext) return; _context.Undo(); if (_token != IntPtr.Zero) { CloseHandle(_token); } _context = null; } } 

Uso:

 var impersonationContext = new WrappedImpersonationContext(Domain, Username, Password); impersonationContext.Enter(); //do your stuff here impersonationContext.Leave(); 

Una opción que podría funcionar es usar WindowsIdentity.Impersonate (y cambiar el hilo principal) para convertirse en el usuario deseado, como tal . De vuelta a p / invoke, sin embargo, me temo …

Otra opción descarada (e igualmente ideal) podría ser generar un proceso para hacer el trabajo … ProcessStartInfo acepta un .UserName , .Password y .Domain .

Finalmente, ¿quizás ejecutar el servicio en una cuenta dedicada que tenga acceso? (eliminado como ha aclarado que esta no es una opción).

OK … puedo resond ..

Descargo de responsabilidad: Acabo de tener un día de más de 18 horas (de nuevo) .. Soy viejo y olvidadizo … No puedo deletrear … Tengo un lapso de atención corto, así que mejor respondo rápido .. 🙂

Pregunta:

¿Es posible cambiar el hilo principal a un usuario sin cuenta en la máquina local?

Responder:

Sí, puede cambiar un subproceso principal incluso si las credenciales que está utilizando no están definidas localmente o están fuera del “bosque”.

Acabo de encontrarme con este problema cuando trato de conectarme a un servidor SQL con autenticación NTLM desde un servicio. Esta llamada utiliza las credenciales asociadas con el proceso, lo que significa que necesita una cuenta local o una cuenta de dominio para autenticarse antes de poder suplantar. Bla, bla …

Pero…

Llamar a LogonUser (..) con el atributo de ???? _ NEW_CREDENTIALS devolverá un token de seguridad sin tratar de autenticar las credenciales. Kewl .. No tiene que definir la cuenta dentro del “bosque”. Una vez que tenga el token, es posible que deba llamar a DuplicateToken () con la opción de habilitar la suplantación que resulte en un nuevo token. Ahora llame a SetThreadToken (NULL, token); (¿Podría ser & token?) .. Una llamada a ImpersonateLoggedonUser (token); podría ser necesario, pero no lo creo. Búscalo …

Haz lo que necesitas hacer..

Llame a RevertToSelf () si llamó a ImpersonateLoggedonUser () y luego a SetThreadToken (NULL, NULL); (Creo que … búscalo) y luego CloseHandle () en los identificadores creados.

Sin promesas, pero esto funcionó para mí … ¡Esto está fuera de mi cabeza (como mi pelo) y no puedo deletrear!

Para VB.lovers el equivalente de VB.NET del código de Luke Quinane (¡gracias Luke!)

 Imports System Imports System.Net Imports System.Runtime.InteropServices Imports System.ComponentModel Public Class NetworkConnection Implements IDisposable Private _networkName As String Public Sub New(networkName As String, credentials As NetworkCredential) _networkName = networkName Dim netResource = New NetResource() With { .Scope = ResourceScope.GlobalNetwork, .ResourceType = ResourceType.Disk, .DisplayType = ResourceDisplaytype.Share, .RemoteName = networkName } Dim userName = If(String.IsNullOrEmpty(credentials.Domain), credentials.UserName, String.Format("{0}\{1}", credentials.Domain, credentials.UserName)) Dim result = WNetAddConnection2(NetResource, credentials.Password, userName, 0) If result <> 0 Then Throw New Win32Exception(result, "Error connecting to remote share") End If End Sub Protected Overrides Sub Finalize() Try Dispose (False) Finally MyBase.Finalize() End Try End Sub Public Sub Dispose() Implements IDisposable.Dispose Dispose (True) GC.SuppressFinalize (Me) End Sub Protected Overridable Sub Dispose(disposing As Boolean) WNetCancelConnection2(_networkName, 0, True) End Sub  _ Private Shared Function WNetAddConnection2(netResource As NetResource, password As String, username As String, flags As Integer) As Integer End Function  _ Private Shared Function WNetCancelConnection2(name As String, flags As Integer, force As Boolean) As Integer End Function End Class  _ Public Class NetResource Public Scope As ResourceScope Public ResourceType As ResourceType Public DisplayType As ResourceDisplaytype Public Usage As Integer Public LocalName As String Public RemoteName As String Public Comment As String Public Provider As String End Class Public Enum ResourceScope As Integer Connected = 1 GlobalNetwork Remembered Recent Context End Enum Public Enum ResourceType As Integer Any = 0 Disk = 1 Print = 2 Reserved = 8 End Enum Public Enum ResourceDisplaytype As Integer Generic = &H0 Domain = &H1 Server = &H2 Share = &H3 File = &H4 Group = &H5 Network = &H6 Root = &H7 Shareadmin = &H8 Directory = &H9 Tree = &HA Ndscontainer = &HB End Enum 

Deberías mirar agregar un me gusta como este:

  

En tu web.config

Más información.

Si no puede crear un token de seguridad válido localmente, parece que ha descartado todas las barras de opciones API Win32 y WNetAddConnection *.

Montones de información en MSDN sobre WNet: información de PInvoke y código de muestra que se conecta a una ruta UNC aquí:

http://www.pinvoke.net/default.aspx/mpr/WNetAddConnection2.html#

Referencia de MSDN aquí:

http://msdn.microsoft.com/en-us/library/aa385391(VS.85).aspx

También portado a F # para usar con FALSO

 module NetworkShare open System open System.ComponentModel open System.IO open System.Net open System.Runtime.InteropServices type ResourceScope = | Connected = 1 | GlobalNetwork = 2 | Remembered = 3 | Recent = 4 type ResourceType = | Any = 0 | Disk = 1 | Print = 2 | Reserved = 8 type ResourceDisplayType = | Generic = 0x0 | Domain = 0x01 | Server = 0x02 | Share = 0x03 | File = 0x04 | Group = 0x05 | Network = 0x06 | Root = 0x07 | Shareadmin = 0x08 | Directory = 0x09 | Tree = 0x0a | Ndscontainer = 0x0b //Uses of this construct may result in the generation of unverifiable .NET IL code. #nowarn "9" [] type NetResource = struct val mutable Scope : ResourceScope val mutable ResourceType : ResourceType val mutable DisplayType : ResourceDisplayType val mutable Usage : int val mutable LocalName : string val mutable RemoteName : string val mutable Comment : string val mutable Provider : string new(name) = { // lets preset needed fields NetResource.Scope = ResourceScope.GlobalNetwork ResourceType = ResourceType.Disk DisplayType = ResourceDisplayType.Share Usage = 0 LocalName = null RemoteName = name Comment = null Provider = null } end type WNetConnection(networkName : string, credential : NetworkCredential) = [] static let Mpr = "mpr.dll" [] static extern int connect(NetResource netResource, string password, string username, int flags) [] static extern int disconnect(string name, int flags, bool force) let mutable disposed = false; do let userName = if String.IsNullOrWhiteSpace credential.Domain then credential.UserName else credential.Domain + "\\" + credential.UserName let resource = new NetResource(networkName) let result = connect(resource, credential.Password, userName, 0) if result <> 0 then let msg = "Error connecting to remote share " + networkName new Win32Exception(result, msg) |> raise let cleanup(disposing:bool) = if not disposed then disposed <- true if disposing then () // TODO dispose managed resources here disconnect(networkName, 0, true) |> ignore interface IDisposable with member __.Dispose() = disconnect(networkName, 0, true) |> ignore GC.SuppressFinalize(__) override __.Finalize() = cleanup(false) type CopyPath = | RemotePath of string * NetworkCredential | LocalPath of string let createDisposable() = { new IDisposable with member __.Dispose() = () } let copyFile overwrite destPath srcPath : unit = use _srcConn = match srcPath with | RemotePath(path, credential) -> new WNetConnection(path, credential) :> IDisposable | LocalPath(_) -> createDisposable() use _destConn = match destPath with | RemotePath(path, credential) -> new WNetConnection(path, credential) :> IDisposable | LocalPath(_) -> createDisposable() match srcPath, destPath with | RemotePath(src, _), RemotePath(dest, _) | LocalPath(src), RemotePath(dest, _) | RemotePath(src, _), LocalPath(dest) | LocalPath(src), LocalPath(dest) -> if FileInfo(src).Exists |> not then failwith ("Source file not found: " + src) let destFilePath = if DirectoryInfo(dest).Exists then Path.Combine(dest, Path.GetFileName src) else dest File.Copy(src, destFilePath, overwrite) let rec copyDir copySubDirs filePattern destPath srcPath = use _srcConn = match srcPath with | RemotePath(path, credential) -> new WNetConnection(path, credential) :> IDisposable | LocalPath(_) -> createDisposable() use _destConn = match destPath with | RemotePath(path, credential) -> new WNetConnection(path, credential) :> IDisposable | LocalPath(_) -> createDisposable() match srcPath, destPath with | RemotePath(src, _), RemotePath(dest, _) | LocalPath(src), RemotePath(dest, _) | RemotePath(src, _), LocalPath(dest) | LocalPath(src), LocalPath(dest) -> let dir = DirectoryInfo(src) if dir.Exists |> not then failwith ("Source directory not found: " + src) let dirs = dir.GetDirectories() if Directory.Exists(dest) |> not then Directory.CreateDirectory(dest) |> ignore let files = dir.GetFiles(filePattern) for file in files do let tempPath = Path.Combine(dest, file.Name) file.CopyTo(tempPath, false) |> ignore if copySubDirs then for subdir in dirs do let subdirSrc = match srcPath with | RemotePath(_, credential) -> RemotePath(Path.Combine(dest, subdir.Name), credential) | LocalPath(_) -> LocalPath(Path.Combine(dest, subdir.Name)) let subdirDest = match destPath with | RemotePath(_, credential) -> RemotePath(subdir.FullName, credential) | LocalPath(_) -> LocalPath(subdir.FullName) copyDir copySubDirs filePattern subdirDest subdirSrc