从远程文件共享下载文件会引发访问被拒绝
Downloading a file from a Remote File share throws access denied
我在 ASP.Net 通用处理程序中有以下代码来下载文件。
// generate you file
// set FilePath and FileName variables
string stFile = FilePath + FileName;
try {
response.Clear();
response.ContentType = "application/pdf";
response.AppendHeader("Content-Disposition", "attachment; filename=" + FileName + ";");
response.TransmitFile(stFile);
} catch (Exception ex) {
// any error handling mechanism
} finally {
response.End();
}
它适用于本地路径 - 例如 "D:\Files\Sample.pdf" 或“\localserver\Files\Sample.pdf”。
但是在尝试访问“\anotherServer\Files\Sample.pdf”之类的网络文件共享时抛出访问被拒绝的错误。
跟双跳有关吗?我是否可以使用 spsecurity.runwithelevatedprivileges 来解决这个问题?或者其他选项是什么?
根据这篇文章https://weblogs.asp.net/owscott/iis-windows-authentication-and-the-double-hop-issue,这似乎是一个双跳问题。我该如何解决这个问题?我想要的只是最终用户应该能够使用 asp.net 通用处理程序下载远程资源。
您需要提供文件权限,一旦您的工作完成,您需要撤销它们。
最好使用 try finally 块 in finally 块你可以撤销权限
有关文件权限的示例,您可以在 MSDN Link
中找到
文件名不应包含空格,因为它会破坏 mozilla 浏览器中的下载
我必须处理一次类似的事情,并通过将网络共享映射到特定驱动器来让它工作。
在尝试访问网络共享之前将凭据添加到网络共享的解决方案无效(但您可以首次尝试)
NetworkCredential nc = new NetworkCredential("<login>", "<pass>");
CredentialCache cache = new CredentialCache();
cache.Add(new Uri("<your network share>"), "Basic", nc);
如果这不起作用,请使用将网络共享映射到驱动器的组合解决方案。使用 class NetworkDrive
使 WinApi 调用 WNetAddConnection2
和 WNetCancelConnection2
:
public class NetworkDrive
{
public enum ResourceScope
{
RESOURCE_CONNECTED = 1,
RESOURCE_GLOBALNET,
RESOURCE_REMEMBERED,
RESOURCE_RECENT,
RESOURCE_CONTEXT
}
public enum ResourceType
{
RESOURCETYPE_ANY,
RESOURCETYPE_DISK,
RESOURCETYPE_PRINT,
RESOURCETYPE_RESERVED
}
public enum ResourceUsage
{
RESOURCEUSAGE_CONNECTABLE = 0x00000001,
RESOURCEUSAGE_CONTAINER = 0x00000002,
RESOURCEUSAGE_NOLOCALDEVICE = 0x00000004,
RESOURCEUSAGE_SIBLING = 0x00000008,
RESOURCEUSAGE_ATTACHED = 0x00000010,
RESOURCEUSAGE_ALL = (RESOURCEUSAGE_CONNECTABLE | RESOURCEUSAGE_CONTAINER | RESOURCEUSAGE_ATTACHED),
}
public enum ResourceDisplayType
{
RESOURCEDISPLAYTYPE_GENERIC,
RESOURCEDISPLAYTYPE_DOMAIN,
RESOURCEDISPLAYTYPE_SERVER,
RESOURCEDISPLAYTYPE_SHARE,
RESOURCEDISPLAYTYPE_FILE,
RESOURCEDISPLAYTYPE_GROUP,
RESOURCEDISPLAYTYPE_NETWORK,
RESOURCEDISPLAYTYPE_ROOT,
RESOURCEDISPLAYTYPE_SHAREADMIN,
RESOURCEDISPLAYTYPE_DIRECTORY,
RESOURCEDISPLAYTYPE_TREE,
RESOURCEDISPLAYTYPE_NDSCONTAINER
}
[StructLayout(LayoutKind.Sequential)]
private class NETRESOURCE
{
public ResourceScope dwScope = 0;
public ResourceType dwType = 0;
public ResourceDisplayType dwDisplayType = 0;
public ResourceUsage dwUsage = 0;
public string lpLocalName = null;
public string lpRemoteName = null;
public string lpComment = null;
public string lpProvider = null;
}
[DllImport("mpr.dll")]
private static extern int WNetAddConnection2(NETRESOURCE lpNetResource, string lpPassword, string lpUsername, int dwFlags);
[DllImport("mpr.dll")]
static extern int WNetCancelConnection2(string lpName, Int32 dwFlags, bool bForce);
private const int CONNECT_UPDATE_PROFILE = 0x1;
private const int NO_ERROR = 0;
public int MapNetworkDrive(string unc, string drive, string user, string password)
{
NETRESOURCE myNetResource = new NETRESOURCE();
myNetResource.lpLocalName = drive;
myNetResource.lpRemoteName = unc;
myNetResource.lpProvider = null;
int result = WNetAddConnection2(myNetResource, password, user, 0);
if (result == 0)
return result;
else
throw new Win32Exception(Marshal.GetLastWin32Error());
}
public int UnmapNetworkDrive(string drive)
{
int result = WNetCancelConnection2(drive, CONNECT_UPDATE_PROFILE, true);
if (result != NO_ERROR)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
return result;
}
}
并像这样使用它:
NetworkDrive nd = new NetworkDrive();
try
{
if (nd.MapNetworkDrive("<your network share>", "Z:", "<login>", "<pass>") == 0)
{
NetworkCredential nc = new NetworkCredential("<login>", "<pass>");
CredentialCache cache = new CredentialCache();
cache.Add(new Uri("<your network share>"), "Basic", nc);
// access network share using UNC path (not your drive letter)
}
}
finally
{
nd.UnmapNetworkDrive("Z:");
}
我运行之前遇到过同样的问题,你的代码对我来说似乎没问题,你需要检查的是安全性和文件权限。
假设您的处理程序托管在 IIS 下,您的应用程序池身份必须设置为域用户,运行 IIS 进程具有该用户的权限。
因此您必须授予该用户 read/write 您共享文件夹内容的权限。
我通常这样做是为了访问简单应用程序的数据库和 SAN 存储。
如果您使用的是默认生成的 IIS 应用程序池标识,您的工作进程将尝试以 domain-registered 计算机帐户(示例:yourdomain\machinename$)访问网络资源。有关 IIS 在这些情况下如何运行的更多详细信息,请参见 here。
只要您尝试访问的服务器在同一个域中,您只需设置 "Share" 权限和 NTFS 权限(在共享文件夹的安全下)以允许该域计算机帐户访问共享。
注意:在安全对话框中选择帐户时,默认情况下不会列出计算机帐户 - 您需要在 "Select Users and Groups" 对话框中单击 "Object Types" 并添加 "Computers"。有关 IIS 在这些情况下如何运行的更多详细信息,请参阅 here。
就我而言,它(最终)在下载过程中添加了这一行:
Response.AddHeader("传输编码", "身份");
在我的网络应用程序中,我也这样做了:
- 我使用 UNC 路径下载文件
- 我冒充网络应用程序在我的用户下工作,我是管理员
- 我还为应用程序池设置了我的用户
我在 ASP.Net 通用处理程序中有以下代码来下载文件。
// generate you file
// set FilePath and FileName variables
string stFile = FilePath + FileName;
try {
response.Clear();
response.ContentType = "application/pdf";
response.AppendHeader("Content-Disposition", "attachment; filename=" + FileName + ";");
response.TransmitFile(stFile);
} catch (Exception ex) {
// any error handling mechanism
} finally {
response.End();
}
它适用于本地路径 - 例如 "D:\Files\Sample.pdf" 或“\localserver\Files\Sample.pdf”。
但是在尝试访问“\anotherServer\Files\Sample.pdf”之类的网络文件共享时抛出访问被拒绝的错误。
跟双跳有关吗?我是否可以使用 spsecurity.runwithelevatedprivileges 来解决这个问题?或者其他选项是什么?
根据这篇文章https://weblogs.asp.net/owscott/iis-windows-authentication-and-the-double-hop-issue,这似乎是一个双跳问题。我该如何解决这个问题?我想要的只是最终用户应该能够使用 asp.net 通用处理程序下载远程资源。
您需要提供文件权限,一旦您的工作完成,您需要撤销它们。 最好使用 try finally 块 in finally 块你可以撤销权限
有关文件权限的示例,您可以在 MSDN Link
中找到文件名不应包含空格,因为它会破坏 mozilla 浏览器中的下载
我必须处理一次类似的事情,并通过将网络共享映射到特定驱动器来让它工作。
在尝试访问网络共享之前将凭据添加到网络共享的解决方案无效(但您可以首次尝试)
NetworkCredential nc = new NetworkCredential("<login>", "<pass>");
CredentialCache cache = new CredentialCache();
cache.Add(new Uri("<your network share>"), "Basic", nc);
如果这不起作用,请使用将网络共享映射到驱动器的组合解决方案。使用 class NetworkDrive
使 WinApi 调用 WNetAddConnection2
和 WNetCancelConnection2
:
public class NetworkDrive
{
public enum ResourceScope
{
RESOURCE_CONNECTED = 1,
RESOURCE_GLOBALNET,
RESOURCE_REMEMBERED,
RESOURCE_RECENT,
RESOURCE_CONTEXT
}
public enum ResourceType
{
RESOURCETYPE_ANY,
RESOURCETYPE_DISK,
RESOURCETYPE_PRINT,
RESOURCETYPE_RESERVED
}
public enum ResourceUsage
{
RESOURCEUSAGE_CONNECTABLE = 0x00000001,
RESOURCEUSAGE_CONTAINER = 0x00000002,
RESOURCEUSAGE_NOLOCALDEVICE = 0x00000004,
RESOURCEUSAGE_SIBLING = 0x00000008,
RESOURCEUSAGE_ATTACHED = 0x00000010,
RESOURCEUSAGE_ALL = (RESOURCEUSAGE_CONNECTABLE | RESOURCEUSAGE_CONTAINER | RESOURCEUSAGE_ATTACHED),
}
public enum ResourceDisplayType
{
RESOURCEDISPLAYTYPE_GENERIC,
RESOURCEDISPLAYTYPE_DOMAIN,
RESOURCEDISPLAYTYPE_SERVER,
RESOURCEDISPLAYTYPE_SHARE,
RESOURCEDISPLAYTYPE_FILE,
RESOURCEDISPLAYTYPE_GROUP,
RESOURCEDISPLAYTYPE_NETWORK,
RESOURCEDISPLAYTYPE_ROOT,
RESOURCEDISPLAYTYPE_SHAREADMIN,
RESOURCEDISPLAYTYPE_DIRECTORY,
RESOURCEDISPLAYTYPE_TREE,
RESOURCEDISPLAYTYPE_NDSCONTAINER
}
[StructLayout(LayoutKind.Sequential)]
private class NETRESOURCE
{
public ResourceScope dwScope = 0;
public ResourceType dwType = 0;
public ResourceDisplayType dwDisplayType = 0;
public ResourceUsage dwUsage = 0;
public string lpLocalName = null;
public string lpRemoteName = null;
public string lpComment = null;
public string lpProvider = null;
}
[DllImport("mpr.dll")]
private static extern int WNetAddConnection2(NETRESOURCE lpNetResource, string lpPassword, string lpUsername, int dwFlags);
[DllImport("mpr.dll")]
static extern int WNetCancelConnection2(string lpName, Int32 dwFlags, bool bForce);
private const int CONNECT_UPDATE_PROFILE = 0x1;
private const int NO_ERROR = 0;
public int MapNetworkDrive(string unc, string drive, string user, string password)
{
NETRESOURCE myNetResource = new NETRESOURCE();
myNetResource.lpLocalName = drive;
myNetResource.lpRemoteName = unc;
myNetResource.lpProvider = null;
int result = WNetAddConnection2(myNetResource, password, user, 0);
if (result == 0)
return result;
else
throw new Win32Exception(Marshal.GetLastWin32Error());
}
public int UnmapNetworkDrive(string drive)
{
int result = WNetCancelConnection2(drive, CONNECT_UPDATE_PROFILE, true);
if (result != NO_ERROR)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
return result;
}
}
并像这样使用它:
NetworkDrive nd = new NetworkDrive();
try
{
if (nd.MapNetworkDrive("<your network share>", "Z:", "<login>", "<pass>") == 0)
{
NetworkCredential nc = new NetworkCredential("<login>", "<pass>");
CredentialCache cache = new CredentialCache();
cache.Add(new Uri("<your network share>"), "Basic", nc);
// access network share using UNC path (not your drive letter)
}
}
finally
{
nd.UnmapNetworkDrive("Z:");
}
我运行之前遇到过同样的问题,你的代码对我来说似乎没问题,你需要检查的是安全性和文件权限。
假设您的处理程序托管在 IIS 下,您的应用程序池身份必须设置为域用户,运行 IIS 进程具有该用户的权限。
因此您必须授予该用户 read/write 您共享文件夹内容的权限。
我通常这样做是为了访问简单应用程序的数据库和 SAN 存储。
如果您使用的是默认生成的 IIS 应用程序池标识,您的工作进程将尝试以 domain-registered 计算机帐户(示例:yourdomain\machinename$)访问网络资源。有关 IIS 在这些情况下如何运行的更多详细信息,请参见 here。
只要您尝试访问的服务器在同一个域中,您只需设置 "Share" 权限和 NTFS 权限(在共享文件夹的安全下)以允许该域计算机帐户访问共享。
注意:在安全对话框中选择帐户时,默认情况下不会列出计算机帐户 - 您需要在 "Select Users and Groups" 对话框中单击 "Object Types" 并添加 "Computers"。有关 IIS 在这些情况下如何运行的更多详细信息,请参阅 here。
就我而言,它(最终)在下载过程中添加了这一行:
Response.AddHeader("传输编码", "身份");
在我的网络应用程序中,我也这样做了:
- 我使用 UNC 路径下载文件
- 我冒充网络应用程序在我的用户下工作,我是管理员
- 我还为应用程序池设置了我的用户