Как указать имя пользователя и пароль при подключении к сетевому ресурсу
При подключении к общему сетевому ресурсу, для которого текущий пользователь (в моем случае пользователь службы с включенной сетью) не имеет прав, необходимо указать имя и пароль.
Я знаю, как сделать это с функциями Win32 (WNet*
семья из mpr.dll
), но хотел бы сделать это с помощью.Net (2.0).
Какие варианты доступны?
Может быть, поможет дополнительная информация:
- Вариант использования - это служба Windows, а не приложение Asp.Net.
- Служба работает под учетной записью, которая не имеет прав на общий ресурс.
- Учетная запись пользователя, необходимая для общего ресурса, неизвестна на стороне клиента.
- Клиент и сервер не являются членами одного домена.
12 ответов
Вы можете либо изменить идентификатор потока, либо P/Invoke WNetAddConnection2. Я предпочитаю последнее, так как мне иногда нужно поддерживать несколько учетных данных для разных мест. Я обертываю его в IDisposable и вызываю WNetCancelConnection2, чтобы впоследствии удалить кредиты (избегая ошибки нескольких имен пользователей):
using (new NetworkConnection(@"\\server\read", readCredentials))
using (new NetworkConnection(@"\\server2\write", writeCredentials)) {
File.Copy(@"\\server\read\file", @"\\server2\write\file");
}
Мне так понравился ответ Mark Brackett, что я сделал свою собственную быструю реализацию. Вот если это нужно кому-то в спешке:
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);
}
}
~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
}
Сегодня, 7 лет спустя, я столкнулся с той же проблемой, и я хотел бы поделиться своей версией решения.
Это готово для копирования и вставки:-) Вот оно:
Шаг 1
В вашем коде (когда вам нужно что-то сделать с разрешениями)
ImpersonationHelper.Impersonate(domain, userName, userPassword, delegate
{
//Your code here
//Let's say file copy:
if (!File.Exists(to))
{
File.Copy(from, to);
}
});
Шаг 2
Файл Helper, который делает магию
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;
}
}
}
}
Я искал много методов, и я сделал это по-своему. Вы должны открыть соединение между двумя машинами с помощью команды NET USE из командной строки и после завершения работы очистить соединение с помощью командной строки NET USE "myconnection" /delete.
Вы должны использовать процесс командной строки из кода, например:
var savePath = @"\\servername\foldername\myfilename.jpg";
var filePath = @"C:\\temp\myfileTosave.jpg";
Использование простое:
SaveACopyfileToServer(filePath, savePath);
Вот функции:
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);
}
А также функция 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;
}
Эти функции работали очень быстро и стабильно для меня.
Решение Luke Quinane выглядит хорошо, но в моем приложении ASP.NET MVC работало только частично. Имея два общих ресурса на одном сервере с разными учетными данными, я мог использовать олицетворение только для первого.
Проблема с WNetAddConnection2 заключается также в том, что он ведет себя по-разному в разных версиях Windows. Вот почему я искал альтернативы и нашел функцию LogonUser. Вот мой код, который также работает в 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;
}
}
Использование:
var impersonationContext = new WrappedImpersonationContext(Domain, Username, Password);
impersonationContext.Enter();
//do your stuff here
impersonationContext.Leave();
Наверное, это самый глупый способ, но в последнее время он пригодился мне и до смешного прост.
Работает только на Windows, конечно.
Process.Start("CMDKEY", @"/add:""NetworkName"" /user:""Username"" /pass:""Password""");
Тогда вы, вероятно, захотите использовать WaitForExit() , прежде чем пытаться получить доступ к общему ресурсу.
Вы можете удалить учетные данные в конце, используя команду CMDKEY еще раз.
Для VB.lovers VB.NET эквивалент кода Люка Куинана (спасибо Люк!)
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
<DllImport("mpr.dll")> _
Private Shared Function WNetAddConnection2(netResource As NetResource, password As String, username As String, flags As Integer) As Integer
End Function
<DllImport("mpr.dll")> _
Private Shared Function WNetCancelConnection2(name As String, flags As Integer, force As Boolean) As Integer
End Function
End Class
<StructLayout(LayoutKind.Sequential)> _
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
ОК... Я могу ответить..
Отказ от ответственности: у меня только что был 18+ часовой день (снова).. Я стар и забыл... Я не могу произнести по буквам... У меня короткая продолжительность концентрации внимания, поэтому я лучше отвечаю быстро..:-)
Вопрос:
Можно ли сменить субъект потока на пользователя без учетной записи на локальном компьютере?
Ответ:
Да, вы можете изменить принципала потока, даже если используемые учетные данные не определены локально или находятся за пределами "леса".
Я просто столкнулся с этой проблемой при попытке подключиться к серверу SQL с проверкой подлинности NTLM из службы. В этом вызове используются учетные данные, связанные с процессом, а это означает, что для аутентификации вам потребуется либо локальная учетная запись, либо учетная запись домена. Бла, бла...
Но...
Вызов LogonUser(..) с атрибутом????_NEW_CREDENTIALS вернет маркер безопасности без попытки аутентификации учетных данных. Kewl.. Не нужно определять учетную запись в "лесу". Когда у вас есть токен, вам, возможно, придется вызвать DuplicateToken() с возможностью включить олицетворение, в результате чего будет получен новый токен. Теперь вызовите SetThreadToken( NULL, токен); (Это может быть &token?).. Вызов ImpersonateLoggedonUser( token); может потребоваться, но я так не думаю. Поищи это..
Делай то, что тебе нужно сделать..
Вызовите RevertToSelf(), если вы вызвали ImpersonateLoggedonUser(), затем SetThreadToken( NULL, NULL); (Я думаю... посмотрите), а затем CloseHandle() на созданные дескрипторы..
Никаких обещаний, но это сработало для меня... Это на макушке моей головы (как мои волосы), и я не могу по буквам!!!
Один из возможных вариантов использования WindowsIdentity.Impersonate
(и измените субъект потока), чтобы стать желаемым пользователем, вот так. Вернуться к п / я вызываю, хотя, боюсь...
Другой дерзкий (и в то же время далеко не идеальный) вариант может заключаться в создании процесса для выполнения работы... ProcessStartInfo
принимает .UserName
, .Password
а также .Domain
,
Наконец - возможно, запустить службу в выделенной учетной записи, которая имеет доступ? (удалено, поскольку вы пояснили, что это не вариант).
Вы должны смотреть на добавление как это:
<identity impersonate="true" userName="domain\user" password="****" />
В ваш web.config.
Если вы не можете создать локально действительный токен безопасности, похоже, что вы исключили все опции панели Win32 API и WNetAddConnection*.
Тонны информации на MSDN о WNet - информация PInvoke и пример кода, который подключается к пути UNC здесь:
http://www.pinvoke.net/default.aspx/mpr/WNetAddConnection2.html
Ссылка MSDN здесь:
http://msdn.microsoft.com/en-us/library/aa385391(VS.85).aspx
Также портирован на F# для использования с 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"
[<StructLayout(LayoutKind.Sequential)>]
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) =
[<Literal>]
static let Mpr = "mpr.dll"
[<DllImport(Mpr, EntryPoint = "WNetAddConnection2")>]
static extern int connect(NetResource netResource, string password, string username, int flags)
[<DllImport(Mpr, EntryPoint = "WNetCancelConnection2")>]
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