新闻资讯
看你所看,想你所想

CredentialProvider

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 登录体系结构
图 1 GINA 登录体系结构
计算机上已注册的 GINA 载入到 winlogon 进程空间中。(还可能载入一个称作“GINA 连结”的配置,但测试和支持这样的複杂配置很困难。)最后,GINA 调用 LogonUser 以及相关的身份验证 API。
在 Windows Vista 中,会话 0 不再用于互动式登录(请参见图 2)。这有利于提高安全性,因为已有一个会话边界将所有的计算机进程与各个用户的进程分隔开来。此外,对核心全局命名空间的控制也更加严格,因为默认情况下由用户应用程式创建的对象已不在核心全局命名空间之内。
图 2 新的登录体系结构图 2 新的登录体系结构
图 2 新的登录体系结构
除会话0 之外的每一个会话仍会有一个 winlogon 实例。图 2 显示,系统中已注册了几个凭据提供程式,并已通过新的 LogonUI 进程载入。
在由哪个组件负责显示登录图形界面方面也有一个重要的改动。以前,这是由 GINA 来处理的,因此,显示界面的工作可能一直由第三方组件来完成。在新的体系结构中,这是由作业系统的一个内置组件 LogonUI 来负责完成的。
那幺每个提供程式的用户提示行为在新的模型中是如何实现的呢?“凭据提供程式”体系结构要求每个提供程式都要列举说明它所需要的 UI 元素。例如,在某个指定的方案中,提供程式可能会向 LogonUI 表明它需要两个编辑框、两个标题、一个複选框和一个点阵图。然后,LogonUI 为凭据提供程式显示这些控制项。这对实现以前讨论的目标大有帮助,即用一致的外观和方法来广泛支持不断修改完善的用户验证方案。
负责“凭据提供程式”开发的 Microsoft 开发团队原以为外部开发人员会更愿意基于 COM 来开发外挂程式模型。然而,在 Windows Vista 开发周期的早期阶段,新接口最初的内部设计(类似于 GINA)完全基于LoadLibrary 和函式指针。之后,基于 COM 的重新设计吸取了第一次的教训,使得设计出来的界面更加简洁和易用。

混合凭据程式

此新外挂程式模型的计时功能臻于完美(当然,或许早就应该有这样的功能了)。开发人员可以更轻鬆地满足多因素身份验证方案需求,同时提供与 Microsoft 原有的相一致的登录体验。
儘管如此,新的接口仍显得相当抽象。有关它的描述说明也同样令人费解,让人感到乏味!要想了解它,一个让人能提起兴趣的方式就是体验一下新凭据提供程式的设计、开发和测试过程。而且,这可以很好地弥补 Microsoft 提供的现有文档的不足。

混合凭据要求

混合凭据提供程式应达到以下这些要求:
使之基于智慧卡运行
最大限度地增加代码的重用
最大限度地减少额外配置和基础结构需求

设计

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);
};
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);
};
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);
};
混合凭据提供程式调用序列
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 登录萤幕中只键入部分密码然后便暂停时所发生的情况类似。最终,登录对话框将逾时,文本将被清除。

相关推荐

声明:此文信息来源于网络,登载此文只为提供信息参考,并不用于任何商业目的。如有侵权,请及时联系我们:yongganaa@126.com