C# 连接到网络共享时如何提供用户名和密码
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/295538/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to provide user name and password when connecting to a network share
提问by gyrolf
When connecting to a network share for which the current user (in my case, a network enabled service user) has no rights, name and password have to be provided.
当连接到当前用户(在我的情况下,启用网络的服务用户)没有权限的网络共享时,必须提供名称和密码。
I know how to do this with Win32 functions (the WNet*
family from mpr.dll
), but would like to do it with .Net (2.0) functionality.
我知道如何使用 Win32 函数(WNet*
来自的家族mpr.dll
)执行此操作,但希望使用 .Net (2.0) 功能执行此操作。
What options are available?
有哪些选择?
Maybe some more information helps:
也许更多的信息有帮助:
- The use case is a windows service, not an Asp.Net application.
- The service is running under an account which has no rights on the share.
- The user account needed for the share is not known on the client side.
- Client and server are not members of the same domain.
- 用例是 Windows 服务,而不是 Asp.Net 应用程序。
- 该服务在没有共享权限的帐户下运行。
- 客户端不知道共享所需的用户帐户。
- 客户端和服务器不是同一个域的成员。
采纳答案by Mark Brackett
You can either change the thread identity, or P/Invoke WNetAddConnection2. I prefer the latter, as I sometimes need to maintain multiple credentials for different locations. I wrap it into an IDisposable and call WNetCancelConnection2 to remove the creds afterwards (avoiding the multiple usernames error):
您可以更改线程标识,或 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");
}
回答by GEOCHET
You should be looking at adding a like like this:
你应该考虑添加一个像这样的:
<identity impersonate="true" userName="domain\user" password="****" />
Into your web.config.
进入你的 web.config。
回答by Marc Gravell
One option that might work is using WindowsIdentity.Impersonate
(and change the thread principal) to become the desired user, like so. Back to p/invoke, though, I'm afraid...
一种可行的选择是使用WindowsIdentity.Impersonate
(并更改线程主体)成为所需的用户,就像这样。回到 p/invoke,不过,恐怕……
Another cheeky (and equally far from ideal) option might be to spawn a process to do the work... ProcessStartInfo
accepts a .UserName
, .Password
and .Domain
.
另一个厚颜无耻(同样远非理想)的选择可能是产生一个进程来完成这项工作......ProcessStartInfo
接受一个.UserName
,.Password
和.Domain
。
Finally - perhaps run the service in a dedicated account that has access?(removed as you have clarified that this isn't an option).
最后 - 也许在具有访问权限的专用帐户中运行该服务?(已删除,因为您已澄清这不是一个选项)。
回答by stephbu
If you can't create an locally valid security token, it seems like you've ruled all out every option bar Win32 API and WNetAddConnection*.
如果您无法创建本地有效的安全令牌,那么您似乎已经排除了所有选项栏 Win32 API 和 WNetAddConnection*。
Tons of information on MSDN about WNet - PInvoke information and sample code that connects to a UNC path here:
MSDN 上有关 WNet 的大量信息 - PInvoke 信息和连接到此处的 UNC 路径的示例代码:
http://www.pinvoke.net/default.aspx/mpr/WNetAddConnection2.html#
http://www.pinvoke.net/default.aspx/mpr/WNetAddConnection2.html#
MSDN Reference here:
此处的 MSDN 参考:
http://msdn.microsoft.com/en-us/library/aa385391(VS.85).aspx
http://msdn.microsoft.com/en-us/library/aa385391(VS.85).aspx
回答by Luke Quinane
I liked Mark Brackett's answer so much that I did my own quick implementation. Here it is if anyone else needs it in a hurry:
我非常喜欢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
}
回答by Craig Armstrong
OK... I can resond..
好的...我可以共鸣...
Disclaimer: I just had an 18+ hour day (again).. I'm old and forgetfull.. I can't spell.. I have a short attention span so I better respond fast.. :-)
免责声明:我刚刚度过了超过 18 小时的一天(再次)。我老了,健忘......我不会拼写......我的注意力持续时间很短,所以我最好快速响应......:-)
Question:
题:
Is it possible to change the thread principal to an user with no account on the local machine?
是否可以将线程主体更改为在本地计算机上没有帐户的用户?
Answer:
回答:
Yes, you can change a thread principal even if the credentials you are using are not defined locally or are outside the "forest".
是的,即使您使用的凭据未在本地定义或在“森林”之外,您也可以更改线程主体。
I just ran into this problem when trying to connect to an SQL server with NTLM authentication from a service. This call uses the credentials associated with the process meaning that you need either a local account or a domain account to authenticate before you can impersonate. Blah, blah...
我在尝试从服务使用 NTLM 身份验证连接到 SQL 服务器时遇到了这个问题。此调用使用与进程关联的凭据,这意味着您需要本地帐户或域帐户才能进行身份验证,然后才能模拟。呜呜呜……
But...
但...
Calling LogonUser(..) with the attribute of ????_NEW_CREDENTIALS will return a security token without trying to authenticate the credentials. Kewl.. Don't have to define the account within the "forest". Once you have the token you might have to call DuplicateToken() with the option to enable impersonation resulting in a new token. Now call SetThreadToken( NULL, token ); (It might be &token?).. A call to ImpersonateLoggedonUser( token ); might be required, but I don't think so. Look it up..
使用 ????_NEW_CREDENTIALS 属性调用 LogonUser(..) 将返回一个安全令牌,而不尝试验证凭据。Kewl.. 不必在“森林”中定义帐户。获得令牌后,您可能必须调用 DuplicateToken() 并选择启用模拟以生成新令牌。现在调用 SetThreadToken( NULL, token ); (可能是 &token?)。调用 ImpersonateLoggedonUser( token ); 可能需要,但我不这么认为。查一下。。
Do what you need to do..
做你需要做的..
Call RevertToSelf() if you called ImpersonateLoggedonUser() then SetThreadToken( NULL, NULL ); (I think... look it up), and then CloseHandle() on the created handles..
如果您调用了 ImpersonateLoggedonUser() 则调用 RevertToSelf() 然后 SetThreadToken( NULL, NULL ); (我认为......查找它),然后在创建的句柄上使用 CloseHandle() ..
No promises but this worked for me... This is off the top of my head (like my hair) and I can't spell!!!
没有承诺,但这对我有用......这是我的头顶(就像我的头发),我不会拼写!!!
回答by Hakan KOSE
I searched lots of methods and i did it my own way. You have to open a connection between two machine via command prompt NET USE command and after finishing your work clear the connection with command prompt NET USE "myconnection" /delete.
我搜索了很多方法,我按自己的方式做了。您必须通过命令提示符 NET USE 命令打开两台机器之间的连接,并在完成工作后使用命令提示符 NET USE "myconnection" /delete 清除连接。
You must use Command Prompt process from code behind like this:
您必须像这样从后面的代码中使用命令提示符进程:
var savePath = @"\servername\foldername\myfilename.jpg";
var filePath = @"C:\temp\myfileTosave.jpg";
Usage is simple:
用法很简单:
SaveACopyfileToServer(filePath, savePath);
Here is functions:
这里是函数:
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);
}
And also ExecuteCommand function is:
而且 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;
}
This functions worked very fast and stable for me.
这个功能对我来说非常快速和稳定。
回答by VladL
The Luke Quinane solution looks good, but did work only partially in my ASP.NET MVC application. Having two shares on the same server with different credentials I could use the impersonation only for the first one.
Luke Quinane 解决方案看起来不错,但在我的 ASP.NET MVC 应用程序中仅部分起作用。在具有不同凭据的同一服务器上有两个共享,我只能对第一个使用模拟。
The problem with WNetAddConnection2 is also that it behaves differently on different windows versions. That is why I looked for alternatives and found the LogonUserfunction. Here is my code which also works in ASP.NET:
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;
}
}
Usage:
用法:
var impersonationContext = new WrappedImpersonationContext(Domain, Username, Password);
impersonationContext.Enter();
//do your stuff here
impersonationContext.Leave();
回答by Alessandro Bernardi
For VB.lovers the VB.NET equivalent of Luke Quinane's code (thanks Luke!)
对于 VB.lovers 的 VB.NET 相当于 Luke Quinane 的代码(感谢 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
<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
回答by python_kaa
Also ported to F#to use with 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