首页  编辑  

验证用户名和密码是否合法

Tags: /超级猛料/OS.操作系统/权限控制/   Date Created:

How To Validate User Credentials on Microsoft Operating Systems

Article ID : 180548

Last Review : March 21, 2005

Revision : 3.3

This article was previously published under Q180548

On this page

SUMMARY

MORE INFORMATION

SUMMARY

Occasionally, you may want an application to verify a user's user name and password (hereafter referred to as credentials). You can do this a couple of different ways, depending on the operating system on which the application runs.

This article describes all of the common ways to verify a user's credentials and the special requirements for each method.

Note Collecting user credentials from a User-mode application can be annoying to the users and can provide a possible security hole in the enterprise computing environment. The Unified Logon requirement (a requirement that the user should only be required to type their credentials one time at the CTRL+ALT+DEL screen), was added to the Microsoft BackOffice logo requirements for these very reasons. It is important to make sure that you really need to gather credentials and that some other method of client/server validation is not more appropriate. Consult the security documentation in the Platform SDK for more information on impersonation and programming secured servers.

MORE INFORMATION

The LogonUser API has been available and documented since Windows NT 3.51, and is commonly used to verify user credentials. This API is available on Windows NT, Windows 2000, and Windows XP. Unfortunately, there are some restrictions on using LogonUser that are not always convenient to satisfy.

The first and biggest of these restrictions is that on Windows NT and Windows 2000, the process that is calling LogonUser must have the SE_TCB_NAME privilege (in User Manager, this is the "Act as part of the Operating System" right). The SE_TCB_NAME privilege is very powerful and should not be granted to any arbitrary user just so that they can run an application that needs to validate credentials. The recommended method is to call LogonUser from a service that is running in the local system account, because the local system account already has the SE_TCB_NAME privilege.

Note LogonUser Win32 API does not require TCB privilege in Microsoft Windows Server 2003, however, for downlevel compatibility, this is still the best approach.

On Windows XP, it is no longer required that a process have the SE_TCB_NAME privilege in order to call LogonUser. Therefore, the simplest method to validate a user's credentials on Windows XP, is to call the LogonUser API.

One other problem with LogonUser is that the API is not implemented on Windows 95, Windows 98, or Windows Millennium Edition.

As another option, you can use the Security Support Provider Interface (SSPI) to do a network style logon with provided user credentials. This method of validation has the advantage of not requiring any special privilege, as well as working on all versions of Windows. The end result of using the SSPI services to validate the credentials is a logon that is analogous to calling the LogonUser API with the LOGON32_LOGON_NETWORK logon type. The biggest downside to this type of logon is that you cannot access remote network resources after impersonating a network type logon. If your application is calling LogonUser with the LOGON32_LOGON_INTERACTIVE logon type to workaround Windows NT's inability to perform delegation, then the SSPI logon/validation will probably not be a viable alternative.

The sample code provided below shows how to call the SSPI services to perform credential validation.

To use this method on Windows 95, Windows 98, and Windows Millennium Edition, you also have to enable the NTLM security services by opening Control Panel, Network, Access Control, and then selecting User-level access control.

On Windows XP, the ForceGuest registry value is set to 1 by default in the following registry key:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa

On a Windows XP computer that is a member of a workgroup: * If ForceGuest is enabled (set to 1), SSPI will always try to log on using the Guest account.  

* If the Guest account is enabled, an SSPI logon will succeed as Guest for any user credentials.  

* If the Guest account is disabled, an SSPI logon will fail even for valid credentials.  

* If ForceGuest is disabled (set to 0), SSPI will log on as the specified user.  

Sample Code

///////////////////////////////////////////////////////////////////////////////

//

//  SSPI Authentication Sample

//

//  This program demonstrates how to use SSPI to authenticate user credentials.

//

//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF

//  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED

//  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A

//  PARTICULAR PURPOSE.

//

//  Copyright (C) 2001.  Microsoft Corporation.  All rights reserved.

///////////////////////////////////////////////////////////////////////////////

#define SECURITY_WIN32

#include <windows.h>

#include <tchar.h>

#include <stdio.h>

#include <sspi.h>

// Older versions of WinError.h does not have SEC_I_COMPLETE_NEEDED #define.

