Microsoft オペレーティング システムでユーザー資格情報の認証を行う方法

概要

場合によって、アプリケーションでユーザーのユーザー名とパスワードを確認する必要が生じることがあります (ここでは、ユーザー名とパスワードを資格情報と呼びます)。この検証を行うには、アプリケーションを実行するオペレーティング システムに応じて、いくつかの方法があります。


この資料では、ユーザーの資格情報を検証するための一般的な方法をすべて紹介し、それぞれの方法の要件について説明します。


: ユーザー モード アプリケーションでユーザーの資格情報を収集すると、ユーザーを煩わせる場合や、エンタープライズ コンピューティング環境においてセキュリティ ホールとなる可能性があります。まさにこの理由により、Microsoft BackOffice ロゴを取得するための要件に Unified Logon (統合ログオン) が追加されました。統合ログオンでは、Ctrl + Alt + Del キーを押して表示されるログオン画面以外では資格情報の入力は求められません。資格情報の収集が不可欠であることと、その他の方法によるクライアント/サーバー認証は適していないことを確認しておくことが重要です。偽装、および、セキュリティで保護されたサーバーのプログラミングの関連情報については、プラットフォーム SDK のセキュリティに関するドキュメントを参照してください。

詳細

Windows NT 3.51 以降では、LogonUser という API およびその説明が提供されており、ユーザー資格情報の確認によく使用されています。この API は、Windows NT、Windows 2000 および Windows XP で使用することができます。ただし、LogonUser の使用については、いくつかの制限事項があり、場合によってはこれを満たすことが容易でないことがあります。


最も重要な、1 つ目の制限事項は、Windows NT および Windows 2000 では、LogonUser の呼び出し元のプロセスが SE_TCB_NAME の特権を持つ必要があることです (この特権は、ユーザー マネージャでは、[オペレーティング システムの一部として機能] と表示されます)。SE_TCB_NAME の特権は非常に強力であるため、資格情報の認証が必要なアプリケーションを実行できるようにする目的で、任意のユーザーに付与することはお勧めしません。推奨される方法は、ローカル システム アカウントで実行されているサービスから LogonUser を呼び出すことです。この方法が推奨される理由は、ローカル システム アカウントには、SE_TCB_NAME の特権が事前に付与されているためです。


: Microsoft Windows Server 2003 では、LogonUser Win32 API の呼び出しに TCB の特権は必要ありません。ただし、下位互換性を維持するためにこの方法を使用することをお勧めします。


Windows XP では、LogonUser を呼び出すプロセスに SE_TCB_NAME の特権が付与されている必要はありません。このため、Windows XP では、ユーザーの資格情報を認証するための最も簡単な方法は LogonUser API を呼び出すことです。


LogonUser に関するもう 1 つの問題点は、Windows 95、Windows 98 および Windows Millennium Edition (Me) ではこの API が実装されていないことです。


もう 1 つの方法は、セキュリティ サポート プロバイダ インターフェイス (SSPI) を使用して、提供されたユーザーの資格情報でネットワーク型のログオンを実行することです。この認証方法の長所は、特殊な特権が不要であること、および、すべてのバージョンの Windows で使用できることです。資格情報の認証に SSPI を使用した場合、結果として、ログオン タイプとして LOGON32_LOGON_NETWORK を指定して LogonUser API を呼び出した場合と同様のログオンが実行されます。このログオン方法の最大の問題点は、ネットワーク型のログオンを偽装した後はリモート ネットワーク リソースにアクセスできないことです。Windows NT で委任を実行できない問題を回避するために、アプリケーションでログオン タイプ LOGON32_LOGON_INTERACTIVE を指定して LogonUser を呼び出している場合、SSPI のログオン (認証) は、多くの場合、代替策としては使用できません。


以下のサンプル コードは、SSPI サービスを呼び出して資格情報の認証を実行する方法を示したものです。


Windows 95、Windows 98 および Windows Millennium Edition (Me) でこの方法を使用するには、NTLM セキュリティ サービスを有効にする必要があります。このサービスを有効にするには、コントロール パネルを開き、[ネットワーク] をダブルクリックし、[アクセスの制御] タブまたは [アクセス権の管理] タブをクリックし、[ユーザー レベルでアクセスを制御する] または [ユーザー レベルのアクセス管理] をクリックします。


Windows XP では、レジストリ値 ForceGuest に 1 を設定します。このレジストリ値は、デフォルトでは以下のレジストリ キーの下にあります。
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa
ワークグループのメンバの Windows XP コンピュータでの動作は以下のとおりです。
  • ForceGuest に 1 (有効) を設定すると、SSPI では常に Guest アカウントを使用してログオンが実行されます。
  • Guest アカウントが有効な場合、すべてのユーザー資格情報について、SSPI で Guest としてログオンすることができます。
  • Guest アカウントが無効な場合、資格情報が有効でも SSPI ログオンは失敗します。
  • ForceGuest に 0 (無効) を設定すると、SSPI では指定されたユーザーとしてログオンが実行されます。

サンプル コード

/////////////////////////////////////////////////////////////////////////////// 
//
// 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");
}

関連情報

この資料は米国 Microsoft Corporation から提供されている Knowledge Base の Article ID 180548 (最終更新日 2005-03-21) を基に作成したものです。


この資料に含まれているサンプル コード/プログラムは英語版を前提に書かれたものをありのままに記述しており、日本語環境での動作は確認されておりません。
プロパティ

文書番号:180548 - 最終更新日: 2008/02/20 - リビジョン: 1

フィードバック