通过 WIF 实现基于声明的授权

在过去的几年中,联合安全模型和基于声明的访问控制日趋流行。在联合安全模型中,可以通过安全令牌服务 (STS) 来执行身份验证,STS 可以颁发携带声明的安全令牌,这些声明可以对被验证用户的身份标识和用户访问权限进行断言。如果建立了域间的信任关系,则通过使用联合安全模型,用户可以在自己的域内进行验证,同时获权访问其他域内的应用程序和服务。通过这种方式,单个用户不必设置和管理重复的帐户,并且可以启用单一登录 (SSO) 方案。基于声明的访问是联合安全模型的核心,通过此方式,应用程序和服务将根据来自受信任域中颁发者 (STS) 的声明来授权对相应特性和功能的访问。声明可以包含有关用户、角色或权限的信息,这构成了一种非常灵活的授权模型。联合安全与基于声明的访问相结合使用,可在更广的生态系统中启用跨应用程序、部门和合作伙伴的一系列集成方案。

这一领域的平台工具也取得了长足的进步。Windows Identity Foundation (WIF) 是一个功能丰富的身份标识模型框架,设计用于构建基于声明的应用程序及服务,并用于支持主动和被动的联合安全方案。使用 WIF,可以为任何 ASP.NET 应用程序启用被动联合,并且可以将基于声明的授权模型集成到您的 ASP.NET 应用程序和 WCF 服务中,整个过程不费吹灰之力。并且,WIF 提供了用于构建自定义 STS 实现的探测功能,并包含用于支持身份验证方案的功能和控件,这些身份验证方案涉及托管信息卡和诸如 Windows CardSpace 的身份选择器。

对于涉及到基于声明的联合安全性的丰富应用程序方案,WIF 显著减少了实现这些方案所需的代码。本文分为两个部分,我将着重介绍框架中的以下核心功能:在 ASP.NET 应用程序中启用被动联合;在 WCF 和 ASP.NET 中支持基于声明的安全模型。本文将着重介绍 WCF,下一篇文章着重介绍 ASP.NET。

为什么要使用联合的和基于声明的安全性?

联合的和基于声明的安全性的优点体现于实现以下各种目标:

  • 将身份验证机制从应用程序和服务中分离出来。
  • 使用声明替代角色,作为更加灵活精确的授权对象。
  • 降低 IT 部门设置和撤消用户的难度。
  • 对受信任域(其中可能包括外部联合合作伙伴)授予对应用程序特性和功能的访问。

即使您的应用程序方案只涉及以上目标中的一个,采用或间接引入联合安全性的基于声明的模型都会让您受益匪浅。

在设计应用程序和服务时,需要设计身份验证和授权模型。例如,Intranet 应用程序一般需要用户使用他们的 Windows 凭据进行到特定的域身份验证,而 Internet 应用程序一般使用诸如 Microsoft SQL Server 的自定义凭据存储。应用程序还可要求进行证书或智能卡身份验证,或者支持多种凭据类型,以便不同组的用户可以使用其适用的类型。如果您的应用程序只(并且始终)要求用户使用单一类型凭据进行身份验证,那么您的工作会比较简单。然而更常见的情况是,应用程序所支持的凭据类型可以演化为支持各种同样有效的身份验证模式,或支持适合不同组用户的其他模式。

例如,某个应用程序可能既支持某个域内防火墙后面的内部用户,又支持 Internet 上的外部用户。当某个应用程序的安全模型与身份验证模式分离时(基于声明的模型即如此),引入新的身份验证模式对应用程序几乎毫无影响。

同理,如果授权不与固定的角色集进行捆绑,应用程序会更加灵活。如果您的应用程序进行访问授权时始终依赖一组特定角色,并且这些角色在功能和特性的访问权限方面始终具有不变的含义,则您的工作也会比较容易。但是,这些角色的含义时常根据使用这些应用程序的部门发生变化,因此需要自定义这些角色。这可能意味着需要根据用户的域进行不同的角色评估,或允许创建自定义角色以控制访问权限。WIF 使基于声明的安全模型易于采用,因此您可以从授权机制中将角色分离出来(如果条件适合)。通过这种方式,可以将逻辑角色映射到更加精细的声明集,并且应用程序可以根据这些声明进行访问授权。如果更改的角色或新角色授权发布一组新的声明,则应用程序不会受到影响。

当然,声明的意义远不止角色或权限。使用基于声明的模型的附加优点之一是声明可以携带被验证用户的信息,如电子邮件地址、全名和出生日期等。例如,使用声明还可在不透漏用户实际年龄或出生日期(许多用户不希望公开的信息)的情况下对信息进行验证。声明可以指示某位用户是否达到了执行某操作的规定年龄(布尔型声明指示 IsOver21 或 IsOver13),也可在不透露某位用户所在部门完整列表的情况下验证该用户是否属于某特定部门。

