常数和结构声明
对于许多 Win32 API 调用,您都需要一组支持常数(在本文中,我选择将其表示为枚举),并且还可能需要一个或两个结构声明。凭据 API 也遵循上述一般性规则,并且需要多种常数和一个结构。在我的 .NET 类中,我添加了一个枚举来表示 CredUIPromptForCredentials 的标志参数,添加了另外一个枚举来表示全部三个凭据 API 调用可能产生的返回代码集,并添加了一个 CREDUI_INFO 结构声明。
Public Enum CREDUI_FLAGS
INCORRECT_PASSWORD = &H1
DO_NOT_PERSIST = &H2
REQUEST_ADMINISTRATOR = &H4
EXCLUDE_CERTIFICATES = &H8
REQUIRE_CERTIFICATE = &H10
SHOW_SAVE_CHECK_BOX = &H40
ALWAYS_SHOW_UI = &H80
REQUIRE_SMARTCARD = &H100
PASSWORD_ONLY_OK = &H200
VALIDATE_USERNAME = &H400
COMPLETE_USERNAME = &H800
PERSIST = &H1000
SERVER_CREDENTIAL = &H4000
EXPECT_CONFIRMATION = &H20000
GENERIC_CREDENTIALS = &H40000
USERNAME_TARGET_CREDENTIALS = &H80000
KEEP_USERNAME = &H100000
End Enum
Public Enum CredUIReturnCodes As Integer
NO_ERROR = 0
ERROR_CANCELLED = 1223
ERROR_NO_SUCH_LOGON_SESSION = 1312
ERROR_NOT_FOUND = 1168
ERROR_INVALID_ACCOUNT_NAME = 1315
ERROR_INSUFFICIENT_BUFFER = 122
ERROR_INVALID_PARAMETER = 87
ERROR_INVALID_FLAGS = 1004
End Enum
Public Structure CREDUI_INFO
Public cbSize As Integer
Public hwndParent As IntPtr
Public pszMessageText As String
Public pszCaptionText As String
Public hbmBanner As IntPtr
End Structure
为 API 调用创建包装函数
此步骤不是必需的。您可以简单地将 API 声明为 Public(而不是像我的代码一样,使其声明为 Private),并从应用程序中直接调用它们。不过,我发现调用 API 经常需要完成一些工作,因而我喜欢通过包装 API 调用来向代码的最终用户隐藏这些调用细节。
Private Const MAX_USER_NAME As Integer = 100
Private Const MAX_PASSWORD As Integer = 100
Private Const MAX_DOMAIN As Integer = 100
Public Shared Function PromptForCredentials( _
ByRef creditUI As CREDUI_INFO, _
ByVal targetName As String, _
ByVal netError As Integer, _
ByRef userName As String, _
ByRef password As String, _
ByRef save As Boolean, _
ByVal flags As CREDUI_FLAGS) _
As CredUIReturnCodes
Dim saveCredentials As Integer
Dim user As New StringBuilder(MAX_USER_NAME)
Dim pwd As New StringBuilder(MAX_PASSWORD)
saveCredentials = Convert.ToInt32(save)
creditUI.cbSize = Marshal.SizeOf(creditUI)
Dim result As CredUIReturnCodes
result = CredUIPromptForCredentials( _
creditUI, targetName, _
IntPtr.Zero, netError, _
user, MAX_USER_NAME, _
pwd, MAX_PASSWORD, _
saveCredentials, flags)
save = Convert.ToBoolean(saveCredentials)
userName = user.ToString
password = pwd.ToString
Return result
End Function
Public Shared Function ParseUserName(ByVal userName As String, _
ByRef userPart As String, _
ByRef domainPart As String) _
As CredUIReturnCodes
Dim user As New StringBuilder(MAX_USER_NAME)
Dim domain As New StringBuilder(MAX_DOMAIN)
Dim result As CredUIReturnCodes
result = CredUIParseUserName(userName, _
user, MAX_USER_NAME, _
domain, MAX_DOMAIN)
userPart = user.ToString()
domainPart = domain.ToString()
Return result
End Function
Public Shared Function ConfirmCredentials(ByVal target As String, _
ByVal confirm As Boolean) As CredUIReturnCodes
Return CredUIConfirmCredentials(target, confirm)
End Function
注:为了便于使用,我已经使我的所有函数都成为 Shared/Static 函数。因为它们没有将任何信息存储为类级别属性或变量,所以实在没有任何理由强制开发人员在调用这些函数之前创建类实例。
在声明并包装这些 API 之后,我建议将代码放入它自己的程序集中,方法是在 Microsoft?Visual Studio?.NET 中创建一个 Class Library 项目(就像我在本文随附的示例中所做的那样)并且将代码编译为它的唯一类。通过根据这些代码创建程序集,以后可以从需要这些代码的任何项目中进行引用。但是,如果您愿意,也可直接将该类包含到您的项目中
请求用户凭据
在完整声明 API 调用、关联的枚举数和结构之后,现在就可以开始从您自己的应用程序中使用凭据 API 了。为了演示调用该 API 的各种方法,我创建了一个简单的示例应用程序,它使用 SQL 身份验证连接到本地 Microsoft?SQL Server 数据库。通常,我总是尝试对我的 SQL Server 使用集成身份验证,但为了该示例,我将假装正在使用不支持这种身份验证的数据库服务器。当您调用 CredUIPromptForCredentials 时,有多种可以设置的标志。尽管所有这些标志都在该 API 的参考页上进行了说明,我仍将对应用程序中使用的几个标志进行详细介绍。
ALWAYS_SHOW_UI 通知 API 调用弹出凭据对话框,即使您已经在过去输入并保存了一些凭据。如果不使用该标志并且使用持久性凭据,则当您的应用程序随后建立连接时,用户将不会看到任何提示。如果您预料用户可能在登录时使用不同的凭据信息,则这会非常有用。
EXPECT_CONFIRMATION 与 CredUIConfirmCredentials API 调用(也包含在本文前面的代码中)配合使用。要避免永久保留“坏”凭据,可以通过该标志实现一个两阶段过程。首先从用户那里获得凭据,然后尝试使用这些凭据进行连接,并且仅当连接成功时才确认这些凭据(并保留它们)。
GENERIC_CREDENTIALS 指定您只是在查找用户 ID/密码组合,而不是在查找域凭据。我只是将该 API 用于此目的,而由于要求域凭据的受保护资源通常应该由操作系统处理,因此我总是忽略该标志。
KEEP_USERNAME 修改凭据对话框的用户界面,以便只能输入密码。在某些情况下,如使用数据库密码集连接到 Microsoft?Access 数据库,此时用户 ID 已经固定(或者,如果使用 Access 数据库密码,则用户 ID 不存在),该标志有助于在凭据用户界面中表示这一情况。
SHOW_SAVE_CHECK_BOX 确保凭据对话框包含一个复选框,以使用户能够对存储这些凭据进行控制。用户的选择将在 CredUI.PromptForCredentials 的 save (boolean) 参数中返回。
以下为一个示例,该示例使用凭据 API 和上述标志在连接到 SQL 数据库之前请求密码。首先,我创建了 CREDUI_INFO 结构,并用我的目标(父窗口)和一个标题字符串填充了该结构。
Dim host As String = "MyServer"
Dim info As New CREDUI_INFO()
With info
.hwndParent = Me.Handle
.pszCaptionText = host
.pszMessageText = _
String.Format("Please Enter Credentials for {0}", host)
End With
接下来,我为这一 CredUIPromptForCredentials 调用指定一些标志,在此,这些标志将告诉 API 以下信息:我将请求普通凭据(而不是域凭据);我希望用户界面包含 Save 复选框;即使用户以前已经输入并保存了一组凭据,对话框仍然应该出现;在保留任何凭据之前,都将通过 CredUIConfirmCredentials API 提供确认。
Dim flags As CREDUI_FLAGS
flags = CREDUI_FLAGS.GENERIC_CREDENTIALS Or _
CREDUI_FLAGS.SHOW_SAVE_CHECK_BOX Or _
CREDUI_FLAGS.ALWAYS_SHOW_UI Or _
CREDUI_FLAGS.EXPECT_CONFIRMATION
Once I have my flags and CREDUI_INFO structure, I then call the PromptForCredentials API call, passing in all of my information.
Dim result As CredUIReturnCodes
result = CredUI.PromptForCredentials(info, _
host, 0, _
userid, password, savePwd, flags)
如果我尚未指定 CREDUI_FLAGS.ALWAYS_SHOW_UI,则仅当该特定目标没有可用的已存储凭据时,凭据对话框才会出现。一般情况下,这意味着(没有该标志)对话框仅当您首次调用该 API 时才会出现,从而提供良好的用户体验。无论该对话框是否出现,该 API 调用都将返回指示成功或失败的结果代码,您在使用返回的用户 ID 和密码值以前,都应该对该结果代码进行检查