专注于互联网--专注于架构

最新标签
网站地图
文章索引
Rss订阅

首页 »Windows » vista自定义桌面:使用 Windows Vista 的凭据提供程序创造自定义的登录体验 »正文

vista自定义桌面:使用 Windows Vista 的凭据提供程序创造自定义的登录体验

来源: 发布时间:星期二, 2009年2月10日 浏览:12次 评论:0
  本文举例源代码或素材下载

目录

  新旧两种体系结构比较

  混合凭据提供

  要求

  设计

  混合凭据提供

  混合方式实现

  改进可能性

  测试和调试

  智能卡和

  Windows Vista 在平台集成方面为开发人员提供了许多新机会凭据提供模型是变动最大方面的由于它出现实现操作系统支持新用户身份验证方案变得容易了许多它已取代了 GINA(图形标识和身份验证)模型而直言不讳地说后者开发人员难以理解和实现以及昂贵 Microsoft 支持费用而广为诟病

  那么 Windows® 登录插件接口个变化竟会如此令人兴奋其原因何在?用户打开计算机时首先看到是登录屏幕由于登录体验是由凭据提供来控制和管理这使得自定义登录体验以及集成最符合组织需要身份验证思路方法变得容易了许多简而言的凭据提供为开发和实现更好、更可靠安全性提供了种更容易方式

  新旧两种体系结构比较

  我不想过细地阐述基于 GINA 登录体系结构不过花点时间比较下这两个体系结构帮助您更好地理解新体系结构以及其中变动这也很值当

  在 Windows Vista™ 的前环境中每个会话都有个 winlogon 例子它负责控制该会话交互式登录序列(图 1 显示了 Windows XP 和 Windows Server® 2003 旧登录体系结构)在刚启动系统中控制台位置交互式登录始终在会话 0 中执行会话 0 承载运行系统服务以及其他关键进程包括“本地安全机构”(Local Security Authority) 进程(换句话说在会话 0 中运行许多进程都没有在图 1 中显示出来)



  图 1GINA 登录体系结构

  计算机上已注册 GINA 加载到 winlogon 进程空间中(还可能加载个称作“GINA 链接”配置但测试和支持这样复杂配置很困难)最后GINA LogonUser 以及相关身份验证 API

  在 Windows Vista 中会话 0 不再用于交互式登录(请参见图 2)这有利于提高安全性现在已有个会话边界将所有计算机进程和各个用户进程分隔开来此外现在对内核全局命名空间控制也更加严格默认情况下由用户应用创建对象已不在内核全局命名空间的内



  图 2新登录体系结构

  除会话 0 的外个会话仍会有个 winlogon 例子图 2 显示系统中已注册了几个凭据提供并已通过新 LogonUI 进程加载

  在由哪个组件负责显示登录图形界面方面也有个重要改动以前这是由 GINA 来处理因此显示界面工作可能直由第 3方组件来完成在新体系结构中这是由操作系统个内置组件 LogonUI 来负责完成

  那么每个提供用户提示行为在新模型中是如何实现呢?“凭据提供”体系结构要求每个提供都要列举介绍说明它所需要 UI 元素例如在某个指定方案中提供可能会向 LogonUI 表明它需要两个编辑框、两个标题、个复选框和个位图然后LogonUI 为凭据提供显示这些Control控件这对实现以前讨论目标大有帮助即用外观和思路方法来广泛支持不断修改完善用户验证方案

  负责“凭据提供”开发 Microsoft 开发团队(Team)原以为外部开发人员会更愿意基于 COM 来开发插件模型然而在 Windows Vista 开发周期早期阶段新接口最初内部设计(类似于 GINA)完全基于 LoadLibrary 和指针的后基于 COM 重新设计吸取了第教训使得设计出来界面更加简洁和易用现在我们转到举例代码来帮助引导我们深入了解凭据提供接口

  混合凭据提供

  此新插件模型计时功能臻于完美(当然或许早就应该有这样功能了)现在开发人员可以更轻松地满足多原因身份验证方案需求同时提供和 Microsoft 原有登录体验

  尽管如此接口仍显得相当抽象有关它描述介绍说明也同样令人费解让人感到乏味!要想了解它个让人能提起兴趣方式就是体验下新凭据提供设计、开发和测试过程而且这可以很好地弥补 Microsoft 提供现有文档不足 - 有关指针方面内容请参阅侧栏“其他资源”

  我创建了个举例即“混合凭据提供它会演示些全新功能混合凭据提供允许将用户名、密码和域名存储在智能卡上插入智能卡后用户会自动登录(可从MSDN® 杂志网站WebSite下载举例代码)我没有重新编写代码而是将以下 3个来源代码合在起:

  Microsoft® Windows SDK 中提供基于密码凭据提供举例

  以前 PropCert 举例同样来自 SDK其核心是个用于读取基于证书智能卡凭据 Win32® 线程

  这篇文章讨论了如何通过托管代码和 Windows 智能卡子系统建立接口

  对于我在 2006 年 11 月发表那篇文章中所提供举例代码我需要作进介绍说明凭据提供体系结构及其主机只支持本机代码尽管我篇文章讲主要是托管代码但其中也包含了个本机帮助 DLL以便于公开新智能卡模块接口混合凭据提供便基于这个帮助 DLL如需该 DLL 完整源代码同样可以通过 2006 年 11 月发表那篇文章随附下载内容中获得

  总的混合凭据提供代码中有很大部分都不是新写其最终结果是将测试和调试时间减到最少实际上核心调试阶段花费了不到时间这证明了新接口易用性

  现在详细地讨论下我使用此举例凭据提供所要执行操作

  要求

  构思此混合凭据提供我希望达到以下这些要求:

  使的基于智能卡运行

  最大限度地增加代码重用

  最大限度地减少额外配置和基础结构需求

  因此我采用了我所谓混合思路方法换言的就是密码(为了安全起见)加智能卡(为了方便起见)由于混合提供概念是基于用户名和密码因此我将从 Platform SDK 分离出来密码提供举例作为我设计该思路方法起点然后我添加了来自 SDK PropCert 举例;其中包括了用于枚举智能卡读卡器、卡和数字证书逻辑过程我觉得我要做就是将 PropCert 中基于证书逻辑代码替换为用于读取我自己凭据数据些新代码然后简单地将这两个举例连在起就可以了!

  由于我们将从智能卡读取密码登录信息这意味着还有个需求:个使用该凭据化智能卡工具化工具将放在最后讨论

  根据这些要求我们来看看凭据提供体系结构设计以及它如何影响我举例代码设计

  设计

  我们先从运行时凭据提供角度来讨论下凭据提供体系结构设计

  虽然我尚未详细讨论混合举例但我将使用它作为分析运行中新凭据提供体系结构基础为了便于讨论举例代码包含了调试跟踪此跟踪包含从每个已实现凭据提供例程进行 OutputDebugString 在这些跟踪我使用两个缩写对新 ICredentialProvider 接口(请参见图 3)以“Provider::”开头对 ICredentialProviderCredential 接口(请参见图 4)以“Credential::”开头请注意所有凭据提供相关接口都是在新公共头文件 credentialprovider.h 中定义

