标题：CredentialProvider
概述
Windows Vista 在平台集成方面为开发人员提供了许多新的机会。新的凭据提供程序模型是变动最大的方面之一，由于它的出现，实现操作系统支持的新用户身份验证方案变得容易了许多。它已取代了 GINA（图形标识与身份验证）模型，而直言不讳地说，后者一向因为开发人员难以理解和实现以及昂贵的 Microsoft支持费用而广为诟病。那么 Windows® 登录插件接口的一个变化竟会如此令人兴奋，其原因何在？用户打开计算机时首先看到的是登录屏幕。由于登录体验是由凭据提供程序来控制和管理的，这使得自定义登录体验以及集成最符合组织需要的身份验证方法变得容易了许多。简而言之，凭据提供程序为开发和实现更好、更可靠的安全性提供了一种更容易的方式。
两种体系结构比较
在 Windows Vista™ 之前的环境中，每个会话都有一个 winlogon 实例，它负责控制该会话的交互式登录序列。（图 1 显示了 Windows XP 和 Windows Server® 2003 旧的登录体系结构。）在刚启动的系统中，控制台位置的交互式登录始终在会话 0 中执行。会话 0 承载运行系统服务以及其他关键进程，包括“本地安全机构”(Local Security Authority) 进程。（换句话说，在会话 0 中运行的许多进程都没有在图 1 中显示出来。）
  图 1 GINA 登录体系结构
图 1 GINA 登录体系结构 
计算机上已注册的 GINA 加载到 winlogon 进程空间中。（还可能加载一个称作“GINA 链接”的配置，但测试和支持这样的复杂配置很困难。）最后，GINA 调用 LogonUser 以及相关的身份验证 API。在 Windows Vista 中，会话 0 不再用于交互式登录（请参见图 2）。这有利于提高安全性，因为已有一个会话边界将所有的计算机进程与各个用户的进程分隔开来。此外，对内核全局命名空间的控制也更加严格，因为默认情况下由用户应用程序创建的对象已不在内核全局命名空间之内。
  图 2 新的登录体系结构
