WCF安全性

本章内容

q 序幕

q 利用声明保证资源安全

q 使用XSI运用基于声明的安全

q 基于声明的安全和联合安全

10.1 序幕

从华盛顿州的西雅图向南,驾车前往俄勒冈,在刚路过雄伟的奥林匹亚州议会大厦后你就需要选择一下线路了。你可以继续在州际5号公路上行驶,或者先向西然后往南转上美国101号高速公路。

在州际5号公路上开车会让你感觉很轻松,因为往波特兰的大部分路段都是双向多车道的,能满足很大的车流量,并且沿途众多的加油站、餐厅、商店和旅馆都有清晰的指示牌。

相反,美国101号高速公路大部分的路段都是双向单车道的。路上的一些紧连着的弯道使司机很少甚至没有犯错的余地。在陡坡上被一辆大卡车一直挡着而又不能超车,是常见的事情。路边的设施又少又不大好,而且还隔得很远。

然而这条路的风景却让人情不自禁地赞叹,那些紧连着的弯道两旁,一边是茂密的森林,一边就是浩瀚的太平洋。

101高速公路后不久,你还可以在格雷斯港县的卡拉克斯餐厅停车休息一下,问问服务小姐,在那一长串的菜单里有什么可以推荐的。她的回答会让你有点吃惊:上面所有的都很不错啊。不过,最后你也会发现她说的一点也不假。

最重要的是,再经过几个小时之后,你会到达一个地方,它完全被卡侬海滩包围着。连绵数英里的宽阔海岸刚被细雨洗刷过,还有那宏伟的山景,这种大自然的壮丽都已经无法用任何语言来描述了。

软件安全之路就像在州际5号公路和美国101高速之间的选择一样。州际5号就是当今的Windows安全系统,它有丰富的设施能让你快速地到达目的地。101号高速则是基于声明的安全系统。虽然这条路会更远,设施也贫乏些,但它不仅漂亮,而且能带你直达天堂。本章要像卡拉克斯餐厅一样,让疲惫的旅行者能看到满菜单的精致美食,使他们在前行的路上精力倍增。

声明这个概念已经在前面几章多次出现过了。在第7章,我们解释了WCFXSIExtensible Security Infrastructure,可扩展安全框架)会接受所有用来验证用户本身的凭据,并且将它们翻译成一个声明集。然后在介绍WCS的第8章和第9章提出了数字标识只是一个可信也可不信的声明集合,而CardSpace为用户提供了一种管理多个数字标识的方法。现在,声明这个概念会从树丛间跳到空地上,一起让我们来看看它的真面目吧。

利用声明保证资源安全

考虑下面这个场景:一个人走进了一间酒吧,把他的驾照和一叠钱放在吧台上,然后向侍者要了一扎啤酒。侍者看了看那人的驾照,拿了钱,然后给了他啤酒。

这里,酒吧提供了一种服务。给顾客啤酒是这种服务的一个操作,而啤酒在这里是一种受保护的资源。为了得到啤酒,一个人必须达到法定的饮酒年龄,而且还要为此付点钱。

那个司机的驾照和钱代表了一个声明集(claim set)。声明集是一些由同一个发布者提供的声明。对司机的驾照来说,发布者就是向司机颁发驾照的政府部门。而对钱来说,发布者就是国家的中央银行。这些发布者本身只是体现为一些声明的集合,比如图标、签名还有司机的驾照和钱上面的图案。

声明包含了类型、权限和值。在司机驾照所体现的声明集中,其中一个是司机的出生日期。这个声明的类型是出生日期。一个声明赋予其所有者的权限定义了他可以对声明值所做的操作。对于司机的出生日期,其权限只是持有权。司机可以持有这个出生日期,但是不能更改它。

检验司机的驾照和钱时,侍者将关于司机驾照所提供的出生日期的声明翻译成关于驾照持有者年龄的声明。侍者也将吧台上每一张钞票的面值翻译成关于这些钱总和的声明。当侍者在把输入声明集翻译成输出声明集时,这个过程中所遵循的规则便构成了他的授权策略(authorization policy)。授权策略的输入声明集称为评估上下文(evaluation context),而输出声明集称为授权上下文(authorization context)。一组授权策略构成了一个授权域(authorization domain)。

在收钱和给顾客啤酒时,侍者将关于顾客年龄的声明和最小的饮酒年龄进行比较,然后又比较所付的钱的总数和啤酒的价格。在这个过程中,侍者是在对授权策略生成的授权上下文和给顾客啤酒操作的请求进行比较。由于授权上下文声明集(顾客的年龄和所付钱的总和)满足了对操作的访问请求,所以侍者给了那人一扎啤酒。

这就是XSI所提供的基于声明的安全系统工作原理。访问一个作用在受保护资源上的操作是通过基于声明来授权的。声明有类型、权限和值。声明集是一些由同一个发布者提供的声明集合。声明集的发布者本身也是一个声明集。基于声明的授权通过两步实现:首先,执行一个授权策略,它将一个评估上下文声明集作为输入,并翻译成一个授权上下文作为输出;其次,授权上下文声明集中的声明与操作的访问请求进行比较,然后,根据比较的结果对这个操作的请求予以拒绝或接受

10.2.1 基于声明授权和基于角色授权

基于角色授权(role-based authorization)经常被用来控制用户对软件应用程序的操作使用。那么这种基于声明的授权与它相比又如何呢?在这里,基于角色授权的定义可以帮助我们回答这个问题。

“基于角色授权是这样一种机制,它利用角色来赋予用户进行系统操作的适当权限和访问资源的许可”(Tulloch 2003281)。角色是一个“集合了具有相同等级安全权限用户的符号类”(Tulloch 2003281)。

基于角色授权要求首先识别用户,然后确定用户被赋予的角色,最后对这些角色和被授权可以访问某个资源的角色进行比较。所以,例如在微软.NET基于角色的安全机制提供的基于角色授权系统中,最重要的元素就是原对象(principle object),它集成了用户标识和其所具有的角色(.NET Framework Class Library 2006; Freeman and Jones 2003, 249)。

相反,在前一个场景中,侍者在决定是否给顾客啤酒时,识别那个人的身份并不重要。当然,驾照也可以用来标识那个人的身份(因为司机的驾照通常给出了关于持有者身份的声明),但是这些声明对侍者来说并不重要,侍者仅仅对驾照里关于持有者出生日期的声明感兴趣。但是,如果顾客后来抢劫了侍者,识别他的身份当然就变得重要了。

一般地,基于声明授权包括了基于角色授权。更准确一点,标识只是对声明值的一种权限,也就是利用声明值标识自己的权限。你没有权限用出生日期这个声明值来标识自己,因为很多人都有同样的出生日期。而身份照片,你确实可以用这个声明值来标识你自己。另外,角色也只是一种类型的声明而已。

10.2.2 基于声明授权和访问控制列表

ACLAccess Control List,访问控制列表)是在管理网络资源访问时普遍采用的方式。XSI提供的基于声明的授权方式和这种利用ACL控制资源使用的方式比较起来又如何呢?同样,ACL的定义会帮助我们回答这个问题。

ACL由一系列ACEAccess Control Entry,访问控制条目)组成,而ACE定义了一个用户或组对某个资源可以进行的操作。”(Tulloch 20037)。ACE包含了一个用来标识用户或组的SIDsecurity identifier,安全标识符),以及一组访问权限,这些权限定义了这个用户或组在这个资源上被许可或被禁止的操作(Tulloch 20037)。

ACL“用在微软Windows平台上,用来控制对文件、进程、服务、共享和打印机等需要安全保护的资源的访问”(Tulloch 20037)。具体地说,“当在微软的Windows平台上创建一个用户账号时,它被赋予了一个SID,用来唯一标识针对该操作系统中的这个账号”(Tulloch 20037)。当用户使用这个账号登录时,系统会生成一个包含该账号的SID和该账号所在组的SID的访问令牌。这个令牌“然后就被复制到这个账号拥有的所有进程和线程中”(Tulloch 20037)。当用户试图访问一个受ACL保护的资源时,令牌包含的所有SID就会与ACL中的每个ACE包含的SID进行比较,直到发现有匹配的SID为止,最后根据比较的结果对访问予以接受或拒绝。

同样,ACL实际上是基于声明授权的一个特例,也就是说它是被基于声明授权涵盖的。用户用来登录操作系统的凭证和访问令牌包含的SID都是声明集。操作系统将用户登录使用的凭证转换成访问令牌中包含的SID,这恰恰就是一个授权策略执行的实例。比较访问令牌中的SIDACL里的SID,也是授权上下文声明集里的声明和受ACL保护的资源的操作访问请求相比较的一个示例。