Figure4ICredentialProviderCredential 接口

ICredentialProviderCredential : public IUnknown
{
  HRESULT STDMETHODCALLTYPE Advise(
    /* [in] */ ICredentialProviderCredentialEvents *pcpce);
  
  HRESULT STDMETHODCALLTYPE UnAdvise( void);
  
  HRESULT STDMETHODCALLTYPE SetSelected(
    /* [out] */ BOOL *pbAutoLogon);
  
  HRESULT STDMETHODCALLTYPE 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,
    /* [][out] */ LPWSTR *ppsz);
  
  HRESULT STDMETHODCALLTYPE GetBitmapValue(
    /* [in] */ DWORD dwFieldID,
    /* [out] */ HBITMAP *phbmp);
  
  HRESULT STDMETHODCALLTYPE GetCheckboxValue(
    /* [in] */ DWORD dwFieldID,
    /* [out] */ BOOL *pbChecked,
    /* [][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,
    /* [][out] */ LPWSTR *ppszItem);
  
  HRESULT STDMETHODCALLTYPE SetStringValue(
    /* [in] */ DWORD dwFieldID,
    /* [][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);
};
Figure3ICredentialProvider 接口

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);
  HRESULT STDMETHODCALLTYPE Advise(
    /* [in] */ ICredentialProviderEvents *pcpe,
    /* [in] */ UINT_PTR upAdviseContext);
  HRESULT STDMETHODCALLTYPE UnAdvise( void);
  HRESULT STDMETHODCALLTYPE GetFieldDescriptorCount(
    /* [out] */ DWORD *pdwCount);
  HRESULT STDMETHODCALLTYPE 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);
};
  了解这些缩写后我们来看图 5 中调试事件列表这些事件是在某举例方案期间发生(我会详细介绍其中大部分事件)用于生成序列方案非常简单首先将 Windows Vista 工作站加入域然后用您用户名、密码和域名配置智能卡再将智能卡插入连接到该工作站读卡器然后重新启动系统

Figure5混合凭据提供序列

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; do name field)
25. Credential::GetFieldState (dwFieldID = 4; do 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 枚举在 HKLMSoftwareMicrosoftWindowsCurrentVersionAunticationCredential Providers 下注册所有凭据提供每个提供 DLL 会被加载并接收到个 Provider::CreateInstance 对于混合凭据提供这将创建个 CHybridProvider(请参见图 5 中步骤 1 到 4)

  用户现在将看到登录屏幕假定用户按了 Ctrl+Alt+Delete 并且每个提供都收到了 Provider::SetUsageScenario CPUS_LOGON 通知这向提供表明用户想进行交互式登录现在该混合凭据提供将尝试从所插入任何智能卡中读取凭据如果找到了个可读智能卡会将个 CHybridCredential 例子化并将其和当前 CHybridProvider 关联然后将有个对 Credential::Initialize (请参见图 5 中步骤 5 到 7)

  LogonUI 随后为每个加载提供 Provider::AdviseAdvise 是为提供提供种机制将对可见 UI 元素(当前还未创建)所做任何预期更改通知给 LogonUI内置智能卡提供给出了有关如何使用该机制个很好例子化的后无论何时插入卡都会增大可用凭据数而取出卡则会减小该数字发生此类情况时将通过这种机制通知 LogonUI:

ICredentialProviderEvents : public IUnknown
{
  HRESULT STDMETHODCALLTYPE CredentialsChanged(
    /* [in] */ UINT_PTR upAdviseContext);
};
  为简单起见混合凭据提供不对卡插入和取出进行动态处理因此它不跟踪通过 Advise 传递给它 ICredentialProviderEvents 接口

  LogonUI 执行个接口 Provider::GetCredentialCount即图 5 中步骤 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个域:个位图、个用户名输入域、个密码输入域、个提交按钮和个域名输入域即使实际上这些元素从不显示您仍可以看到混合凭据提供中保存了这些元素这将完成图 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)

0

相关文章

读者评论

发表评论

  • 昵称:
  • 内容: