检查Active Directory中用户认证状态。

7

我想知道用户是否输入了正确的域、用户和密码来登录他的Active Directory用户。

我尝试编写一个非常简单的程序,它无法连接,但通过阅读错误消息,我可以知道用户名/密码是否正确。

这是基于技巧的(逻辑在读取异常消息),但是我在两个服务器上测试了这个原型,并注意到异常消息会因服务器而异,因此这不可靠。

uses adshlp, ActiveDs_TLB;
// 3 TEdit and a TButton

procedure TForm4.Button1Click(Sender: TObject);
Var
 aUser : IAdsUser;
 pDomain, pUser, pPassword : string;
 myResult : HRESULT;
 Counter: integer;
begin
  pDomain := edtDomain.Text;
  pUser:= edtUser.Text;
  pPassword := edtPwd.Text;
  Counter := GetTickCount;


 Try
    myResult := ADsOpenObject(Format('LDAP://%s',[pDomain]),Format('%s\%s',[pDomain,pUser]),pPassword,
    ADS_READONLY_SERVER,
    IAdsUser,aUser);
 except
    On E : EOleException do
    begin
    if (GetTickCount - Counter > 3000) then  ShowMessage ('Problem with connection') else
    if Pos('password',E.Message) > 0  then ShowMessage ('wrong username or password') else
     if Pos('server',E.Message) > 0 then ShowMessage ('Connected') else
     ShowMessage('Unhandled case');
    memLog.Lines.Add(E.Message);
    end;

 end
end;

如果消息包含“server”,我将设置“Connected”的原因是,在我的本地机器上(实际上是在我的公司ldap服务器上),如果一切正常(域,用户和密码),服务器会回复“该服务器需要更安全的身份验证”,因此“server”单词就在其中,而在其他情况下,它会说“用户名或密码错误”。由于这必须在意大利和英语服务器上运行,所以我将“server”和“password”设置为可靠的单词。无论如何,我在另一个提供不同错误的服务器上进行了测试。
我从此问题的答复开始做到了以上内容。
如何使用类似的技术更可靠地检查用户是否设置了正确的密码?
更新(已找到解决方案)
感谢回复,我设法编写了这个函数以满足我的需求。到目前为止,它似乎相当可靠,我在这里分享,希望它能帮助其他人:
// This function returns True if the provided parameters are correct
// login credentials for a user in the specified Domain
// From empirical tests it seems reliable
function UserCanLogin(aDomain, aUser, aPassword: string): Boolean;
var
  hToken: THandle;
begin
  Result := False;
  if (LogonUser(pChar(aUser), pChar(aDomain), pChar(aPassword), LOGON32_LOGON_INTERACTIVE,
   LOGON32_PROVIDER_DEFAULT, hToken)) then
  begin
    CloseHandle(hToken);
    Result := True;
  end;
end;
3个回答

4
您需要检查ADsOpenObject返回的错误代码。不要基于返回的异常消息进行错误检查。
如果函数成功,则会返回S_OK,否则您需要参考ADSI错误代码,特别是ADSI的LDAP错误代码

当LDAP服务器生成错误并将错误传递给客户端时,LDAP客户端将其转换为字符串。

这种方法类似于ADSI的Win32错误代码。在此示例中,客户端错误代码是WIN32错误0x80072020。

确定ADSI的LDAP错误代码

  1. 从WIN32错误代码中删除8007。在本例中,剩余的十六进制值为2020。
  2. 将剩余的十六进制值转换为十进制值。在本例中,剩余的十六进制值2020转换为十进制值8224。
  3. 在WinError.h文件中搜索十进制值的定义。在本例中,8224L对应于错误ERROR_DS_OPERATIONS_ERROR
  4. 将前缀ERROR_DS替换为LDAP_。在本例中,新定义为LDAP_OPERATIONS_ERROR
  5. 在Winldap.h文件中搜索LDAP错误定义的值。在本例中,LDAP_OPERATIONS_ERROR的值在Winldap.h文件中为0x01。
例如,对于0x8007052e结果(0x052e = 1326),您将得到ERROR_LOGON_FAILURE
从您的评论中:

由于该函数总是引发异常,我无法读取代码

