Como fornecer nome de usuário e senha ao se conectar a um compartilhamento de rede

Ao se conectar a um compartilhamento de rede para o qual o usuário atual (no meu caso, um usuário de serviço habilitado para rede) não tem direitos, nome e senha devem ser fornecidos.

Eu sei como fazer isso com funções do Win32 (a família WNet* do mpr.dll ), mas gostaria de fazê-lo com a funcionalidade .Net (2.0).

Quais opções estão disponíveis?

Talvez mais algumas informações ajudem:

  • O caso de uso é um serviço do Windows, não um aplicativo Asp.Net.
  • O serviço está sendo executado em uma conta que não tem direitos sobre o compartilhamento.
  • A conta de usuário necessária para o compartilhamento não é conhecida no lado do cliente.
  • Cliente e servidor não são membros do mesmo domínio.

Você pode alterar a identidade do encadeamento ou P / Invoke WNetAddConnection2. Eu prefiro o último, pois às vezes preciso manter várias credenciais para locais diferentes. Eu envolvê-lo em um IDisposable e chamar WNetCancelConnection2 para remover os creds depois (evitando o erro de vários usuários):

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

Gostei tanto da resposta de Mark Brackett que fiz minha própria implementação rápida. Aqui está, se alguém mais precisar disso com pressa:

 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 } 

Hoje, 7 anos depois, estou enfrentando o mesmo problema e gostaria de compartilhar minha versão da solução.

É copiar e colar pronto 🙂 Aqui está:

Passo 1

Em seu código (sempre que você precisar fazer algo com permissions)

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

Passo 2

O arquivo Helper que faz uma mágica

 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; } } } } 

Eu procurei muitos methods e fiz do meu jeito. Você tem que abrir uma conexão entre duas máquinas via prompt de comando NET USE comando e depois de terminar o seu trabalho limpar a conexão com o prompt de comando NET USE “myconnection” / delete.

Você deve usar o processo de Prompt de Comando do código por trás como este:

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

O uso é simples:

 SaveACopyfileToServer(filePath, savePath); 

Aqui está as funções:

 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); } 

E também a function ExecuteCommand é:

 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; } 

Essas funções funcionaram muito rápido e estável para mim.

A solução Luke Quinane parece boa, mas funcionou apenas parcialmente no meu aplicativo ASP.NET MVC. Tendo dois compartilhamentos no mesmo servidor com credenciais diferentes, eu poderia usar a representação apenas para o primeiro.

O problema com o WNetAddConnection2 é também que ele se comporta de maneira diferente em diferentes versões do Windows. É por isso que procurei alternativas e encontrei a function LogonUser . Aqui está o meu código, que também funciona no 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(); 

Uma opção que pode funcionar é usar o WindowsIdentity.Impersonate (e alterar o principal do thread) para se tornar o usuário desejado, assim . De volta ao p / invoke, no entanto, receio …

Outra opção atrevida (e igualmente longe do ideal) pode ser gerar um processo para fazer o trabalho … ProcessStartInfo aceita um .UserName , .Password e .Domain .

Finalmente – talvez execute o serviço em uma conta dedicada que tenha access? (removido como você esclareceu que isso não é uma opção).

OK … eu posso resond ..

Disclaimer: Acabei de ter uma 18 + hora dia (novamente) .. Eu sou velho e forgetfull .. Eu não posso soletrar .. Eu tenho uma atenção curta, então é melhor eu responder rápido .. 🙂

Questão:

É possível alterar o principal da thread para um usuário sem conta na máquina local?

Responda:

Sim, você pode alterar uma entidade de thread mesmo se as credenciais que você estiver usando não estiverem definidas localmente ou estiverem fora da “floresta”.

Eu apenas corri para este problema ao tentar se conectar a um servidor SQL com autenticação NTLM de um serviço. Essa chamada usa as credenciais associadas ao processo, o que significa que você precisa de uma conta local ou de uma conta de domínio para autenticar antes de representar. Blá, blá …

Mas…

Chamar LogonUser (..) com o atributo de ???? _ NEW_CREDENTIALS retornará um token de segurança sem tentar autenticar as credenciais. Kewl .. Não precisa definir a conta dentro da “floresta”. Depois de ter o token, talvez seja necessário chamar DuplicateToken () com a opção para ativar a representação, resultando em um novo token. Agora chame SetThreadToken (NULL, token); (Pode ser & token?) .. Uma chamada para ImpersonateLoggedonUser (token); pode ser necessário, mas eu não penso assim. Procure..

Faça o que você precisa fazer..

Chame RevertToSelf () se você chamou ImpersonateLoggedonUser () e depois SetThreadToken (NULL, NULL); (Acho que … procure) e, em seguida, CloseHandle () nas alças criadas ..

Sem promises, mas isso funcionou para mim … Isso está fora do topo da minha cabeça (como o meu cabelo) e eu não posso soletrar!

Para VB.lovers o equivalente VB.NET do código de Luke Quinane (obrigado 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 

Você deve estar olhando para adicionar um como este:

  

No seu web.config.

Mais Informações.

Se você não pode criar um token de segurança válido localmente, parece que você excluiu todas as barras de opções Win32 API e WNetAddConnection *.

Toneladas de informações no MSDN sobre WNet – informações PInvoke e código de exemplo que se conecta a um caminho UNC aqui:

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

Referência MSDN aqui:

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

Também portado para F # para usar com FAKE

 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