虽然从应用程序和服务分离出身份验证机制和特定角色之后,对更改的处理将变得更加容易,但基于声明的模型仍然是联合安全方案的核心,因为此模型大大简化了对任何受信任域内的用户进行访问授权的过程。联合安全方案减少了身份标识管理方面的 IT 开销和风险。使用这种方案,无需再跨多个应用程序和域维护用户凭据,这有助于减少跨域设置和撤消帐户时的风险(如忘记在多个位置删除某个帐户)。由于不再管理帐户的多个副本,密码同步问题也不复存在。联合安全方案还能帮助实现 SSO 方案,因为用户在登录一个应用程序之后,无需再次进行身份验证即可获权访问另一个应用程序(可能会跨安全域)。最后一点,通过使用诸如 Active Directory Federation Server (ADFS) 和 WIF 的联合安全平台,在域之间添加新的信任关系也变得更加容易。将应用程序扩展到企业实体内的其他域,甚至扩展到外部合作伙伴域的工作也因此更容易进行。

通过 WIF 实现主动联合

主动联合方案基于 WS-Federation 主动请求者配置文件(请参见 WS-Federation TC,网址为:oasis-open.org/committees/tc_home.php?wg_abbrev=wsfed)以及 WS-Trust 规范(请参见 WS-Trust 1.3,网址为 docs.oasis-open.org/ws-sx/ws-trust/200512/ws-trust-1.3-os.html)。从宏观的角度看,WS-Trust 使用四种服务操作来描述一个约定:颁发、验证、续订和取消。客户端分别调用这些操作来请求安全令牌、验证安全令牌、续订过期的安全令牌以及取消不应再继续使用的安全令牌。根据 WS-Trust 规范,每个操作都必须以“请求安全令牌”(RST) 的格式处理消息,并以“RST 响应”(RSTR) 的格式发送响应。这些 WS-Trust 功能通过 STS(令牌颁发者)来实现,STS 在所有联合安全方案中都是重要参与者。

图 1 演示了一个简单的主动联合方案。此方案涉及一个 Windows 客户端应用程序(请求者),一个 WCF 服务(依赖方,即 RP)和一个属于 RP 域的 STS (RP-STS)。如图所示,客户端使用一个 WCF 代理协调到 RP-STS 的首次身份验证,然后请求一个安全令牌,继而调用 RP,随请求一起传递颁发的安全令牌。


图 1 简单主动联合方案

在此方案中,RP-STS 也是用于进行用户对 RP 域的身份验证的身份标识提供程序 (IdP)。这表示 RP-STS 负责对用户进行身份验证,断定这些用户的身份,并颁发与 RP 相关的声明以用于授权。RP 会验证安全令牌是否由 RP-STS 颁发,并根据颁发的声明授权访问。

我创建了一个 Todo List 应用程序,以帮助讨论此方案的实现。附带的代码示例包含一个 WPF 客户端,一个 WCF 服务以及一个通过 WIF 实现的主动 STS。为提供进一步的上下文,WCF 服务 TodoListService 实现了图 2 所示的 ITodoListService 约定。客户端使用 WCF 代理调用该服务,以获取所有 Todo 项并添加、更新或删除各项。TodoListService 根据创建、读取、更新和删除声明来授权对其操作的访问。

图 2 ITodoListService 定义

                复制代码            
[ServiceContract(Namespace="urn:TodoListApp/2009/06")]
public interface ITodoListService
{
    [OperationContract]
    List<TodoItem> GetItems();
    [OperationContract]
    string CreateItem(TodoItem item);
    [OperationContract]
    void UpdateItem(TodoItem item);
    [OperationContract]
    void DeleteItem(string id);
}

要实现此主动联合方案,您需要按以下四个步骤操作:

  1. 公开 TodoListService 的联合安全 WCF 端点。
  2. 为客户端应用程序生成一个 WCF 代理,并使用进行 RP-STS 的身份验证的凭据来初始化该代理。
  3. 为 TodoListService 启用 WIF,以便启用基于声明的授权。
  4. 设置权限要求 (IsInRole) 或其他授权检查,以控制对服务操作或其他功能的访问。

我将在接下来的部分中讨论这些步骤。

公开联合端点

基于声明的 WCF 服务一般公开收到所颁发令牌(如基于 SAML 标准的令牌)的联合端点。WCF 提供两个绑定,用于支持 WS-Trust 联合安全方案。WSFederationHttpBinding 是基于 WS-Trust 2005(该协议的早期版本)的原始标准绑定,WS2007FederationHttpBinding 是该绑定的最新版本(随 Microsoft .NET Framework 3.5 一起发布),该版本支持得到审批的 WS-Trust 1.3 标准。一般来说,除非互操作性要求规定使用早期版本,否则应使用 WS2007FederationHttpBinding。基于 ADFS 版本 2 或 WIF 的 STS 可支持 WS-Trust 的任何一个版本。