XSI所提供的这种更全面的模型远比ACL更加符合分布式系统中访问授权的要求。这有3个原因:第一,访问令牌从来没有被设计成可在不同平台间交换。相反,声明已经可以用标准的可互操作的格式来表示,比如SAML。第二,访问令牌是由操作系统发布的,而声明却可以由任何源发布。第三,也是最重要的,访问令牌和ACL中的SID一般只在发布这些令牌的操作系统范围内才有效。比如,如果操作系统是一个域控制系统,它发布的SID在这个域里都有效。相反,对于声明来说,只要发布者可信,它就是有意义的。尽管基于声明授权比基于角色授权和ACL有更多优势,你也不一定要马上丢掉基于角色授权和ACL而转向使用基于角色授权。因为对于基于角色授权和ACL,已经有大量的强大工具支持着它们的运用。这些工具很多就内嵌在Microsoft Windows系统中,并且网络管理员也习惯了使用它们。而对于基于声明授权的支持却只限于XSIWCS和活动目录联合服务(Active Directory Federation Services)。

所以,一个更明智的方法是将它们都利用起来,发挥它们各自在不同场合中的优势,而不应该把基于声明授权看成是基于角色授权和ACL的高级替代品。基于声明授权在跨平台、跨机构访问控制方面非常有效。所以,如果一个机构的用户需要访问另外一个机构系统管理的资源,就可以将访问令牌转换成声明,这样另一个机构就可以用它来决定要不要授予用户访问权限。

那这样的解决方案应该怎样来实现呢?WS-Trust Web Services Trust LanguageWS-Trust语言)就是这样一种用来请求和发布声明集的标准语言。利用这种语言发布声明的系统称为STSGudginNadalin 20057CabreraKurt 200524-27)。如果一个机构(A)的用户需要访问另外一个机构(B)的服务,它可以给用户提供一个安全令牌服务,这样用户就可以向它请求机构B所能够接受的声明集。这个安全令牌服务可以将用户访问令牌中SID的声明作为输入,然后运行一个授权策略生成一组类型、权限和机构B都能理解的声明。而机构B也会有一个安全令牌服务用来接收这些声明,然后运行它自己的一个授权策略生成一组新的声明,使得这个机构中的其他系统可以用这些新的声明来决定是不是要让用户访问他们的资源。图10-1描述了这个方案,这个方案有下列几个非常重要的优点。

10-1 跨机构的基于声明授权

第一,互信关系被最小化了,而且互信关系的管理也集中了。具体地来看,具有保护资源的服务只需要信任一个发布者,也就是他们自己机构的安全令牌服务。这个安全令牌服务可以通过设置,从而信任其他任意机构的安全令牌服务所发布的声明。而且,将一个安全令牌服务配置成信任其他机构的安全令牌服务,只需要让它能使用那个机构的公钥即可。

第二,一个机构为其用户生成的用来访问另一机构服务的声明,对于另一机构的这些服务是不可见的。这些服务所信任的安全令牌服务的授权策略将这些声明隐藏了起来。安全令牌服务运行授权策略将另一个机构生成的声明转换成了这些服务所熟悉的声明。这个将各种其他机构生成的声明翻译成一系列服务所熟悉的声明的过程,一般称为声明正则化(claim normalization)。

第三,对服务访问的控制是相对独立的。在一个机构中,系统管理员可以通过添加/删除用户或提升/降低用户的权限来控制用户访问其他机构的服务的权限,而这个过程并不需要另一个机构的系统管理员参与。这样做的好处可以在下面的练习中得到很好的体现。

10.3 使用XSI运用基于声明的安全

在这个练习里,我们首先建创一个通过基于角色授权来控制对一个网络资源进行访问的WCF解决方案。这个方案将讲述如何利用现有的大家所熟悉的Microsoft Windows和微软.NET工具来保护WCF应用。这种方式将节省系统管理员学习新知识和工具的时间,也减少了软件开发工程师学习新概念和新类库的时间。

然后,这个练习将继续向大家讲述,通过利用XSI的基于声明方式的授权访问请求,使同一个资源可以被部署在另一个联合机构里的同一客户端所访问。在这过程中,让人惊讶的是,不管是客户端的代码还是管理被访问资源的服务端代码都不需要做任何改动,但仍然可以完成访问控制方式的彻底改变。这其实再一次演示了WCF为软件通信所提供的软件工厂模板的强大,无须修改代码,只要对模型做些更改就可以彻底地改变一个应用程序的运行方式。

10.3.1 利用Windows标识授权对局域网资源的访问

第一步,我们先来演示在WCF解决方案中使用基于角色授权来控制对局域网资源的访问。基于角色授权使用了.NET Role-Based SecurityASP.NET 2.0提供的AuthorizationStoreRoleProviderWindows授权管理器(通常指AzMan)。

使用Windows XP SP2 的读者可以通过安装Windows Server 2003 Service Pack 1 Administration Tools Pack来安装Windows Server 2003授权管理器。这个安装包可以在微软的下载中心通过查找Windows Server 2003 SP1 Administration Tools Pack来获得。

按下面的步骤开始吧。

(1) http://www.cryptmaker.com/WindowsCommunicationFoundationUnleashed下载本章相关的代码并将它们复制到C:\WCFHandsOn目录下。这里要用到的所有代码都在AdvancedSecurity目录下。这个目录有两个子目录,其中一个叫做IntranetSecurity,它里面有一个Visual Studio解决方案Security.sln

(2) Visual Studio里打开这个解决方案。Service这个项目用来创建一个承载WCF服务的控制台程序。Client项目包含了一个Windows Forms程序,它用来访问这个服务所提供的资源。

(3) 如果因为某些原因这个解决方案不在C:\WCFHandsOn\AdvancedSecurity\IntranetSolution目录里,打开Service项目里的App.config文件,然后修改下面这项:

so that it refers to the actual path to the AuthorizationStore.xml file that is in the same folder as the Security.sln solution file itself.

 

使其指向AuthorizationStore.xml所在的那个目录,这个目录也应该包含了Security.sln解决方案文件。

(4) Visual Studio中选择DebugStart Debugging进行调试。如图10-2所示,服务的控制台程序和资源访问客户端程序的用户界面都应该显示出来了。在客户端的用户界面里有两个按钮,左边的按钮上有一张煤块的图片,而右边的按钮上有一张钻石图片。

(5) 在服务的控制台程序显示了一些活动之后,点击煤块按钮。如图10-3所示,有个消息框会弹出来并显示已经得到煤块这个不怎么值钱的资源了。

(6) 现在点击钻石按钮。对宝贵钻石的访问应该会被拒绝。如图10-4所示的消息框会弹出来。

                                           

10-2 Resource Access客户端用户界面图             10-3 成功地获取了煤块图

10-4 尝试获取钻石时失败了

(7) Visual Studio的菜单里依次选择DebugStop Debugging,关闭服务的控制台程序。

下面这几步就来解释为什么煤块能被访问而钻石不能被访问。先通过下面的步骤打开AzMan用户界面。

(1) Windows开始菜单里选择Run,然后输入回车,启动Microsoft Management Console

(2) Microsoft Management Console的菜单里依次选择FileAdd/Remove Snap-in

(3) 点击Add/Remove Snap-in对话框中的Add按钮。

(4) Add Standalone Snap-in对话框里选择Authorization Manager,然后点击Add按钮,再点击Close按钮。

(5) 重新回到Add/Remove Snap-in对话框,点击OK按钮。

这样,Windows授权管理器的用户界面应该就已经打开了,如图10-5所示。我们继续检查一下用来控制服务资源访问的授权库。

(6) Microsoft Management Console里,右击Authorization Manger用户界面左边框中的树型列表里的授权管理器,然后从显示的菜单里选择Open Authorization Store

(7) 点击图10-6Open Authorization Store对话框的Browse按钮,在文件对话框里选择C:\WCFHandsOn\AdvancedSecurity\IntranetSolution\AuthorizationStore.xml这个文件,然后点击Open按钮。

10-5 Windows Authorization Manager用户界面

10-6 打开一个授权存储

(8) 如图10-7所示,展开左边框的Authorization Manager的树状列表。从中选择StaffMember,观察右边框可以发现Everyone组里的用户已经被赋予了StaffMember角色。

10-7 角色指派

(9) 再选择Manager节点,可以发现没有任何用户被指派为这个角色。

(10) 右击Manager节点,从右键菜单中选择Assign Windows Users and Groups。在Select Users and Groups对话框里,输入当前登录用户的用户名,然后选择OK

(11) 重新开始调试程序。

(12) 当服务的控制台显示了一些活动之后,点击Resource Access客户端用户界面里的钻石按钮。应该会出现图10-8所示的对话框,显示钻石现在可以被访问了。

10-8 成功获取钻石

(13) Visual Studio的菜单里依次选择DebugStop Debugging,关闭服务的控制台。

(14) 回到授权存储控制台,选择Role Assignments下的Manager节点。右击右边的Administrator条目,然后从右键菜单中选择Delete,将当前登录用户的访问权限恢复成原来的状态。