// So, in such SDK environment setup, we will include issperr.h which has the

// definition for SEC_I_COMPLETE_NEEDED. Include issperr.h only if

// SEC_I_COMPLETE_NEEDED is not defined.

#ifndef SEC_I_COMPLETE_NEEDED

#include <issperr.h>

#endif

typedef struct _AUTH_SEQ {

  BOOL fInitialized;

  BOOL fHaveCredHandle;

  BOOL fHaveCtxtHandle;

  CredHandle hcred;

  struct _SecHandle hctxt;

} AUTH_SEQ, *PAUTH_SEQ;

// Function pointers

ACCEPT_SECURITY_CONTEXT_FN       _AcceptSecurityContext     = NULL;

ACQUIRE_CREDENTIALS_HANDLE_FN    _AcquireCredentialsHandle  = NULL;

COMPLETE_AUTH_TOKEN_FN           _CompleteAuthToken         = NULL;

DELETE_SECURITY_CONTEXT_FN       _DeleteSecurityContext     = NULL;

FREE_CONTEXT_BUFFER_FN           _FreeContextBuffer         = NULL;

FREE_CREDENTIALS_HANDLE_FN       _FreeCredentialsHandle     = NULL;

INITIALIZE_SECURITY_CONTEXT_FN   _InitializeSecurityContext = NULL;

QUERY_SECURITY_PACKAGE_INFO_FN   _QuerySecurityPackageInfo  = NULL;

///////////////////////////////////////////////////////////////////////////////

void UnloadSecurityDll(HMODULE hModule) {

  if (hModule)

     FreeLibrary(hModule);

  _AcceptSecurityContext      = NULL;

  _AcquireCredentialsHandle   = NULL;

  _CompleteAuthToken          = NULL;

  _DeleteSecurityContext      = NULL;

  _FreeContextBuffer          = NULL;

  _FreeCredentialsHandle      = NULL;

  _InitializeSecurityContext  = NULL;

  _QuerySecurityPackageInfo   = NULL;

}

///////////////////////////////////////////////////////////////////////////////

HMODULE LoadSecurityDll() {

  HMODULE hModule;

  BOOL    fAllFunctionsLoaded = FALSE;

  TCHAR   lpszDLL[MAX_PATH];

  OSVERSIONINFO VerInfo;

  //

  //  Find out which security DLL to use, depending on

  //  whether we are on NT or Win95 or 2000 or XP or Windows Server 2003

  //  We have to use security.dll on Windows NT 4.0.

  //  All other operating systems, we have to use Secur32.dll

  //

  VerInfo.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);

  if (!GetVersionEx (&VerInfo))   // If this fails, something has gone wrong

  {

     return FALSE;

  }

  if (VerInfo.dwPlatformId == VER_PLATFORM_WIN32_NT &&

     VerInfo.dwMajorVersion == 4 &&

     VerInfo.dwMinorVersion == 0)

  {

     lstrcpy (lpszDLL, _T("security.dll"));

  }

  else

  {

     lstrcpy (lpszDLL, _T("secur32.dll"));

  }

  hModule = LoadLibrary(lpszDLL);

  if (!hModule)

     return NULL;

  __try {

     _AcceptSecurityContext = (ACCEPT_SECURITY_CONTEXT_FN)

           GetProcAddress(hModule, "AcceptSecurityContext");

     if (!_AcceptSecurityContext)

        __leave;

#ifdef UNICODE

     _AcquireCredentialsHandle = (ACQUIRE_CREDENTIALS_HANDLE_FN)

           GetProcAddress(hModule, "AcquireCredentialsHandleW");

#else

     _AcquireCredentialsHandle = (ACQUIRE_CREDENTIALS_HANDLE_FN)

           GetProcAddress(hModule, "AcquireCredentialsHandleA");

#endif

     if (!_AcquireCredentialsHandle)

        __leave;

     // CompleteAuthToken is not present on Windows 9x Secur32.dll

     // Do not check for the availablity of the function if it is NULL;

     _CompleteAuthToken = (COMPLETE_AUTH_TOKEN_FN)

           GetProcAddress(hModule, "CompleteAuthToken");

     _DeleteSecurityContext = (DELETE_SECURITY_CONTEXT_FN)

           GetProcAddress(hModule, "DeleteSecurityContext");

     if (!_DeleteSecurityContext)

        __leave;

     _FreeContextBuffer = (FREE_CONTEXT_BUFFER_FN)

           GetProcAddress(hModule, "FreeContextBuffer");

     if (!_FreeContextBuffer)

        __leave;

     _FreeCredentialsHandle = (FREE_CREDENTIALS_HANDLE_FN)

           GetProcAddress(hModule, "FreeCredentialsHandle");

     if (!_FreeCredentialsHandle)

        __leave;

#ifdef UNICODE

     _InitializeSecurityContext = (INITIALIZE_SECURITY_CONTEXT_FN)

           GetProcAddress(hModule, "InitializeSecurityContextW");

#else

     _InitializeSecurityContext = (INITIALIZE_SECURITY_CONTEXT_FN)

           GetProcAddress(hModule, "InitializeSecurityContextA");

#endif

     if (!_InitializeSecurityContext)

        __leave;

#ifdef UNICODE

     _QuerySecurityPackageInfo = (QUERY_SECURITY_PACKAGE_INFO_FN)

           GetProcAddress(hModule, "QuerySecurityPackageInfoW");

#else

     _QuerySecurityPackageInfo = (QUERY_SECURITY_PACKAGE_INFO_FN)

           GetProcAddress(hModule, "QuerySecurityPackageInfoA");

#endif

     if (!_QuerySecurityPackageInfo)

        __leave;

     fAllFunctionsLoaded = TRUE;

  } __finally {

     if (!fAllFunctionsLoaded) {

        UnloadSecurityDll(hModule);

        hModule = NULL;

     }

  }

  return hModule;

}