公开服务的联合端点时,通常会提供有关预期安全令牌格式、要求的和可选的声明类型以及受信任令牌颁发者的信息。图 3 显示了 TodoListService 的 system.serviceModel 列表,该列表在 WS2007FederationHttpBinding 上公开了一个联合端点。

图 3 由 TodoListService 公开的联合端点

                复制代码            
<system.serviceModel>
  <services>
    <service name="TodoList.TodoListService" 
behaviorConfiguration="serviceBehavior">
      <endpoint address="" binding="ws2007FederationHttpBinding" bindingConfiguration="wsFed" contract="Contracts.ITodoListService" />
      <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
      <host>
        <baseAddresses>
          <add baseAddress="http://localhost:8000/TodoListService"/>
        </baseAddresses>
      </host>
    </service>
  </services>
  <bindings>
    <ws2007FederationHttpBinding>
      <binding name="wsFed">
        <security mode="Message" issuedTokenType=
“http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-.1#SAMLV1.1" issuedKeyType="SymmetricKey" negotiateServiceCredential="true">
          <message>
            <claimTypeRequirements>
              <add claimType= 
“http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" isOptional="false"/>
              <add claimType= "urn:TodoListApp/2009/06/claims/permission" 
isOptional="false"/>
            </claimTypeRequirements>
            <issuerMetadata address="http://localhost:8010/rpsts/mex" />
          </message>
        </security>
      </binding>
    </ws2007FederationHttpBinding>
  </bindings>
  <behaviors>
    <serviceBehaviors>
      <behavior name="serviceBehavior">
        <serviceMetadata/>
      </behavior>
    </serviceBehaviors>
  </behaviors>
</system.serviceModel>

联合安全方案通常依赖于 SAML 令牌,但对此没有严格要求。此方案使用的是 SAML 1.1 令牌,如 issuedTokenType URI (docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.0.pdf) 所示。对于其他令牌类型,如 SAML 1.0 或 SAML 2.0,请使用该标准相应的 URI。当然,前提是联合绑定配置中指明的 STS 必须支持您所请求的令牌类型。

消息部分中的其他相关设置包括 issuedKeyType 和 negotiateServiceCredential。issuedKeyType 设置指示证明密钥(请参见 blogs.msdn.com/vbertocci/archive/2008/01/02/on-prooftokens.aspx)是对称密钥(默认)还是非对称密钥(开销较大)。此设置也必须与 STS 兼容。如果将 negotiateServiceCredential 设置为 true,则客户端开始不需要访问 RP 公钥,但协商不是可互操作的协议。如果客户端不是 WCF 客户端,则应将 negotiateServiceCredential 设置为 false。不过别担心。如果将其设置为 false,则使用进行的 SvcUtil 代理生成将为客户端提供 RP 公钥的 base64 编码副本。

claimTypeRequirements 部分中提供的声明类型指示服务用来进行授权的声明类型是必需还是可选的。在这种情况下,该服务需要一个名称声明来标识用户,并至少需要一个权限声明,即一个自定义声明类型,来指示创建、读取、更新或删除 Todo 项的用户权限(这些声明类型在后面的图 4 中列出)。声明类型列表包含在服务元数据中,以便使客户端能够在 RST 中包含此信息。STS 往往知道其要针对特定 RP 颁发的声明,这表示联合绑定中不需要在列表中包含全部内容。

图 4 针对 Todo 列表应用程序方案颁发的每用户声明

此方案中的受信任令牌颁发者是 RP-STS,此颁发者恰好是使用 WIF 实现的。RP-STS 在 http://localhost:8010/rpsts 公开一个 WS-Trust 端点,其元数据交换地址位于 http://localhost:8010/rpsts/mex。在图 3 中,在 issuerMetadata 部分中提供颁发者元数据地址,这样当客户端生成代理时,可以发现可用的 STS 端点。

假设 STS 要公开多个端点,例如,要在 http://localhost:8010/rpsts/internal 使用 Windows 凭据对 Intranet 用户进行身份验证;要在 http://localhost:8010/rpsts/external 地址使用用户名和密码对 Internet 用户进行身份验证。RP 服务可能会选择指定一个与其联合端点配置关联的特定颁发者端点,以便当客户端生成代理时,与 STS 进行通信的配置与该端点相匹配,而不是与第一个兼容的端点相匹配。实现此目的的途径是为 issuerMetadata 和颁发者元素都提供地址,如下所示:

                复制代码            
