web-dev-qa-db-ja.com

ネットワーク共有に接続するときにユーザー名とパスワードを提供する方法

現在のユーザー(私の場合、ネットワーク対応のサービスユーザー)に権限がないネットワーク共有に接続する場合、名前とパスワードを入力する必要があります。

Win32関数(WNet*ファミリーのmpr.dllから)でこれを行う方法は知っていますが、.Net(2.0)機能でそれをしたいと思っています。

どのようなオプションが利用可能ですか?

たぶんいくつかの情報が役立ちます:

  • ユースケースはWindowsサービスであり、Asp.Netアプリケーションではありません。
  • サービスは、共有に対する権限を持たないアカウントで実行されています。
  • 共有に必要なユーザーアカウントがクライアント側で認識されていません。
  • クライアントとサーバーは同じドメインのメンバーではありません。
164
gyrolf

スレッドIDを変更するか、WNetAddConnection2をP/Invokeできます。後者のほうが好きです。場所ごとに複数の資格情報を維持する必要がある場合があるからです。それをIDisposableにラップし、WNetCancelConnection2を呼び出して、後でクレデンシャルを削除します(複数のユーザー名のエラーを回避します)。

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

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
}
291
Luke Quinane

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

魔法をかけるヘルパーファイル

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;
            }
        }
    }
}
43
Pavel Kovalev

私は多くのメソッドを検索し、自分のやり方でやった。コマンドPrompt Net Useコマンドを使用して2台のマシン間の接続を開く必要があります。作業が完了したら、コマンドPrompt 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;
    } 

この機能は非常に高速かつ安定して動作しました。

27
Hakan KOSE

Luke Quinaneソリューションは良さそうに見えますが、ASP.NET MVCアプリケーションでは部分的にしか機能しませんでした。同じサーバー上に異なる資格情報を持つ2つの共有がある場合、最初の1つに対してのみ偽装を使用できます。

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();
11
VladL

VB.loversの場合、Luke Quinaneのコードに相当するVB.NET(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
3

動作する可能性のあるオプションの1つは、WindowsIdentity.Impersonateを使用して(そしてスレッドプリンシパルを変更して)目的のユーザーになる like so です。 p/invokeに戻りますが、私は恐れています...

別の生意気な(そして理想からはほど遠い)オプションは、作業を行うプロセスを生成することかもしれません... ProcessStartInfo.UserName.Passwordおよび.Domainを受け入れます。

最後に-おそらくアクセス権を持つ専用アカウントでサービスを実行しますか? (これはオプションではないことを明確にしたため削除されました)。

3
Marc Gravell

OK ...私は共鳴できる..

免責事項:私はちょうど18時間以上の日を過ごしました(再び)。

質問:

ローカルマシンにアカウントのないユーザーにスレッドプリンシパルを変更することは可能ですか?

回答:

はい、使用している資格情報がローカルで定義されていない場合や「フォレスト」の外部にある場合でも、スレッドプリンシパルを変更できます。

サービスからNTLM認証を使用してSQLサーバーに接続しようとしたときに、この問題に遭遇しました。この呼び出しは、プロセスに関連付けられた資格情報を使用します。つまり、偽装する前に認証するにはローカルアカウントまたはドメインアカウントが必要です。何とか….

しかし...

???? _ NEW_CREDENTIALSの属性でLogonUser(..)を呼び出すと、資格情報を認証しようとせずにセキュリティトークンが返されます。 Kewl ..「フォレスト」内でアカウントを定義する必要はありません。トークンを取得したら、オプションでDuplicateToken()を呼び出して、偽装を有効にし、新しいトークンを作成する必要があります。 SetThreadToken(NULL、token);を呼び出します。 (&token?).. ImpersonateLoggedonUser(token);への呼び出し。必要かもしれませんが、そうは思いません。調べる..

必要なことを行います。

ImpersonateLoggedonUser()を呼び出してからSetThreadToken(NULL、NULL)を呼び出した場合は、RevertToSelf()を呼び出します。 (考えてみてください...)そして、作成されたハンドルでCloseHandle().

約束はありませんが、これは私のために働いた...これは私の髪の毛のように頭のてっぺんから外れていて、私は綴ることができません!!!

3
Craig Armstrong

ローカルで有効なセキュリティトークンを作成できない場合、すべてのオプションバーWin32 APIおよびWNetAddConnection *をすべて除外しているようです。

WNetに関するMSDNの膨大な情報-PInvoke情報と、ここでUNCパスに接続するサンプルコード:

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

MSDNリファレンスはこちら:

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

1
stephbu

また、 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
1
python_kaa

次のようなものを追加する必要があります。

<identity impersonate="true" userName="domain\user" password="****" />

Web.configに。

詳細情報

0
GEOCHET