///////////////////////////////////////////////////////////////////////////////

BOOL GenClientContext(PAUTH_SEQ pAS, PSEC_WINNT_AUTH_IDENTITY pAuthIdentity,

     PVOID pIn, DWORD cbIn, PVOID pOut, PDWORD pcbOut, PBOOL pfDone) {

/*++

Routine Description:

  Optionally takes an input buffer coming from the server and returns

  a buffer of information to send back to the server.  Also returns

  an indication of whether or not the context is complete.

Return Value:

  Returns TRUE if successful; otherwise FALSE.

--*/

  SECURITY_STATUS ss;

  TimeStamp       tsExpiry;

  SecBufferDesc   sbdOut;

  SecBuffer       sbOut;

  SecBufferDesc   sbdIn;

  SecBuffer       sbIn;

  ULONG           fContextAttr;

  if (!pAS->fInitialized) {

     ss = _AcquireCredentialsHandle(NULL, _T("NTLM"),

           SECPKG_CRED_OUTBOUND, NULL, pAuthIdentity, NULL, NULL,

           &pAS->hcred, &tsExpiry);

     if (ss < 0) {

        fprintf(stderr, "AcquireCredentialsHandle failed with %08X\n", ss);

        return FALSE;

     }

     pAS->fHaveCredHandle = TRUE;

  }

  // Prepare output buffer

  sbdOut.ulVersion = 0;

  sbdOut.cBuffers = 1;

  sbdOut.pBuffers = &sbOut;

  sbOut.cbBuffer = *pcbOut;

  sbOut.BufferType = SECBUFFER_TOKEN;

  sbOut.pvBuffer = pOut;

  // Prepare input buffer

  if (pAS->fInitialized)  {

     sbdIn.ulVersion = 0;

     sbdIn.cBuffers = 1;

     sbdIn.pBuffers = &sbIn;

     sbIn.cbBuffer = cbIn;

     sbIn.BufferType = SECBUFFER_TOKEN;

     sbIn.pvBuffer = pIn;

  }

  ss = _InitializeSecurityContext(&pAS->hcred,

        pAS->fInitialized ? &pAS->hctxt : NULL, NULL, 0, 0,

        SECURITY_NATIVE_DREP, pAS->fInitialized ? &sbdIn : NULL,

        0, &pAS->hctxt, &sbdOut, &fContextAttr, &tsExpiry);

  if (ss < 0)  {

     // <winerror.h>

     fprintf(stderr, "InitializeSecurityContext failed with %08X\n", ss);

     return FALSE;

  }

  pAS->fHaveCtxtHandle = TRUE;

  // If necessary, complete token

  if (ss == SEC_I_COMPLETE_NEEDED || ss == SEC_I_COMPLETE_AND_CONTINUE) {

     if (_CompleteAuthToken) {

        ss = _CompleteAuthToken(&pAS->hctxt, &sbdOut);

        if (ss < 0)  {

           fprintf(stderr, "CompleteAuthToken failed with %08X\n", ss);

           return FALSE;

        }

     }

     else {

        fprintf (stderr, "CompleteAuthToken not supported.\n");

        return FALSE;

     }

  }

  *pcbOut = sbOut.cbBuffer;

  if (!pAS->fInitialized)

     pAS->fInitialized = TRUE;

  *pfDone = !(ss == SEC_I_CONTINUE_NEEDED

        || ss == SEC_I_COMPLETE_AND_CONTINUE );

  return TRUE;

}

