我目前正在将几个 Windows 桌面应用程序移植到一个网站。
当前设置包括几个 SQL 服务器后端数据库,仅配置了 Windows 身份验证 (SSPI),并且每个用户/组/角色都对特定对象具有特定权限。这很方便,因为应用层不需要实现任何访问控制。
我希望它与 Web 服务器保持相同的方式,即 Windows 机器上的 Apache。但是每个与数据库的连接都是使用 Apache 的帐户进行的。这是可以理解和预料到的,事实上,Apache 被故意授予访问公共(public)数据的权限,以便能够提供公共(public)内容。
但是如果域用户登录(登录过程已经实现),我希望处理请求的 Apache 进程模拟该用户,从而在整个过程中充当他们请求。
起初,我尝试了 php 的 fastcgi.impersonate技巧,使用 IIS 作为 Web 服务器。但我最终放弃了,主要是因为(1)无论如何我们都必须移植到 Apache 和(2)它是特定于 php 的,结果我们应该将 Web 服务器作为一个整体来定位......
所以我将搜索重定向到 Apache 模块。几个月的研究没有结果,除了mod_auth_sspi等等,这显然不是我要找的(身份验证和模拟是两个不同的东西)。
最后我决定制作自己的模块。我能找到的大多数“101”示例都是用 C 编写的,但我设法在 Lazarus/FPC 中找到了 2-3 个,这是我已经使用了很长一段时间的东西,但从来没有这样的任务。
我知道我必须构建一个 .dll 项目,我知道(或多或少)要使用什么单元,我知道像 LogonUser()
和 ImpersonateLoggedOnUser()
这样的函数> 应该在我的工具箱里。
有人做过类似的事情吗?谁能指出我正确的方向?
一个示例将不胜感激,即使它只是一个简单的概念证明。这个问题远非要求最终的、确定的解决方案。
最佳答案
我最终想出了以下内容:
library mod_winimpersonate;
{$mode objfpc}{$H+}
uses SysUtils, Windows, httpd, apr, Classes;
function DefaultHandler(r: Prequest_rec): Integer; cdecl;
Var
cookies:TStringList;
logindata,username,password:String;
p:Integer;
begin
RevertToSelf;
cookies:=TStringList.Create;
cookies.Delimiter:=';';
cookies.DelimitedText:=apr_table_get(r^.headers_in,'COOKIE');
logindata:=URLDecode(cookies.Values['WinImpersonate']);
If Length(logindata)>0 then
Begin
p:=Pos(':',logindata);
username:=LeftStr(logindata,p-1);
password:=RightStr(logindata,Length(logindata)-p);
ChangeLoggedInUser(username,password,'');
End;
Result:=DECLINED;
end;
procedure RegisterHooks(p: Papr_pool_t); cdecl;
begin
ap_hook_handler(@DefaultHandler, nil, nil, APR_HOOK_REALLY_FIRST);
end;
var
TheModule: module;
exports TheModule name 'winimpersonate_module';
begin
FillChar(TheModule, sizeof(TheModule), 0);
STANDARD20_MODULE_STUFF(TheModule);
with TheModule do
begin
name := 'mod_winimpersonate.dll';
register_hooks := @RegisterHooks;
end;
end.
这绝不是最终解决方案,但它是一个开始。逻辑如下:
恢复到 Apache 帐户。这是必须,以防我们使用以前模拟其他人的回收 Apache 线程。
从名为“WinImpersonate”的 cookie 中检索用户凭据,格式为
username:password
。这需要更多工作(可能加密凭据,或将它们存储在安全的(?)服务器上或更安全的地方)在
windows
单元的帮助下模拟用户。返回
DECLINED
,这样 Apache 就知道我们没有处理请求,它应该继续向模块请求正确的处理程序。
要达到良好的安全级别,需要解决许多问题。其中,凭证保护和浏览器缓存。但正如我所说,这是一个开始,也是一个概念证明。
您会注意到上面的 list 中缺少两个实用函数:
URLDecode
解码 url 编码的字符串:
// Convert URLEncoded string to utf8 string
function URLDecode(const s: String): String;
var
sAnsi: String;
sUtf8: String;
sWide: WideString;
i, len: Cardinal;
ESC: string[2];
CharCode: integer;
c: char;
begin
sAnsi := PChar(s);
SetLength(sUtf8, Length(sAnsi));
i := 1;
len := 1;
while (i <= Cardinal(Length(sAnsi))) do
begin
if (sAnsi[i] <> '%') then
begin
if (sAnsi[i] = '+') then c := ' ' else c := sAnsi[i];
sUtf8[len] := c;
Inc(len);
end
else
begin
Inc(i);
ESC := Copy(sAnsi, i, 2);
Inc(i, 1);
try
CharCode := StrToInt('$' + ESC);
c := Char(CharCode);
sUtf8[len] := c;
Inc(len);
except
end;
end;
Inc(i);
end;
Dec(len);
SetLength(sUtf8, len);
sWide := UTF8Decode(sUtf8);
len := Length(sWide);
Result := sWide;
end;
ChangeLoggedInUser
尝试使用提供的凭据登录用户,并在成功后尝试冒充他:
Function ChangeLoggedInUser(username, password, domain: string):Boolean;
var
creds: Cardinal;
begin
Result:=False;
try
if LogonUser(PChar(username)
,PChar(domain)
,PChar(password)
,LOGON32_LOGON_NETWORK_CLEARTEXT
,LOGON32_PROVIDER_DEFAULT
,creds
) then
begin
ImpersonateLoggedOnUser(creds);
Result:=True;
end;
finally
//wipe the memory for security
FillChar(username,SizeOf(username),#0);
FillChar(password,SizeOf(username),#0);
FillChar(domain,SizeOf(username),#0);
end; //try-finally
end;
希望有人觉得这很有用。 非常欢迎发表评论。
关于Lazarus 中的 Windows 模拟 Apache 模块,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19212448/