很明显,WCF服务管理的资源访问现在是根据客户端用户的角色来控制的,这些角色是由Windows Server 2003授权管理器的授权存储C:\WCFHandsOn\AdvancedSecurity\IntranetSolution\ AuthorizationStore.xml指派的。下面的步骤将显示这是如何完成的。

(1) Visual StudioSecurity解决方案里,打开Service项目的ResourceAccessServiceType.cs文件。代码清单10-1显示了该文件中的代码。这里可以明显地看出服务中对资源的访问操作是由.NET Role-Base Security System.Security.Permissions.PrincipalPermission中特性控制的,它指定了只有赋予了Manager角色的用户才可以访问更加宝贵的钻石资源。

代码清单10-1 使用PrincipalPermission特性

using System;

using System.Collections;

using System.Collections.Generic;

using System.Security.Permissions;

using System.Runtime.Serialization;

using System.ServiceModel;

using System.Text;

using System.Web;

using System.Web.Security;

namespace Service

{

    public class ResourceAccessServiceType: IResourceAccessContract

    {

        #region IResourceAccessContract Members

        [PrincipalPermission(SecurityAction.Demand, Role = "StaffMember")]

        [PrincipalPermission(SecurityAction.Demand, Role = "Manager")]

        string IResourceAccessContract.AccessCoal()

        {

            return "Here is your coal!";

        }

        [PrincipalPermission(SecurityAction.Demand, Role = "Manager")]

        string IResourceAccessContract.AccessDiamond()

        {

            return "Here is your diamond!";

        }

        #endregion

    }

}

 

(2) 打开Service项目里的App.config文件。代码清单10-2中列出了文件的内容。

Windows通信基础服务的绑定是netTcpBinding。默认情况下,这个绑定通过KerberosNTLM使用Windows凭据来标识那些使用服务的用户。

服务还有一个名为ServiceBehavior(这个名称是随意取的)的System.ServiceModel.Descri- ption.ServiceAuthorization行为。这个行为的PrincipalPermissionMode属性值被赋为System. ServiceModel. Description.PrincipalPermissionMode.UseAspNetRoles,它的RoleProviderName属性值被赋为名为AuthorizationStoreProvider(这个名称也是随意取的)的角色提供程序。

这个角色提供程序将在配置文件的下面一点定义。它被配置成使用C:\WCFHandsOn\Advan- cedSecurity\IntranetSolution\AuthorizationStore.xml作为它的授权存储。

所以,根据这样的配置,服务就被配置成根据用户的Windows凭据来验证用户,然后根据用户属于授权存储里维护的哪些组来决定是否授权他们访问申请的资源。这种授权方式在第7章中已经介绍过,这里就不再赘述了。

代码清单10-2 服务配置

<configuration>

  <system.serviceModel>

    <behaviors>

      <serviceBehaviors>

        <behavior name='ServiceBehavior'>

          <serviceAuthorization

            principalPermissionMode='UseAspNetRoles'

           roleProviderName='AuthorizationStoreRoleProvider' />

           <serviceMetadata

             httpGetEnabled ='true'/>

        </behavior>

      </serviceBehaviors>

    </behaviors>

    <services>

      <service

        name="Service.ResourceAccessServiceType"

        behaviorConfiguration='ServiceBehavior'>

        <host>

          <baseAddresses>

            <add baseAddress='net.tcp://localhost:9000/Woodgrove'/>

            <add baseAddress='http://localhost:8000/Woodgrove'/>

          </baseAddresses>

        </host>

        <endpoint address="ResourceAccess"

          binding="netTcpBinding"

          contract="Service.IResourceAccessContract" />

        <endpoint address="mex"

          binding="mexHttpBinding"

          contract="IMetadataExchange" />

      </service>

    </services>

  </system.serviceModel>

  <system.web>

    <roleManager defaultProvider="AuthorizationStoreRoleProvider"

      enabled="true"

      cacheRolesInCookie="true"

      cookieName=".ASPROLES"

      cookieTimeout="30"

      cookiePath="/"

      cookieRequireSSL="false"

      cookieSlidingExpiration="true"

      cookieProtection="All" >

    <providers>

      <clear />

      <add

        name="AuthorizationStoreRoleProvider"

        type="System.Web.Security.AuthorizationStoreRoleProvider"

        connectionStringName="AuthorizationServices"

        applicationName="RoleProvider" />

    </providers>

  </roleManager>

</system.web>

<connectionStrings>

  <add

    name="AuthorizationServices"

    connectionString="msxml://C:\WCFHandsOn\AdvancedSecurity\IntranetSolution\

AuthorizationStore.xml" />

  </connectionStrings>

</configuration>

 

10.3.2 改进初始方案

这个解决方案的缺点就是它严重依赖于System.Security.Permissions.PrincipalPermission特性来控制对服务操作的访问。使用这些特性就会将验证的代码和服务的代码混杂到一起。

在第7章讲到,XSI可以让我们做得更好。它可以将任何用来验证的凭据都翻译成一列声明,并且可以将对这些声明的检查放到由服务的配置指定的其他程序集里。这就立即给服务的设计带来了两个好处:第一,明显地,它可以将业务逻辑的编程和服务访问的编程及管理分开;第二,它还带来一个看似细小但是又非常重要的好处,它使服务的访问与具体的凭据发布者分开了(或者用Web Services Federation Language规范里的术语,即服务的访问与具体的安全域[security realm]分开了 [KalerNadalin 2003])。之前服务的访问和某个Windows域的凭据绑定在一起,现在它将根据声明来授权,这些声明可以来自任意的发布者,甚至不一定需要有关用户标识的声明。

通过修改服务以使用XSI服务授权管理器授权对其资源的访问,这样大的转变实现起来非常简单。下面的步骤就会证明给你看。

(1) 打开Service项目的ResourceAccessServiceType.cs文件,然后将System.Security.Permis- sions. PrincipalPermission特性注释掉。如代码清单10-3所示。

代码清单10-3 去掉PrincipalPermission特性

using System;

using System.Collections;

using System.Collections.Generic;

using System.Security.Permissions;

using System.Runtime.Serialization;

using System.ServiceModel;

using System.Text;

using System.Web;

using System.Web.Security;

namespace Service

{

    public class ResourceAccessServiceType: IResourceAccessContract

    {

        #region IResourceAccessContract Members

        //[PrincipalPermission(SecurityAction.Demand, Role = "StaffMember")]

        //[PrincipalPermission(SecurityAction.Demand, Role = "Manager")]

        string IResourceAccessContract.AccessCoal()

        {

            return "Here is your coal!";

        }

        //[PrincipalPermission(SecurityAction.Demand, Role = "Manager")]

        string IResourceAccessContract.AccessDiamond()

        {

            return "Here is your diamond!";

        }

        #endregion

    }

}

(2) Service项目里的App.config文件修改成如代码清单10-4所示。为了减少读者手工修改代码的麻烦,在C:\WCFHandsOn\AdvancedSecurity\IntranetSolution\Listing10.4文件夹里有一份配置文件的副本,这里最主要的改动就是System.ServiceModel.Description.ServiceAuthorization行为的配置。现在它被配置成将授权管理交给一个XSI服务授权管理器。这里,就是Service- AuthorizationManager程序集(名称是随便取的)里的Service.AccessChecker类型:

 

<behavior name='ServiceBehavior'>

        <serviceAuthorization

serviceAuthorizationManagerType='Service.AccessChecker,

ServiceAuthorizationManager'

                principalPermissionMode='None'  />

        [...]

</behavior>

代码清单10-4 ServiceAuthorizationManagerType配置

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

  <configSections>

    <section

      name="operationRequirements"

      type="Service.OperationRequirementsConfigurationSection,

      ServiceAuthorizationManager" />

  </configSections>

  <operationRequirements>

    <operation

    identifier="http://tempuri.org/IResourceAccessContract/AccessCoal">

      <role name="Manager"/>

      <role name="StaffMember"/>

    </operation>

    <operation

    identifier="http://tempuri.org/IResourceAccessContract/AccessDiamond">

      <role name="Manager"/>

    </operation>

  </operationRequirements>

  <system.serviceModel>

    <behaviors>

      <serviceBehaviors>

        <behavior name="'ServiceBehavior'">

          <serviceAuthorization

          serviceAuthorizationManagerType=

          "Service.AccessChecker,ServiceAuthorizationManager"

          principalPermissionMode="None" />

          <serviceMetadata httpGetEnabled ="true"/>

        </behavior>

      </serviceBehaviors>

    </behaviors>

    <services>

      <service

        name="Service.ResourceAccessServiceType"

        behaviorConfiguration="ServiceBehavior">

        <host>

          <baseAddresses>

            <add baseAddress="net.tcp://localhost:9000/Woodgrove"/>

            <add baseAddress="http://localhost:8000/Woodgrove"/>

          </baseAddresses>

        </host>

        <endpoint address="ResourceAccess"

        binding="netTcpBinding"

        contract="Service.IResourceAccessContract"/>

        <endpoint address="mex"

        binding="mexHttpBinding"

        contract="IMetadataExchange" />

      </service>

    </services>

  </system.serviceModel>

  <! Role Provider Configuration >

  <system.web>

    <roleManager defaultProvider="AuthorizationStoreRoleProvider"

    enabled="true"

    cacheRolesInCookie="true"

    cookieName=".ASPROLES"

    cookieTimeout="30"

    cookiePath="/"

    cookieRequireSSL="false"

    cookieSlidingExpiration="true"

    cookieProtection="All" >

      <providers>

        <clear />

        <add

        name="AuthorizationStoreRoleProvider"

        type="System.Web.Security.AuthorizationStoreRoleProvider"

        connectionStringName="AuthorizationServices"

        applicationName="RoleProvider" />

      </providers>

    </roleManager>

  </system.web>

  <! Connection Strings >

  <connectionStrings>

    <add

    name="AuthorizationServices"

    connectionString="msxml://C:\WCFHandsOn\AdvancedSecurity\

