EternalWindows
Credentials Management / クレデンシャルの入力

Windows XPから登場したクレデンシャルマネージャ(credui.dll)は、 ユーザーがクレデンシャル(認証情報や資格情報と呼ぶことも)を入力するための標準的なダイアログを提供します。 特定のユーザーをログオンさせるアプリケーションなどは、 このダイアログを通じてユーザー名とパスワードを受け取ることができます。

ユーザー名を入力するコンボボックスでドロップダウンを表示した場合は、 いくつかのユーザー名が列挙されることがあります。 実は、クレデンシャルマネージャは入力されたクレデンシャルを保存することができ、 一度入力したクレデンシャルは再利用することが可能となっています。 クレデンシャルの保存は、「パスワードを記憶する」というチェックボックスにチェックを付けるだけでよく、 これによりクレデンシャルが永続的に保存されることになります。 一般的にはこの機能のことを「ユーザー名およびパスワードの保存」と呼びます。

上図のダイアログを表示するには、CredUIPromptForCredentialsを呼び出します。

DWORD WINAPI CredUIPromptForCredentials(
  PCREDUI_INFO pUiInfo,
  PCTSTR pszTargetName,
  PCtxtHandle Reserved,
  DWORD dwAuthError,
  PCTSTR pszUserName,
  ULONG ulUserNameMaxChars,
  PCTSTR pszPassword,
  ULONG ulPasswordMaxChars,
  PBOOL pfSave,
  DWORD dwFlags
);

pUiInfoは、ダイアログをカスタマイズするための情報を格納するCREDUI_INFO構造体のアドレスを指定します。 不要な場合は、NULLを指定することができます。 pszTargetNameは、保存されたクレデンシャルを識別するターゲット名を指定します。 命名は自由ですが、認証先であるコンピュータ名を指定することが多いと思われます。 Reservedは、予約されているためNULLを指定します。 dwAuthErrorは、ダイアログに考慮してもらいたいエラー定数を指定します。 たとえば、ERROR_PASSWORD_EXPIREDを指定するとパスワード期限切れダイアログが表示されます。 pszUserNameは、ダイアログに既定で表示するユーザー名を格納したバッファを指定します。 関数が成功すると入力されたユーザー名が返されます。 ulUserNameMaxCharsは、pszUserNameのサイズを指定します。 pszPasswordは、ダイアログに既定で表示するパスワードを格納したバッファを指定します。 関数が成功すると入力されたパスワードが返されます。 ulPasswordMaxCharsは、pszPasswordのサイズを指定します。 pfSaveは、認証情報が保存されたかどうかを受け取る変数のアドレスを指定します。 dwFlagsは、定義されている定数を指定します。 関数が成功してOKボタンを押した場合は、NO_ERRORが返ります。 ダイアログでキャンセルをした場合は、ERROR_CANCELLEDが返ります。

dwFlagsに指定できる一部の定数を次に示します。

定数 意味
CREDUI_FLAGS_ALWAYS_SHOW_UI ユーザー名とパスワードが保存されている既定のターゲット名を指定した場合でも、 必ずダイアログを表示するようにする。ユーザー名とパスワードは既定で入力されることになる。 この定数を指定する場合は、CREDUI_FLAGS_GENERIC_CREDENTIALSも共に指定する。
CREDUI_FLAGS_DO_NOT_PERSIST 「パスワードを記憶する」というチェックボックスを表示しない。
CREDUI_FLAGS_EXPECT_CONFIRMATION 「パスワードを記憶する」というチェックボックスにチェックが付けられてOKボタンが押されても、 クレデンシャルを保存しない。アプリケーションはpfSaveがTRUEである場合に、明示的に保存処理を行うことになる。
CREDUI_FLAGS_GENERIC_CREDENTIALS ユーザー名とパスワードがクレデンシャルとして保存されるようになる。 また、xxxに接続中ではなく、xxxにようこそと表示されるようになる。
CREDUI_FLAGS_INCORRECT_PASSWORD ダイアログを表示すると共に認証失敗のバルーンチップを表示する。 ユーザー名が既定で入力されている必要がある。
CREDUI_FLAGS_PERSIST 「パスワードを記憶する」というチェックボックスを表示しないが、 OKボタンが押された場合はクレデンシャルを保存する。 pfSaveにはTRUEが格納される。
CREDUI_FLAGS_SHOW_SAVE_CHECK_BOX pfSaveにTRUEが格納されている場合は、チェックボックスにチェックが付けられる。

CREDUI_FLAGS_EXPECT_CONFIRMATIONやCREDUI_FLAGS_PERSISTを指定する場合は、 CREDUI_FLAGS_GENERIC_CREDENTIALSも共に指定するべきといえます。 そうでないと、保存されるクレデンシャルの名前が<DomainName>\<UserName>という形式になり、 ユーザー名を抽出する際にCredUIParseUserNameを呼び出す手間が生じます。 また、パスワードは保存されなくなります。 通常、こうした動作を望むことはありませんから、 基本的にCREDUI_FLAGS_GENERIC_CREDENTIALSは指定するべきといえます。

