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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 22:03:18  来源:igfitidea点击:

How to provide user name and password when connecting to a network share

c#.netwinapinetworkingpasswords

提问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。

More Information.

更多信息。

回答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... ProcessStartInfoaccepts a .UserName, .Passwordand .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

还移植到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