IntranetSolution\AuthorizationStore.xml" />

  </connectionStrings>

</configuration>

 

(3) Visual Studio的菜单里依次选择FileAddExisting Project,然后从C:\WCFHandsOn\ AdvancedSecurity\IntranetSolution\IntranetServiceAuthorizationManager文件夹中选择ServiceAuthorzation- Manager.csproj,将创建ServiceAuthorizationManager程序集的项目加入到解决方案中。小心,不要从InternetServiceAuthorizationManager文件夹里错选了那个名称与此类似的项目——这个项目待会儿会用到的。

(4) 研究一下ServiceAuthorizationManager项目中的AccessChecker.cs文件中的System.Access Checker类的代码。代码清单10-5给出了全部的代码。System.AccessChecker类由于继承自XSISystem. ServiceModel.ServiceAuthorizationManager基类,并重写它的CheckAccess()虚函数,所以它是一个XSI Service Authorization Manager,每一个请求进来时这个方法都会被调用。在这之前,XSI已经把通过NetTcpBinding验证过的Windows凭据魔术般地翻译成一组声明了。System. ServiceModel.ServiceAuthorizationManager的子类System.AccessChecker会检查这些声明,由它决定声明是否满足处理该请求的要求(请求的实质是由它的Action头决定的)。这里,处理每个请求的要求定义在配置文件的一个定制段中(虽然它们可以在任意地方定义)。

 

<operationRequirements>

  <operation

  identifier="http://tempuri.org/IResourceAccessContract/AccessCoal">

    <role name="Manager"/>

    <role name="StaffMember"/>

  </operation>

  <operation

  identifier="http://tempuri.org/IResourceAccessContract/AccessDiamond">

    <role name="Manager"/>

  </operation>

  </operationRequirements>

 

判断提交的声明是否满足要求是通过从声明中选择一条有关用户名称的声明,然后查询刚才用过的授权存储来决定用户是否指派了某个给定的角色完成的。

 

if (string.Compare(

    claim.ClaimType,

    "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name",

    true) == 0)

    {

         userName = (string)claim.Resource;

         foreach (string requiredRole in requiredRoles)

         {

              if (Roles.Provider.IsUserInRole(

                               userName,

                                  requiredRole))

              {

                    return true;

              }

         }

    }

 

实际上,对声明的判断可以使用任何方式或标准来完成。后面,我们会使用Windows Workflow Foundation的规则集来完成判断。

代码清单10-5 ServiceAuthorizationManager的实现

sing System;

using System.Collections.Generic;

using System.Configuration;

using System.IdentityModel.Claims;

using System.IdentityModel.Policy;

using System.IO;

using System.ServiceModel;

using System.Web.Security;

namespace Service

{

    public class AccessChecker : ServiceAuthorizationManager

    {

        private Dictionary<string, string[]> accessRequirements = null;

        public AccessChecker()

        {

            this.accessRequirements = new Dictionary<string, string[]>();

            OperationRequirementsConfigurationSection

                operationRequirementsConfigurationSection

                = ConfigurationManager.GetSection("operationRequirements")

                as OperationRequirementsConfigurationSection;

            OperationRequirementsCollection requirements =

           operationRequirementsConfigurationSection.OperationRequirements;

            List<string> roles = null;

            foreach (OperationElement operationElement in requirements)

            {

                roles = new List<string>(operationElement.Roles.Count);

                foreach (RoleElement roleElement in operationElement.Roles)

                {

                    roles.Add(roleElement.Name);

                }

 

                this.accessRequirements.Add(

                    operationElement.Identifier,

                    roles.ToArray());

            }

        }

 

        public override bool CheckAccess(OperationContext operationContext)

        {

            string header =

            operationContext.RequestContext.RequestMessage.Headers.Action;

            string[] requiredRoles = null;

            if (!(this.accessRequirements.TryGetValue(

                                header,

                                out requiredRoles)))

            {

                return false;

            }

 

            string userName = null;

            foreach(ClaimSet claimSet in

operationContext.ServiceSecurityContext.AuthorizationContext.ClaimSets)

            {

                foreach (Claim claim in claimSet)

                {

                    if (string.Compare(

                claim.ClaimType,

    "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name",

                true) == 0)

                    {

                        userName = (string)claim.Resource;

                        foreach (string requiredRole in requiredRoles)

                        {

                            if (Roles.Provider.IsUserInRole(

                                                                userName,

                                                                requiredRole))

                            {

                                return true;

                            }

                        }

                    }

                }

            }

            return false;

        }

    }

}

 

按照下面的步骤测试修改后的解决方案。

(1) 依次选择DebugStart Debugging,开始调试程序。

(2) 当服务的控制台显示一些活动后,在Resource Access客户端用户界面里点击煤块按钮和原来一样,确认已经取得煤块的消息就会出现。

(3) 点击Resouce Access客户端用户界面里的钻石按钮,拒绝访问钻石的消息就会出现。

(4) Visual Studio的菜单里依次选择DebugStop Debugging

由于对原来的WCF应用程序进行了更改,用户的授权就不再由服务本身的代码来完成了。应用程序的配置指定了一个类——XSI服务授权管理器,负责对服务操作访问的管理。另外,授权的完成也不再与具体到某个安全域发布的某种类型的凭据关联在一起。XSL会将所有的凭据都翻译成声明的集合,验证机制就可以判定这些声明是否符合要求。虽然在这个例子里我们使用了一个需要有关Windows用户名声明的XSI授权管理器,在判定声明时也用到了Windows Server 2003授权管理器的授权存储,但是只需在配置里做些修改就可以使用不同的授权机制。本章后面的示例里就会包含这样的更改。

10.3.3 添加STS作为联合的基础

假定现在创建的Intranet服务部署在名为Woodgrove的机构里,下面我们就来做一些工作让这个服务可以被Fabrikam机构访问。

我们根据图10-1里给出的架构来完成这项任务。FabrikamWoodgrove都会提供STSWoodgrove STS会配置成信任由FabrikamSTS发布的有关用户的声明,Woodgrove服务被配置成信任由Woodgrove STS发布的有关用户的声明。当Fabrikam的一个用户使用Resource Access客户端程序访问由Woodgrove服务提供的操作时,这个程序将会从Fabrikam STS请求一组声明。Fabrikam STS会执行一个授权策略以决定它应该签发给用户什么样的声明。这个用到的授权策略使用用户的Windows凭据进行验证,然后利用ASP.NET 2.0 AuthorizatioinStoreRoleProviderWindows Server 2003 授权管理器来判断用户所分配的角色。根据用户所分配的角色,Fabrikam STS将给Resource Access程序发布一组有关这个用户角色的声明。

Resource Access客户端程序会将从Fabrikam STS获取的声明提交给Woodgrove STS,而后者信任由Fabrikam STS发布的声明。Woodgrove STS会执行一个授权策略,将Fabrikam STS给出的有关用户角色的声明翻译成另一组Woodgrove服务所熟悉的有关用户角色的声明。

Resource Access客户端程序会将由Woodgrove STS发布的有关用户角色的这组声明提交给Woodgrove服务,而后者信任由Woodgrove STS发布的声明。服务将Woodgrove STS有关用户角色的声明和有权访问这个操作(即用户通过Resource Access客户端所请求的操作)的角色进行对比。 这样,它就可以判断用户是否应该被授权访问这个操作了。

1. 证书安装

Woodgrove STS配置成信任由Fabrikam STS发布的有关用户的声明,也就是要确保Woodgrove STS可以验证由Fabrikam STS签名的声明。所以,需要安装一些X.509证书。依照下面的步骤完成这项任务。

(1) 根据7.4.1节中介绍的内容为WoodgroveFabrikam Enterprises安装X.509证书。

