构建自定义安全令牌服务
[转:http://msdn.microsoft.com/zh-cn/magazine/dd347547.aspx]
本文基于 "Geneva" 框架的预发布版本撰写而成。所有信息均有可能发生变更。
本文将介绍以下内容:
- 使用 Geneva 框架实现安全令牌服务
- 联合安全性
- 声明转换
本文使用了以下技术:
Windows Communication Foundation、ASP.NET、Geneva 框架
目录
安全令牌服务入门
构建自定义主动 STS
扩展 SecurityTokenService
托管和配置 STS
安全令牌处理程序
构建自定义被动 STS
FederatedPassiveTokenService 控件
SessionAuthenticationModule
用户身份验证
声明转换
总结
Microsoft 基于声明的访问 (CBA) 平台战略(代号为 "Geneva")包括 "Geneva" 框架、"Geneva" 服务器和 Windows CardSpace "Geneva"。Geneva 框架可为开发人员提供相关工具来构建基于声明的应用程序和服务(这些服务包括由“安全令牌服务”(STS) 所颁发的令牌),还提供相关工具来构建自定义 STS 和启用 CardSpace 的 Windows 应用程序。虽然 Geneva 服务器是企业级 STS,但 Geneva 框架可以为不需要企业级功能的环境构建自定义 STS。Windows CardSpace Geneva 由 Windows 客户端计算机中作为标识选择器和标识提供者的 Windows CardSpace 演变而来。
在我上一篇介绍 Geneva 框架的文章中,我曾讨论过依靠 STS 颁发的令牌来构建基于声明的 Windows Communication Foundation (WCF) 服务的一种较好方法。在这里,我将使用 Geneva 框架来构建自定义 STS。
在继续阅读本文之前,建议您先阅读一下由 Keith Brown 和 Sesha Mani 合著的Geneva 框架开发人员白皮书和我的上一篇文章“Geneva 框架构建基于声明的 WCF 服务的更好方法”。
安全令牌服务入门
无论是基于 Geneva 服务器的 STS 还是使用 Geneva 框架构建的 STS,其主要作用都是作为安全网关对调用方进行身份验证,并颁发附带描述调用方声明的安全令牌。从前面提到的文章中,您应该能够回想起 STS 身份验证支持多种方案:
- 从身份验证机制中分离出应用程序和服务,从而使其能够专注于授权相关声明。
- 支持多种凭据类型,而不会使应用程序和服务的实现变复杂。
- 支持联合方案,用户可以通过在自身的域中进行身份验证来获得对另一个域中资源的访问权限(通过建立不同域的 STS 之间的信任关系)。
- 简化标识委派方案,经过身份验证的用户将获得对下游服务的访问权限。
- 简化声明转换,使相关声明能够用于对应用程序和服务进行授权。
其中的任何方案都可以基于被动联合(基于浏览器)或主动联合(基于 Windows 客户端)。下面我将详细介绍这些方案,同时说明如何使用 Geneva 框架在自定义 STS 中构建相关逻辑。
在深入探讨 STS 的实现之前,让我们先来回顾一些基础知识。在主动联合中使用的 STS 是 WS-Federation 主动请求者配置文件(请参阅 WS-Federation TC)和(主要)WS-Trust 规范(请参阅 WS-Trust 1.3)的实现。
从较高层次看,WS-Trust 使用四种服务操作来描述一个约定:颁发、验证、续订和取消。客户端分别调用这些操作来请求安全令牌、验证安全令牌、续订已过期的安全令牌以及取消不应再继续使用的安全令牌。根据 WS-Trust 规范,每个操作都必须以“请求安全令牌”(RST) 的格式发送消息,并以“RST 响应”(RSTR) 的格式返回消息。在本文中,我假定颁发的令牌是“安全声明标记语言”(SAML) 1.1 或 SAML 2.0 令牌。
图 1 展示了主动令牌颁发时 RST 和 RSTR 的核心内容。RST 消息包括请求安全令牌所需的必要信息,其中涉及待颁发令牌的类型(在本讨论中是 SAML)、将要被纳入到颁发令牌中的“依赖方”(RP) 所请求的声明、有关 RP (AppliesTo) 的信息(包括 URL 和通常用于标识 RP 的证书)以及将被用于 RSTR 所返回的“证明密钥”(proof key) 的可选(未显示)密钥材料。
图 1 主动联合方案的令牌颁发
如果令牌颁发成功,RSTR 将包括颁发的 SAML 令牌和证明密钥(假定 STS 决定使用哪个证明密钥,因此必须在 RSTR 中返回它)。SAML 令牌将包括已验证方的相关声明,它由 STS 签名来保护令牌不被篡改,令牌中将包含用于 RP 加密的证明密钥,并且它会为 RP 加密自身以确保只有目标接收节点才能处理该令牌。
客户端使用证明密钥来签署发往 RP 的消息。RP 必须能够在 SAML 令牌中解密证明密钥,否则拒收该消息。如果令牌中的证明密钥与消息中的签名匹配,则证明此次对 RP 的调用是由请求该令牌的调用方所发送的。
被动联合方案基于 WS-Federation 被动请求者配置文件,其中涉及基于浏览器的通信。虽然底层消息传递仍基于 WS-Trust,但 RST 却在 STS URL 中被分解为查询字符串参数,而 RSTR 通常会作为表单参数被发布到 RP。被动 STS 和 RP 使用联合 Web 处理程序来截取这些参数。被动 STS 可以直接处理 WS-Trust 请求,或者将其传递到底层 WS-Trust 实现。图 2 说明了在被动联合方案中对 RST 和 RSTR 的处理方式。
图 2 被动联合方案的令牌颁发
主动与被动联合方案的一个本质差异在于颁发的 SAML 令牌的类型。主动联合通常依靠使用“密钥所有者”(holder-of-key) 类型主体确认的 SAML 令牌,这意味着正如我前面所述,在实际当中存在着一个证明密钥,它可以证明发出令牌进行验证的客户端是请求该令牌(也称为 ActAs 行为)的主体。被动联合方案通常涉及带有“持有者”(bearer) 类型主体确认的 SAML 令牌(也称为持有者令牌)。这种类型的令牌不包含证明密钥,有时也称为无密钥令牌。它依靠传输来确保从 STS 获得该令牌并将其传递给 RP。
这些概念(WS-Federation、WS-Trust 和 SAML 令牌)是下面要讨论的主题的重要背景信息。首先,我将说明如何使用 Geneva 框架来构建主动 STS。然后,我将讨论如何构建被动 STS,最后会介绍一些它们各自的扩展方案。
构建自定义主动 STS
在简单的主动联合方案中(如图 3 所示),通常存在以下参与者:
- RP,由客户端调用的服务。
- 单独的 STS(也作为服务实现),支持 WS-Trust 协议。此 STS 将对调用方进行身份验证并颁发具有标识调用方声明的安全令牌,也称为标识提供者或 IP-STS。
- 客户端,在本例中为基于 Windows 的应用程序,它依靠代理对 STS 进行身份验证、检索令牌颁发结果以及发送消息给 RP(它会提供用于身份验证和授权的颁发令牌)。
图 3 使用单一 RP 和主动 IP-STS 的简单联合方案
图 4 说明了在此实现中可以灵活设置的部分。其中包括自定义 SecurityTokenService 实现、使用 ServiceHost 扩展 (WSTrustServiceHost) 初始化用于联合的运行时、使用 WSTrustContract 类型的派生配置一个或多个 WS-Trust 端点以及用于标识模型运行时的其他相关配置设置。在接下来的章节中,我将回顾基于 Geneva 框架的自定义 STS 实现中的各个要素。
图 4 自定义主动 STS 和主动 IP-STS 的实现体系结构
扩展 SecurityTokenService
Geneva 框架通过 Microsoft.IdentityModel.SecurityTokenService 命名空间中的 SecurityTokenService 类型为构建自定义 STS 提供核心功能。此抽象类担负着传递 RST 和 RSTR 消息和生成安全令牌的重任。自定义 STS 类型将继承该类并提供(至少)以下功能:
- 构造函数,用于接受自定义 SecurityTokenServiceConfiguration 实例以配置 STS 的一些基本功能(稍后讨论)。
- GetScope 的重载函数,用于验证请求的目标 RP,并为该 RP 提供正确的加密凭据和用于安全令牌的签名凭据。
- GetOutputClaimsIdentity 的重载函数,用于为生成的安全令牌提供声明。
图 5 显示了使用此功能实现的简单自定义 STS 的部分代码。请回想一下在图 1 和图 2 中介绍过的主动 STS 通信流程。STS 的实现 IdentitySTS 将在调用 GetScope 时验证传入 RST——通过验证 RST 的 AppliesTo 元素是否确实指向信任的 URI。假定 STS 管理着一组可为其颁发令牌的可信 RP 及其证书。如果 AppliesTo 通过了验证,GetScope 将把该范围的 EncryptingCredentials 属性设置为正确的证书,在本例中为 "RPKey"。此外,SigningCredentials 属性也将被设置为正确的证书,此证书将被用于为颁发的令牌签名。通常情况下,它是 STS 的私钥,在本例中为 "IPKey"。
图 5 简单的自定义 STS 实现
public class IdentitySTS : SecurityTokenService { public IdentitySTS(SecurityTokenServiceConfiguration config) : base( config ) { } protected override IClaimsIdentity GetOutputClaimsIdentity( IClaimsPrincipal principal, RequestSecurityToken request, Scope scope) { IClaimsIdentity claimsIdentity = new ClaimsIdentity(); claimsIdentity.Claims.Add(new Claim(ClaimTypes.Name, principal.Identity.Name)); claimsIdentity.Claims.Add(new Claim(ClaimTypes.Role, "Users")); return claimsIdentity; } protected override Scope GetScope( Microsoft.IdentityModel.Claims.IClaimsPrincipal principal, RequestSecurityToken request) { Scope scope = new Scope(request); scope.EncryptingCredentials = this.GetCredentialsForAppliesTo( request.AppliesTo); scope.SigningCredentials = new X509SigningCredentials(CertificateUtil.GetCertificate(StoreName.My, StoreLocation.LocalMachine, "CN=IPKey")); return scope; } private X509EncryptingCredentials GetCredentialsForAppliesTo(Endpoint Address appliesTo) { if (appliesTo == null || appliesTo.Uri ==null || string.IsNullOrEmpty(appliesTo.Uri.AbsolutePath)) { throw new InvalidRequestException( "AppliesTo must be supplied in the RST."); } X509EncryptingCredentials creds = null; if (appliesTo.Uri.AbsoluteUri.StartsWith( "http://localhost:8000/RelyingPartyService")) { creds = new X509EncryptingCredentials( CertificateUtil.GetCertificate(StoreName.TrustedPeople, StoreLocation.LocalMachine, "CN=RPKey")); } else throw new InvalidRequestException(String.Format( "Invalid relying party address: {0}", appliesTo.Uri.AbsoluteUri)); return creds; } }
当调用 GetOutputClaimsIdentity 时,运行时将传递 ClaimsPrincipal 及经过验证的调用方标识。此标识通常用于决定要授予调用方的适当声明。在图 5 中,代码将为调用方生成一个名称声明和硬编码的角色声明,并会以 ClaimsIdentity 的形式返回。此 ClaimsIdentity 将向运行时提供待颁发令牌的声明。
此 STS 实现还可以针对以下功能进行扩展:
- GetOutputClaimsIdentity 可以包括用于在自定义凭据存储库中查找用户及查找其他声明的代码。例如,角色列表、有关用户的其他相关详细信息(如电子邮件地址),或者代表更精细应用程序权限(如创建、读取、更新或删除)的自定义声明。
- GetScope 可以在列出所有可信 RP 及其关联证书的自定义数据库中查找 AppliesTo URI。
托管和配置 STS
如果熟悉 WCF,那您一定清楚必须配置一个或多个端点才能使客户端将消息发送到某个服务。对于 STS 而言,将要用于各个端点的服务约定必须基于 WS-Trust 协议,其中包括四个操作:颁发、验证、续订和取消。事实上,STS 能够实现两个版本的 WS-Trust 协议:
- WS-Trust 1.3:最新版本的 WS-Trust 规范。
- WS-Trust 2005 年 2 月:许多行业合作伙伴在等待标准通过认可期间实施的 WS-Trust 版本。
您还可以在 SecurityTokenService 类型中提供 GetScope()、GetOutputClaimsIdentity() 等方法的异步实现。这将提高 I/O 密集型操作(如访问证书或与声明数据交互)的可伸缩性。为 STS 配置端点时,必须选择要为端点公开的约定。Microsoft.IdentityModel.Protocols 命名空间包括两个用于 STS 端点的服务约定:IWSTrust13SyncContract 和 IWSTrustFeb2005SyncContract。图 6 显示了具有两个端点的 STS 服务配置,每个端点都有一个约定。请注意还有用于实现异步代理的异步版本的约定:IWSTrust13AsyncContract 和 IWSTrustFeb2005AsyncContract。
图 6 具有多个 WS-Trust 端点的 STS 服务配置
<service name= "Microsoft.IdentityModel.Protocols.WSTrust.WSTrustServiceContract" behaviorConfiguration="stsBehavior"> <endpoint address="WSTrustFeb05" binding="wsHttpBinding" contract="Microsoft.IdentityModel.Protocols.WSTrust. IWSTrustFeb2005SyncContract"/> <endpoint address="WSTrust13" binding="wsHttpBinding" contract="Microsoft.IdentityModel.Protocols.WSTrust. IWSTrust13SyncContract"/> </service>
STS 应该基于 WS-Trust 2005 年 2 月的版本公开端点,以便向后兼容早期的客户端。实现这两种约定的服务类型是 Microsoft.IdentityModel.Protocols.WSTrust 命名空间中的 WSTrustServiceContract 类型。这是一个应在 STS 的 <service> 配置部分引用的类型。
如图 4 中的图表所示,<service> 配置及其端点被用来使用正确的 WSTrustServiceContract 类型初始化主机。在主机初始化期间,还会使用对自定义 SecurityTokenService 实现的引用来初始化此类型。这就是运行时直接将消息传递到自定义 STS 的方式。
在图 6 中,两个 STS 端点都依赖于 Windows 凭据来验证调用方(wsHttpBinding 的默认行为)。STS 可以使用其他绑定配置来公开多个端点,以便支持不同的凭据类型。这还包括为每种凭据类型配置正确的安全令牌处理程序。稍后我将讨论令牌处理程序的配置设置。
Geneva 框架将提供用于托管 STS 实例的自定义 ServiceHost 类型 WSTrustServiceHost。下面的代码说明了如何在自托管环境中构造 WSTrustServiceHost 类型:
WSTrustServiceHost stsHost = new WSTrustServiceHost(new IdentitySTSConfiguration()); stsHost.Open();
WSTrustServiceHost 依赖自定义 SecurityTokenServiceConfiguration 实例来初始化 WS-Trust 端点的运行时、启用 STS 的元数据交换行为以及配置元数据交换端点。
在 IIS 中托管时,WSTrustServiceHostFactory 类型用于存档相同的结果。在 .svc 文件中,@ServiceHost 配置将指定工厂类型和自定义 SecurityTokenServiceConfiguration 类型,如下所示:
<%@ ServiceHost Factory="Microsoft.IdentityModel.Protocols.WSTrust. WSTrustServiceHostFactory" Service="STS.IdentitySTSConfiguration" %>
工厂在激活后将使用指定的配置来初始化 WSTrustServiceHost。
为 STS 初始化 WSTrustServiceHost 时,必须使用自定义的 SecurityTokenServiceConfiguration 类型。图 7显示了名为 IdentitySTSConfiguration 的自定义实现。
图 7 自定义 SecurityTokenServiceConfiguration
public class IdentitySTSConfiguration: SecurityTokenServiceConfiguration { public IdentitySTSConfiguration(): base("http://localhost:8010/sts") { this.TokenIssuerName = "http://localhost:8010/sts"; this.SigningCredentials = new X509SigningCredentials(CertificateUtil.GetCertificate(StoreName.My, StoreLocation.LocalMachine, "CN=IPKey")); this.SecurityTokenService = typeof( IdentitySTS); } }
此类型必须为 STS 提供一个 URI、签名的凭据以及对与该配置相关联的 STS 类型的引用。在 STS 颁发托管卡时,URL 必须有效,否则 Windows CardSpace 将无法导入这些卡。基类型要求为 TokenIssuerName 的构造函数传入一个字符串值,但我建议在代码中重载它,以便能够从配置中动态设置 URI,而不是硬编码这个需要传入构造函数的值。
SecurityTokenServiceConfiguration 类型也公开了一些属性,它们可以用于设置密钥大小和令牌类型的默认值、禁用对元数据的访问、控制令牌生存期和时钟偏差、设置自定义 RST 和 RSTR 序列化程序、配置用来验证调用方的令牌处理程序以及配置 WS-Trust 端点。
下面的示例显示了如何禁用元数据访问以及如何通过编程方式而不是依赖 <service> 配置设置来初始化 WS-Trust 端点(类似于图 6 中所示):
IdentitySTSConfiguration config = new IdentitySTSConfiguration(); config.DisableWsdl = true; config.TrustEndpoints.Add(new ServiceHostEndpointConfiguration("WSTrustFeb05", new WSHttpBinding(), typeof(IWSTrustFeb2005SyncContract))); config.TrustEndpoints.Add(new ServiceHostEndpointConfiguration( "WSTrust13", new WSHttpBinding(), typeof(IWSTrust13SyncContract))); WSTrustServiceHost stsHost = new WSTrustServiceHost(config);
基本 SecurityTokenServiceConfiguration 类型还通过读取 <microsoft.identityModel> 配置部分来初始化相关的 STS 配置设置。能够为自定义 STS 进行声明性配置的设置包括最大时钟偏差、用于身份验证和颁发的安全令牌处理程序、声明验证管理器、颁发者名称注册以及令牌解析程序。图 8 显示了一些为 STS 配置的有用设置。
图 8 STS 的 配置
<microsoft.identityModel> <maximumClockSkew value="00:05:00"/> <claimsAuthenticationManager type="STS. CustomClaimsAuthenticationManager, STS"/> <securityTokenHandlers> <remove type="Microsoft.IdentityModel.Tokens. WindowsUserNameSecurityTokenHandler, Microsoft.IdentityModel, Version=0.5.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> <add type="Microsoft.IdentityModel.Tokens. MembershipUserNameSecurityTokenHandler, Microsoft.IdentityModel, Version=0.5.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"> <usernameSecurityTokenHandlerRequirement membershipProvider="CustomProviders.CustomMembershipProvider, CustomProviders, Version=1.0.0.0, Culture=neutral, PublicKeyToken=c03h5a64f15d0b3f" /> </add> </securityTokenHandlers> </microsoft.identityModel>
对于非 Windows 凭据,会调用一种自定义的 ClaimsAuthenticationManager 类型,它可以在 GetOutputClaimsIdentity 中颁发声明之前向运行时提供自定义 ClaimsPrincipal 类型。自定义安全令牌处理程序可能被配置为提供一些设置来覆盖特定处理程序的默认行为,或更改特定凭据类型的安全令牌处理程序的选项。
安全令牌处理程序
您可能需要一种服务行为配置来确定为每个 STS 端点进行身份验证和授权时所采用的方式。请回想一下,我在上一篇文章中曾讲过 Geneva 框架处理事情的方式略有不同。对于身份验证来说,<securityTokenHandlers> 集合将指定可用于验证传入请求的 SecurityTokenHandler 类型。
此集合只能为每个 SecurityTokenHandler 类型包含一个项目。例如,只能为各凭据类型注册一个 KerberosSecurityTokenHandler、UserNameSecurityTokenHandler、X509SecurityTokenHandler、Saml11SecurityTokenHandler 或 Saml2SecurityTokenHandler 来处理请求。如果 STS 端点需要 UserName 凭据,则默认注册的 UserNameSecurityTokenHandler 是 WindowsUserNameSecurityTokenHandler。图 8说明了如何删除 WindowsUserNameSecurityTokenHandler 并添加 MembershipUserNameSecurityTokenHandler 作为其替换处理程序——包括与成员关系提供者相关的配置设置。
您还可以创建自定义 SecurityTokenHandler 类型。请记住,只要它们是从与令牌类别(例如 UserNameSecurityTokenHandler)匹配的相应基类派生而来的,那么您就可以使用新的自定义处理程序来替换默认处理程序。图 9 说明了名为 CustomUserNameSecurityTokenHandler 的自定义 UserNameSecurityTokenHandler 实现。
图 9 自定义 UserNameSecurityTokenHandler
public class CustomUserNameSecurityTokenHandler: UserNameSecurityTokenHandler { public override ClaimsIdentityCollection ValidateToken(SecurityToken token) { UserNameSecurityToken userNameToken = token as UserNameSecurityToken; AuthenticateUser(userNameToken.UserName, userNameToken.Password); return new ClaimsIdentityCollection(new IClaimsIdentity[] { new ClaimsIdentity( new Claim(System.IdentityModel.Claims.ClaimTypes.Name, userNameToken.UserName), "CustomUserNameSecurityTokenHandler")}); } public override bool CanValidateToken { get { return true; } } }
至少,必须在自定义 SecurityTokenHandler 实现中重载 ValidateToken 和 CanValidateToken。在 ValidateToken 内部,您要负责根据相应的凭据存储库来验证身份。该验证的结果应该是一组声明,此声明可被返回给运行时以便附加到请求线程的 ClaimsPrincipal。
图 10 使用单一 RP 和被动 IP-STS 的简单联合方案
重载 CanValidateToken 并返回 True 也非常重要。如果没有这一重载,令牌处理程序将无法注册到集合中,从而无法被调用。
构建自定义被动 STS
在简单的被动联合方案中,也存在着与主动联合方案同样的参与者(如图 3 所示),不同之处在于其客户端是浏览器而 RP 是 Web 应用程序,另外 IP-STS 还需要作为 Web 应用程序的前端以处理基于 HTTP 的通信。图 10 说明了被动联合的参与者和通信流。
对于核心 STS 而言,此方案中可以灵活设置的部分与图 4 中所示的内容相似,但在被动 STS 对身份验证的处理方式和对底层 STS 功能的调用方式方面也存在着明显的差异。图 10 中的图表从较高级别说明了这些不同之处。被动 STS 是作为网站来实现,它需要 SSL 加密以确保令牌颁发过程的安全。默认页面 (Default.aspx) 承载了一个通过底层自定义 STS 来促进通信的控件,它的配置过程与主动 STS 一样。
STS 站点必须在令牌颁发之前验证调用方,而这正是传统的 ASP.NET 配置执行其验证和授权功能的地方。在图 11 中,STS 应用程序被配置为用于窗体身份验证,因此如果请求尚未经过 FormsAuthenticationModule 的验证,则它们将被重定向到登录页面 (Login.aspx)。
只需进行一个细微的改动,被动 STS 就可以与主动 STS 共享相同的核心 STS 实现。在 GetScope 重载函数中(如图 5 所示),被动 STS 必须设置 ReplyToAddress 属性,只有这样 STS 才能在颁发令牌后重定向。这通常设置为 RP 的默认页面(根据随 RST 提供的 AppliesTo 地址而定):
Scope scope = new Scope(request); scope.ReplyToAddress = scope.AppliesToAddress + "/default.aspx"; // other scope settings
图 11 使用窗体身份验证的被动 STS 的实现体系结构
被动 STS 的 Geneva 框架配置与主动 STS 并无二致。SecurityTokenServiceConfiguration 类型被用于初始化 STS(如图 7 所示),另外还需要考虑 <microsoft.identityModel> 配置部分中的所有相关设置。
FederatedPassiveTokenService 控件
Geneva 框架提供了一个实现被动 STS 必要功能的控件。具体来说,它可以处理登录和注销 HTTP 请求,并可以将每个请求转换到 RST 然后再调用底层 STS 实现。此控件还处理 RSTR 响应和 RP 重定向,并为经过验证的调用方编写会话 cookie。
请按照如下方式将此控件放在被动 STS 站点的默认页面并将其 Service 属性设置为自定义 SecurityTokenServiceConfiguration 实现:
<idfx:FederatedPassiveTokenService ID="FederatedPassiveTokenService1" runat="server" Service="STS.IdentityProviderSTSConfiguration, STS"> </idfx:FederatedPassiveTokenService>
此控件要求用户必须经过身份验证,它会在其 PreRender 事件中对此进行检查。需要对 STS 站点进行适当配置以确保在用户到达此默认页面之前将其重定向到其他页面进行身份验证。
只要经过身份验证的用户始终定向到此默认页面,则无需其他配置即可处理请求。此控件还提供 Error、PreSignInRequested、PostSignInRequested、PreSignOutRequested 和 PostSignOutRequested 事件来处理异常以及挂接登录和注销请求。
SessionAuthenticationModule
作为 FederatedPassiveTokenService 控件的替代方法,您可以通过编程方式启用被动 STS 功能。首先在 <microsoft.identityModel> 配置部分启用联合验证:
<microsoft.identityModel> <federatedAuthentication enabled="true"/> </microsoft.identityModel>
然后,为被动 STS 启用联合模块,即来自 Microsoft.IdentityModel.Web 命名空间的 SessionAuthenticationModule 模块:
<modules> <add name="SessionAuthentication" type="Microsoft.IdentityModel.Web.SessionAuthenticationModule, Microsoft.IdentityModel, Version=0.5.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> </modules>
这将得到与 FederatedPassiveTokenService 控件相同的结果,请求将被发送到 STS 网站中的任意页面。该模块将把未验证的调用方重定向到登录页面。成功登录后,调用方将被重定向到最初请求的 STS 页面。这种编程方法为开发人员提供了除 FederatedPassiveTokenService 控件以外的其他控件。例如,该模块公开了以下事件以便与初始化、安全令牌管理、登录和注销进行交互:ConfigurationLoading、ConfigurationLoaded、SecurityTokenReceived、SecurityTokenValidated、SessionSecurityTokenCreated、SessionSecurityTokenReceived、SignedIn、SigningOut、SignedOut、SignInError 和 SignOutError。
用户身份验证
STS 站点负责根据支持的凭据类型来验证用户。虽然使用 WCF 实现的主动 STS 能够轻松配置多个端点来支持不同的身份验证机制,但被动 STS 只能支持 ASP.NET 网站本身配置的一种身份验证机制。因此,必须为每种支持的身份验证机制提供不同的被动 STS 站点。当然,这些站点都可以共享相同的核心 STS 实现。
被动 STS 身份验证基于 ASP.NET 配置技术。典型选项是 Windows 身份验证、窗体身份验证和 Windows CardSpace 身份验证。对于 Windows 身份验证,将使用以下配置:
<authentication mode="Windows"/> <authorization> <deny users="?"/> </authorization>
在用户到达 STS 默认页面前,会通过交互式对话框对其进行身份验证,因此在这种情况下不需要自定义登录页面。
图 11 说明了使用窗体身份验证的示例。FormsAuthenticationModule 将未验证的调用重定向到登录页面,在这里用户可以提供凭据。通过验证后,登录页面将重定向到默认 STS 页面,在这里联合控件将继续处理最初的请求。对应的 ASP.NET 配置如下:
<authentication mode="Forms"/> <authorization> <deny users="?"/> </authorization>
对于 Windows CardSpace 身份验证,可以将 STS 站点配置为窗体身份验证,但登录页面将包括一个 InformationCard 控件用于 Windows CardSpace 身份验证。
声明转换
声明转换对于联合的安全性至关重要——它可能在联合过程中的任意时刻发生。在 IP-STS 和 RP 属于同一域的简单联合方案中,IP-STS 负责将声明从身份验证期间用户所提供的一组初始身份标识声明转换为 RP 能够用以授权调用的声明。用户可以为 IP-STS 提供任何支持的凭据类型来进行身份验证,其中每个都会评估一组代表该凭据的声明。
IP-STS 将这些声明转换成一组标准的应用程序声明,RP 可以通过这组声明授权调用——它们可以是角色或更精细的声明,如创建、读取、更新或删除等权限。图 12 中的图表对这种方案进行了说明,其中 Admin 用户登录并被授予 Role 声明和几项 Action 声明,其中包括创建、读取、更新和删除等。
图 12 IP-STS 声明转换
这种类型的声明转换非常有用,因为对 STS 的身份验证会得到一个令牌,其中包含授予该用户的所有声明。也有一些情况需要使用其他方法进行声明转换;例如为了减少颁发给那些仅与当前调用上下文相关的声明、为了保护声明的隐私性,或者为了促进跨域联合等。
向经过验证的调用方授予与 RP 公开的所有功能相关的完整声明列表既无必要,有时可能也并不合适。这不但会使列表变得非常长,而且还可能待授予的声明取决于调用上下文,因此在不具备该上下文的情况下不应授予该声明。例如,如果某用户正在与客户进行订单交互,则只授予 Delete 声明即可,但如果他正直接与客户记录交互,则不应授予该权限。
在其他一些类似情况中,如果 RP 能够只从 IP-STS 请求少量声明即可标识调用方,然后使用一组特定的仅适用于该调用上下文的附加声明来请求一个新令牌,则将会非常有帮助。例如,如果用户正在 RP 服务中调用 DeleteCustomer 操作,在授权执行此操作之前,RP 将会调用从 IP-STS 传入令牌的 RP-STS,并在 DeleteCustomer 操作的上下文中请求 Delete 声明。如果该声明存在,则调用将获得授权。图 13 中的图表进一步说明了此示例。
此外,有时还存在 STS 颁发的声明不应与 RP 直接共享的情况。例如,可以不必通过颁发 Age 声明来使 RP 了解用户的年龄,RP 可以请求 IsOver13 声明来确保调用方的年龄符合使用 RP 功能的条件。这样可以确保 Age 声明的实际值不会离开 STS。当然,这也表明 ST 将提供既能避免共享个人详细信息又可以包含 RP 所需数据的声明。
图 13 RP-STS 声明转换
当属于一个域的用户被授予对另一个域中的 RP 的访问权限时,联合方案中也会发生声明转换。在这种情况下会涉及两个 STS——用户域的 IP-STS 和 RP 所属域的 RP-STS。
同样也是在这种情况下,IP-STS 会授予一些 RP-STS 能够理解且达成一致的声明;但这些声明可能无法在 RP 应用程序中直接使用。相反,RP-STS 可能会负责将另一组可信声明转换为能够被 RP 域所理解的声明。
图 14 说明了这种方案。当 Joe 试图在没有令牌的情况下访问 RP 时,他将登录到自己所在域(域 B)的 IP-STS。该请求将申请 RP 能够理解的声明(在本例中为 RPClaim),以便 IP-STS 知道应该颁发 RP 能够使用的令牌。当 RP-STS 接收到此令牌后,它将把该声明转换为 RP 专用的声明。为了使此联合方案能够工作,必须在 RP-STS 和 IP-STS 之间建立信任关系,而且它们必须对 IP-STS 应该为其用户颁发的将被授予对 RP 访问权限的一组声明达成一致。
图 14 联合方案中的声明转换
总结
对于那些热衷于构建自定义 STS 而又不需要 STS 平台全部功能(如 Geneva 服务器)的用户而言,Geneva 框架是一种非常受欢迎的实用工具。但即便使用 Geneva 框架,构建自定义 STS 也不是一项简单的任务,强烈建议您尽量使用完整功能的 STS 以减少出现安全漏洞的风险。
无论使用什么平台,主动和被动 STS 实现的通信流程都是一致的,并且声明转换的原理也相同。与 STS 实现相关的其他概念还包括标识委派和逐级验证。您可以在 Geneva 框架 SDK 中找到与这些及其他概念相关的示例和文档。
Michele Leroux Bustamante 是 IDesign Inc. 的首席架构师、圣地亚哥的 Microsoft 区域总监和互联系统的 Microsoft MVP。她的最新著作是《学习 WCF》。可通过 mlb@idesign.net 或访问 idesign.net与她取得联系。Michele 的博客网址是 dasblonde.net。