///////////////////////////////////////////////////////////////////////////////

BOOL GenServerContext(PAUTH_SEQ pAS, PVOID pIn, DWORD cbIn, PVOID pOut,

     PDWORD pcbOut, PBOOL pfDone) {

/*++

Routine Description:

   Takes an input buffer coming from the client and returns a buffer

   to be sent to the client.  Also returns an indication of whether or

   not the context is complete.

Return Value:

   Returns TRUE if successful; otherwise FALSE.

--*/

  SECURITY_STATUS ss;

  TimeStamp       tsExpiry;

  SecBufferDesc   sbdOut;

  SecBuffer       sbOut;

  SecBufferDesc   sbdIn;

  SecBuffer       sbIn;

  ULONG           fContextAttr;

  if (!pAS->fInitialized)  {

     ss = _AcquireCredentialsHandle(NULL, _T("NTLM"),

           SECPKG_CRED_INBOUND, NULL, NULL, NULL, NULL, &pAS->hcred,

           &tsExpiry);

     if (ss < 0) {

        fprintf(stderr, "AcquireCredentialsHandle failed with %08X\n", ss);

        return FALSE;

     }

     pAS->fHaveCredHandle = TRUE;

  }

  // Prepare output buffer

  sbdOut.ulVersion = 0;

  sbdOut.cBuffers = 1;

  sbdOut.pBuffers = &sbOut;

  sbOut.cbBuffer = *pcbOut;

  sbOut.BufferType = SECBUFFER_TOKEN;

  sbOut.pvBuffer = pOut;

  // Prepare input buffer

  sbdIn.ulVersion = 0;

  sbdIn.cBuffers = 1;

  sbdIn.pBuffers = &sbIn;

  sbIn.cbBuffer = cbIn;

  sbIn.BufferType = SECBUFFER_TOKEN;

  sbIn.pvBuffer = pIn;

  ss = _AcceptSecurityContext(&pAS->hcred,

        pAS->fInitialized ? &pAS->hctxt : NULL, &sbdIn, 0,

        SECURITY_NATIVE_DREP, &pAS->hctxt, &sbdOut, &fContextAttr,

        &tsExpiry);

  if (ss < 0)  {

     fprintf(stderr, "AcceptSecurityContext failed with %08X\n", ss);

     return FALSE;

  }

  pAS->fHaveCtxtHandle = TRUE;

  // If necessary, complete token

  if (ss == SEC_I_COMPLETE_NEEDED || ss == SEC_I_COMPLETE_AND_CONTINUE) {

     if (_CompleteAuthToken) {

        ss = _CompleteAuthToken(&pAS->hctxt, &sbdOut);

        if (ss < 0)  {

           fprintf(stderr, "CompleteAuthToken failed with %08X\n", ss);

           return FALSE;

        }

     }

     else {

        fprintf (stderr, "CompleteAuthToken not supported.\n");

        return FALSE;

     }

  }

  *pcbOut = sbOut.cbBuffer;

  if (!pAS->fInitialized)

     pAS->fInitialized = TRUE;

  *pfDone = !(ss = SEC_I_CONTINUE_NEEDED

        || ss == SEC_I_COMPLETE_AND_CONTINUE);

  return TRUE;

}

///////////////////////////////////////////////////////////////////////////////