(2) 下面的这些步骤将这些证书从本地机器的个人存储复制到本地机器的Trusted People存储。实际上,Fabrikam Enterprises的证书将放在Woodgrove STS所在机器的Trusted People存储里,Woodgrove的证书也会放在Fabrikam STS所在机器的Trusted People存储里,这样也就将两个STS配置成互相信任的关系。在这个示例里,我们假定Fabrikam STSWoodgrove STS都运行在同一台机器上,所以两个证书都将在同一个Trusted People存储里。我们应该认识到,将Woodgrove的证书放在那里,Fabrikam STS就可以信任WoodgroveSTS;将Fabrikam Enterprises的证书放在那里,这样Woodgrove STS就可以信任Fabrikam STS的声明了。下面我们继续,从Windows开始菜单里选择Run,输入下面的命令打开Microsoft Management Console

 

:mmc

 

(3) Microsoft Management Console的菜单里依次选择FileAdd/Remove Snap-In

(4) Add/Remove Snap-In对话框里点击Add

(5) 从可用的独立管理单元列表中选择Certificates,然后点击Add按钮。

(6) Certificates管理单元对话框里选择Computer Account,然后点击OK按钮。

(7) 接受Select Computer对话框的默认设置,然后点击Finish按钮。

(8) 点击Add Standalone Snap-In对话框的Close按钮。

(9) 点击Add/Remove Snap-In对话框的OK按钮。

(10) 展开Microsfot Mangement Console左边框里显示的CertificateLocal Computer)节点。

(11) 展开Certificates节点的Personal子节点。

(12) 选择Personal节点的Certificates子节点。

(13) 把右边显示的FabrikamEnterprises证书和Woodgrove证书都选上。这可以通过下面的操作完成:按住Shift键不放,先点击FabrikamEnterprises证书,然后点击Woodgrove证书。

(14) 右击选中的证书,从右键菜单中选择Copy

(15) 右击CertificatesLocal Computer Node)的Trusted People子节点,从右键菜单中选择Paste

2. Fabrikam STS添加到解决方案中

现在我们继续将Fabrikam STS添加到解决方案中。

(1) C:\WCFHandsOn\AdvancedSecurity\IntranetSolution\FabrikamSecurityTokenService里的FabrikamSecurityTokenService.csproj项目添加到Security解决方案中。不要冒险从头写一个STS,可以先用那些已经被公认为不错的STS。所以,在这个练习里我们提供了已经创建好的STS。这些STS是由Martin Gudgin编写的,Martin就是WS-Trust规范(这个规范定义了STS)其中的一个作者。STS的行为会在后面的步骤里定制。

(2) 打开Security解决方案中FabrikamSecurityTokenService项目的IsecurityTokenService.cs文件,查看一下Fabrikam STS实现的IsecurityTokenService服务契约:

 

[ServiceContract(

        Name = "SecurityTokenService",

        Namespace = "http://tempuri.org")]

public interface ISecurityTokenService

{

    [OperationContract(Action = Constants.Trust.Actions.Issue,

                       ReplyAction = Constants.Trust.Actions.IssueReply)]

        Message ProcessRequestSecurityToken(Message rstMessage);

}

 

在这份安全契约的定义中,重要的元素是Constants.Trust.Actions.IssueConstants.Trust. Actions.IssueReply,它们定义了请求消息的合法WS-Addressing Action头和响应消息的WS- Addressing Action头。它们是在同个项目里的Constant.cs文件里定义的:

 

public const string Issue =

        "http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue";

public const string IssueReply =

        "http://schemas.xmlsoap.org/ws/2005/02/trust/RSTR/Issue";

 

这些为WS-Addressing Action头定义的值是由Web Services Trust LanguageWS-Trust)(Gudgin and Nadalin 2005)规范定义的,它们规定了与STS交换的SOAP消息的WS-Addressing Action头。这样,服务契约就定义了一个操作,它可以接收和返回具有WS-Trust协议所定义的WS-Addressing Action头的SOAP消息。所以,这个服务契约实际上描述了由WS-协议规范所定义的STS接口。或者换句话说,IsecurityTokenService协议定义了由WS-Trust规范所定义的STS服务契约。

(3) 打开Security解决方案里FabrikamSecurityTokenService项目的App.config文件,看看Fabrikam STS是如何配置的。代码清单10-6给出了配置的内容。

配置里的appSettingsSTS的代码用来标识它自己发布的用于签名和加密安全令牌的证书。

 

<appSettings>

        [...]

        <add key="ProofKeyCertificateIdentifier"

                value="CN=Woodgrove"/>

        <add key="IssuerCertificateIdentifier"

                value="CN=FabrikamEnterprises"/>

</appSettings>

 

令牌将使用Fabrikam Enterprises的私钥签名,使用Woodgrove的公钥加密。接收STS(这里即Woodgrove STS)的证书称为证明密钥,或更正式一点,在WS-Trust Language里它称为持有证明令牌([proof-of-possession token][GudginNadalin 20057]),因为只有持有与该证书对应的私钥,接收者才可以解密和使用令牌。

在配置中为STS终结点指定的绑定是标准的WSHttpBinding,实际上可以是任意的绑定。默认情况下,配置为使用WSHttpBinding的服务会使用Windows凭据来标识用户。

代码清单10-6 Fabrikam配置

<configuration>

  <appSettings>

    <add

      key="SecurityTokenServiceName"

      value="FabrikamEnterprises"/>

    <add

      key="ProofKeyCertificateIdentifier"

      value="CN=Woodgrove"/>

    <add

      key="IssuerCertificateIdentifier"

      value="CN=FabrikamEnterprises"/>

  </appSettings>

  <system.serviceModel>

    <services>

      <service

      name="SecurityTokenService.ConcreteSecurityTokenService"

      behaviorConfiguration="FabrikamSecurityTokenServiceBehavior">

        <host>

          <baseAddresses>

            <add baseAddress="http://localhost:8001/Fabrikam"/>

          </baseAddresses>

        </host>

        <endpoint

          address=""

          binding="wsHttpBinding"

          contract="SecurityTokenService.ISecurityTokenService"/>

        <endpoint

          address="mex"

          binding="mexHttpBinding"

          contract="IMetadataExchange" />

      </service>

    </services>

    ...[]

  <system.serviceModel>

</configuration>

 

3. Fabrikam STS的授权策略

在这个例子里,当Resource Access客户端程序准备使用Woodgrove Resource Access服务时,它的用户会首先用他们的Windows凭据访问Fabrikam STSFabrikam STS就会为他们发布一个安全令牌,它包含了Woodgrove服务可以理解的声明。发布这些令牌时,Fabrikam STS实际上会把从Fabrikam用户提供的Windows凭据里所得到的声明翻译成Woodgrove服务可以处理的声明。

回想一下之前那个人使用驾照和钱在酒吧里买酒喝的比喻。当时发生了些什么呢?侍者把驾照所提供的出生日期相关的声明翻译成有关驾照持有者年龄的声明,他还将放在吧台上的每张钞票的面额翻译成所付钱总额的声明。侍者将这些输入声明集翻译成输出声明集的过程中所执行的规则称为他的授权策略。授权策略的输入声明集成为评估上下文,输出声明集成为授权上下文。

所以,Fabrikam STS为了将Fabrikam用户的Windows凭据翻译成服务能处理的声明,它就必须实现某种授权策略。

(1) 再重新看看FabrikamSecurityTokenServcie项目的App.config文件。它使用 System.Service- Model.Description.ServiceAuthorization行为为STS定义了一个授权策略。

 

<behaviors>

        <serviceBehaviors>

                <behavior name='FabrikamSecurityTokenServiceBehavior'>

                        [...]

                        <serviceAuthorization>

                                <authorizationPolicies>

                                        <add

policyType='SecurityTokenService.AuthorizationPolicy,

FabrikamSecurityTokenService'/>

                                </authorizationPolicies>

                        </serviceAuthorization>

                </behavior>

        </serviceBehaviors>

</behaviors>

(2) 查看一下这个授权策略,它在项目的AuthorizationPolicy.cs文件中定义。代码清单10-7给出了这段代码。

授权策略通过实现System.IdentityModel.Policy.IauthorizationPolicy接口来定义。该接口定义的主要成员就是Evaluate()方法。

在这个方法的实现中,它接收一组XSI从用户的凭据中抽取的声明,然后将它们放到一个System.IdentityModelPolicy.EvaluationContext对象里。Evaluate()方法实现的代码所要做的事情就是检查System.IdentityModelPolicy.EvaluationContext对象里的声明,并且决定发布一组什么样的声明。不管System.IdentityModelPolicy.EvaluationContext对象里是什么声明,Evaluate()方法所返回的声明就是STS将包含在发布给用户的令牌中的声明。

在这个例子中,Evalute()方法的实现会先取得从用户的Windows凭据里抽取的声明,查找用户在授权管理器的授权存储里所分配的角色,然后发布有关用户所在角色的声明。这里可以使用任何翻译机制,唯一的限制就是发布的声明必须是Woodgrove STS可以识别的声明。

代码清单10-7 Fabrikam STS授权策略

