Win32: How to validate credentials against Active Directory?

前端 未结 5 683
一个人的身影
一个人的身影 2020-11-30 01:08

It has been asked, and answered for .NET, but now it\'s time to get an answer for native Win32 code:

How do i validate a Windows username and password?

5条回答
  •  栀梦
    栀梦 (楼主)
    2020-11-30 02:02

    I might as well post the native code to validate a set of Windows credentials. It took a while to implement.

    function TSSPLogon.LogonUser(username, password, domain: string; packageName: string='Negotiate'): HRESULT;
    var
        ss: SECURITY_STATUS;
        packageInfo: PSecPkgInfoA;
        cbMaxToken: DWORD;
        clientBuf: PByte;
        serverBuf: PByte;
        authIdentity: SEC_WINNT_AUTH_IDENTITY;
        cbOut, cbIn: DWORD;
        asClient: AUTH_SEQ;
        asServer: AUTH_SEQ;
        Done: boolean;
    begin
    {
        If domain is blank will use the current domain.
        To force validation against the local database use domain "."
    
        sspiProviderName is the same of the Security Support Provider Package to use. Some possible choices are:
                - Negotiate (Preferred)
                            Introduced in Windows 2000 (secur32.dll)
                            Selects Kerberos and if not available, NTLM protocol.
                            Negotiate SSP provides single sign-on capability called as Integrated Windows Authentication.
                            On Windows 7 and later, NEGOExts is introduced which negotiates the use of installed
                            custom SSPs which are supported on the client and server for authentication.
                - Kerberos
                            Introduced in Windows 2000 and updated in Windows Vista to support AES) (secur32.dll)
                            Preferred for mutual client-server domain authentication in Windows 2000 and later.
                - NTLM
                            Introduced in Windows NT 3.51 (Msv1_0.dll)
                            Provides NTLM challenge/response authentication for client-server domains prior to
                            Windows 2000 and for non-domain authentication (SMB/CIFS)
                - Digest
                            Introduced in Windows XP (wdigest.dll)
                            Provides challenge/response based HTTP and SASL authentication between Windows and non-Windows systems where Kerberos is not available
                - CredSSP
                            Introduced in Windows Vista and available on Windows XP SP3 (credssp.dll)
                            Provides SSO and Network Level Authentication for Remote Desktop Services
                - Schannel
                            Introduced in Windows 2000 and updated in Windows Vista to support stronger AES encryption and ECC (schannel.dll)
                            Microsoft's implementation of TLS/SSL
                            Public key cryptography SSP that provides encryption and secure communication for
                            authenticating clients and servers over the internet. Updated in Windows 7 to support TLS 1.2.
    
        If returns false, you can call GetLastError to get the reason for the failure
    }
    
    
        // Get the maximum authentication token size for this package
        ss := sspi.QuerySecurityPackageInfoA(PAnsiChar(packageName), packageInfo);
        if ss <> SEC_E_OK then
        begin
            RaiseWin32Error('QuerySecurityPackageInfo "'+PackageName+'" failed', ss);
            Result := ss;
            Exit;
        end;
    
        try
            cbMaxToken := packageInfo.cbMaxToken;
        finally
            FreeContextBuffer(packageInfo);
        end;
    
        // Initialize authorization identity structure
        ZeroMemory(@authIdentity, SizeOf(authIdentity));
        if Length(domain) > 0 then
        begin
            authIdentity.Domain := PChar(Domain);
            authIdentity.DomainLength := Length(domain);
        end;
    
        if Length(userName) > 0 then
        begin
            authIdentity.User := PChar(UserName);
            authIdentity.UserLength := Length(UserName);
        end;
    
        if Length(Password) > 0 then
        begin
            authIdentity.Password := PChar(Password);
            authIdentity.PasswordLength := Length(Password);
        end;
    
        AuthIdentity.Flags := SEC_WINNT_AUTH_IDENTITY_ANSI; //SEC_WINNT_AUTH_IDENTITY_UNICODE
    
        ZeroMemory(@asClient, SizeOf(asClient));
        ZeroMemory(@asServer, SizeOf(asServer));
    
        //Allocate buffers for client and server messages
        GetMem(clientBuf, cbMaxToken);
        GetMem(serverBuf, cbMaxToken);
        try
            done := False;
            try
                // Prepare client message (negotiate)
                cbOut := cbMaxToken;
                ss := Self.GenClientContext(@asClient, authIdentity, packageName, nil, 0, clientBuf, cbOut, done);
                if ss < 0 then
                begin
                    RaiseWin32Error('Error generating client context for negotiate', ss);
                    Result := ss;
                    Exit;
                end;
    
                // Prepare server message (challenge).
                cbIn := cbOut;
                cbOut := cbMaxToken;
                ss := Self.GenServerContext(@asServer, packageName, clientBuf, cbIn, serverBuf, cbOut, done);
                if ss < 0 then
                begin
                    {
                        Most likely failure: AcceptServerContext fails with SEC_E_LOGON_DENIED in the case of bad username or password.
                        Unexpected Result:   Logon will succeed if you pass in a bad username and the guest account is enabled in the specified domain.
                    }
                    RaiseWin32Error('Error generating server message for challenge', ss);
                    Result := ss;
                    Exit;
                end;
    
                // Prepare client message (authenticate).
                cbIn := cbOut;
                cbOut := cbMaxToken;
                ss := Self.GenClientContext(@asClient, authIdentity, packageName, serverBuf, cbIn, clientBuf, cbOut, done);
                if ss < 0 then
                begin
                    RaiseWin32Error('Error generating client client for authenticate', ss);
                    Result := ss;
                    Exit;
                end;
    
                // Prepare server message (authentication).
                cbIn := cbOut;
                cbOut := cbMaxToken;
                ss := Self.GenServerContext(@asServer, packageName, clientBuf, cbIn, serverBuf, cbOut, done);
                if ss < 0 then
                begin
                    RaiseWin32Error('Error generating server message for authentication', ss);
                    Result := ss;
                    Exit;
                end;
            finally
                //Free resources in client message
                if asClient.fHaveCtxtHandle then
                    sspi.DeleteSecurityContext(@asClient.hctxt);
    
                if asClient.fHaveCredHandle then
                    sspi.FreeCredentialHandle(@asClient.hcred);
    
                //Free resources in server message
                if asServer.fHaveCtxtHandle then
                    sspi.DeleteSecurityContext(@asServer.hctxt);
    
                if asServer.fHaveCredHandle then
                    sspi.FreeCredentialHandle(@asServer.hcred);
            end;
        finally
            FreeMem(clientBuf);
            FreeMem(serverBuf);
        end;
    
        Result := S_OK;
    end;
    

    Note: Any code released into public domain. No attribution required.

提交回复
热议问题