导航

Geneva------构建基于声明的WCF 服务的更好方法(转)

Posted on 2009-03-16 16:58  鸡尾虾的壳  阅读(418)  评论(0)    收藏  举报
From: http://msdn.microsoft.com/zh-cn/magazine/dd278426.aspx
本文基于“Geneva”框架的预发布版本撰写而成。所有信息均有可能发生变更。

本文将介绍以下内容:
  • 使用 Geneva 框架实现安全性
  • WCF 安全性
  • 基于声明的安全性
  • 安全令牌服务
本文使用以下技术:
Windows Communication Foundation
“Geneva”框架(以前称为“Zermatt”)是用于构建基于声明的应用程序和服务以及实现联合安全方案的新框架代号。它的功能包括用于构建自定义安全令牌服务 (STS) 的探测功能、要求从 ASP.NET 应用程序进行联合身份验证的机制,以及简化 ASP.NET 应用程序和 Windows Communication Foundation (WCF) 服务的基于声明的授权的对象模型。
Geneva 框架还包括支持 Windows CardSpace 的功能(如托管信息卡颁发)和用于简化 Windows CardSpace 登录体验创建过程的 ASP.NET 控件。(有关 Windows CardSpace 的详细信息,请阅读“身份标识:使用 Windows CardSpace 保证您的 ASP.NET 应用程序和 WCF 服务的安全”。)显然,Geneva 框架包含多种安全功能,但其核心功能是基于声明的安全性。
虽然 WCF 一直以来都提供了对基于声明的安全模型的本机支持,但是 Geneva 框架简化了运行时对声明的访问过程并提供支持基于声明的授权的机制,使授权主体与已在 Microsoft .NET Framework 中提供的基于角色的授权主体一致,进而改善了此体验。ASP.NET 应用程序利用 Geneva 框架获得基于声明的授权功能,该框架与增强基于角色的安全性的现有 ASP.NET 登录控件兼容。在本文中,我将着重介绍实现基于声明的安全模型的价值、描述如何使用 Geneva 框架获得基于声明的 WCF 服务,并将此方法与 WCF 在不使用 Geneva 框架的情况下处理基于声明的安全性的方法进行比较。
在继续阅读本文之前,我建议您阅读一下由 Keith Brown 和 Sesha Mani 合著的针对开发人员的 Geneva 框架白皮书。该白皮书概述了 Geneva 框架的功能以及有关基于声明的安全性概念的一些背景,并介绍了如何在 ASP.NET 应用程序和 WCF 服务中启用这些功能(但以前者为重点)。此外,您还可以在 Keith Brown 于 2007 年 9 月发布的“安全简报”专栏中了解有关 WCF 和基于声明的安全性的详细信息。

为什么要使用基于声明的安全性?
您为什么希望移至基于声明的安全模型?在考虑使用 Geneva 框架实现解决方案之前,您必须知道此问题的答案。假定应用程序角色的定义从未发生过更改,并且只有一个身份验证机制会将安全主体映射到这些角色,则基于角色的安全性就已经足够了。但基于声明的安全模型有助于应用程序和服务的设计,以便它们不会绑定到特定凭据类型或特定角色组。这是基于声明的安全模型的价值主张之一。
从角色中分离应用程序和服务允许对角色名称和意义进行更改,而不会影响系统。可以为经过身份验证的用户分配适用于授权的更精细的项目 — 声明。声明可以根据经过身份验证的用户进行分配,如图 1 所示,也可以根据经过身份验证的用户的角色进行分配,如图 2 所示。
图 1 根据经过身份验证的用户分配声明
图 2 根据经过身份验证的用户的角色分配声明
后者仍然支持根据声明进行授权的模型,同时将角色用作在后台更轻松地对声明进行分组的一种方法。从这个角度来看,虽然受信任的颁发者通过添加安全权益来保证声明,但基于声明的安全性具有的属性类似于基于权限的安全性的属性。(请注意,图 1 和图 2 说明了如何为经过身份验证的用户分配名称声明以及如何分配这几个自定义权限声明 — Create、Read、Update 和 Delete。)
当声明和该声明向其授予权限的功能或资源之间的关联未发生更改(至少不经常发生更改)时,根据声明授予访问权限尤为有用。同时,规定如何为用户和角色分配声明的规则可以随意更改,并且不会影响授权逻辑。例如,系统中的所有删除操作可能都需要 Delete 声明,然而,引入新的超级用户角色时,管理员角色并不总是授予该声明。
基于声明的安全模型还可以帮助应用程序和服务支持多种凭据类型。一个简单(且常见)的示例是依靠同一域中内部用户的 Windows 凭据的应用程序和此域外的外部用户的自定义用户名和密码帐户。支持多种凭据类型可能会使身份验证过程和相关授权代码变得复杂。支持多种凭据类型的 ASP.NET 应用程序增加了每种凭据类型角色的登录支持、配置和初始化的复杂程度。
WCF 服务还需要其他工作,才能支持不同的凭据类型,包括配置不同凭据的行为和适当初始化 AuthorizationContext。如果 ASP.NET 应用程序和 WCF 服务可以接收包含所需声明(角色或其他内容)的凭据,则可以将授权工作规范化为通用规则集,并且不受对用户进行身份验证所采用的方式的限制。
理想情况下,STS 可以处理不同凭据类型的身份验证,并生成包含这些声明的安全令牌。此令牌很可能是 SAML(安全声明标记语言)令牌,可用于根据服务的数字签名对服务进行身份验证,并且该令牌生成的声明可用于授予访问权限。
联合安全方案本身也适用于基于声明的安全性。在联合安全方案中,可以使用用户自己的安全域对用户进行身份验证,而且由此域颁发的令牌可用于对另一个域进行身份验证。