public class AuthorizationPolicy : IAuthorizationPolicy

{

        [...]

 

    bool IAuthorizationPolicy.Evaluate(

       EvaluationContext evaluationContext,

       ref object state)

    {

        List<Claim> claimsToAdd = new List<Claim>();

        ReadOnlyCollection<ClaimSet> inputClaims =

            evaluationContext.ClaimSets;

        for (int index = 0; index < inputClaims.Count; index++)

        {

            foreach (Claim claim in

                           inputClaims[index].FindClaims(ClaimTypes.Name, null))

            {

                string[] roles = Roles.Provider.GetRolesForUser(

                    (string)claim.Resource);

                foreach (string role in roles)

                {

                    claimsToAdd.Add(

                        new DefaultClaimSet(

                            ClaimSet.System,

                            new Claim[] {

                    new Claim(

                        "http://schemas.fabrikam.com/2005/05/ClaimType/Role",

                        role,

                        Rights.PossessProperty) })[0]);

                }

            }

        }

 

        if (claimsToAdd.Count > 0)

        {

            evaluationContext.AddClaimSet(

                this, new DefaultClaimSet(

                this.Issuer, claimsToAdd));

        }

 

        return true;

    }

 

    [...]

 

}

 

4. Woodgrove STS添加到解决方案中

Woodgrove STS将接收来自Fabrikam用户的请求,向他们发布可以用来访问Woodgrove Resource Access服务的安全令牌。在向Woodgrove STS请求安全令牌时,Fabrikam用户将通过Fabrikam STS发布的令牌被验证。所以,Woodgrove STS会检查Fabrikam STS发布的声明并决定发布什么样的声明(WoodgroveResource Access服务能够理解这些发布的声明)。

很自然地,WoodgroveSTS不仅提供令牌给Resource Access服务使用,而且还提供给任何部署在Woodgrove中的服务使用。同样地,它不仅管理Fabrikam用户的访问,而且还可以管理任何Woodgrove的合作者的用户访问。也就是说,Woodgrove STS将负责声明正则化,即将各合作机构对其用户所做出的声明翻译成可以被所有部署在Woodgrove中的服务所理解的声明。

与所有STS所做的声明翻译工作一样,Woodgrove STS的声明正则化也被编写成一个授权策略,我们可以使用各种方法编写Woodgrove STS,使其完成这项任务。现在,我们使用WFF来做这件事情。更具体一点,当把接收的声明翻译成它将要发布的声明时所用到的规则会被定义成一组WFF规则。所接收的声明的翻译工作将通过执行一个顺序工作流来完成,这个工作流中包含了一个执行这些规则的策略活动。

在添加创建Woodgrove STS本身的项目之前,必须添加用来创建这个策略活动和工作流的项目(它将被STS项目引用)。

(1) 将位于C:\WCFHandsOn\AdvancedSecurity\IntranetSolution\ClaimMappingActivity目录下的ClaimMappingActivity.csproj项目添加到解决方案中。

(2) 选择该项目中的ClaimMappingActivity.cs文件,从Visual Studio的菜单中选择ViewDesigner

(3) 在设计窗口中,右击图10-9中高亮显示的ClaimMappingPolicy活动,在右键菜单中选择Properties

(4) 在属性编辑器中,点击RuleSetReference属性值旁边的省略号按钮(如图10-9所示)。这时会出现图10-10所示的Select Rule Set对话框。

(5) 点击Edit Rule Set按钮打开如图10-11所示的Rule Set EditorRule Set Editor显示了一条规则,Woodgrove通过这条规则将一条用户是经理的声明(接收到的声明)翻译成这个用户是执行官的声明(STS将发布的声明)。

(6) 选择Cancel关闭Rule Set Editor

(7) 选择Cancel关闭Select Rule Set对话框。

(8) C:\WCFHandsOn\AdvancedSecurity\IntranetSolution\ClaimMappingWorkflow文件夹中的ClaimMappingWorkflow.csproj项目添加到解决方案中。

10-9 Claim-mapping活动

     

10-10 Select Rule Set对话框           10-11 Rule Set Editor 

(9) 选择这个项目中的ClaimMappingWorkflow.cs文件,然后在Visual Studio的菜单里选择View Designer。这时将会显示图10-12中的顺序工作流。这个工作流中有一个System.Workflow.Activities. Replicator活动,它包含了由ClaimMappingActivity项目创建的声明映射活动。System.Workflow. Activities.Replicator活动被设计用来多次执行一个活动。这里,因为在接收的声明里可能包含多个声明,因而多次执行是必须的。也就是说,claim-mapping活动需要为每个接收的声明执行一次,这样它们才可以全部被翻译为输出声明。

现在,Woodgrove STS所依赖的Windows工作流组中已经添加到解决方案中,通过下面这些步骤添加Woodgrove STS项目。

10-12 Claim-mapping工作流

(1) C:\WCFHandsOn\AdvancedSecurity\IntranetSolution\WoodgroveSecurityTokenSerVice文件夹中的WoodgroveSecurityTokenService.csproj项目添加到解决方案中。

(2) 打开WoodgroveSecurityTokenService项目的App.config文件查看Woodgrove STS是如何配置的。代码清单10-8给出了配置文件的内容。

Woodgrove被配置成使用预定义的WSFederationHttpBinding,通过这个绑定,服务可以基于所信任的STS发布的安全令牌来验证请求。在这里,Fabrikam STS被指定为可信任的安全令牌发布者。

Fabrikam STS一样,Woodgrove STS的授权策略被配置成使用System.ServiceModel. Description.ServiceAuthorization行为,授权策略为SecurityTokenService.Authorization- Policy类。

代码清单10-8 Woodgrove STS配置

<services>

  <service

  name="SecurityTokenService.ConcreteSecurityTokenService"

  behaviorConfiguration="WoodgroveSecurityTokenServiceBehavior">

    <host>

      <baseAddresses>

        <add baseAddress="http://localhost:8002/Woodgrove"/>

      </baseAddresses>

    </host>

    <endpoint

      address=""

      binding="wsFederationHttpBinding"

      bindingConfiguration="WoodgroveSecurityTokenServiceBinding"

      contract="SecurityTokenService.ISecurityTokenService"  />

    <endpoint address="mex"

    binding="mexHttpBinding"

    contract="IMetadataExchange" />

  </service>

</services>

<bindings>

  <wsFederationHttpBinding>

    <binding name="'WoodgroveSecurityTokenServiceBinding'">

      <security mode="'Message'">

        <message>

          <issuerMetadata

          address="http://localhost:8001/Fabrikam/mex" >

          <identity>

            <dns value ="FabrikamEnterprises"/>

          </identity>

          </issuerMetadata>

        </message>

      </security>

    </binding>

  </wsFederationHttpBinding>

</bindings>

<behaviors>

  <serviceBehaviors>

    <behavior name="WoodgroveSecurityTokenServiceBehavior">

      [...]

      <serviceAuthorization>

        <authorizationPolicies>

          <add

            policyType="SecurityTokenService.AuthorizationPolicy,

            WoodgroveSecurityTokenService"/>

        </authorizationPolicies>

      </serviceAuthorization>

    </behavior>

  </serviceBehaviors>

</behaviors>

 

(3) 查看AuthorizationPolicy.cs文件中的这个授权策略,代码清单10-9复制了该文件的内容。

与其他的授权策略类型一样,它实现了System.IdentityModel.Policy.IAuthorizationPolicy接口。其中最重要的工作是由实现接口的Evaluate()方法的代码完成的。

在这段代码执行之前,XSI已经从和请求一同发给Woodgrove STS的安全令牌中抽取了一组声明(这里,这个安全令牌也就是由Fabrikam STS发布的安全令牌)。XSI从接收到的安全令牌中抽取的这组声明会被传递给Evaluate()方法,该方法又会将其传给前面所创建的声明映射工作流的一个实例。该工作流里的声明映射策略活动将为每个接收到的声明执行一次规则,把接收的声明翻译成输出声明。授权策略的Evaluate()方法中的代码在取得这些规则的运行所生成的声明后,会将它们添加到评估上下文对象中。STS从评估上下文对象中取得这些声明,然后将一个包含这些声明的安全令牌发布给请求者。

代码清单10-9 Woodgrove STS授权策略

public class AuthorizationPolicy : IAuthorizationPolicy

{

    [...]

    private string[] MapClaims(string[] inputClaims)

    {

        if (Thread.CurrentThread.Name == null)

        {

            Thread.CurrentThread.Name = Guid.NewGuid().ToString();

        }

        using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())