您遇到了一个 EOleException,因为您的 ADsOpenObject 函数是使用safecall 调用约定定义的。而其他实现可能正在使用stdcall。当使用safecall 时,Delphi 将引发一个 EOleException,并且HResult 将在EOleException.ErrorCode 中反映;否则(stdcall)不会引发异常,并且HResult 将由ADsOpenObject 函数返回。

由于该函数总是引发异常,我无法阅读代码,但无论如何,我已经按照问题描述解决了我的问题,感谢被接受的答案。非常感谢您的帮助! - UnDiUdin
我将函数从safecall改为stdcall,现在它可以正常工作了:function ADsGetObject(lpszPathName:WideString; const riid:TGUID; out ppObject):HRESULT; stdcall; - Weles

2
我想知道用户是否输入了正确的域、用户名和密码来登录他的Active Directory用户。
您可以使用LogonUser函数来验证用户登录,例如:
  if (LogonUser(pChar(_Username), pChar(_ADServer), pChar(_Password), LOGON32_LOGON_INTERACTIVE,
   LOGON32_PROVIDER_DEFAULT, hToken)) then
  begin
    CloseHandle(hToken);
    //...
    //DoSomething
  end
  else raise Exception.Create(SysErrorMessage(GetLastError));

注意:您必须在域计算机内使用 LogonUser 才能使用域登录,否则该函数将始终返回“用户名或密码不正确”。
另一种选择是使用 TLDAPSend,例如:
function _IsAuthenticated(const lpszUsername, lpszDomain, lpszPassword: string): Boolean;
var
  LDAP : TLDAPSend;
begin
  Result := False;
  if ( (Length(lpszUsername) = 0) or (Length(lpszPassword) = 0) )then Exit;
  LDAP := TLDAPSend.Create;
  try
    LDAP.TargetHost := lpszDomain;
    LDAP.TargetPort := '389';
    ....
    LDAP.UserName := lpszUsername + #64 + lpszDomain;;
    LDAP.Password := lpszPassword;
    Result := LDAP.Login;
  finally
    LDAP.Free;
  end;
end;

如何使用类似的技术以更可靠的方式检查用户是否设置了正确的密码?
尝试使用FormatMessage函数。
 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER or FORMAT_MESSAGE_FROM_SYSTEM,
                nil,
                myResult,
                LANG_ENGLISH or SUBLANG_ENGLISH_US,
                lpMsg,
                0,
                nil);
 MessageBox(0, lpMsg, 'Msg', 0);

谢谢,使用LogonUser函数我成功地实现了我的目标(我现在更新我的问题以分享我编写的简单函数)。与ADsOpenObject不同,LogonUser不会引发异常。我在3个不同的服务器上(使用3个不同的域控制器)尝试了我的函数,看起来它是有效的。 - UnDiUdin
此外,使用 LogonUser 解决方案更简单,因为不需要 adshlp 和 ActiveDs_TLB。 - UnDiUdin
2
你可以使用SysErrorMessage(myResult)代替FormatMessage(),反正你也没有释放已分配的缓冲区。不过说实话,我并不理解答案中的那部分内容。可能是我误读了问题... - Sertac Akyuz
2
@SertacAkyuz,你没有读错问题。FormatMessage在这里是无关紧要的。 - kobik

1
当我使用错误的密码时,会收到(HRESULT) 0x8007052e (2147943726) "未知用户名或密码错误"的提示。如果没有EOleException,请尝试:
      hr := ADsOpenObject('LDAP://'+ ADomain + '/OU=Domain Controllers,' + APath,
                       AUser, APwd,
                       ADS_SECURE_AUTHENTICATION or ADS_READONLY_SERVER,
                       IID_IADs, pObject);
   if (hr=HRESULT(2147943726)) then ShowMessage ('wrong username or password')

不幸的是,我总是收到EOleException异常,因此我的结果始终为0,因为该异常不允许ADsOpenObject将值返回给MyResult。我该怎么办?我开始使用E.Message是因为我可以读取它,请给予建议。 - UnDiUdin
@user193655,我已经更新了答案,并测试了在DCs 2003到2016上的可行性。 - FredS

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接