<issuerMetadata address="http://localhost:8010/rpsts/mex" />
<issuer address="http://localhost:8010/rpsts/mex/external" />

此方法的优点为:当存在多个可选的 STS 端点,并且 RP 希望影响端点选择时,能够简化客户端代理的生成过程。如果 RP 不介意客户端进行到哪个端点的身份验证,则最好只提供 issuerMetadata 设置,并由客户端应用程序决定适合身份验证的端点。

请记住,如果服务配置省略了 issuerMetadata 元素,只提供了颁发者地址,则该地址应计算为颁发者的逻辑 URI (http://localhost:8010/rpsts/issuer),此地址不必映射到物理 STS 端点地址。客户端中的等效配置会提示用户选择来自同一颁发者的托管信息卡(通过 Windows CardSpace),该卡也必须符合所要求的令牌格式和声明类型条件。有关使用 Windows CardSpace 的主动联合方案的更多信息,请参见 wpfandcardspace.codeplex.com

生成客户端代理

当使用 SvcUtil 或 Add Service Reference 生成 Windows 客户端代理时,颁发者的元数据交换地址用于收集有关颁发者公开的端点的信息。在此重申,以下是一些可能的方案:

  • 如果 RP 服务端点的联合绑定提供颁发者元数据地址,而未提供特定颁发者地址,则客户端配置将包含第一个与协议兼容的 STS 端点,并对所有其他兼容端点进行注释以供客户端开发人员选用。
  • 如果 RP 服务的联合绑定提供颁发者元数据地址和特定颁发者地址,则客户端配置将包含该特定地址(假设该地址与协议兼容)。
  • 如果 RP 服务的联合绑定只提供元数据地址,则客户端配置也会只包含数据地址,而不包含颁发者的绑定配置。这表示将触发诸如 CardSpace 的身份选择器(如上所述)。

假设客户端生成了一个 TodoListService 个代理(配置如图 3 所示),并且 STS 只公开一个端点,则 WS2007FederationHttpBinding 配置的客户端版本将包含以下颁发者和 issuerMetadata 设置:

                复制代码            
<issuer address="http://localhost:8010/rpsts" 
        binding="ws2007HttpBinding" 
        bindingConfiguration="http://localhost:8010/rpsts">
  <identity>
    <certificate encodedValue="[base64 encoded RP-STS certificate]" />
  </identity>
</issuer>
<issuerMetadata address="http://localhost:8010/rpsts/mex" />

请注意,颁发者元素指定颁发者端点和与该端点进行通信所需的绑定配置。在这种情况下,客户端使用消息安全性通过用户名和密码进行到 STS 的身份验证,如以下 WS2007HttpBinding 配置所示:

                复制代码            
<ws2007HttpBinding>
    <binding name="http://localhost:8010/rpsts" >
        <security mode="Message">
            <message clientCredentialType="UserName" 
                     negotiateServiceCredential="false" 
                     algorithmSuite="Default" 
                     establishSecurityContext="false" />
        </security>
    </binding>
</ws2007HttpBinding>

客户端端点将联合绑定配置与 RP 端点相关联:

                复制代码            
<client>
  <endpoint address="http://localhost:8000/TodoListService" 
            binding="ws2007FederationHttpBinding" 
            bindingConfiguration="wsFed"
            contract="TodoList.ITodoListService" name="default">
    <identity>
      <certificate encodedValue="[base64 encoded RP certificate" />
    </identity>
  </endpoint>
</client>

使用此配置,在调用服务之前客户端代理只需使用有效的用户名和密码进行初始化:

                复制代码            
TodoListServiceProxy _Proxy = new TodoListServiceProxy("default");

if (!ShowLogin()) return;

this._Proxy.ClientCredentials.UserName.UserName = this.Username;
this._Proxy.ClientCredentials.UserName.Password = this.Password;
this._TodoItems = this._Proxy.GetItems();

颁发令牌

代理首先提供凭据以进行 RP-STS 的身份验证,此时它发送请求 SAML 1.1 令牌的 RST,并指示 RP 至少需要一个名称和一个权限声明。将根据 STS 凭据存储对该用户进行身份验证,并为通过身份验证的用户颁发合适的声明。然后,该代理处理携带已颁发的令牌的 RSTR,并将该令牌传递到 RP,以便为经过身份验证的用户建立安全会话。

此示例使用 WIF 构建 STS,并根据自定义凭据存储对用户进行身份验证,根据图 4 为每一位用户颁发声明。

清注意,基于 ADFS 版 2 的 STS 根据 Windows 域对用户进行身份验证,并且根据您的 ADFS 配置颁发声明。基于 WIF 的自定义 STS 可以根据您所选择的凭据存储对用户进行身份验证,但您必须运行代码管理凭据存储以及相关的声明映射过程。

身份标识模型配置

要使用 WIF 为您的 WCF 服务启用基于声明的授权,请初始化 ServiceHost 实例以进行联合。可以调用由 FederatedServiceCredentials 类型公开的 ConfigureServiceHost 方法,从而以编程方式初始化该实例,如下所示:

                复制代码            
ServiceHost host = new ServiceHost(typeof(TodoList.TodoListService));
FederatedServiceCredentials.ConfigureServiceHost(host);
host.Open();

也可以使用行为扩展 ConfigurationServiceHostBehaviorExtension,以声明方式获得同样的结果:

                复制代码            
<serviceBehaviors>
  <behavior name="fedBehavior" > 
    <federatedServiceHostConfiguration/>
    <serviceMetadata />
  </behavior>
</serviceBehaviors>

无论在哪种情况下,ServiceHost 都会分得一个 FederatedServiceCredentials 类型的实例,以促成该服务的基于声明的授权行为。可以通过编程方式,或通过服务的 microsoft.identityModel 配置部分初始化此类型。身份标识模型设置特定于 WIF,并为 ASP.NET 和 WCF 应用程序中基于声明的授权提供设置,图 5 中总结了其中的大部分设置。

图 5 基本 microsoft.identityModel 元素摘要

对于使用 WIF 的 WCF 服务,您不再需要使用典型 WCF 身份验证和授权行为初始化 ServiceHost 实例。WIF 取代了这些行为,并提供更简洁的方法进行一般安全配置。(WIF 的适用范围不限于基于声明的联合方案)。图 6 显示用于 TodoListService 的身份标识模型设置。

图 6 通常为 WCF 服务提供的身份标识模型设置

                复制代码            
<microsoft.identityModel>
  <service>
    <issuerNameRegistry type="Microsoft.IdentityModel.Tokens.
      ConfigurationBasedIssuerNameRegistry, Microsoft.IdentityModel, 
      Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
      <trustedIssuers>
        <add name="http://localhost:8010/rpsts" thumbprint=
"c3 95 cd 4a 74 09 a7 77 d4 e3 de 46 d7 08 49 86 76 1a 99 50"/>
      </trustedIssuers>
    </issuerNameRegistry>
    <serviceCertificate>
      <certificateReference findValue="CN=RP" storeLocation="LocalMachine" 
         storeName="My" x509FindType="FindBySubjectDistinguishedName"/>
    </serviceCertificate>
    <audienceUris mode="Always">
      <add value="http://localhost:8000/TodoService"/>
    </audienceUris>
    <certificateValidation certificateValidationMode="PeerTrust" />         
    <securityTokenHandlers>
      <remove type="Microsoft.IdentityModel.Tokens.Saml11.
         Saml11SecurityTokenHandler, Microsoft.IdentityModel, 
         Version=1.0.0.0, Culture=neutral, 
         PublicKeyToken=31bf3856ad364e35"/>
      <add type="Microsoft.IdentityModel.Tokens.Saml11.
         Saml11SecurityTokenHandler, Microsoft.IdentityModel, 
         Version=1.0.0.0, Culture=neutral, 
         PublicKeyToken=31bf3856ad364e35">
        <samlSecurityTokenRequirement >
          <roleClaimType 
            value="urn:TodoListApp/2009/06/claims/permission"/>
        </samlSecurityTokenRequirement>
      </add>
    </securityTokenHandlers>
    <claimsAuthorizationManager 
      type="TodoList.CustomClaimsAuthorizationManager, TodoList"/>
  </service>
</microsoft.identityModel>

issuerNameRegistry 设置用于指定任何受信任的证书颁发者。如果您使用 ConfigurationBasedIssuerNameRegistry,如图 6 所示,则必须通过指定受信任证书颁发者指纹的形式提供受信任证书颁发者列表。在运行时,ConfigurationBasedIssuerNameRegistry 根据此列表检查 X509 安全令牌,并拒绝在列表中找不到相应指纹的项。您可以使用 SimpleIssuerNameRegistry 允许任何 X509 或 RSA 令牌,但您更可能提供一个自定义 IssuerNameRegistry 类型,以便在 ConfigurationBasedIssuerNameRegistry 不起作用时使用您自己的探测功能验证令牌。

图 6 中的配置拒绝所有未经 RP-STS(使用 CN=RPSTS 的证书指纹)签名的令牌。以下配置改为指定自定义 IssuerNameRegistry 类型 TrustedIssuerNameRegistry:

                复制代码            
<issuerNameRegistry type="TodoListHost.TrustedIssuerNameRegistry, TodoListHost"/>

TrustedIssuerNameRegistry 实现用于获取同样的结果:通过检查传入令牌的使用者名称,拒绝未经 CN=RPSTS 签名的令牌:

                复制代码            
public class TrustedIssuerNameRegistry : IssuerNameRegistry
{
    public override string GetIssuerName(SecurityToken securityToken)
    {
        X509SecurityToken x509Token = securityToken as
            X509SecurityToken;
        if (x509Token != null)
        {
            if (String.Equals(x509Token.Certificate.SubjectName.Name,
                "CN=RPSTS"))
            {
                return x509Token.Certificate.SubjectName.Name;
            }
        }

        throw new SecurityTokenException("Untrusted issuer.");
    }
}

图 6 中的 serviceCertificate 设置指示该证书用于对传入的安全令牌进行解密(假设颁发者 STS 已针对 RP 对令牌进行加密)。对于 Todo List 应用程序,RP-STS 使用 RP 的公钥 (CN=RP) 加密令牌。

通常,SAML 令牌包含评估为 RP 的受众 URI 元素,指示令牌的接收者。可以显式拒绝发送目标不是 RP 的令牌。默认情况下,audienceUris 模式设置为 Always,表示必须至少提供一个 URI 用于根据传入的令牌进行验证。在图 6 中,配置只允许包含与 TodoListService 地址相匹配的受众 URI 的 SAML 令牌。也可将 audienceUris 模式设置为 Never,以便抑制对传入 SAML 令牌的受众限制条件的评估(但一般不建议这样做):

                复制代码            
<audienceUris mode="Never"/>

请注意,当客户端向 STS 发送 RST 时,RST 中通常包含 AppliesTo 设置,以指示颁发令牌的接收者,即 RP。STS 可以使用此信息填充 SAML 令牌受众 Uri。

certificateValidation 设置控制如何验证传入 X509 令牌(例如那些用于令牌签名的令牌)。在图 6 中,certificateValidationMode 设置为 PeerTrust,表示证书只有在 TrustedPeople 存储中发现关联证书的情况下有效。此设置比 PeerOrChainTrust(默认)更适合进行令牌颁发者验证,因为前者要求在证书存储中显式安装受信任证书。PeerOrChainTrust 指示如果信任根证书授权机构 (CA),则也对签名进行授权,在大多数计算机上都包含受信任授权机构列表。

稍后将简单讨论图 5图 6 中的其他几个设置。有关 WIF 初始化主题需要指出的另外一点是,您也能以编程方式初始化 FederatedServiceCredentials 的实例并将其传递到 ConfigureServiceHost,而不是从 microsoft.identityModel 部分进行初始化。下面的代码说明了这一点:

                复制代码            
ServiceHost host = new ServiceHost(typeof(TodoList.TodoListService));

ServiceConfiguration fedConfig = new ServiceConfiguration();
fedConfig.IssuerNameRegistry = new TrustedIssuerNameRegistry();
fedConfig.AudienceRestriction.AudienceMode = AudienceUriMode.Always;
fedConfig.AudienceRestriction.AllowedAudienceUris.Add(new 
Uri("http://localhost:8000/TodoListService"));
fedConfig.CertificateValidationMode = 
X509CertificateValidationMode.PeerTrust;
fedConfig.ServiceCertificate = CertificateUtil.GetCertificate(
StoreName.My, StoreLocation.LocalMachine, "CN=RP");

FederatedServiceCredentials fedCreds = 
new FederatedServiceCredentials(fedConfig);

FederatedServiceCredentials.ConfigureServiceHost(host,fedConfig);
host.Open();

编程方式尤其适用于从应用于整个服务器场的数据库设置初始化 ServiceHost。

WIF 组件体系结构

将 WIF 行为应用到 ServiceHost 时,将初始化几个 WIF 组件,以帮助进行基于声明的授权,这些组件中的大部分都是 WCF 扩展。最终,这将导致 ClaimsPrincipal 附加到请求线程,并支持基于声明的授权。图 7 捕获核心 WIF 组件与 ServiceHost 之间的关系。


图 7 通过 WIF 安装的核心组件

FederatedServiceCredentials 类型将替换默认 ServiceCredentials 行为,IdentityModelServiceAuthorizationManager(在初始化 FederatedServiceCredentials 时安装)将替换默认 default ServiceAuthorizationBehavior。FederatedServiceCredentials 还会构造一个 FederatedSecurityTokenManager 实例。在 ClaimsAuthenticationManager、ClaimsAuthorizationManager 和应用于特定请求的 SecurityTokenHandler 的帮助下,所有这些类型合在一起为每个请求驱动身份验证和授权。

图 8 演示了到这些组件的通信流,通过这些通信流将为请求线程(在此处为 ClaimsPrincipal 类型)构造安全主体,并创造根据此安全主体进行访问授权的机会。


图 8 创建 ClaimsPrincipal 并可据其授权的组件

FederatedSecurityTokenManager 为请求返回合适的令牌处理程序(在此处为 Saml11SecurityTokenHandler),并为请求提供对 ClaimsAuthorizationManager 的引用。该令牌处理程序根据传入的令牌构造一个 ClaimsIdentity,创建 ClaimsPrincipal(通过一个包装类),并将该主体传递到 ClaimsAuthorizationManager 的 ValidateToken 方法。这样就可以修改或替换要附加到请求线程的 ClaimsPrincipal。默认实现只返回所提供的 ClaimsPrincipal:

                复制代码            
public virtual IClaimsPrincipal Authenticate(string resourceName, IClaimsPrincipal incomingPrincipal)
{
    return incomingPrincipal;
}

您可以考虑提供一个自定义 ClaimsAuthenticationManager,以便将传入的声明从安全令牌转换为 RP 可以用于授权访问的其他项。但在此示例中,SAML 令牌携带由 RP-STS 颁发的相应 RP 声明,因此根据这些声明构建的 ClaimsPrincipal 将适用于授权。

然后,引用了 ClaimsAuthorizationManager 的 IdentityModelServiceAuthorizationManager 调用其 CheckAccess 方法,从而创造自定义控制访问方式的机会。默认实现并不限制访问:

                复制代码            
public virtual bool CheckAccess(AuthorizationContext context)
{
    return true;
}

AuthorizationContext 参数提供对 ClaimsPrincipal 及其关联声明的访问、与请求相关的操作集合(例如指示待调用服务操作的 URI)以及有关与请求关联的资源的信息(例如服务 URI),这些上下文可将通过同一授权路径传递的对多个服务的调用区分开来。要实现集中授权,可以提供一个自定义 ClaimsAuthorizationManager。在讨论授权方法时我将介绍一个相关示例。

要在 .NET Framework 中建立基于角色的安全性,前提是将基于 IPrincipal 的安全主体附加到每个线程,并且此安全主体在 IIdentity 实现中包装通过身份验证的用户的身份标识。如果没有 WIF,WCF 将根据 system.serviceModel 配置将安全主体附加到每个请求线程,以用于身份验证和授权。IIdentity 类型根据为身份验证提供的凭据类型构造。例如,Windows 凭据将评估为 WindowsIdentity,X.509 证书评估为 X509Identity;UserName 令牌评估为 GenericIdentity。ServiceAuthorizationBehavior 控制身份标识的 IPrincipal 包装类型。例如,对于 Windows 授权,将构造 WindowsPrincipal;对于 ASP.NET 成员提供程序,将构造 RoleProviderPrincipal。另外,还可以使用自定义授权策略构造您选择的 IPrincipal 对象。IPrincipal 对象将公开一个 IsInRole 方法,可以通过权限要求直接或间接调用该方法,以便控制对特性和功能的访问。

WIF 通过提供 ClaimsPrincipal 和 ClaimsIdentity 类型(基于 IClaimsPrincipal 和 IClaimsIdentity)扩展此模型,这两种类型最终是从 IPrincipal 和 IIdentity 派生的。所有令牌都通过 WIF 映射到 ClaimsIdentity。验证每个传入安全令牌之后,与之关联的 SecurityTokenHandler 类型会构造一个 ClaimsIdentity,为令牌提供一个相应的声明。此 ClaimsIdentity 包装在 ClaimsIdentityCollection(如果令牌生成多个 ClaimsIdentity 实例)中,此集合包装在 ClaimsPrincipal 中并附加到请求线程。此 ClaimsPrincipal 是 WCF 服务进行 WIF 授权的核心。

基于声明的授权

对于 WCF 服务,授权方法可能会用到以下方法之一:

  • 使用 ClaimsPrincipal 执行动态 IsInRole 检查。
  • 使用 PrincipalPermission 类型执行动态权限请求。
  • 使用 PrincipalPermissionAttribute 在每次操作时提供声明性权限请求。
  • 提供自定义 ClaimsAuthorizationManager 以将访问权限检查集中在一个控件中。

上述的前三个选项最终依赖于由 ClaimsPrincipal 类型公开的 IsInRole 方法。这并不一定表示执行的是基于角色的安全性,只是表明您选择了角色声明类型,以便根据传递到 IsInRole 的请求的声明来检查声明的正确性。WIF 的默认角色声明类型是 schemas.microsoft.com/ws/2008/06/identity/claims/role。如果与联合方案关联的 STS 颁发此种类型的声明,您可以选择基于此声明类型对访问进行控制。对于 Todo List 应用程序方案,前面提到将自定义声明类型用于授权,因此身份标识模型配置必须将此自定义声明类型指定为角色声明类型以帮助进行 IsInRole 检查。

您将该角色声明类型提供给预期令牌类型的 SecurityTokenHandler(此处为 Saml11SecurityTokenHandler)。如图 6 所示,您可以修改 SecurityTokenHandler 的默认配置,方法是将该处理程序删除后再重新添加,在此过程中指定首选的属性设置。SAML 令牌处理程序包含一个 samlSecurityTokenRequirement 节,可在其中提供用于名称或角色声明类型的设置,以及与证书验证和 Windows 令牌相关的其他设置。对于此方案,我提供了一个自定义角色声明类型:

                复制代码            
<samlSecurityTokenRequirement >
  <roleClaimType value= "urn:TodoListApp/2009/06/claims/permission"/>
</samlSecurityTokenRequirement>

这表示每当为 ClaimsPrincipal 调用 IsInRole 时,都将检查授权声明的有效性。实现此目的的方法之一是在执行需要特定声明的代码段之前显式调用 IsInRole。您可以通过 Thread.CurrentPrincipal 属性访问当前主体,如下所述:

                复制代码            
if (!Thread.CurrentPrincipal.
IsInRole("urn:TodoListApp/2009/06/claims/permission/delete"))
  throw new SecurityException("Access is denied.");

除了在运行时显式检查 IsInRole 之外,还可以使用 PrincipalPermission 类型写入经典的基于角色的权限要求。还可以使用所要求的角色声明(第二个构造函数参数)初始化该类型,当调用要求时,将调用当前主体的 IsInRole 方法。如果找不到该声明,则引发异常:

                复制代码            
PrincipalPermission p = new PrincipalPermission("", "urn:TodoListApp/2009/06/claims/permission/delete");
p.Demand();

您还可以构建一个 PermissionSet 来收集要检查的声明:

                复制代码            
PermissionSet ps = new PermissionSet(PermissionState.Unrestricted);
ps.AddPermission(new PrincipalPermission("", "urn:TodoListApp/2009/06/claims/permission/create"));
ps.AddPermission(new PrincipalPermission("", "urn:TodoListApp/2009/06/claims/permission/read"));
ps.Demand();

如果访问检查应用到整个服务操作,您可以改为应用 PrincipalPermissionAttribute,这是一个将请求的声明以声明方式关联到正在调用的操作的好方法。可将这些属性堆叠起来以检查多个声明:

                复制代码            
[PrincipalPermission(SecurityAction.Demand, Role = Constants.Permissions.Create)]
[PrincipalPermission(SecurityAction.Demand, Role = Constants.Permissions.Read)]
public string CreateItem(TodoItem item)

有时,您可能希望将授权集中到单个组件中,这表示您将提供一个自定义 ClaimsAuthorizationManager 来执行访问检查。图 6 演示了如何配置自定义 ClaimsAuthorizationManager,为 TodoListService 实施此配置的过程在图 9 中显示(为简洁起见,只列出部分内容)。

图 9 自定义 ClaimsAuthorizationManager 实现

                复制代码            
class CustomClaimsAuthorizationManager : ClaimsAuthorizationManager
{
    public CustomClaimsAuthorizationManager()
    {
    }

    public override bool CheckAccess(AuthorizationContext context)
    {
        
        if (context.Resource.Where(x=> x.ClaimType == 
            System.IdentityModel.Claims.ClaimTypes.Name && x.Value == 
            "http://localhost:8000/TodoListService").Count() > 0)
        {
            if (context.Action.Where(x=> x.ClaimType == 
                System.IdentityModel.Claims.ClaimTypes.Name && x.Value == 
                Constants.Actions.GetItems).Count() > 0)
            {
                return
                    context.Principal.IsInRole(
                       Constants.Permissions.Read);
            }

		// other action checks for TodoListService
        }
        return false;
    }  
}

ClaimsAuthorizationManager 提供对 CheckAccess 的重写,重写方法所接收的 AuthorizationContext 参数包含以下内容:对资源(此处指服务 URI)的引用、操作集合(此处是指示服务操作 URI 的单个操作)以及 ClaimsPrincipal(尚未附加到请求线程)。如果在服务间共享组件,您可以对资源进行检查,如此示例中所做的演示。您主要是根据服务操作 URI 列表检查操作,并根据操作要求执行 IsInRole 检查。

我一般不太喜欢将授权从受保护操作或代码块中分离出来。在操作上下文中的某个位置声明的代码非常易于维护。

待续

至此,您应当已经非常了解如何使用 WCF 和 WIF 设置主动联合方案,包括其中的 WCF 联合绑定和代理生成语义、令牌颁发过程、在服务中配置 WIF 以及实现各种基于声明的授权的方法。在后续文章中,我将介绍通过 ASP.NET 和 WIF 实现被动联合。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!
提供的源码资源涵盖了小程序应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值