        {

            workflowRuntime.StartRuntime();

            workflowRuntime.WorkflowCompleted += this.ClaimMappingCompleted;

            Type type = typeof(ClaimMappingWorkflow);

            Dictionary<string, object> parameters =

                new Dictionary<string, object>();

            parameters.Add(

                "RequestIdentifier",

                Thread.CurrentThread.Name);

            parameters.Add(

                "InputClaims",

                inputClaims);

            AutoResetEvent waitHandle = new AutoResetEvent(false);

            lock (this.waitHandlesLock)

            {

                this.waitHandles.Add(Thread.CurrentThread.Name, waitHandle);

            }

            workflowRuntime.CreateWorkflow(type, parameters).Start();

            waitHandle.WaitOne();

            workflowRuntime.StopRuntime();

        }

        string[] outputClaims = null;

        lock (this.outputClaimsLock)

        {

            this.outputClaims.TryGetValue(

                Thread.CurrentThread.Name,

                out outputClaims);

            this.outputClaims.Remove(

                Thread.CurrentThread.Name);

        }

        return outputClaims;

    }

    #region IAuthorizationPolicy Members

    bool IAuthorizationPolicy.Evaluate(

                EvaluationContext evaluationContext,

        ref object state)

    {

        List<Claim> claimsToAdd = new List<Claim>();

        List<string> inputClaims = new List<string>();

        for (

                        int index = 0;

                        index < evaluationContext.ClaimSets.Count;

                        index++)

        {

            foreach (Claim claim in

                                evaluationContext.ClaimSets[index].FindClaims(

"http://schemas.fabrikam.com/2005/05/ClaimType/Role", null))

            {

                inputClaims.Add(claim.Resource.ToString());

            }

        }

        string[] roleClaims = this.MapClaims(inputClaims.ToArray());

        Claim targetClaim = null;

        foreach (string roleClaim in roleClaims)

        {

            targetClaim = new DefaultClaimSet(

                ClaimSet.System,

                new Claim[] {

                    new Claim(

                        "http://schemas.woodgrove.com/2005/05/ClaimType/Role",

                        roleClaim,

                        Rights.PossessProperty) })[0];

            claimsToAdd.Add(targetClaim);

        }

        if (claimsToAdd.Count > 0)

        {

            evaluationContext.AddClaimSet(this, new DefaultClaimSet(

                this.Issuer, claimsToAdd));

        }

        return true;

    }

}

 

10.3.4 重新配置Resource Access服务

Woodgrove STS已经部署好了,现在可以通过下面的步骤重新配置Resource Access服务,使其要求用户使用由Woodgrove STS发布的安全令牌来验证自己。

(1) 使用代码清单10-10里的配置替换了Service项目中的App.config中的内容。为了方便起见,我们已经将列表中的内容存放在C:\WCFHandsOn\AdvancedSecurity\IntranetSolution\Listing10.10文件夹下的App.config文件中。

新的配置中选择了预定义的WSFederationHttpBinding作为服务的绑定。再解释一遍,通过这个绑定,服务可以基于由一个信任的STS发布的安全令牌来验证请求者。这里,这个信任的STS也就是Woodgrove STS

代码清单10-10 重新配置服务

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

  <configSections>

    <section name="operationRequirements"

    type="Service.OperationRequirementsConfigurationSection,

    ServiceAuthorizationManager" />

  </configSections>

  <! Operation Requirements >

  <operationRequirements>

    <operation

    identifier="http://tempuri.org/IResourceAccessContract/AccessCoal">

      <role name="Executive"/>

      <role name="Other"/>

    </operation>

    <operation

identifier="http://tempuri.org/IResourceAccessContract/AccessDiamond">

      <role name="Executive"/>

    </operation>

  </operationRequirements>

  <! Service Configuration >

  <system.serviceModel>

    <services>

      <service name="Service.ResourceAccessServiceType"

      behaviorConfiguration="'ServiceBehavior'">

        <host>

          <baseAddresses>

            <add baseAddress="http://localhost:8000/Woodgrove"/>

          </baseAddresses>

        </host>

        <endpoint

          address="ResourceAccess"

          binding="wsFederationHttpBinding"

          bindingConfiguration="ResourceAccessBinding"

          contract="Service.IResourceAccessContract"/>

        <endpoint

          address="mex"

          binding="mexHttpBinding"

          contract="IMetadataExchange" />

      </service>

    </services>

    <bindings>

      <wsFederationHttpBinding>

        <binding name="'ResourceAccessBinding'">

          <security mode="Message">

            <message>

              <issuerMetadata

              address="http://localhost:8002/Woodgrove/mex">

              <identity>

                <dns value="Woodgrove"/>

              </identity>

              </issuerMetadata>

            </message>

          </security>

        </binding>

      </wsFederationHttpBinding>

  </bindings>

    <behaviors>

      <serviceBehaviors>

        <behavior name="ServiceBehavior">

          <serviceAuthorization

          serviceAuthorizationManagerType=

          "Service.AccessChecker,ServiceAuthorizationManager" />

          <serviceCredentials>

            <serviceCertificate

            findValue="CN=Woodgrove"

            x509FindType="FindBySubjectDistinguishedName"

            storeLocation="LocalMachine"

            storeName="My" />

            <issuedTokenAuthentication>

              <knownCertificates>

                <add

                findValue="CN=Woodgrove"

                x509FindType="FindBySubjectDistinguishedName"

                storeLocation="LocalMachine"

                storeName="TrustedPeople" />

              </knownCertificates>

            </issuedTokenAuthentication>

          </serviceCredentials>

          <serviceMetadata

          httpGetEnabled ="'true'"/>

        </behavior>

      </serviceBehaviors>

    </behaviors>

  </system.serviceModel>

</configuration>

 

(2) 现在,请求者将通过Woodgrove STS发布的安全令牌进行验证,此外还需要对Resource Access服务进行修改。服务配置使用的XSI服务授权管理器(代码清单10-5中显示了其代码)更适合于之前的intranet应用场景,而现在的场景中,其他的机构会通过Internet使用服务。

现有的XSI服务授权管理器取得XSIWindows凭据抽取的声明,然后根据Windows Server 2003 授权管理器的授权存储中的数据来评估这些声明是否满足要求。

代码清单10-11给出了新的XSI服务授权管理器,它更适合Internet的场景。它只需要XSIWoodgrove STS发布的安全令牌中抽取的声明,然后决定这些声明是否符合访问服务的要求。

因为服务已经增强可以使用XSI Service Authorization Manager了,所使用的特别的服务授权管理器可以非常容易地更改。实际上,这也是使用XSI授权管理器而不是System.Security. Permissions. PrincipalPermission特性的一个优点。在这种情况下,只要替换包含了服务授权管理器的程序集就可以完成这个改动。将Service AuthorizationManager项目从解决方案中删除,然后将C:\WCFHandsOn\AdvancedSecurity\IntranetSolution\InternetServiceAuthorizationManager文件夹中的ServiceAuthorization Manager项目添加到解决方案中。注意,别错误地将C:\WCFHandsOn\ AdvancedSecurity\IntranetSolution\IntranetServiceAuthorization Manager中的项目添加进来了,它是刚刚从解决方案中删掉的项目。

代码清单10-11 新的Service Authorization Manager

public class AccessChecker : ServiceAuthorizationManager

{

    public AccessChecker()

    {

        [...]

 

    }

 

    public override bool CheckAccess(OperationContext operationContext)

    {

        string header =

            operationContext.RequestContext.RequestMessage.Headers.Action;

        Claim[] requiredClaims = null;

        if (!(accessRequirements.TryGetValue(header, out requiredClaims)))

        {

            return false;

        }

 

        AuthorizationContext authorizationContext =

            operationContext.ServiceSecurityContext.AuthorizationContext;

 

        foreach (Claim requiredClaim in requiredClaims)

        {

            for (

                int index = 0;

                index < authorizationContext.ClaimSets.Count;

                index++)

            {

                if (

                    authorizationContext.ClaimSets[index].

                    ContainsClaim(requiredClaim))

                {

                    return true;

                }

            }

        }

        return false;

    }

}

10.3.5 重新配置客户端

现在,Resource Access服务已经配置为要求用户使用Woodgrove STS发布的安全令牌来标识自己。Woodgrove STS也已经完成了配置,它将为使用了Fabrikam STS发布的安全令牌的请求发布相应的安全令牌。所以,希望使用Resource AccessFabrikam用户就可以先从Fabrikam STS请求安全令牌,然后他们就可以用这些安全令牌从Woodgrove STS那里交换他们需要的安全令牌。现在还需要做的就是重新配置Resource Access客户端程序来反映这些新改变。

(1) 右击Visual Studio Solution Explorer中的Security解决方案,然后从右键菜单中选择Set Startup Projects

(2) 修改解决方案的startup project属性,如图10-13所示。

10-13 Security解决方案的startup项目属性

(3) 开始调试该解决方案。

(4) Windows开始菜单里依次选择All ProgramsMicrosoft Windows SDKDMD Shell,打开Microsoft Windows Vista Debug Build Environment

(5) 在命令提示行中输入cd命令使C:\WCFHandsOn\AdvancedSecurity\IntranetSolution成为当前目录。