安全令牌服务
STS 在基于声明的安全方案(无论该方案是否为联合方案)中起着重要作用。要根据声明授予访问权限,必须存在应用程序可以使用的受信任声明集。对用户进行身份验证后,STS 可以颁发包含请求声明的安全令牌。应用程序只需要信任安全令牌即可,这通常意味着信任其数字签名。图 3 描述了两个应用程序(依赖方),即 WCF 服务和 ASP.NET 应用程序,这两个应用程序都信任 STS 颁发的 SAML 令牌。通信的高级流程如下所示:
图 3 依赖方信任 STS 颁发的令牌
  1. 用户对 STS 进行身份验证。
  2. STS 为经过身份验证的用户分配声明,并构建 SAML 令牌以包含这些声明(如 SAML 属性)。STS 使用其私钥 (IPKey) 对令牌进行签名,并对令牌进行加密以用于使用根据请求提供的公钥 (RPKey) 的应用程序。
  3. 客户端应用程序或浏览器向应用程序(依赖方)出示令牌。此令牌在 WCF 方案中随消息一起传递,而在 ASP.NET 方案中作为 Cookie 进行传递。如果令牌签名可信 (IPKey),则其包含的声明对于授权也是可信的。
如果某一应用程序或服务支持多种凭据类型,则 STS 可以处理每种凭据类型的身份验证步骤,并颁发安全令牌,该令牌包含适合每个经过身份验证的用户的声明。同样,应用程序只需信任安全令牌即可,根本不用考虑 STS 所支持的对每个用户进行身份验证的凭据类型。图 4 说明了接受使用 Windows、用户名和密码、证书或 SAML 令牌进行身份验证的 STS,并为经过身份验证的用户生成适当的声明。
图 4 STS 对多种凭据类型进行身份验证
图 3 和图 4 说明了在相同安全域中使用 STS 的方案。在联合方案中,会在两个或多个安全域之间建立信任关系,以便用户可以对管理其凭据的域进行身份验证,同时仍可以获得另一个域中的资源的访问权限。
联合身份验证降低了与身份管理相关的许多风险。使用联合身份验证,就不必再跨多个应用程序或域维护用户凭据,这有助于降低与跨域配置和解除配置帐户相关的风险,如忘记删除多个位置的帐户。当无需管理帐户的多个副本时,自然也就不存在密码同步的问题了。除了上述优点,联合身份验证还有助于实施单一登录 (SSO) 方案,因为用户登录到一个应用程序后,就会授予该用户另一个应用程序(可能位于另一安全域中)的访问权限,不需要再次进行身份验证。图 5 说明了适用于 Web 应用程序的联合方案。流程如下所示:
  1. 用户浏览至域 B 中的 Web 应用程序,然后域 B 中的 STS 对该用户进行身份验证。
  2. STS 为该用户颁发 SAML 令牌,然后此令牌作为 Cookie 返回到浏览器。
  3. 用户浏览至域 A 中的 Web 应用程序,将 SAML 令牌作为 Cookie 进行传递。
  4. 域 A 中的 STS 信任令牌签名,这是因为它与域 B 中的 STS 存在信任关系。
  5. 域 A 中的 STS 为该用户颁发新的 SAML 令牌(包含与域 A 相关的声明)。此令牌作为 Cookie 返回到浏览器。