图 2 新的登录体系结构
除会话0 之外的每一个会话仍会有一个 winlogon 实例。图 2 显示，系统中已注册了几个凭据提供程序，并已通过新的 LogonUI 进程加载。在由哪个组件负责显示登录图形界面方面也有一个重要的改动。以前，这是由 GINA 来处理的，因此，显示界面的工作可能一直由第三方组件来完成。在新的体系结构中，这是由操作系统的一个内置组件 LogonUI 来负责完成的。那么每个提供程序的用户提示行为在新的模型中是如何实现的呢？“凭据提供程序”体系结构要求每个提供程序都要列举说明它所需要的 UI 元素。例如，在某个指定的方案中，提供程序可能会向 LogonUI 表明它需要两个编辑框、两个标题、一个复选框和一个位图。然后，LogonUI 为凭据提供程序显示这些控件。这对实现以前讨论的目标大有帮助，即用一致的外观和方法来广泛支持不断修改完善的用户验证方案。负责“凭据提供程序”开发的 Microsoft 开发团队原以为外部开发人员会更愿意基于 COM 来开发插件模型。然而，在 Windows Vista 开发周期的早期阶段，新接口最初的内部设计（类似于 GINA）完全基于LoadLibrary 和函数指针。之后，基于 COM 的重新设计吸取了第一次的教训，使得设计出来的界面更加简洁和易用。
混合凭据提供程序
此新插件模型的计时功能臻于完美（当然，或许早就应该有这样的功能了）。开发人员可以更轻松地满足多因素身份验证方案需求，同时提供与 Microsoft 原有的相一致的登录体验。尽管如此，新的接口仍显得相当抽象。有关它的描述说明也同样令人费解，让人感到乏味！要想了解它，一个让人能提起兴趣的方式就是体验一下新凭据提供程序的设计、开发和测试过程。而且，这可以很好地弥补 Microsoft 提供的现有文档的不足。
混合凭据的要求
混合凭据提供程序应达到以下这些要求：
使之基于智能卡运行
最大限度地增加代码的重用
最大限度地减少额外配置和基础结构需求
设计
1、ICredentialProviderCredential 接口ICredentialProviderCredential : public IUnknown{HRESULT STDMETHODCALLTYPE Advise( /* [in] */ ICredentialProviderCredentialEvents *pcpce);HRESULT STDMETHODCALLTYPE UnAdvise( void);HRESULT STDMETHODCALLTYPE SetSelected( /* [out] */ BOOL *pbAutoLogon);HRESULTSTDMETHODCALLTYPE SetDeselected( void);HRESULT STDMETHODCALLTYPE GetFieldState( /* [in] */ DWORD dwFieldID,/* [out] */ CREDENTIAL_PROVIDER_FIELD_STATE *pcpfs,/* [out] */ CREDENTIAL_PROVIDER_FIELD_INTERACTIVE_STATE *pcpfis);HRESULT STDMETHODCALLTYPE GetStringValue( /* [in] */ DWORD dwFieldID,/* [string][out] */ LPWSTR *ppsz);HRESULT STDMETHODCALLTYPE GetBitmapValue( /* [in] */ DWORD dwFieldID,/* [out] */ HBITMAP *phbmp);HRESULT STDMETHODCALLTYPE GetCheckboxValue( /* [in] */ DWORD dwFieldID,/* [out] */ BOOL *pbChecked,/* [string][out] */ LPWSTR *ppszLabel);HRESULT STDMETHODCALLTYPE GetSubmitButtonValue( /* [in] */ DWORD dwFieldID,/* [out] */ DWORD *pdwAdjacentTo);HRESULT STDMETHODCALLTYPE GetComboBoxValueCount( /* [in] */ DWORD dwFieldID,/* [out] */ DWORD *pcItems,/* [out] */ DWORD *pdwSelectedItem);HRESULT STDMETHODCALLTYPE GetComboBoxValueAt( /* [in] */ DWORD dwFieldID,DWORD dwItem,/* [string][out] */ LPWSTR *ppszItem);HRESULT STDMETHODCALLTYPE SetStringValue( /* [in] */ DWORD dwFieldID,/* [string][in] */ LPCWSTR psz);HRESULT STDMETHODCALLTYPE SetCheckboxValue( /* [in] */ DWORD dwFieldID,/* [in] */ BOOL bChecked);HRESULT STDMETHODCALLTYPE SetComboBoxSelectedValue( /* [in] */ DWORD dwFieldID,/* [in] */ DWORD dwSelectedItem);HRESULT STDMETHODCALLTYPE CommandLinkClicked( /* [in] */ DWORD dwFieldID);HRESULT STDMETHODCALLTYPE GetSerialization( /* [out] */ CREDENTIAL_PROVIDER_GET_SERIALIZATION_RESPONSE *pcpgsr,/* [out] */ CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION *pcpcs,/* [out] */ LPWSTR *ppszOptionalStatusText,/* [out] */ CREDENTIAL_PROVIDER_STATUS_ICON *pcpsiOptionalStatusIcon);HRESULT STDMETHODCALLTYPE ReportResult( /* [in] */ NTSTATUS ntsStatus,/* [in] */ NTSTATUS ntsSubstatus,/* [out] */ LPWSTR *ppszOptionalStatusText,/* [out] */ CREDENTIAL_PROVIDER_STATUS_ICON *pcpsiOptionalStatusIcon);};2、ICredentialProvider接口ICredentialProvider :public IUnknown{HRESULT STDMETHODCALLTYPE SetUsageScenario( /* [in] */ CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus,/* [in] */ DWORD dwFlags);HRESULT STDMETHODCALLTYPE SetSerialization( /* [in] */ const CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION*pcpcs);
HRESULTSTDMETHODCALLTYPE Advise( /* [in] */ ICredentialProviderEvents *pcpe,/* [in] */ UINT_PTR upAdviseContext);
HRESULTSTDMETHODCALLTYPE UnAdvise( void);
HRESULTSTDMETHODCALLTYPE GetFieldDescriptorCount( /* [out] */ DWORD *pdwCount);
HRESULTSTDMETHODCALLTYPE GetFieldDescriptorAt( /* [in] */ DWORD dwIndex,/* [out] */ CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR **ppcpfd);HRESULT STDMETHODCALLTYPE GetCredentialCount( /* [out] */ DWORD *pdwCount,/* [out] */ DWORD *pdwDefault,/* [out] */ BOOL *pbAutoLogonWithDefault);HRESULT STDMETHODCALLTYPE GetCredentialAt( /* [in] */ DWORD dwIndex,/* [out] */ ICredentialProviderCredential **ppcpc);};3、ICredentialProviderEvents接口ICredentialProviderCredentialEvents: public IUnknown{HRESULT STDMETHODCALLTYPE SetFieldState( /* [in] */ ICredentialProviderCredential *pcpc,/* [in] */ DWORD dwFieldID,/* [in] */ CREDENTIAL_PROVIDER_FIELD_STATE cpfs);HRESULT STDMETHODCALLTYPE SetFieldInteractiveState( /* [in] */ ICredentialProviderCredential *pcpc,/* [in] */ DWORD dwFieldID,/* [in] */ CREDENTIAL_PROVIDER_FIELD_INTERACTIVE_STATE cpfis);HRESULT STDMETHODCALLTYPE SetFieldString( /* [in] */ ICredentialProviderCredential *pcpc,/* [in] */ DWORD dwFieldID,/* [unique][string][in] */ LPCWSTR psz);HRESULT STDMETHODCALLTYPE SetFieldCheckbox( /* [in] */ ICredentialProviderCredential *pcpc,/* [in] */ DWORD dwFieldID,/* [in] */ BOOL bChecked,/* [in] */ LPCWSTR pszLabel);HRESULT STDMETHODCALLTYPE SetFieldBitmap( /* [in] */ ICredentialProviderCredential *pcpc,/* [in] */ DWORD dwFieldID,/* [in] */ HBITMAP hbmp);HRESULT STDMETHODCALLTYPE SetFieldComboBoxSelectedItem( /* [in] */ ICredentialProviderCredential *pcpc,/* [in] */ DWORD dwFieldID,/* [in] */ DWORD dwSelectedItem);HRESULT STDMETHODCALLTYPE DeleteFieldComboBoxItem( /* [in] */ ICredentialProviderCredential *pcpc,/* [in] */ DWORD dwFieldID,/* [in] */ DWORD dwItem);HRESULT STDMETHODCALLTYPE AppendFieldComboBoxItem( /* [in] */ ICredentialProviderCredential *pcpc,/* [in] */ DWORD dwFieldID,/* [string][in] */ LPCWSTR pszItem);HRESULT STDMETHODCALLTYPE SetFieldSubmitButton( /* [in] */ ICredentialProviderCredential *pcpc,/* [in] */ DWORD dwFieldID,/* [in] */ DWORD dwAdjacentTo);HRESULT STDMETHODCALLTYPE OnCreatingWindow( /* [out] */ HWND *phwndOwner);};4、混合凭据提供程序调用序列1. [The system boots]
2. [LogonUI.exe process is created]
3. [Credential provider DLLs are loaded]
4. Provider::CreateInstance 
5. [User presses Ctrl+Alt+Del]
6. Provider::SetUsageScenario (CPUS_LOGON)
7. Credential::Initialize 
8. Provider::Advise 
9. Provider::GetCredentialCount 
10. Provider::GetCredentialAt (dwIndex = 0)
11. Provider::GetFieldDescriptorCount 
12. Provider::GetFieldDescriptorAt (dwIndex =0)
13. Provider::GetFieldDescriptorAt (dwIndex =1)
14. Provider::GetFieldDescriptorAt (dwIndex =2)
15. Provider::GetFieldDescriptorAt (dwIndex =3)
16. Provider::GetFieldDescriptorAt (dwIndex =4)
17. Credential::GetBitmapValue (dwFieldID =0; tile image)
18. Credential::GetStringValue (dwFieldID =1; user name field)
19. Credential::GetFieldState (dwFieldID = 1;user name field)
20. Credential::GetStringValue (dwFieldID =2; password field)
21. Credential::GetFieldState (dwFieldID = 2;password field)
22. Credential::GetSubmitButtonValue(dwFieldID = 3; submit button)
23. Credential::GetFieldState (dwFieldID = 3;submit button)
24. Credential::GetStringValue (dwFieldID =4; domain name field)
25. Credential::GetFieldState (dwFieldID = 4;domain name field)
26. Credential::Advise 
27. Credential::GetSerialization 
28. Credential::UnAdvise 
29. Provider::UnAdvise 
30. [The WinLogon process calls LogonUser]
31. Credential::Advise 
32. Credential::ReportResult (ntsStatus = 0)
33. Credential::UnAdvise
首先，winlogon启动控制台会话 LogonUI 进程。创建后，LogonUI 枚举在HKLM\Software\Microsoft\Windows\CurrentVersion\Auntication\Credential Providers下注册的所有凭据提供程序。每个提供程序 DLL 会被加载，并接收到一个Provider::CreateInstance 调用。对于混合凭据提供程序，这将创建一个CHybridProvider。（请参见步骤 1 到4。）用户将看到登录屏幕。假定用户按了 Ctrl+Alt+Delete 并且每个提供程序都收到了Provider::SetUsageScenario CPUS_LOGON 通知。这向提供程序表明，用户想进行交互式登录。该混合凭据提供程序将尝试从所插入的任何智能卡中读取凭据。如果找到了一个可读的智能卡，会将一个CHybridCredential 实例化并将其与当前的CHybridProvider 关联。然后将有一个对Credential::Initialize 的调用。（请参见步骤5 到 7。）LogonUI 随后为每个加载的提供程序调用Provider::Advise。Advise 的目的是为提供程序提供一种机制，将对可见的 UI 元素（当前还未创建）所做的任何预期的更改通知给 LogonUI。内置的智能卡提供程序给出了关于如何使用该机制的一个很好的例子。初始化之后，无论何时插入卡都会增大可用凭据数，而取出卡则会减小该数字。发生此类情况时，将通过这种机制通知 LogonUI：复制代码ICredentialProviderEvents : public IUnknown{HRESULT STDMETHODCALLTYPE CredentialsChanged( /* [in] */ UINT_PTR upAdviseContext);};
为简单起见，混合凭据提供程序不对卡的插入和取出进行动态处理。因此，它不跟踪通过 Advise 传递给它的 ICredentialProviderEvents 接口。LogonUI 执行的下一个接口调用是调用 Provider::GetCredentialCount，即步骤 9。如果创建了混合凭据（由于插入的智能卡），混合凭据提供程序将执行一些操作。它首先将 GetCredentialCount *pdwCount 输出参数设置为 1。该值指的是提供程序要枚举的凭据图块数。（混合凭据提供程序只能处理 1 个。）首次安装 Windows Vista 并加入域时，可以根据显示的图块数推断 Microsoft 密码凭据提供程序将什么 pdwCount 值返回到 LogonUI。混合凭据提供程序然后将GetCredentialCount *pdwDefault 输出参数设置为 0。该值是一个从 0 开始的索引值，用于对每个提供程序假定要维护的凭据数组进行索引。如何实现提供程序跟踪其凭据由实施人员来完成，而在一组给定凭据对象的生存期内会一直对索引进行维护。多个提供程序枚举一个默认凭据是完全可能的。例如，在当前的方案中，可以预期内置的密码凭据提供程序将枚举它自己的一个默认凭据。LogonUI 如何提示用户从多个默认和非默认凭据中作出选择而不会使用户无从下手？一般来讲，对于每一个凭据，都会向用户显示一个图块，并且会将焦点设置到代表默认凭据的那个图块。在存在多个默认凭据的情况下，实际的默认凭据是在枚举各个默认凭据时通过一系列优先规则选出的。对于各个凭据而言，如果已有一个没有自动登录的默认凭据，并且此凭据将要执行自动登录，则它将成为默认凭据。如果此凭据来自最后登录 (LLO) 提供程序并且尚还没有自动登录的默认凭据，则此凭据将成为默认凭据。最后，如果还没有默认凭据，则此凭据将成为默认凭据。尽管说了这么多，我的混合凭据提供程序的自动登录语义使得该讨论没有什么实际意义。只要枚举的混合凭据包含有效的登录信息，用户就永远都看不到任何图块。我已提到了与优先规则有关的最后登录提供程序，但应指出，LLO 的意义会根据用户是否正在登录或者它是否是登录后的情况（如桌面锁定或密码更改）而变化。登录时，LLO 提供程序是用于最后的控制台登录的最后一个提供程序。登录后，LLO 提供程序只是用于登录到那个会话的提供程序。其原则就是如果始终用智能卡登录，则智能卡凭据提供程序默认图块将在重新启动后成为默认凭据。但如果因智能卡丢失而使用密码登录，则解锁时密码凭据提供程序的图块将成为该会话的默认凭据。混合凭据提供程序始终都会将*pbAutoLogonWithDefault 输出参数设置为 TRUE。这用于向 LogonUI 发送通知，指示它应立即查询此提供程序的默认凭据以获得登录信息，而无需先向用户发送提示。请注意，通过使用可以存储在注册表中的密码自动登录信息（可选），内置的密码凭据提供程序也具有相同的功能。实际上，如果 Windows Vista 检测到该计算机上只有一个用户并且没有密码，则这就是默认行为。对于有多个凭据提供程序将 *pbAutoLogonWithDefault 设置为 TRUE 的情况，LogonUI 的行为尚不明确。执行了 GetCredentialCount 调用之后，LogonUI 将调用 Provider::GetCredentialAt。对于混合凭据提供程序，此例程最多调用一次，它反映此提供程序的最大凭据计数。作为响应，提供程序会返回该凭据实例的一个与请求的索引对应的 ICredentialProviderCredential 指针。接下来，LogonUI 调用 Provider::GetFieldDescriptorCount，提供程序通过此调用返回在其凭据中可以找到的 UI 元素的最大数目。例如，我的密码凭据提供程序示例有五个域：一个位图、一个用户名输入域、一个密码输入域、一个提交按钮和一个域名输入域。即使实际上这些元素从不显示，您仍可以看到混合凭据提供程序中保存了这些元素。这将完成图 5 中的步骤 11。LogonUI 然后将为每个 UI 元素分别调用一次 Provider::GetFieldDescriptorAt，以便检索其类型。例如，执行了对应于位图索引的调用之后，该示例将返回 CREDENTIAL_PROVIDER_FIELD_TYPE CPFT_TILE_IMAGE。混合凭据提供程序中未使用的一个功能是与只读文本域相对的可写文本域。如果修改了混合凭据提供程序来提示用户输入智能卡 PIN，则此功能将通过CPFT_PASSWORD_TEXT 来完成。可以显示从智能卡读取的用户名，以便提供某些上下文来提示用户输入信息。但就技术而言，用户名应为只读，因为它已绑定到同样存储在卡上的密码。因此，可能会使用 CPFT_LARGE_TEXT 字段类型（与 CPFT_EDIT_TEXT 相对）。（有关选项的完整列表，请参见 credentialprovider.h。）完成字段描述符的枚举之后，LogonUI将根据每个凭据字段的类型对凭据提供程序执行一系列调用。例如，对于 CPFT_TILE_IMAGE 字段类型，LogonUI 接下来将调用 Credential::GetBitmapValue。对于 CPFT_LARGE_TEXT 之类的用于用户名编辑框的文本值，随后会调用 Credential::GetStringValue 和 Credential::GetFieldState。由于已从智能卡读取了我的混合凭据提供程序所需的所有登录信息（用户名、密码和域名），此时可以获得对应于每个文本字段的字符串，这些字符串通过 GetStringValue 的 ppwz 输出参数返回。此时，作为对 GetStringValue 的响应，其他提供程序则可能会返回 NULL 字符串值，因为用户还没来得及键入任何内容。请注意可能造成混淆的一点：文本字段名是通过 GetFieldDescriptorAt 检索的，而字段中当前的文本值则是通过 GetStringValue 检索的。（字段名或字段标签将显示为空编辑控件中的提示文本。）在完全描述了各种 UI元素之后，LogonUI 将调用 Credential::Advise。（请参见步骤 26。）它与前面调用的 Provider::Advise 接口的作用类似；每个凭据都可以将影响 LogonUI 的 UI 元素状态的相关更改异步通知给 LogonUI。例如，在取消选择示例密码凭据提供程序的某一凭据图块时，提供程序便使用这一机制。在这种情况下，凭据对象将使用 ICredentialProviderCredentialEventsSetFieldString来清除密码域。这与在 Windows XP 登录屏幕中只键入部分密码然后便暂停时所发生的情况类似。最终，登录对话框将超时，文本将被清除。[1]