(6) 输入下面的命令,使WCFService Metadata Tool为客户端程序生成必要的配置:

 

svcutil /config:app.config http://localhost:8000/Woodgrove

 

(7) 选择DebugStop Debugging

(8) Security解决方案的Client项目中删除App.config,这也就删除了客户端的当前配置。

(9) 使用Service Metadata Tool生成的配置文件替换刚删除的配置,也就是将C:\WCFHandsOn\ AdvancedSecurity\IntranetSolution\app.config文件添加到Security解决方案的Client项目中。

(10) 我们对生成的配置文件做些必要的修改。打开Security解决方案中Client项目的App.config文件,在那里修改Woodgrove服务的终结点定义,为终结点提供一个名字、更改指定契约的方式并且指定一个行为配置。最后,终结点的配置将如下面的内容所示:

 

<client>

        <endpoint

        address="http://localhost:8000/Woodgrove/ResourceAccess"

        binding="wsFederationHttpBinding"

        bindingConfiguration="WSFederationHttpBinding_IResourceAccessContract"

        contract="Client.IResourceAccessContract"

        behaviorConfiguration ="ResourceAccessClientBehavior"

        name="ResourceService">

                <identity>

                        <certificateReference

                                storeLocation="LocalMachine"

                                storeName="TrustedPeople"

                                findValue="CN=Woodgrove"/>

                </identity>

        </endpoint>

</client>

 

(11) 添加前一步骤中指定的行为配置,如代码清单10-12所示。这段配置中的行为详细说明了客户端如何验证Woodgrove Resource Access服务提供的用来验证自己的证书。

代码清单10-12 Internet Client配置

<client>

        <endpoint

        address="http://localhost:8000/Woodgrove/ResourceAccess"

        binding="wsFederationHttpBinding"

        bindingConfiguration="WSFederationHttpBinding_IResourceAccessContract"

        contract="Client.IResourceAccessContract"

        behaviorConfiguration ="ResourceAccessClientBehavior"

        name="ResourceService">

                <identity>

                        <certificateReference

                                storeLocation="LocalMachine"

                                storeName="TrustedPeople"

                                findValue="CN=Woodgrove"/>

                </identity>

        </endpoint>

</client>

<behaviors>

        <endpointBehaviors>

                <behavior name="ResourceAccessClientBehavior">

                        <clientCredentials>

                                <serviceCertificate>

                                        <authentication

                                   certificateValidationMode="PeerOrChainTrust" />

                                </serviceCertificate>

                        </clientCredentials>

                </behavior>

        </endpointBehaviors>

</behaviors>

10.3.6 体验带有XSI的、联合的、基于声明的标识的强大

通过下面的步骤来看看我们已经完成了什么。

(1) Visual Studio Solution Explorer中右击Security解决方案,从右键菜单中选择Set Startup Projects

(2) 修改解决方案的startup project属性,将Client项目添加到开始项目列表中。

(3) Visual Studio的菜单中选择DebugStart Debugging

(4) Fabrikam STSWoodgrove STSResource Access服务的控制台都显示了一些活动之后,点击Resource Access客户端程序用户界面中的煤块按钮。当Fabrikam STS为客户端程序发布Fabrikam安全令牌时,应该可以看到它的控制台正在显示一些活动。然后,当Woodgrove STS响应客户端程序发出的对Woodgrove安全令牌的请求时,也应该会看到它的控制台显示了一些活动。最后,根据Woodgrove安全令牌中的声明,应该会允许对煤块的访问。

(5) 点击Resource Access客户端的用户界面中的钻石按钮,应该会禁止对钻石的访问,因为用户在Fabrikam中并没有被赋予Manager的角色。结果,Fabrkam STS在它发布的安全令牌中包含了一条该用户是普通职员的声明。Woodgrove STS就会将这条声明翻译成一条该用户的角色中没有executive(执行官)角色的声明。Woodgrove Resource Access服务只向那些Woodegrove STS声明的其具有执行官特权的用户授予访问钻石的权限。

(6) Visual Studio的菜单中选择DebugStop Debugging

在余下的这些步骤中,基于声明的安全的好处会被充分地表现出来。Resource Access客户端的用户在Fabrikam机构里会被提拔,从而在访问Woodgrove资源的时候体验访问权限提升带来的好处。

(1) 将当前用户替换为Fabrikam内部的Manager角色。这可以根据本章之前所给出的步骤来完成,使用AzMan用户界面将用户添加到Authorization Manager授权存储里的Manager角色中。

(2) Visual Studio的菜单里选择DebugStart Debugging

(3) 当服务、Woodgrove STSFabrikam STS的控制台都显示了一些活动之后,点击Resource Access客户端程序的钻石按钮。因为用户在Fabrikam内部已经得到提升了,用户现在可以访问之前被禁止访问的Woodgrove中的更宝贵的钻石资源了。

通过Resource Access对钻石资源的访问,XSI所能提供的业务机遇应该变得挺清晰了。在不需要编写任何代码的情况下,原来为在内网中使用而设计的应用程序可以升级为被外部伙伴通过因特网访问。而且,这样的应用程序有非常高的可配置性。双方机构的授权机制都可以被系统管理员修改,他们可以使用AzMan用户界面和Windows Workflow Foundation Rule Set Editor来完成修改。

10.4 基于声明的安全和联合安全

本章已经讲述了基于声明的安全。WCF的文档称之为联合安全(federated security)。它们有什么区别呢?

当联合(federation)一词在WS-Federation Language规范中使用时,它涉及安全域(realm)之间信任的建立,联合是通过域之间相互信任的声明来完成的(Kaler and Nadalin 2003)。然而,声明也可以在某个realm中使用,例如SID(它只有在其所发布的域中才是有意义的)就可以使用声明来描述。所以,基于声明的安全比联合更具一般性,联合只不过是基于声明安全的一种应用(虽然是一种规范的应用)。

同样,对于XSI来说,System.Security.IdentityModel.Claims.Claim类是比System.Service- Model.WSFederationBinding更基础的类。如第7章所示,可以使用XSI来授权对终结点的访问,这时将不使用System.ServiceModel.WSFederationBinding作为终结点的绑定,直接检查一组System.Security. IdentityModel.Claims.Claim对象。

另外,我们再看看联合安全在WCF的文档中是如何定义的:“联合安全是这样的一种机制,它可以把服务和与之相关的对客户端的验证及授权过程清楚地区分开”(Microsoft Corporation 2006)。“清楚地区分开”是如何实现的呢?这是通过让服务信任某种验证和授权的机制实现的。一旦服务将它的安全交给某个它所信任的完全独立的机制时,服务的安全就被联合了,这与政治实体安全的联合方式相似,它会信任其他的政治实体以提供军事保护以及管理公民。

将其安全联合的好处就是对服务的访问会变得更灵活,同时也更容易管理。访问可扩展到任何一方,只要它得到了验证和授权机制中配置的信任机构的担保。反过来,访问也仅限于这些被担保方。单独的验证授权机制成了服务(也可能是其他任意数目的服务)的集中访问控制点。

其安全的联合是通过使服务信任单独的验证和授权机制所做的决定来实现的,这使得联合安全系统也成为基于声明的系统。用户从单独的验证和授权机制处所取得的是声明,服务对它们可信也可不信。所以,就联合安全在WCF文档中的使用来说,它与这里使用的基于声明的安全是可以互换的。

10.5 小结

本章的重点是在本部分的几章中一直出现的声明的概念。声明包含了类型、权限和值。声明是由某些可信或完全不可信的发布者对持有者所做出的。

WCFXSI提供了基于声明安全的基础,它将各种来源的各种类型的凭据翻译成声明集。这些声明可以通过XSI授权策略从一个机构的语言翻译成另外一个机构的语言。服务可以使用XSI授权管理器来评估声明是否满足访问所请求的资源的要求。授权策略和授权管理器都可以通过配置来指定,使得对资源访问的授权过程和资源本身完全独立了。

我们通过一个练习讲述了使用WSI的基于声明的安全的强大功能。在这个练习中,我们把一个使用基于角色授权控制访问的内网服务扩展为基于声明的、允许其他机构的用户访问的服务。在这个过程中,客户端或服务的代码都不需要做任何更改,这再次显示了WCF在复杂的商业应用场景中的强大。

有一点需要记住的是,虽然基于声明安全的优点很明显,但是它也有劣势,那就是对它的支持还比较有限。所以,读者应该注意到,在本章所使用的Fabrikam机构示例应用程序里,安全仍然基于Windows凭据,也就是Resource Acces客户端程序如何向Fabrikam STS标识用户。这种混合使用的安全方式可能是最明智的做法,它依然使用机构中现有的安全工具,但是在机构之间却利用更合适的、强大的基于声明的安全方式。虽然基于声明的安全是通往极乐世界的阳关道,但是这条路还没有完全铺好。

 

posted @ 2011-10-22 16:02  spirit1  阅读(539)  评论(1编辑  收藏  举报