图 5 描述域之间信任关系的联合方案(单击图像可查看大图)
域 A 中的 STS 和域 B 中的 STS 之间的信任关系是指,前者接受后者颁发的令牌并以此作为身份验证的凭据。域 B 颁发的声明可能包括用户信息(用户名和电子邮件地址等)和域 A(即 DomainAReadOnly)所理解的权限。域 A 可以将这些合作伙伴声明转换为对域 A 托管的应用程序有意义的声明,例如,授予 Read 声明。
STS 颁发的声明是公开的,这意味着这些方案具有许多潜在的配置(如委派身份验证和联合验证),但这使您可以基本了解基于声明的安全性的用处所在以及这些声明的颁发方式。接下来,我将介绍实现 WCF 服务和 ASP.NET 应用程序的一些细节。

基于声明的安全性支持
先前,我在白皮书中曾讲过,Geneva 框架具有许多功能。您可以使用 Geneva 框架构建自定义 STS,而无需编写用于公开 WS-Trust 终结点或构建包含声明的 SAML 令牌的所有探测功能。通过此框架,您还可以颁发托管信息卡,以支持身份信息选择器,如 Windows 平台上的 Windows CardSpace。您可以更轻松地支持 ASP.NET 应用程序中的 Windows CardSpace 登录,并利用对 WCF 服务和 ASP.NET 应用程序的集成的、基于声明的支持。
Geneva 框架的主要目标是简化基于声明的应用程序的构建过程。要实现此目标,必须对所有基于 .NET 的应用程序通用的基于角色的现有安全模型进行基于声明的扩展、使用处理安全令牌的挂接功能提取各自在运行时交互的声明;在 ASP.NET 环境下,使用触发调用的机制,以调用可以颁发包含此应用程序所需声明的令牌的 STS。

