|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有帐号?注册
x
<br> <b>简介</b><br> 有时,应用程序要求有用户提供的凭据以便访问受保护的资源,如数据库或 FTP 网站。然而,获取并存储用户的 ID 和密码可能会给您的系统带来安全风险。如有可能,您根本不应该让用户提供凭据(例如,通过对数据库使用集成身份验证),但有时这无法避免。如果您确实要求用户提供凭据,并且您的应用程序将运行在 Microsoft?Windows?XP 或 Microsoft?Windows Server 2003 上,则相应的操作系统会提供函数以使此任务变得容易一些。<br> <br> <b>存储的用户名和密码</b><br> Windows XP 和 Windows Server 2003 使用一种称为“存储的用户名和密码”的功能(参见图 1)将一组凭据与单个 Windows 用户帐户相关联,并使用数据保护 API (DPAPI) 来存储这些凭据。<br> <center> <img src=/bbs/attachments/computer/20081214/200812141117273177801.gif></center><br> <center>图 1. Windows XP 中的 Credential Management 对话框</center><br> <br> 如果您的应用程序运行在 Windows XP 或 Windows .NET 上,则可以使用凭据管理 API 函数来提示用户输入凭据。这些 API 可以为您提供一致的用户界面(参见图 2),并使您可以自动支持由操作系统来缓存这些凭据。<br> <center> <img src=/bbs/attachments/computer/20081214/2008121411172712577802.gif></center><br> <center>图 2. 标准 Windows XP 凭据对话框</center><br> <br> 有关在应用程序中请求、存储和使用用户凭据所涉及问题的详细讨论,请参阅 Michael Howard 与 David LeBlanc 合著的 Writing Secure Code 一书。建议您阅读该书,以获取更多信息。在本文中,我将只是向您说明如何在 Microsoft?Visual Basic?.NET 和 C# 应用程序中使用凭据管理 API。<br> <br> <b>在 .NET 中创建凭据 API 类</b><br> <b>声明 API 函数</b><br> 因为这些凭据管理函数是 Win32 API 调用,您将需要创建 extern (C#) 或 Declare (Visual Basic .NET) 定义以便访问它们。除了这些函数本身以外,还需要使用一些常数和结构来访问它们。这些常数被组织为预定义的常数组,所以我已经选择在 .NET 代码中以枚举形式实现这些常数组,以使 API 调用更加易于使用。<br> <br> Private Declare Unicode _<br> Function CredUIPromptForCredentials _<br> Lib "credui" Alias "CredUIPromptForCredentialsW" _<br> (ByRef creditUR As CREDUI_INFO, _<br> ByVal targetName As String, _<br> ByVal reserved1 As IntPtr, _<br> ByVal iError As Integer, _<br> ByVal userName As StringBuilder, _<br> ByVal maxUserName As Integer, _<br> ByVal password As StringBuilder, _<br> ByVal maxPassword As Integer, _<br> ByRef iSave As Integer, _<br> ByVal flags As CREDUI_FLAGS) _<br> As CredUIReturnCodes<br> <br> Private Declare Unicode _<br> Function CredUIParseUserName _<br> Lib "credui" Alias "CredUIParseUserNameW" _<br> (ByVal userName As String, _<br> ByVal user As StringBuilder, _<br> ByVal userMaxChars As Integer, _<br> ByVal domain As StringBuilder, _<br> ByVal domainMaxChars As Integer) _<br> As CredUIReturnCodes<br> <br> Private Declare Unicode _<br> Function CredUIConfirmCredentials _<br> Lib "credui" Alias "CredUIConfirmCredentialsW" _<br> (ByVal targetName As String, _<br> ByVal confirm As Boolean) _<br> As CredUIReturnCodes<br> <br> Public Declare Auto _<br> Function DeleteObject Lib "Gdi32" _<br> (ByVal hObject As IntPtr) As Boolean<br> <br> 注:我在代码中包括了 GDI32 库中的 DeleteObject API 调用,因为如果您决定将自己的位图传递给 CredUIPromptForCredentials API,则您将需要使用该调用。当我在下文中演示自定义位图的使用时,您将可以了解该 API 的使用方法。<br> <br> <b>常数和结构声明</b><br> 对于许多 Win32 API 调用,您都需要一组支持常数(在本文中,我选择将其表示为枚举),并且还可能需要一个或两个结构声明。凭据 API 也遵循上述一般性规则,并且需要多种常数和一个结构。在我的 .NET 类中,我添加了一个枚举来表示 CredUIPromptForCredentials 的标志参数,添加了另外一个枚举来表示全部三个凭据 API 调用可能产生的返回代码集,并添加了一个 CREDUI_INFO 结构声明。<br> <br> <Flags()> Public Enum CREDUI_FLAGS<br> INCORRECT_PASSWORD = &H1<br> <br> DO_NOT_PERSIST = &H2<br> <br> REQUEST_ADMINISTRATOR = &H4<br> <br> EXCLUDE_CERTIFICATES = &H8<br> <br> REQUIRE_CERTIFICATE = &H10<br> <br> SHOW_SAVE_CHECK_BOX = &H40<br> <br> ALWAYS_SHOW_UI = &H80<br> <br> REQUIRE_SMARTCARD = &H100<br> <br> PASSWORD_ONLY_OK = &H200<br> <br> VALIDATE_USERNAME = &H400<br> <br> COMPLETE_USERNAME = &H800<br> <br> PERSIST = &H1000<br> <br> SERVER_CREDENTIAL = &H4000<br> <br> EXPECT_CONFIRMATION = &H20000<br> <br> GENERIC_CREDENTIALS = &H40000<br> <br> USERNAME_TARGET_CREDENTIALS = &H80000<br> <br> KEEP_USERNAME = &H100000<br> <br> End Enum<br> <br> Public Enum CredUIReturnCodes As Integer<br> NO_ERROR = 0<br> ERROR_CANCELLED = 1223<br> ERROR_NO_SUCH_LOGON_SESSION = 1312<br> ERROR_NOT_FOUND = 1168<br> ERROR_INVALID_ACCOUNT_NAME = 1315<br> ERROR_INSUFFICIENT_BUFFER = 122<br> ERROR_INVALID_PARAMETER = 87<br> ERROR_INVALID_FLAGS = 1004<br> End Enum<br> <br> Public Structure CREDUI_INFO<br> Public cbSize As Integer<br> Public hwndParent As IntPtr<br> Public pszMessageText As String<br> Public pszCaptionText As String<br> Public hbmBanner As IntPtr<br> End Structure<br> <br> <b>为 API 调用创建包装函数</b><br> 此步骤不是必需的。您可以简单地将 API 声明为 Public(而不是像我的代码一样,使其声明为 Private),并从应用程序中直接调用它们。不过,我发现调用 API 经常需要完成一些工作,因而我喜欢通过包装 API 调用来向代码的最终用户隐藏这些调用细节。<br> <br> Private Const MAX_USER_NAME As Integer = 100<br> Private Const MAX_PASSWORD As Integer = 100<br> Private Const MAX_DOMAIN As Integer = 100<br> <br> Public Shared Function PromptForCredentials( _<br> ByRef creditUI As CREDUI_INFO, _<br> ByVal targetName As String, _<br> ByVal netError As Integer, _<br> ByRef userName As String, _<br> ByRef password As String, _<br> ByRef save As Boolean, _<br> ByVal flags As CREDUI_FLAGS) _<br> As CredUIReturnCodes<br> <br> Dim saveCredentials As Integer<br> Dim user As New StringBuilder(MAX_USER_NAME)<br> Dim pwd As New StringBuilder(MAX_PASSWORD)<br> saveCredentials = Convert.ToInt32(save)<br> creditUI.cbSize = Marshal.SizeOf(creditUI)<br> Dim result As CredUIReturnCodes<br> result = CredUIPromptForCredentials( _<br> creditUI, targetName, _<br> IntPtr.Zero, netError, _<br> user, MAX_USER_NAME, _<br> pwd, MAX_PASSWORD, _<br> saveCredentials, flags)<br> save = Convert.ToBoolean(saveCredentials)<br> userName = user.ToString<br> password = pwd.ToString<br> Return result<br> End Function<br> <br> Public Shared Function ParseUserName(ByVal userName As String, _<br> ByRef userPart As String, _<br> ByRef domainPart As String) _<br> As CredUIReturnCodes<br> <br> Dim user As New StringBuilder(MAX_USER_NAME)<br> Dim domain As New StringBuilder(MAX_DOMAIN)<br> Dim result As CredUIReturnCodes<br> result = CredUIParseUserName(userName, _<br> user, MAX_USER_NAME, _<br> domain, MAX_DOMAIN)<br> userPart = user.ToString()<br> domainPart = domain.ToString()<br> Return result<br> End Function<br> <br> Public Shared Function ConfirmCredentials(ByVal target As String, _<br> ByVal confirm As Boolean) As CredUIReturnCodes<br> Return CredUIConfirmCredentials(target, confirm)<br> End Function<br> <br> 注:为了便于使用,我已经使我的所有函数都成为 Shared/Static 函数。因为它们没有将任 < |
|