我的目标是拥有一个附件管理器,它 (1) 按照设计有效地使用SQL FILESTREAM,(2) 将对 SQL FileTable 及其底层 FILESTREAM Windows 目录的访问限制为两个 Active Directory 帐户之一(由网络提供的凭据) /app.config 文件),以及 (3) 有效地忽略当前 Windows Auth 用户对于包含此库的任何站点或应用程序。
我已经看过很多关于这个问题的帖子,我相信我已经尝试了几乎所有我在他们身上找到的所有建议,但仍然几乎没有成功。
我有:
- 在我的连接字符串中激活了集成安全性:
"Server={server};Database=AttachmentsGlobal;User ID={domain}\\{user};Password={password};Trusted_Connection=False;Integrated Security=true;"
- 假设同一用户通过修改文件夹权限对运行 SQL Server 2012 的 Windows Server 2012 上的 FileTable 目录进行 RW 访问。
filestream
授予用户对文件表和列的选择、更改、更新、插入和删除权限。- 实现了一个我在网上找到的自定义模拟类,以在调用
SqlFileStream
对象时模拟同一个用户(见下文)。 - 尝试在 SQL Server 端查找错误。我在事件查看器中所能找到的只是我在下面尝试的模拟成功地登录了用户。
- 确保在 SQL Server 的 FILESTREAM 设置中启用了“允许远程连接”。
每当我禁用下面的using(Impersonator){}
块时它就可以工作SaveFile
,否则它会失败。当我在本地调试时,它似乎也忽略了连接字符串:我的用户帐户可以访问附件文件夹,因为我是管理员,但是当我将连接字符串更改为明确无权访问的用户时,它仍然可以正常上传. 我假设这是将当前应用程序用户的凭据传递给服务器并忽略提供的用户的“集成安全性”设置。我不确定如何很好地测试这一点,没有以另一个用户身份运行 Visual Studio(我已经尝试过,但它失败了)。
请告诉我,我正在做一些愚蠢而愚蠢的事情,并且我相当接近解决方案。这个令人难以置信的模糊“访问被拒绝”错误让我发疯!谢谢。
C#:
// SqlFileItem is just a simple model mapping all the columns of the file
// table (except the filestream itself)
public SqlFileItem UploadFile(Stream fileStream, string fileName,
SqlFileItem parent, AttachmentType restricted) {
const string uploadSproc = "InsertAttachment";
const string metadataSproc = "GetMetadata";
SqlFileItem rt;
using (TransactionScope ts = new TransactionScope()) {
using (SqlConnection conn = GetConnection(restricted)) {
conn.Open();
string serverPath;
byte[] serverXfer;
Guid streamIdOfUploadedFile;
using (SqlCommand cmd = conn.CreateCommand()) {
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = uploadSproc;
cmd.Parameters.Add("@fName", SqlDbType.VarChar).Value =
fileName;
cmd.Parameters.Add("@parent", SqlDbType.VarChar).Value =
parent.path_locator.ToString();
using (SqlDataReader rdr = cmd.ExecuteReader()) {
rdr.Read();
serverPath = rdr.GetSqlString(0).Value;
serverXfer = rdr.GetSqlBinary(1).Value;
streamIdOfUploadedFile = rdr.GetGuid(2);
rdr.Close();
}
}
// See below for SaveFile
SaveFile(fileStream, serverPath, serverXfer, exportRestriction);
using (SqlCommand cmd = conn.CreateCommand()) {
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = metadataSproc;
cmd.Parameters.Add("@streamId",
SqlDbType.UniqueIdentifier).Value =
streamIdOfUploadedFile;
using (SqlDataReader rdr = cmd.ExecuteReader()) {
rt =
DataReaderToList<SqlFileItem>(rdr)
.FirstOrDefault();
}
}
}
ts.Complete();
}
return rt;
}
// Performs the actual streaming/saving operation.
private static void SaveFile(Stream src, string serverPath,
byte[] serverXfer, AttachmentType restricted) {
const int blockSize = 1024*512;
// My attempt at impersonating
Impersonator imp = restricted == AttachmentType.Open
? new Impersonator(@"{openActiveDirectoryUser}", @"{domain}",
@"{password}")
: new Impersonator(@"{restrictedActiveDirectoryUser}", @"{domain}",
@"{password}");
using (imp) {
using (src) {
using (
// The line below is where it breaks with a Win32Exception.
// The full text of it from VS2015 is provided below.
SqlFileStream dest = new SqlFileStream(serverPath,
serverXfer, FileAccess.Write)) {
byte[] buffer = new byte[blockSize];
int bytesRead;
while ((bytesRead = src.Read(buffer, 0, buffer.Length)) >
0) {
dest.Write(buffer, 0, bytesRead);
dest.Flush();
}
dest.Close();
}
src.Close();
}
}
}
SQL:
ALTER PROCEDURE [dbo].[InsertAttachment]
@fileName varchar(255),
@parent hierarchyid
AS
BEGIN
SET NOCOUNT ON;
DECLARE @returns TABLE (stream_id UNIQUEIDENTIFIER)
INSERT INTO Attachments(file_stream, name, is_directory, path_locator)
OUTPUT inserted.stream_id INTO @returns
VALUES (0x, @fileName, 0, dbo.GetCustomPathLocator(@parent.ToString()))
SELECT file_stream.PathName()
,GET_FILESTREAM_TRANSACTION_CONTEXT()
,aft.stream_id
FROM Attachments att
INNER JOIN @returns rt on att.stream_id = rt.stream_id
END
ALTER PROCEDURE [dbo].[GetMetadata]
@streamId uniqueidentifier
AS
BEGIN
SELECT stream_id
-- omit file_stream varbinary(max)
,name
,path_locator
,parent_path_locator
,file_type
,cached_file_size
,creation_time
,last_write_time
,last_access_time
,is_directory
-- omit remaining is_* flags
FROM Attachments
WHERE stream_id = @streamId
END
例外:
System.ComponentModel.Win32Exception was unhandled
ErrorCode=-2147467259
HResult=-2147467259
Message=Access is denied
NativeErrorCode=5
Source=System.Data
StackTrace:
at System.Data.SqlTypes.SqlFileStream.OpenSqlFileStream(String path, Byte[] transactionContext, FileAccess access, FileOptions options, Int64 allocationSize)
at System.Data.SqlTypes.SqlFileStream..ctor(String path, Byte[] transactionContext, FileAccess access, FileOptions options, Int64 allocationSize)
at System.Data.SqlTypes.SqlFileStream..ctor(String path, Byte[] transactionContext, FileAccess access)
at FileTableAttachmentLibrary.AttachmentManager.SaveFile(Stream src, String serverPath, Byte[] serverXfer, AttachmentScope exportRestricted) in C:\_projects\FileTableAttachmentLibrary\FileTableAttachmentLibrary\FileTableAttachmentLibrary\AttachmentManager.cs:line 498
at FileTableAttachmentLibrary.AttachmentManager.UploadFile(Stream fileStream, String fileName, SqlFileItem parent, AttachmentScope exportRestriction) in C:\_projects\FileTableAttachmentLibrary\FileTableAttachmentLibrary\FileTableAttachmentLibrary\AttachmentManager.cs:line 210
at TestAttachments.Program.AttachmentManagerTest() in C:\_projects\FileTableAttachmentLibrary\FileTableAttachmentLibrary\TestAttachments\Program.cs:line 69
at TestAttachments.Program.Main(String[] args) in C:\_projects\FileTableAttachmentLibrary\FileTableAttachmentLibrary\TestAttachments\Program.cs:line 26
InnerException: null