WebBroker-ISAPI模块为何仅在独立线程中可正常访问文件?
问题分析:ISAPI直接写入文件失败,线程中成功的原因
问题重现
使用Delphi 11.1 WebBroker开发ISAPI模块,尝试向Web应用目录写入日志文件:
写入文件的核心过程
procedure WriteMyFile; var fname: string; fs: TFileStream; begin fname := TPath.Combine(WebApplicationDirectory, 'myfile.log'); fs := TFileStream.Create(fname, fmCreate or fmOpenWrite or fmShareDenyNone); fs.Write([$54,$65,$73,$74,$0D,$0A], 6); { 写入"Test"及换行符 } fs.Free; end;
直接调用触发权限异常
在DefaultHandler动作中直接调用时,抛出访问被拒绝异常:
procedure TWebModule1.WebModule1DefaultHandlerAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); begin WriteMyFile; //==> 异常:access denied! Response.Content := 'no error!'; end;
线程中调用功能正常
通过匿名线程调用时,可成功创建并写入文件:
procedure TWebModule1.WebModule1DefaultHandlerAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); var thr: TThread; begin //WriteMyFile; //==> 异常:access denied! thr := TThread.CreateAnonymousThread(WriteMyFile); // 正常执行,文件创建成功 thr.FreeOnTerminate := False; thr.Start; thr.WaitFor; thr.Free; Response.Content := 'no error!'; end;
核心原因:IIS请求线程的身份模拟机制
这是IIS ISAPI的身份模拟特性导致的权限差异:
- 请求线程的身份:ISAPI模块处理HTTP请求时,IIS会自动让请求处理线程模拟当前请求的用户身份(默认是匿名用户
IUSR,若开启认证则为对应认证用户)。你虽然给应用池账户IIS AppPool\MYPOOL配置了文件夹修改权限,但当前请求线程的身份是模拟用户,该用户未被授予目标文件夹的写入权限,因此直接调用会触发“访问被拒绝”。 - 新建线程的身份:通过
TThread.CreateAnonymousThread创建的新线程,不会继承请求线程的模拟令牌,而是使用应用池的原始账户身份(即IIS AppPool\MYPOOL)运行。该账户已被配置文件夹修改权限,因此能成功完成文件操作。
验证与解决方向
- 验证方法:在两种调用场景中分别获取当前线程的身份(调用
Windows.GetUserNameAPI),可观察到直接调用时身份为IUSR,线程中为IIS AppPool\MYPOOL。 - 解决方式:
- 方式一:给模拟用户(如
IUSR或网站认证用户)添加目标文件夹的修改权限。 - 方式二:在代码中临时取消身份模拟,完成文件操作后恢复,示例代码:
procedure WriteMyFile; var fname: string; fs: TFileStream; OldToken: THandle; begin // 保存当前模拟令牌并取消模拟 if ImpersonateSelf(SecurityImpersonation) then begin OldToken := Windows.GetCurrentThreadToken; RevertToSelf; try fname := TPath.Combine(WebApplicationDirectory, 'myfile.log'); fs := TFileStream.Create(fname, fmCreate or fmOpenWrite or fmShareDenyNone); fs.Write([$54,$65,$73,$74,$0D,$0A], 6); fs.Free; finally // 恢复之前的模拟身份 if OldToken <> 0 then SetThreadToken(nil, OldToken); CloseHandle(OldToken); end; end; end;
- 方式一:给模拟用户(如
内容的提问来源于stack exchange,提问作者headfime
相关产品推荐
相关产品推荐