チェックボックスにチェックを付けてOKボタンを押した場合、 ユーザー名とパスワードはクレデンシャルとしてクレデンシャルマネージャに保存されます。 ことのき、pfSaveにはTRUEが格納され、保存されたクレデンシャルはpszTargetNameで識別されることになります。 仮にpszTargetNameで識別されるクレデンシャルが既に存在する場合は、 そのクレデンシャルに含まれるユーザー名とパスワードがpszUserNameとpszPasswordに格納され、 ダイアログが表示されることはありません。 この仕組みは、保存されたクレデンシャルを使用することでクレデンシャルの入力を省略するというものですが、 dwFlagsにCREDUI_FLAGS_ALWAYS_SHOW_UIとCREDUI_FLAGS_GENERIC_CREDENTIALSを指定すれば、 確実にダイアログを表示することができます。

今回のプログラムは、CredUIPromptForCredentialsで取得したクレデンシャルを基にユーザーをログオンさせます。 Cred関数の呼び出しにはwincred.hのインクルードが必要となり、 さらにCredUI関数の呼び出しはcredui.libへのリンクも必要になります。

#include <windows.h>
#include <wincred.h>

#pragma comment (lib, "credui.lib")

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR  szUserName[256] = {0};
	TCHAR  szPassword[256] = {0};
	DWORD  dwResult;
	BOOL   bSave = FALSE;
	HANDLE hToken;

	dwResult = CredUIPromptForCredentials(NULL, TEXT("target-host"), NULL, 0, szUserName, sizeof(szUserName) / sizeof(TCHAR),
		szPassword, sizeof(szPassword) / sizeof(TCHAR), &bSave, CREDUI_FLAGS_ALWAYS_SHOW_UI | CREDUI_FLAGS_GENERIC_CREDENTIALS);
	if (dwResult != NO_ERROR)
		return 0;

	MessageBox(NULL, szUserName, szPassword, MB_OK);

	if (LogonUser(szUserName, NULL, szPassword, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &hToken))
		CloseHandle(hToken);
	else
		MessageBox(NULL, TEXT("ログオンに失敗しました。"), NULL, MB_ICONWARNING);

	SecureZeroMemory(szPassword, sizeof(szPassword));	

	return 0;
}

CredUIPromptForCredentialsの第2引数は、クレデンシャルが保存されたときにそれを識別する名前を指定します。 クレデンシャルを保存するつもりがない場合でも、NULLや空の文字列を指定することはできません。 第5引数と第7引数は入力されたユーザー名とパスワードを受け取るバッファを指定します。 これらは予め0で初期化されているため、既定値は表示されません。 第9引数には、クレデンシャルが保存された場合にTRUEが格納され、 保存されなかった場合はFALSEが格納されます。 初期値については、FALSEで問題ありません。 第10引数は、CREDUI_FLAGS_ALWAYS_SHOW_UIとCREDUI_FLAGS_GENERIC_CREDENTIALSを指定しているので、 第2引数のターゲット名が既に存在する場合でもダイアログが表示されることになります。 ログオンが成功した場合は、入力されたクレデンシャルが正しかったことになります。 使い終えたパスワードはSecureZeroMemoryでクリアします。

CredUIConfirmCredentialsについて

クレデンシャルを保存するという機能は、同じクレデンシャルを何度も入力せずに済むという効果をもたらしますが、 ユーザー名やパスワードが正しくない間違ったクレデンシャルが保存されるのは問題といえます。 たとえば、間違ったクレデンシャルを入力してチェックボックスにチェックを付けた場合は、 当然ながら間違ったクレデンシャルが保存されることになり、 次にダイアログを表示した場合に間違ったクレデンシャルが既定で入力されていることが考えられます。 これを防ぐためには、実際にクレデンシャルが正しいかを確認した後に、 クレデンシャルの保存を実行する必要があります。

#include <windows.h>
#include <wincred.h>

#pragma comment (lib, "credui.lib")

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR  szUserName[256] = {0};
	TCHAR  szPassword[256] = {0};
	DWORD  dwResult;
	BOOL   bSave = FALSE;
	HANDLE hToken;

	dwResult = CredUIPromptForCredentials(NULL, TEXT("target-host"), NULL, 0, szUserName, sizeof(szUserName) / sizeof(TCHAR),
		szPassword, sizeof(szPassword) / sizeof(TCHAR), &bSave, CREDUI_FLAGS_EXPECT_CONFIRMATION | CREDUI_FLAGS_GENERIC_CREDENTIALS);
	if (dwResult != NO_ERROR)
		return 0;
	
	MessageBox(NULL, szUserName, szPassword, MB_OK);

	if (LogonUser(szUserName, NULL, szPassword, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &hToken)) {
		if (bSave)
			CredUIConfirmCredentials(TEXT("target-host"), TRUE);
		CloseHandle(hToken);
	}
	else
		MessageBox(NULL, TEXT("ログオンに失敗しました。"), NULL, MB_ICONWARNING);
	
	SecureZeroMemory(szPassword, sizeof(szPassword));

	return 0;
}

CredUIPromptForCredentialsのフラグにCREDUI_FLAGS_EXPECT_CONFIRMATIONを指定した場合、 チェックボックスにチェックを付けてOKボタンを押しても、 クレデンシャルは即座に保存されません。 CredUIConfirmCredentialsの第2引数にTRUEを指定することで始めて保存されるようになります。 この関数は、LogonUserでログオンが成功した後に呼び出していますから、 クレデンシャルを保存しても問題ないことは既に証明されています。 チェックボックスにチェックが付いているかどうかは、bSaveを確認することで分かります。



戻る