Geneva 框架之前的 WCF 服务
WCF 服务旨在从一开始就支持基于声明的安全性。将消息发送给服务操作后,随该消息一起提供的每个安全令牌都将转换为一组声明,这些声明可通过执行操作的 ServiceSecurityContext 进行访问。如果安全令牌是 Windows 或 UserName 令牌,则开发人员通常会依赖附加到请求线程的安全主体,使用基于角色的经典安全性来授权调用。如果安全主体是一份证书或 SAML 令牌,则这些声明会更有意义,并且开发人员很可能会访问 AuthorizationContext 以评估声明。
假如有这样一个示例:WCF 服务公开“创建”、“读取”、“更新”和“删除”(CRUD) 操作,并希望根据用户对应用程序所拥有的 CRUD 权限来授权调用。DeleteSomething 操作要求经过身份验证的调用方拥有删除权限,这由自定义权限声明(如图 1 和图 2 中所示)表示。图 6 显示了用于在 AuthorizationContext 中搜索 Delete 声明的代码。该代码遍历 AuthorizationContext 中的声明集以查找此声明。如果在所有受信任的声明集中都找不到此声明,则将引发 SecurityException。但至少可以说,用这种方法搜索 AuthorizationContext 的代码非常复杂。Geneva 框架提供了评估声明的替代方法,大大简化了 WCF 中基于声明的授权。
public string DeleteSomething() {
AuthorizationContext authContext =
ServiceSecurityContext.Current.AuthorizationContext;
bool foundClaim = false;
foreach (ClaimSet cs in authContext.ClaimSets) {
Claim claim = new Claim("http://schemas.contoso.com/samples/
2008/09/claims/permission","Delete", Rights.PossessProperty);
if (cs.ContainsClaim(claim)) {
foundClaim = true;
break;
}
}
if (!foundClaim)
throw new SecurityException(
"Access is denied. Required claims not satisfied.");
return String.Format("DeleteSomething() called by user {0}",
System.Threading.Thread.CurrentPrincipal.Identity.Name);
}

颁发的令牌和 WSFederationHttpBinding
WCF 提供联合绑定,以支持 WCF 服务希望获得由 STS 颁发的令牌的方案。这通常是指 SAML 令牌,但这种要求并不严格。WSFederationHttpBinding 是原始标准绑定,支持颁发的令牌,而 WS2007FederationHttpBinding 是对此绑定的更新,支持此绑定采用的 WS-* 协议的最终版本。鉴于本讨论的目的,我将使用 WSFederationHttpBinding 配置 WCF 服务终结点,这些终结点需要由通过 Geneva 框架构建的自定义 STS 所颁发的 SAML 1.1 令牌。
在本示例中,服务约定 ICrudService 包含四个服务操作,如图 7 所示。这些操作表示 CRUD 操作,并且服务实现将依靠 CRUD 权限声明(如图 1 和图 2 中所示的声明)来授予对每个操作的访问权限。
[ServiceContract(Namespace="http://www.contoso.com/samples/2008/09")]
public interface ICrudService
{
[OperationContract]
string CreateSomething();
[OperationContract]
string ReadSomething();
[OperationContract]
string UpdateSomething();
[OperationContract]
string DeleteSomething();
}
图 8 说明了一个示例 WCF 配置,其中一个服务公开了 WSFederationHttpBinding 终结点及其相关的行为配置。绑定配置包括指明所需令牌格式 (SAML 1.1)、所需的 Claim 类型(名称和权限声明)以及 STS WS-Trust 终结点地址和元数据交换地址的设置。此配置提供的信息足以让客户端生成一个代理,此代理能够对 STS 进行身份验证以检索 SAML 令牌,然后使用此令牌对 WCF 服务进行身份验证。
<system.serviceModel>
<services>
<service name="ClaimsBasedServices.CrudService"
behaviorConfiguration ="serviceBehavior">
<endpoint contract="ClaimsBasedServices.ICrudService" binding=
"wsFederationHttpBinding" bindingConfiguration="wsFed"/>
<endpoint contract="IMetadataExchange" binding="mexHttpBinding"
address="mex"/>
</service>
</services>
<bindings>
<wsFederationHttpBinding>
<binding name="wsFed" >
<security mode="Message">
<message issuedTokenType="http://docs.oasis-open.org/wss/
oasis-wss-saml-token-profile-1.1#SAMLV1.1" >
<claimTypeRequirements>
<add claimType="http://schemas.xmlsoap.org/ws/2005/05/
identity/claims/name" isOptional="false"/>
<add claimType="http://schemas.contoso.com/samples/2008/09/
claims/permission" isOptional="false"/>
</claimTypeRequirements>
<issuer address="http://localhost:51213/TokenIssuer/Service.svc" />
<issuerMetadata address="http://localhost:51213/TokenIssuer/
Service.svc/mex" />
</message>
</security>
</binding>
</wsFederationHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="serviceBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceCredentials>
<issuedTokenAuthentication allowUntrustedRsaIssuers="false">
<knownCertificates>
<add findValue="IPKey" storeLocation ="LocalMachine"
storeName="TrustedPeople" x509FindType "FindBySubjectName"/>
</knownCertificates>
</issuedTokenAuthentication>
<serviceCertificate findValue="RPKey" storeLocation=
"LocalMachine" storeName="My" x509FindType="FindBySubjectName"/>
</serviceCredentials>
<serviceAuthorization principalPermissionMode="None" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
服务行为配置包含的一些设置值得说明一下。在 <serviceCredentials> 中,<issuedTokenAuthentication> 部分指示如何对 SAML 令牌进行身份验证。此配置指示只允许受信任的颁发者,也就是说,SAML 令牌必须由已知证书签署。此方案中的已知证书具有使用者名称 IPKey。STS 将使用 IPKey 签署 SAML 令牌,因此,SAML 令牌中包含的所有声明都将受到信任。虽然 Geneva 框架也具有指定受信任颁发者的配置,但启用 Geneva 框架时,本部分的内容仍适用。
<serviceAuthorization> 部分会影响进行身份验证后附加到请求线程的安全主体的类型。通常,对于已颁发的令牌方案来说,如果将此令牌方案配置为“自定义”,则需要您指定自定义授权策略(实现 IAuthorizationPolicy 的类型)。授权策略负责为请求线程创建安全主体(IPrincipal 类型),并将所有相关声明附加到 AuthorizationContext。在这种情况下,会将此类型设置为“无”,因为 Geneva 框架启用 WCF 服务后,此类型将不再有用。

为 WCF 服务启用 Geneva 框架
以 Geneva 框架功能为基础的基于声明的安全模型只需通过几个简单的步骤即可实现。第一步是添加对 Microsoft.Identity 程序集(属于 Geneva 框架的核心程序集)的引用。然后,必须针对 Geneva 框架运行时功能初始化每个 WCF 服务的 ServiceHost,其中包括向 ServiceHost 注册受信任的令牌颁发者列表。配置此框架的最终结果是,将安全主体附加到每个调用的请求线程中 — 获得受信任颁发者提供的声明集合以进行授权。
FederatedServiceCredentials 从 ServiceCredentials 类型派生而来,此类型公开一个静态方法,用于为 Geneva 框架初始化 ServiceHost 实例。图 9 说明了初始化 ServiceHost 的代码在初始化环境中的适当位置。
ServiceHost host = new ServiceHost(typeof(Services.RelyingParty));
try
{
FederatedServiceCredentials.ConfigureServiceHost(host, new
TrustedIssuerNameRegistry());
host.Open();
Console.ReadLine();
}
finally
{
if (host.State != CommunicationState.Faulted)
host.Close();
else
host.Abort();
}
public class TrustedIssuerNameRegistry : IssuerNameRegistry
{
public override string GetIssuerName(SecurityToken securityToken)
{
X509SecurityToken x509Token = securityToken as X509SecurityToken;
if (x509Token != null)
{
if (x509Token.Certificate.SubjectName.Name == "CN=IPKey")
{
return x509Token.Certificate.SubjectName.Name;
}
}
throw new SecurityTokenException("Token signature is not trusted
by the issuer name registry. ");
}
}
打开 ServiceHost 实例之前,先调用 ConfigureServiceHost 方法。此方法执行下列主要操作:读取 <microsoft.identityModel> 配置部分以初始化运行时的各个方面,如令牌处理程序、受信任的颁发者以及令牌有效性的最大时钟偏差。然后,将 principalPermissionMode(请参见图 8)设置为“自定义”,并添加名为 ServiceAuthorizationPolicy 的自定义授权策略(IAuthorizationPolicy 类型)。ServiceAuthorizationPolicy 负责创建 ClaimsPrincipal 类型和初始化 Thread.CurrentPrincipal。它还将删除其他授权策略和清除为请求添加到 AuthorizationContext 中的声明集。这是因为 Geneva 框架希望您继续依靠 ClaimsPrincipal 来评估声明,而不是使用 AuthorizationContext 进行评估。
图 9 中的代码将 IssuerNameRegistry 类型的实现传递到 ConfigureServiceHost,这样就不必在 <microsoft.identityModel> 配置部分中指定此类型了。对 GetIssuerName 的覆盖负责根据受信任的颁发者列表检查签署令牌。本示例使用硬编码的受信任颁发者名称 IPKey。稍后,我将讨论 <microsoft.identityModel> 部分的元素,因为它与为 WCF 服务配置 Geneva 框架相关。

请求声明
如果服务已启用 Geneva 框架,则 ClaimsPrincipal 实例将附加到每个请求线程,附带经过身份验证的用户的声明。要授权对应用程序代码和资源的访问权限,您可以遍历声明集合以查找特定 Claim 类型和值。以下代码可以访问 ClaimsIdentity 类型公开的“声明”集合,使用管理员角色的 Uri 值搜索角色声明:
ClaimsIdentity identity =     Thread.CurrentPrincipal.Identity as ClaimsIdentity;
if (!identity.Claims.Exists(c=>
c.ClaimType=="http://schemas.microsoft.com/ws/2006/04/identity/
claims/role" && c.Value=="http://schemas.contoso.com/
samples/2008/09/roles/administrators"))
throw new SecurityException("Access is denied.");
“声明”集合可能包含来自不同颁发者的声明,因此,可以检查特定颁发者的声明。但是,此步骤通常可有可无,因为既然已建立了对颁发者的信任关系,那么在授权访问时究竟是谁颁发了声明就不重要了。
虽然通过 ClaimsIdentity 实例直接访问“声明”集合非常便利,但 ClaimsPrincipal 提供了可用于请求角色声明的 IsInRole 实现,如下所示:
if !Thread.CurrentPrincipal.IsInRole
("http://schemas.contoso.com/samples/2008/09/roles/administrators"))
throw new SecurityException("Access is denied.");
此方法假定 STS 颁发具有与应用程序相关的适当值的角色声明。默认情况下,Geneva 框架中的角色声明由以下 Uri 表示:“http://schemas.microsoft.com/ws/2006/04/identity/claims/role”。实际角色可以是任何字符串,但是在本例中,它是指示应用程序角色的自定义 Uri:“http://schemas.contoso.com/samples/2008/09/roles/administrators”。
虽然 IsInRole 是执行基于声明的安全检查的一种便利方法,但是也可以使用 PrincipalPermission 和 PrincipalPermissionAttribute 执行经典权限请求。这两项都依靠附加到请求线程的 IPrincipal 类型,在本例中,ClaimsPrincipal 执行此任务。
编程请求可以通过动态方式执行:
PrincipalPermission p = new PrincipalPermission(null,
"http://schemas.contoso.com/samples/2008/09/roles/administrators",
true);
p.Demand();
请求将根据线程的 ClaimsPrincipal 执行 IsInRole 检查;如果无法满足此请求,也可以说,如果找不到指定的角色声明,则将引发异常。PrincipalPermission 实例的集合可以组合为一个 PermissionSet,以查看符合条件的几个角色之一。
当然,更好的解决方案是将 PrincipalPermissionAttribute 应用于服务操作,如下所示:
[PrincipalPermission(SecurityAction.Demand, Role =
"http://schemas.contoso.com/samples/2008/09/roles/administrators")]
public string CreateSomething()
此属性构建 PrincipalPermission 类型并执行相同的请求,但这要在执行应用此属性的操作之前完成。此模型的优点在于它的声明性特性,开发人员可以查看其服务操作所需的角色。还可以堆叠此属性,以便多个角色可以满足权限请求。

配置 <microsoft.identityModel>
Geneva 框架依赖新的配置部分初始化其环境:<microsoft.identityModel>。正如先前在白皮书中讨论的一样,Geneva 框架功能使用此配置部分配置 ASP.NET 和 WCF 应用程序,但在本部分中,我将着重介绍与启用 Geneva 框架的 WCF 服务相关的核心元素。
要启用对该新部分的支持,您的 app.config 或 web.config 中必须包含以下部分(具体取决于 WCF 服务的托管模型):
<configSections>
<section name="microsoft.identityModel"
type="Microsoft.IdentityModel.Configuration.
MicrosoftIdentityModelSection,
Microsoft.IdentityModel,
Version=0.4.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
</configSections>
<microsoft.identityModel> 配置部分并不包括任何所需的元素,但是您可以使用任何核心设置提供图 10 中列出的元素的声明性初始化。
名称 说明
audienceUris 指示 SAML 令牌处理所允许的受众 Uri。引用 SAML 令牌的功能,该功能可通过提供特定 Uri 限制使用令牌的位置。如果在 SAML 令牌中包含此 Uri,则服务可以根据其允许的 Uri 列表来对此 Uri 进行检查,以验证该令牌用于这些受信任的 Uri 之一。避免恶意使用令牌。
issuerNameRegistry 用于以声明的形式指示将用于验证令牌签名的 issuerNameRegistry 类型。
maxClockSkew 为令牌提供对有效时间窗口的控制以防止重放攻击。
securityTokenHandlers 用于为颁发的令牌自定义令牌处理程序类型。例如,SAML 1.1 和 SAML 2.0 处理程序。在本部分中,您可以自定义令牌处理程序的设置或替换令牌处理程序。自定义可以提供备用角色声明类型。
图 11 中显示了初始化这些值的配置部分的一个示例。在配置中指定 issuerNameRegistry 类型时,图 9 中所示的服务主机的初始化将发生更改,如下所示:
  FederatedServiceCredentials.ConfigureServiceHost(host);
<microsoft.identityModel>
<issuerNameRegistry type="Services.TrustedIssuerNameRegistry,
Services"/>
<SecurityTokenHandlers>
<remove type="Microsoft.IdentityModel.Tokens.Saml11.
Saml11SecurityTokenHandler, Microsoft.IdentityModel,Version=0.4.1.0,
Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add type="Microsoft.IdentityModel.Tokens.Saml11.
Saml11SecurityTokenHandler, Microsoft.IdentityModel,
Version=0.4.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
<samlSecurityTokenRequirement audienceUriMode="Never">
<nameClaimType value="http://schemas.contoso.com/ws/2005/05/
claims/name"/>
<roleClaimTypes>
<add value="http://schemas.contoso.com/samples/2008/09/claims/
permission"/>
</roleClaimTypes>
</samlSecurityTokenRequirement>
</add>
</SecurityTokenHandlers>
</microsoft.identityModel>
对于令牌处理程序,您可以创建自定义 securityTokenHandler,也可以使用本部分初始化现有令牌处理程序的功能,图 11 中显示的是后者。在本示例中,使用自定义名称和角色声明类型对 SAML 1.1 令牌处理程序进行初始化(请回忆一下先前白皮书讲述的内容,角色声明类型集合在 Geneva 框架的未来版本中可能会成为单一值,而不是集合)。此处指定的角色声明类型是“schemas.contoso.com/samples/2008/09/claims/permission”,这意味着 STS 可以颁发类似于图 1 或图 2 中所示的权限声明,并且 WCF 服务可以使用先前讨论的基于角色的经典安全方法来执行指定权限的权限请求。

使用 IIS 托管挂接 Geneva 框架
根据您对 WCF 的体验级别,当在 IIS 中托管服务时,您可能熟悉或不熟悉自定义 ServiceHost 初始化的机制,但这是您应该熟悉的,因为只有熟悉此机制,您才可以为 ServiceHost 启用 Geneva 框架。要挂接 ServiceHost 初始化,您应该在 @ServiceHost 指令中提供自定义 ServiceHostFactory 类型,如下所示:
<%@ ServiceHost Factory="ClaimsBasedServices.
ClaimsBasedServiceHostFactory"
Service="ClaimsBasedServices.CrudService" %>
在本示例中,ClaimsBasedServiceHostFactory 类型负责构建指定服务类型的 ServiceHost 实例。您可以直接在工厂中构建 ServiceHost 类型,然后在将该实例传递到运行时之前为 Geneva 框架服务初始化此类型,也可以创建自定义 ServiceHost 类型,然后覆盖 InitializeRuntime 方法以在打开服务通道之前提供自定义代码,图 12 中显示的是后者。
public class ClaimsBasedServiceHostFactory: ServiceHostFactory
{
public ClaimsBasedServiceHostFactory()
{
}
public override System.ServiceModel.ServiceHostBase CreateServiceHost(
string constructorString, Uri[] baseAddresses)
{
Type t = Type.GetType(string.Format("{0}, {1}", constructorString,
constructorString.Substring(0, constructorString.IndexOf("."))));
return new ClaimsBasedServiceHost(t, baseAddresses);
}
protected override System.ServiceModel.ServiceHost CreateServiceHost(
Type serviceType, Uri[] baseAddresses)
{
return new ClaimsBasedServiceHost(serviceType, baseAddresses);
}
}
public class ClaimsBasedServiceHost: ServiceHost
{
public ClaimsBasedServiceHost(object singletonInstance, params Uri[]
baseAddresses): base(singletonInstance, baseAddresses)
{
}
public ClaimsBasedServiceHost(Type serviceType, params
Uri[] baseAddresses) : base(serviceType, baseAddresses)
{
}
protected override void InitializeRuntime()
{
FederatedServiceCredentials.ConfigureServiceHost(this, new
TrustedIssuerNameRegistry());
base.InitializeRuntime();
}
}

从 WCF 到 Geneva 框架
当您为 WCF 服务启用 Geneva 框架时,尤其是,如果此时的您是一位经验丰富的 WCF 开发人员,并且已经使用 Geneva 框架之前的技术实现了基于声明的解决方案,可能会出现一些常见问题。您可能遇到的一个问题与 Geneva 框架不使用 System.IdentityModel.Claims 命名空间中的 ClaimSet 和 Claim 类型的原因有关。答案很简单。在经典的 WCF 中,要根据调用方的声明来授权调用方,您必须遍历 AuthorizationContext(一个复杂的编程模型,如上文所述)中的 ClaimSet 实例集合。Geneva 框架消除了与 ClaimSet 相关的一些复杂性并向 Claim 类型添加必要功能,从而改进了此模型。
ClaimSet 类型根据颁发者对声明进行分组。此模型具有一些限制,例如,如果颁发者不同,则无法合并两个 ClaimSet 实例。无法检查所有声明的单一集合。另一方面,Geneva 框架通过可在授权期间检查的 ClaimsIdentity 公开了单一的声明集合。运行时确定某一特定颁发者提供的声明可信后(因为颁发者可信),对授权代码造成影响的就是 Claim 类型和值,而不是颁发者。
Geneva 框架生成的声明集合使用 Microsoft.IdentityModel.Claims 命名空间中的新 Claim 类型。此 Claim 类型具有 Issuer 属性,因此,您仍然可以通过声明集合执行 LINQ 查询来收集来自特定颁发者的所有声明(如果您确实希望这样做)。
您很可能还希望了解为什么声明的 Issuer 属性是一个简单的 String 类型,而不是 ClaimSet。WCF 中的 Issuer 属性是一个递归属性,其中每个颁发者都由 ClaimSet 定义,而该 ClaimSet 可能包含由另一个 ClaimSet 定义的颁发者,依次类推,直至递归到根(自签名)颁发者。这不仅是一个处理起来比较困难的编程模型,而且在确定您信任令牌身份验证期间的临时颁发者后,此链条在运行时就变得没那么重要了。鉴于此原因,Geneva 框架将 Issuer 属性更改为颁发者的字符串表示形式,但仍然会使用令牌签名建立信任关系,字符串仅仅是以友好方式描述颁发者的一种方式。
看到启用 Geneva 框架后便会清除 AuthorizationContext 后,您也许也会感到惊讶。WCF 基于声明的安全功能特别依赖于 AuthorizationContext,因为 AuthorizationContext 是从受信任的安全令牌中提取而来的,供访问请求的可用声明集时使用。不幸的是,AuthorizationContext 在 WCF 服务之外没有任何意义,这限制了声明编程模型的应用。
Geneva 框架的目标是:更轻松地支持所有基于 .NET 的应用程序中的基于声明的安全性,同时利用 .NET 安全现有的各个方面。通过将 ClaimsPrincipal 分配给请求线程,便可轻松地将基于角色的现有安全性实现迁移到基于声明的安全性。此外,这为 WCF 服务和 ASP.NET 应用程序提供了一致的体验。使用安全主体的另一个优点是,创建新线程时,将使用与原始线程相同的安全主体来初始化这些线程。另一方面,AuthorizationContext 不会自动传递到新线程。
因此,假如您为 WCF 服务启用 Geneva 框架,则 Geneva 框架会清除 AuthorizationContext,而您也就不再依靠 AuthorizationContext 中的声明集来执行基于声明的安全检查。所以,当启用 Geneva 框架时,必须迁移 WCF 服务,该服务当前利用自定义授权策略填充 AuthorizationContext 并为请求线程创建安全主体。

将应用程序迁移至 Geneva 框架
阅读了面向开发人员的上述白皮书和本文后,您应该对使用 Geneva 框架构建基于声明的 WCF 服务和 ASP.NET 应用程序的要求有了很好的了解。让我来总结一下这些要求对 WCF 和 ASP.NET 的影响。
要为 WCF 服务启用 Geneva 框架,要求您至少执行下列步骤:使用一个联合安全绑定公开服务的终结点;为 Geneva 框架初始化 ServiceHost;提供受信任的颁发者列表;根据基于声明的安全模型配置角色声明类型;将权限请求应用到操作以根据角色声明授权调用。已经基于声明的 WCF 服务将需要进行一些更改,因为直接通过附加到请求线程的安全主体就可以访问声明,而不再需要通过 AuthorizationContext 了。
ASP.NET 应用程序可以利用 Geneva 框架实现联合安全方案,将身份验证委派给 STS,并使用受信任的颁发者(可通过 ClaimsPrincipal 和 ClaimsIdentity 实例访问)提供的声明来初始化请求线程。已基于角色的应用程序希望获得与移至基于声明的方案中的角色相匹配的声明,但新的应用程序可以选择使用更精细的声明,如本文中讨论的权限。从任一方面来讲,ASP.NET 登录控件和其他基于角色的安全权限请求都将查看附加到请求线程的 ClaimsPrincipal 以授权调用。在后续文章中,我将进一步介绍 Geneva 框架,以探讨如何为基于声明的联合安全方案构建自定义 STS。

Michele Leroux Bustamante 是 IDesign Inc. 的首席架构师、San Diego 的 Microsoft 区域总监和互联系统的 Microsoft MVP。她的最新著作是《学习 WCF》。可通过 mlb@idesign.net 或访问 idesign.net 与她取得联系。Michele 的博客网址是 dasblonde.net