【问题标题】:Check if user authentication in Active DIrectory检查 Active DIrectory 中的用户身份验证
【发布时间】:2017-10-09 07:54:46
【问题描述】:

我想知道用户是否为其 Active Directory 用户输入了正确的域、用户和密码组合。

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

这是基于技巧的(逻辑是读取异常消息),无论如何我在 2 台服务器上测试了这个原型,我注意到异常消息在服务器之间发生变化,所以这是不可靠的。

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;

如果消息包含“服务器”,我设置“已连接”的原因是我的 本地机器(实际上在我公司的 ldap 服务器上)以防万一(域、用户和密码)服务器回复“服务器需要更安全的身份验证”,因此“服务器”一词在那里,而在其他情况下它说“错误的用户或密码”。因为这必须在意大利语和英语服务器上工作,所以我将“服务器”和“密码”设置为可靠的词。无论如何,我在另一台给出不同错误的服务器上进行了测试。

我从回复this question开始做上面的事情。

如何使用类似技术以更可靠的方式检查用户是否设置了正确的密码?

更新(找到解决方案)

感谢回复,我设法编写了这个函数来满足我的需要。到现在看来还是挺靠谱的,写在这里分享一下,希望对大家有帮助:

// 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;

【问题讨论】:

    标签: delphi ldap delphi-10-seattle


    【解决方案1】:

    您需要检查ADsOpenObject 返回的错误代码。不要将错误检查基于返回的异常messages

    如果函数成功返回S_OK,否则需要参考ADSI Error Codes,具体是LDAP error codes for ADSI

    当 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 返回功能。

    【讨论】:

    • 由于函数总是引发异常,我无法读取代码,无论如何,由于接受了答案,我设法解决了问题中描述的问题。非常感谢您的帮助!
    • 我将函数而不是 safecall 更改为 stdcall,它工作正常: function ADsGetObject(lpszPathName:WideString; const riid:TGUID; out ppObject):HRESULT;标准调用;
    【解决方案2】:

    当我使用错误密码时,我得到 (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,所以 myResult 始终为 0,因为该异常不允许 ADsOpenObject 向 MyResult 返回值。我能怎么做?我开始使用 E.Message,因为我可以阅读它。请建议
    • @user193655,我已经用有效的方法更新了答案,并且已经在 DC 2003 到 2016 上进行了测试。
    【解决方案3】:

    我想知道用户是否输入了正确的组合 他的 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 才能使用域登录,否则该函数将始终返回The user name or password is incorrect

    另一种方法是使用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 function

     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 我能够实现我的结果(我现在更新我的问题以分享我编写的简单函数)。 LogonUser 不会像 ADsOpenObject 那样引发异常。我在 3 个不同的服务器(带有 3 个不同的域控制器)上尝试了我的功能,它似乎工作正常。
    • 此外,使用 LogonUser 的解决方案更简单,因为不需要 adshlp、ActiveDs_TLB
    • 您可以使用SysErrorMessage(myResult) 而不是FormatMessage(),无论如何您都不会释放分配的缓冲区。老实说,我不明白答案的那一部分。可能我看错了问题...
    • @SertacAkyuz,您没有误读这个问题。 FormatMessage 在这里无关紧要。
    猜你喜欢
    • 1970-01-01
    • 2011-01-22
    • 1970-01-01
    • 1970-01-01
    • 2015-12-13
    • 1970-01-01
    • 2014-09-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多