BOOL WINAPI SSPLogonUser(LPTSTR szDomain, LPTSTR szUser, LPTSTR szPassword) {

  AUTH_SEQ    asServer   = {0};

  AUTH_SEQ    asClient   = {0};

  BOOL        fDone      = FALSE;

  BOOL        fResult    = FALSE;

  DWORD       cbOut      = 0;

  DWORD       cbIn       = 0;

  DWORD       cbMaxToken = 0;

  PVOID       pClientBuf = NULL;

  PVOID       pServerBuf = NULL;

  PSecPkgInfo pSPI       = NULL;

  HMODULE     hModule    = NULL;

  SEC_WINNT_AUTH_IDENTITY ai;

  __try {

     hModule = LoadSecurityDll();

     if (!hModule)

        __leave;

     // Get max token size

     _QuerySecurityPackageInfo(_T("NTLM"), &pSPI);

     cbMaxToken = pSPI->cbMaxToken;

     _FreeContextBuffer(pSPI);

     // Allocate buffers for client and server messages

     pClientBuf = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cbMaxToken);

     pServerBuf = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cbMaxToken);

     // Initialize auth identity structure

     ZeroMemory(&ai, sizeof(ai));

#if defined(UNICODE) || defined(_UNICODE)

     ai.Domain = szDomain;

     ai.DomainLength = lstrlen(szDomain);

     ai.User = szUser;

     ai.UserLength = lstrlen(szUser);

     ai.Password = szPassword;

     ai.PasswordLength = lstrlen(szPassword);

     ai.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;

#else

     ai.Domain = (unsigned char *)szDomain;

     ai.DomainLength = lstrlen(szDomain);

     ai.User = (unsigned char *)szUser;

     ai.UserLength = lstrlen(szUser);

     ai.Password = (unsigned char *)szPassword;

     ai.PasswordLength = lstrlen(szPassword);

     ai.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI;

#endif

     // Prepare client message (negotiate) .

     cbOut = cbMaxToken;

     if (!GenClientContext(&asClient, &ai, NULL, 0, pClientBuf, &cbOut, &fDone))

        __leave;

     // Prepare server message (challenge) .

     cbIn = cbOut;

     cbOut = cbMaxToken;

     if (!GenServerContext(&asServer, pClientBuf, cbIn, pServerBuf, &cbOut,

           &fDone))

        __leave;

        // Most likely failure: AcceptServerContext fails with SEC_E_LOGON_DENIED

        // in the case of bad szUser or szPassword.

        // Unexpected Result: Logon will succeed if you pass in a bad szUser and

        // the guest account is enabled in the specified domain.

     // Prepare client message (authenticate) .

     cbIn = cbOut;

     cbOut = cbMaxToken;

     if (!GenClientContext(&asClient, &ai, pServerBuf, cbIn, pClientBuf, &cbOut,

           &fDone))

        __leave;

     // Prepare server message (authentication) .

     cbIn = cbOut;

     cbOut = cbMaxToken;

     if (!GenServerContext(&asServer, pClientBuf, cbIn, pServerBuf, &cbOut,

           &fDone))

        __leave;

     fResult = TRUE;

  } __finally {

     // Clean up resources

     if (asClient.fHaveCtxtHandle)

        _DeleteSecurityContext(&asClient.hctxt);

     if (asClient.fHaveCredHandle)

        _FreeCredentialsHandle(&asClient.hcred);

     if (asServer.fHaveCtxtHandle)

        _DeleteSecurityContext(&asServer.hctxt);

     if (asServer.fHaveCredHandle)

        _FreeCredentialsHandle(&asServer.hcred);

     if (hModule)

        UnloadSecurityDll(hModule);

     HeapFree(GetProcessHeap(), 0, pClientBuf);

     HeapFree(GetProcessHeap(), 0, pServerBuf);

  }

  return fResult;

}

void _tmain(int argc, TCHAR **argv)

{

  if (argc != 4) return;

  // argv[1] - Domain Name

  // argv[2] - User Name

  // argv[3] - Password

  if (SSPLogonUser(argv[1], argv[2], argv[3]))

  {

     printf("User Credentials are valid\n");

  }

  else

     printf("User Credentials are NOT valid\n");

}

TestNTLMLogin.zip (13.1KB)