声明演练:编写 SharePoint 2010 的声明提供程序

**摘要:**了解如何编写声明提供程序以扩充声明,以及如何提供名称解析。使用声明身份验证,您可以根据声明指定权限,而无需知道用户是谁或对其进行身份验证的方式。您只需要知道用户的属性。

上次修改时间: 2012年5月31日

适用范围: Business Connectivity Services | Open XML | SharePoint Designer 2010 | SharePoint Foundation 2010 | SharePoint Online | SharePoint Server 2010 | Visual Studio

本文内容
声明提供程序概述
第 1 部分:扩充声明和注册提供程序
第 2 部分:添加对层次结构节点的支持
第 3 部分:搜索声明
第 4 部分:支持名称解析
结论
其他资源

**供稿人:**Steve Peschka,Microsoft Corporation

目录

  • 声明提供程序概述

  • 第 1 部分:扩充声明和注册提供程序

  • 第 2 部分:添加对层次结构节点的支持

  • 第 3 部分:搜索声明

  • 第 4 部分:支持名称解析

  • 结论

  • 其他资源

单击以获取代码  下载代码:SqlClaimsProvider_MSDNExample.zip(该链接可能指向英文页面)

声明提供程序概述

Microsoft SharePoint 2010 中的声明提供程序发布声明并将声明打包到安全令牌(即用户令牌)中。当用户登录 Microsoft SharePoint Foundation 2010 或 Microsoft SharePoint Server 2010 时,用户令牌将得到验证,然后用于登录 SharePoint。

SharePoint 2010 中的声明提供程序主要有以下两种用途:

  • 扩充声明

  • 提供名称解析

声明扩充是登录声明身份验证网站之后所执行过程的一部分。请务必知晓以下内容可以在 SharePoint 2010 中通过声明身份验证运行:

  • Windows 声明(通过 NTLM 或 Kerberos 协议登录时)

  • 基于表单的身份验证声明(使用 ASP.NET 成员身份和角色提供程序时)

  • 安全声明标记语言 (SAML) 声明(使用安全令牌服务 STS 登录时,例如使用 Active Directory 联合身份验证服务 (AD FS) 2.0)

登录后,会触发该 Web 应用程序所有注册的声明提供程序。触发声明提供程序之后,它们可以添加登录时未提供的声明。例如,您可能使用 Windows 身份验证执行了登录。但是我们希望授予可用于访问 SAP 系统的其他声明。这时我们便需要声明扩充来实现此目的。

名称解析应用于下面的两种部件中:人员选取器控件和输入控件。当您希望允许用户搜索声明并查找您的声明时,声明提供程序可通过人员选取器帮助您实现此目的。输入控件允许您输入用户名称或声明,然后单击解析按钮。您的声明提供程序可以检查该输入并确定其是否有效。如果有效,声明提供程序将"尽全力"对其进行解析。

在抽象地讨论完声明提供程序的作用后,现在我们将开始设计用于构建自定义声明提供程序的方案。在我们的特殊方案中,我们将要基于用户最喜爱的篮球队来分配我们网站集中的权限。这是声明身份验证最强大的功能之一,同时也是您需要熟悉的内容,即我们不在乎用户是谁或向网站对用户进行身份验证的方式;我们只在乎用户的一个属性,那就是用户最喜爱的球队是哪个。

对于我们的 Web 应用程序,我同时启用了 Windows 声明和基于表单的身份验证。我对基于表单的身份验证用户使用标准的 SQL 成员身份和角色提供程序,另外我还在基于表单的验证帐户列表中预写入了 user1user50。为了简化该方案,采用以下方式确定用户最喜爱的球队:

  • 所有 Windows 用户最喜爱的球队是 Consolidated Messenger

  • 对于基于表单的身份验证用户:

    • user1user15 最喜爱的球队是 Consolidated Messenger

    • user16user30 最喜爱的球队是 Wingtip Toys

    • user31user50 最喜爱的球队是 Tailspin Toys

第 1 部分:扩充声明和注册提供程序

我们要做的第一件事就是声明扩充。如前面所述,对用户进行身份验证之后,即会触发我们的声明提供程序。我们使用先前定义的规则将声明(他们最喜爱哪个篮球队)添加到用户令牌。

使用以下步骤编写声明提供程序。该示例使用 Microsoft Visual Studio 2010 创建类库应用程序。

步骤 1:添加引用

首先,添加对 Microsoft.SharePoint 和 Microsoft.IdentityModel 的引用。假设您可以在您的 SharePoint 安装中找到 Microsoft.SharePoint.dll。将 Microsoft.IdentityModel.dll 作为 Windows Identity Foundation 的一部分来安装。以下为 Microsoft.IdentityModel.dll 在我的安装中的完整路径。

<drive>:\Program Files\Reference Assemblies\Microsoft\Windows Identity Foundation\v3.5\Microsoft.IdentityModel.dll

步骤 2:添加 using 语句和类继承

在拥有引用后,接下来必须添加 using 语句并定义我们的类要从其继承的基类。在该示例中,我们使用以下 using 语句。

using Microsoft.SharePoint.Diagnostics;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.Administration.Claims;
using Microsoft.SharePoint.WebControls;

我们的类必须继承自 SPClaimProvider 基类。以下代码演示了我们的类开始的方式;请不要被名称 SqlClaimsProvider 混淆;我最初编写它只是为了用于 SQL 成员身份和角色提供程序,但之后却决定还将其用于 Windows。

namespace SqlClaimsProvider
{
    public class SqlClaims : SPClaimProvider
    {

        public SqlClaims(string displayName)
            : base(displayName)
        {
        }
    }
}

步骤 3:添加所需的实现

无需单步执行您必须实现的所有接口,您只需将鼠标停在上面代码中的 SPClaimProvider 名称处,单击 S 下显示的下拉箭头,然后选择 Implement abstract class 'SPClaimProvider' 命令。

步骤 4:实现提供程序中附带的功能

无论您要执行何种操作,在实现抽象类之后,您都必须实现 SPClaimProvider 类中五个属性:Name、SupportsEntityInformation、SupportsHierarchy、SupportsResolve 和 SupportsSearch。

Supports* 属性开始。如果您只是要执行声明扩充,唯一需要支持的项目就是实体信息。我的类属性列表如下。

public override bool SupportsEntityInformation
{
      get 
      { 
            return true; 
      }
}

public override bool SupportsHierarchy
{
      get 
      { 
            return false; 
      }
}

public override bool SupportsResolve
{
      get 
      { 
            return false; 
      }
}

public override bool SupportsSearch
{
      get 
      { 
            return false; 
      }
}

接下来,实现 Name 属性的支持。您一般会希望同时支持显示名称和可以引用提供程序的"内部"名称。

在这种情况下,我使用内部静态属性,这样我便可以将它们与稍后要添加到项目中的其他类共享。我对 Name 属性的实现如下。

internal static string ProviderDisplayName
{
      get
      {
            return "Basketball Teams";
      }
}


internal static string ProviderInternalName
{
      get
      {
            return "BasketballTeamProvider";
      }
}


public override string Name
{
      get
      {
            return ProviderInternalName;
      }
}

我们已完成了所有的基本实现。为了让您进一步熟悉其使用,我首先建立了一个数组,以在声明提供程序中用于球队。其添加在类的顶部,如下所示。

// Teams that we are using.
private string[] ourTeams = new string[] { "Consolidated Messenger", "Wingtip Toys", "Tailspin Toys " };

接下来,我们必须实现声明提供程序的核心。为了执行声明扩充,我为 FillClaimsForEntity 方法、FillClaimTypes 方法和 FillClaimValueTypes 方法在 SPClaimProvider 类中添加了实现代码。

为了开始此过程,我们创建了一些简单的帮助程序函数,这是因为我们将在创建声明提供程序的过程中调用这些函数。这些帮助程序函数定义 ClaimType(其基本上可以说是我们的声明命名空间或标识符)和 ClaimValueType(例如 string 或 int)。

以下便是为我们执行以上定义的两种帮助程序函数。

private static string SqlClaimType
{
      get
      {
            return "http://schema.steve.local/teams";
      }
}

private static string SqlClaimValueType
{
      get
      {
            return Microsoft.IdentityModel.Claims.ClaimValueTypes.String;
      }
}

此时,您可以根据其确定我们的声明提供程序是否有效。我们将要添加一个名为 http://schema.steve.local/teams 的声明,该声明的值是一个字符串。

从之前的代码和信息中,我们知道值有 Consolidated MessengerWingtip ToysTailspin Toys 这三种情况。对于声明名称本身,没有任何必需的规则对名称的形式进行限制。我们通常采用格式 schemas.SomeCompanyName.com/ClaimName。在本例中,我的域为 steve.local。因此,我使用了 schemas.steve.local,而 teams 就是我希望该声明跟踪的属性。

我们真正要添加该声明的时机在此之后,在 SPClaimProvider 类的 FillClaimsForEntity 方法中,如以下代码所示。

protected override void FillClaimsForEntity(Uri context, SPClaim entity, 
      List<SPClaim> claims)
{
if (entity == null)
      throw new ArgumentNullException("entity");

if (claims == null)
      throw new ArgumentNullException("claims");

// Determine who the user is, so that we know what team to add to their claim
// entity. The value from the input parameter contains the name of the 
// authenticated user. For a SQL forms-based authentication user, it looks similar to
// 0#.f|sqlmembership|user1; for a Windows claims user it looks similar 
// to 0#.w|steve\\wilmaf.
// I will skip some uninteresting code here, to look at that name and determine
// whether it is a forms-based authentication user or a Windows user, and if it is a forms-based authentication user, 
// determine what the number part of the name is that follows "user".

string team = string.Empty;
int userID = 0;
            
// After the uninteresting code, "userID" will equal -1 if it is a Windows user.
// If it is a forms-based authentication user, it will contain the number that follows "user".
            
// Determine what the user's favorite team is.
if (userID > 0)
{
      // Plug in the appropriate team.
      if (userID > 30)
            team = ourTeams[2];
      else if (userID > 15)
            team = ourTeams[1];
      else
            team = ourTeams[0];
}
else
      team = ourTeams[1];  
      // If they are not one of our forms-based authentication users, 
      // make their favorite team Wingtip Toys.
      
// Add the claim.
claims.Add(CreateClaim(SqlClaimType, team, SqlClaimValueType));
}

该方法中最需要指出来说明的部分就是代码的最后一行。我们采用的输入 claims 参数是 SPClaim 对象的列表。我们使用 CreateClaim 帮助程序方法创建新的声明。

提示提示

我强烈建议您在任何可能的情况下都使用帮助程序方法。它们通常不仅仅只是使用默认的构造函数,您一般都会发现使用帮助程序方法时,任务会执行得更为彻底。

最后,在创建声明时,我们将使用我之前介绍的两种帮助程序方法:SqlClaimType 方法和 SqlClaimValueType 方法。

现在我们已完成了此代码最复杂的部分,另外还有两种我们必须为其提供实现的方法:FillClaimTypes 和 FillClaimValueTypes。这些方法很容易实现,因为我们只需对其使用帮助程序方法即可。它们的实现如下。

protected override void FillClaimTypes(List<string> claimTypes)
{
      if (claimTypes == null)
            throw new ArgumentNullException("claimTypes");

      // Add our claim type.
      claimTypes.Add(SqlClaimType);
}

protected override void FillClaimValueTypes(List<string> claimValueTypes)
{
      if (claimValueTypes == null)
            throw new ArgumentNullException("claimValueTypes");

      // Add our claim value type.
      claimValueTypes.Add(SqlClaimValueType);
}

我认为这些应该都很简单。所以,我便不再赘述。

现在我们已完成了声明提供程序的基本实现,其会帮助我们执行声明扩充。但是如何使用它呢?首选的方法是使用声明功能接收器。

步骤 5:创建声明提供程序功能接收器

对于此步骤,我们将从向项目添加类开始。该类会继承自 SPClaimProviderFeatureReceiver 基类。它的实现很简单。因此我已将此部分的所有代码从简单代码文件复制到下面的示例中。

using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.Administration.Claims;
using Microsoft.SharePoint.Diagnostics;


namespace SqlClaimsProvider
{
    public class SqlClaimsReceiver : SPClaimProviderFeatureReceiver
    {

        private void 
              ExecBaseFeatureActivated(
              Microsoft.SharePoint.SPFeatureReceiverProperties properties)
        {
            // Wrapper function for base FeatureActivated. Used because base
            // keyword can lead to unverifiable code inside lambda expression.
            base.FeatureActivated(properties);
        }

        public override string ClaimProviderAssembly
        {
            get 
            {
                return typeof(SqlClaims).Assembly.FullName;
            }
        }

        public override string ClaimProviderDescription
        {
            get 
            { 
                return "A sample provider written by Steve Peschka"; 
            }
        }

        public override string ClaimProviderDisplayName
        {
            get 
            {
                  // This is where we reuse that internal static property.
                return SqlClaims.ProviderDisplayName; 
            }
        }

        public override string ClaimProviderType
        {
            get 
            {
                return typeof(SqlClaims).FullName; 
            }
        }

        public override void FeatureActivated(
              SPFeatureReceiverProperties properties)
        {
            ExecBaseFeatureActivated(properties);
        }
    }
}

我认为需要指出以下事项:

  • SPClaimProviderFeatureReceiver 类的 ClaimProviderAssembly 属性和 ClaimProviderType 属性仅仅使用我们编写的自定义声明提供程序类的类型属性。

  • ClaimProviderDisplayName 使用我们对声明提供程序类创建的内部静态属性。我们创建该属性以便在声明功能接收器中重复使用它。

  • FeatureActivated 事件调用我们特殊的内容方法,以便激活属性。

步骤 6:编译程序集并将其添加到全局程序集缓存中

我将以手动执行的方法来撰写此步骤。但如果您愿意,可以使用解决方案包。在此方案中,我在开发模式下于一台服务器中执行所有操作。因此,我执行的方式与只需将其分发到场中的情况稍有不同。

目前,请确保您的程序集已强命名,然后对其进行编译。为了使项目进展下去,无论您选择使用何种方法,只需将程序集添加到全局程序集缓存中即可(我使用后期生成事件和全局程序集缓存工具 gacutil.exe)。现在我们便可以开始创建和部署功能。

步骤 7:创建和部署声明提供程序功能

对于此步骤,没有任何特定于声明提供程序的内容。我们创建标准的 SharePoint 功能并对其进行部署。如何创建和部署声明提供程序功能的基础知识不在本文的讨论范围之内。下面的 XML 复制于我的 feature.xml 文件,以便您了解其编写过程。

备注

有关如何安装和卸载功能的详细信息,请参阅安装或卸载功能

<Feature   Id="3E864B5C-D855-43e4-B41A-6E054C6C0352" 
           Title="Sql Claims Provider Feature" 
           Scope="Farm" 
           ReceiverAssembly="SqlClaimsProvider, Version=1.0.0.0, Culture=neutral, PublicKeyToken=eaf1ba21464322ae" 
           ReceiverClass="SqlClaimsProvider.SqlClaimsReceiver" 
           xmlns="https://schemas.microsoft.com/sharepoint/" />

通过引用我的 feature.xml 文件,我在 Features 文件夹中创建了名为 SqlClaimsProvider 的目录,并安装了功能。由于它是一个场范围的功能,因此可以自动进行激活。以下是在 Windows PowerShell 中执行激活的命令。

install-spfeature -path SqlClaimsProvider

在做好所有准备工作后,我们可以试用声明提供程序。

步骤 8:登录网站并试用

在最后一步中,我使用本文开始时所介绍的 Web 应用程序。它支持 Windows 声明和基于表单的身份验证声明。

备注

有关如何登录 SharePoint 的详细信息,请参阅传入声明:登录到 SharePoint

为了验证是否已正确扩充我的声明,我创建了显示我的所有声明的 Web 部件。我将向您显示有关它的屏幕快照,以证明声明扩充确实已生效。

首先,我使用 Windows 凭据登录网站。从我们之前的代码可以得知,应始终为 Windows 用户指定 Wingtip Toys 作为他或她最喜爱的球队。图 1 显示了 Windows 用户如上所述的情景。

图 1. Windows 用户的声明扩充

Windows 用户的声明扩大

图 2 显示基于表单的验证用户的类似情景。

图 2. 基于表单的验证用户的声明扩充

基于表单的身份验证的声明扩大

棒极了,我们已让声明扩充生效。在第 2 部分中,我将演示如何开始使用人员选取器,另外还介绍支持层次结构并将其添加到人员选取器。

第 2 部分:添加对层次结构节点的支持

在第 1 部分:扩充声明和注册提供程序中,我演示了如何创建自定义声明提供程序,以及如何执行声明扩充。在第 2 部分中,我将演示将层次结构添加到人员选取器控件中的一个简单方法。整个过程及其结果可能会比较乏味,但在第 3 部分中,您会看到我们如何使用它。

接下来,我们将回到第 1 部分所创建的类,并对 SPClaimProvider 类的一个属性和一个方法执行修改:SupportsHierarchy 属性和 FillHierarchy 方法。

我们希望 SupportsHierarchy 属性返回 true,这样 SharePoint 便知道我们可以填写一个层次结构,如以下代码段所示。

public override bool SupportsHierarchy
{
      get 
      { 
            return true; 
      }
}

我们将 SupportsHierarchy 属性设置为 true 之后,当显示人员选取器时,SharePoint 将会调用 FillHierarchy 方法。

我们将节点添加到层次结构时,必须提供显示名称和节点标识符 (ID)。为了帮助实现此目的并对应至我们一直使用的最喜爱篮球队,我创建了一些简单的帮助程序数组来跟踪标签名称和 ID。以下为示例代码。我还展示了用于我们最喜爱球队的球队名称数组。

// Teams that we are using.
private string[] ourTeams = new string[] { "Consolidated Messenger", "Wingtip Toys", "Tailspin Toys " };

// Keys for our People Picker hierarchy.
private string[] teamKeys = new string[] { "empUS", "empEMEA", "empASIA" };

// Labels for our nodes in the People Picker.
private string[] teamLabels = new string[] { "US Teams", "European Teams", "Asian Teams" };

在知道我们的键和标签后,我将向您演示 FillHierarchy 方法,我使用该方法在显示选取器时添加节点。这只用于演示,我并不需要对此特定方案从功能角度执行此操作。 

此时特别需要注意的是,可以使用声明来设置 SharePoint 网站中几乎所有的安全项目(除网站集管理员以外)。幸运的是,对网站集管理员执行搜索时我们确实获得了一条提示。出现此情况时,entityTypes 数组有一个项目,即 Users。在所有其他情况下,您通常会在 entityTypes 数组中看到至少六个项目。因此我们在填写层次结构时添加了一个特殊检查,因为我们不希望添加我们永远不会使用的层次结构。

protected override void FillHierarchy(Uri context, string[] entityTypes,
      string hierarchyNodeID, int numberOfLevels,
      Microsoft.SharePoint.WebControls.SPProviderHierarchyTree hierarchy)
{                        

// Ensure that People Picker is asking for the type of entity that we 
// return; site collection administrator will not return, for example.
if (!EntityTypesContain(entityTypes, SPClaimEntityTypes.FormsRole))
      return;

// Check to see whether the hierarchyNodeID is null; it is when the control 
// is first loaded, but if a user clicks on one of the nodes, it will return
// the key of the node that was clicked. This lets you build out a 
// hierarchy as a user clicks something, instead of all at once. So I 
// wrote the code for that scenario, but I am not using it in that way.  
// Which means that I could have just as easily used an 
// if (hierarchyNodeID == null) in this particular implementation, instead
// of a switch statement.
switch (hierarchyNodeID)
{
      case null:
            // When it first loads, add all our nodes.
            hierarchy.AddChild(new 
                  Microsoft.SharePoint.WebControls.SPProviderHierarchyNode(
                  SqlClaims.ProviderInternalName, 
                  teamLabels[0], 
                  teamKeys[0], 
                  true));

            hierarchy.AddChild(new
                  Microsoft.SharePoint.WebControls.SPProviderHierarchyNode(
                  SqlClaims.ProviderInternalName,
                  teamLabels[1],
                  teamKeys[1],
                  true));

            hierarchy.AddChild(new
                  Microsoft.SharePoint.WebControls.SPProviderHierarchyNode(
                  SqlClaims.ProviderInternalName,
                  teamLabels[2],
                  teamKeys[2],
                  true));
            break;
      default:
            break;
} 
}

以上所有代码都是为了检查 hierarchyNodeID 是否为空,也是控件首次加载的内容。如果检查结果为空,则将我的节点添加到左侧窗格的树中。为了让该代码生效,我重新编译了程序集,将其添加回全局程序集缓存,并执行了 IISRESET。然后我就可以打开网站并尝试修改权限以显示人员选取器。图 3 显示了实现该代码后的人员选取器。

图 3. 具有层次结构节点的人员选取器

具有层次结构节点的人员选取器

此处也只需要说明一点。如果您是完全按照我所述的执行,则您可能会奇怪为什么人员选取器将声明提供程序名称显示为 Steve's SQL Claims Provider,而不是您在代码 Basketball Teams 中看到的 Name 属性的值。简单的说,只是因为我偷了下懒。您看到的是我最初为声明提供程序命名的名称。完成此方案之后,我本打算更改该名称,但是没有执行完在声明提供程序列表中更新该名称的全部步骤。所以您才会看到现在的名称。

在第 3 部分中,我们将研究如何使用您的自定义声明提供程序来在人员选取器中启用对声明的搜索。

第 3 部分:搜索声明

在本文的前两个部分中,我们已经了解如何创建自定义声明提供程序、如何执行声明扩充和注册提供程序,以及如何将层次结构添加到人员选取器中。在第 3 部分中,我将介绍如何实现使用自定义声明提供程序在人员选取器中对声明执行搜索。如果无法选择自定义声明,我们也将无法在网站中根据最喜爱的篮球队设置权限。

首先,我要好好地阐述下其中的缘由,因为这非常重要,而且不好理解。根据用户的属性而不是用户是谁来设置权限,这句话有很多种理解。它可以理解为我不关心用户是谁,即在此方案中的含义。我不关心您登录的方式。我也不关心您是 Windows 用户还是基于表单的验证用户。

我只关心您最喜爱的篮球队是谁。以下是 Microsoft Office SharePoint Server 2007 中的一个典型问题,该问题依然存在于 SharePoint 2010 中:我上班时,使用我的 Windows 凭据 Steve Peschka 登录到 SharePoint 网站。但我回到家时,我通过我的 Extranet 打开网站,并且我必须使用基于表单的身份验证。

对于我们的方案,假设我是一名基于表单身份验证的用户 user1。会存在什么问题呢?对于 SharePoint 来说,存在着两种用户。在 SharePoint Server 2007 或 SharePoint 2010 中都无法执行同种用户映射,也就是我们说的"Steve Peschka Windows 用户和 user1 基于表单身份验证的用户其实是同一个用户。"

您知道吗?我们不用再担心这种问题了。这是为什么呢?因为我们不根据用户是谁来指定权限。我们根据用户最喜爱的篮球队是谁来指定权限。我所实现的方案是经过简化后的示例。但是您可以想象一下,每条公司元数据都与各个员工关联,声明提供程序对某个其他系统执行查询来确定员工使用的所有不同标识(Windows 身份验证、基于表单的身份验证、SAP 和 CRM 等),并可以将某个其他标识符或一组声明映射至该标识。然后将这些声明用于授予对资源的访问权限。

您是否希望在不同的 Web 应用程序之间实现单一登录?虽然 Web 应用程序 1 中的 user1 与 Web 应用程序 2 中的 user1 不同,但是谁在乎呢?只要使用我最喜爱的篮球队声明,即可在这些 Web 应用程序之间无缝移动,这是因为每次他们进行身份验证时都会扩充该声明。我不能说这方法很完美,因为它根本不完美。但是它确实比以往的任何一种安全模型更接近于完美。

好吧,有关该问题的阐述就到这里。我们开始讨论有关在人员选取器中支持搜索的问题。要让自定义声明提供程序支持搜索,我们必须为 SPClaimProvider 类的以下属性和方法添加支持:SupportsSearch、SupportsResolve、FillSearch、FillResolve(将 SPClaim 作为输入参数的重载)、FillSchema 和 FillEntityTypes。

这里的属性应该非常简单,因此我们先处理它们。如以下代码所示。

public override bool SupportsSearch
{
      get 
      { 
            return true; 
      }
}

public override bool SupportsResolve
{
      get 
      { 
            return true; 
      }
}

FillSearch 方法对您来说可能最有意义。我们在以下代码中对其进行研究。

protected override void FillSearch(Uri context, string[] entityTypes, 
      string searchPattern, string hierarchyNodeID, int maxCount,
      Microsoft.SharePoint.WebControls.SPProviderHierarchyTree searchTree)
{

// Ensure that People Picker is asking for the type of entity that we 
// return; site collection administrator will not return, for example.
if (!EntityTypesContain(entityTypes, SPClaimEntityTypes.FormsRole))
      return;

// The counter to track what node we are in; it will be used to call into
// our helper arrays that were covered in part 1 and part 2 of this article.
int teamNode = -1;

// NOTE: If we were not using hierarchies in the People Picker, we would
// probably use a list of PickerEntity instances so that we could
// add them all to the People Picker in one call. I have stubbed it out
// here for demonstration purposes so you can see how you 
// would do it. 
// Picker entities that we will add if we find something.
// List<PickerEntity> matches = new List<PickerEntity>();

// The node where we will place our matches.
Microsoft.SharePoint.WebControls.SPProviderHierarchyNode matchNode = null;

// Look to see whether the value that is typed in matches any of our teams.
foreach (string team in ourTeams)
{
      // Increment team node tracker.
      teamNode += 1;

      // Simple way to do a string comparison to the search criteria.
      // This way, all a person has to type in to find Consolidated Messenger is "b".
      if (team.ToLower().StartsWith(searchPattern.ToLower()))
      {
            // We have a match, create a matching entity.
            // This is a helper method that I will explain later.
            PickerEntity pe = GetPickerEntity(team);

            // If we did not have a hierarchy, we would add it here
            // by using the list described previously.
            // matches.Add(pe);

            // Add the team node where it should be displayed; 
            // ensure that we have not already added a node to the tree
            // for this team's location.
            if (!searchTree.HasChild(teamKeys[teamNode]))
            {
                  // Create the node so that we can show our match in there too.
                  matchNode = new 
                  SPProviderHierarchyNode(SqlClaims.ProviderInternalName,
                  teamLabels[teamNode],
                  teamKeys[teamNode],
                  true);

                  // Add it to the tree.
                  searchTree.AddChild(matchNode);
             }
             else
                  // Get the node for this team.
                  matchNode = searchTree.Children.Where(theNode =>
                  theNode.HierarchyNodeID == teamKeys[teamNode]).First();

            // Add the picker entity to our tree node.
            matchNode.AddEntity(pe);
      }
}

// If we were adding all the matches at once,that is, if we were not using
// a hierarchy, then we would add the list of matches here.
// if (matches.Count > 0)
//    searchTree.AddEntities(matches);
}

以上代码也是非常简单的,一看就能明白。基本上,目前所做的就是用户在人员选取器的搜索框中输入了一些内容,并单击了搜索按钮。由于 SupportsSearch 属性返回 true,因此调用声明提供程序。调用的方法是 FillSearch。在该方法中,我们检查用户输入的内容,该内容通过 searchPattern 输入参数提供给我们。我们检查所有的球队名称,确定是否有名称以搜索框中输入的值开头。如果有,则创建新的 PickerEntity,使用该球队的位置找到或创建搜索树层次结构节点,然后将 PickerEntity 实体添加到该节点。

现在,上面的代码向我们演示了使用名为 GetPickerEntity 的特殊帮助程序函数,我在该函数中传递我们找到的球队名称。如以下代码所示。

private PickerEntity GetPickerEntity(string ClaimValue)
{

// Use the helper function!
PickerEntity pe = CreatePickerEntity();

// Set the claim that is associated with this match.
pe.Claim = CreateClaim(SqlClaimType, ClaimValue, SqlClaimValueType);

// Set the tooltip that is displayed when you pause over the resolved claim.
pe.Description = SqlClaims.ProviderDisplayName + ":" + ClaimValue;

// Set the text that we will display.
pe.DisplayText = ClaimValue;

// Store it here, in the hashtable **
pe.EntityData[PeopleEditorEntityDataKeys.DisplayName] = ClaimValue;

// We plug this in as a role type entity.
pe.EntityType = SPClaimEntityTypes.FormsRole;

// Flag the entry as being resolved.
pe.IsResolved = true;

// This is the first part of the description that shows
// above the matches, like Role: Forms Auth when
// you do an forms-based authentication search and find a matching role.
pe.EntityGroupName = "Favorite Team";

return pe;
}

此时我们要创建新的 PickerEntity 对象。如代码注释所说明的,SPClaimProvider 类的 CreatePickerEntity 方法是 SharePoint 提供的另一个帮助程序函数。再次,我强烈建议您在可以的情况下都使用帮助程序函数。

创建 PickerEntity 对象之后,我们需要找到某种方法解释"it"指的是什么。在本例中,"it"指的是最喜爱篮球队的声明。我们告知系统有关该内容的方法就是创建一个声明,创建过程与我们为声明扩充代码所采用的相同。这就是我们向该帮助程序函数传递球队名称的原因,以便其将用户的最喜爱球队放入我们最喜爱球队的声明中。在这之后,我们对 PickerEntity 对象设置了几个属性,以使其显示和行为和其他声明(Windows 声明或基于表单的身份验证声明)一样。然后,我们返回创建的 PickerEntity。

然而,我们还没有完全完成任务。如果我们就此停止,则可以在人员选取器中看到声明,并可以选择它。当您单击"确定"按钮以关闭"人员选取器"对话框时,则会在输入控件中看到声明。但是它会显示有红色的波浪下划线,表明尚未对其进行解析。您无法在该位置对其进行解析。

实际发生的情况是,当您在"人员选取器"对话框上单击"确定"按钮以关闭该对话框时,SharePoint 会尝试在声明提供程序中再调用一次 FillResolve 方法。该方法为重载方法。关闭"人员选取器"对话框时调用的方法是包含 SPClaim 输入参数的方法。让我们看下该方法的实现,如以下代码所示。

protected override void FillResolve(Uri context, string[] entityTypes, 
      SPClaim resolveInput, 
      List<Microsoft.SharePoint.WebControls.PickerEntity> resolved)
{

// Ensure that People Picker is asking for the type of entity that we 
// return; site collection administrator will not return, for example.
if (!EntityTypesContain(entityTypes, SPClaimEntityTypes.FormsRole))
      return;

// Same sort of code as in search, to validate that we have a match.
foreach (string team in ourTeams)
{
if (team.ToLower() == resolveInput.Value.ToLower())
{
      // We have a match; create a matching entity with helper method.
      PickerEntity pe = GetPickerEntity(team);

      // Add it to the return list of picker entries.
      resolved.Add(pe);
}
}

FillResolve 方法非常简单。您可能记得本文第 1 部分:扩充声明和注册提供程序中,SPClaim 类的 Value 属性就包含该声明的值。因为有可能在人员选取器中选择了多个项目,当在声明中传递时,我们再次通过球队列表进行枚举,以查看声明值是否与球队之一匹配。如果匹配,则调用帮助程序方法以创建 PickerEntity 对象,然后将其添加到 SharePoint PickerEntity 实例的列表。现在,我们在人员选取器中选择后便会在输入控件中看到解析的条目。

还有两个您必须实现的方法:SPClaimProvider 基类的 FillSchema 方法和 FillEntityTypes 方法。在 FillSchema 方法中,我们添加最少的属性来用于人员选取器(显示名称)中。通过将显示名称添加到架构中可实现此目的。

在 FillEntityTypes 方法中,我们希望返回声明提供程序使用的声明类型。在我们的案例中,我们已在 GetPickerEntity 方法中将声明类型定义为 SPClaimEntityTypes.FormsRole。因此,我已将该角色添加到实体类型列表中,如以下代码所示。

protected override void 
FillSchema(Microsoft.SharePoint.WebControls.SPProviderSchema schema)
{
// Add the schema element that we need at a minimum in our picker node.
schema.AddSchemaElement(new 
      SPSchemaElement(PeopleEditorEntityDataKeys.DisplayName, 
      "Display Name", SPSchemaElementType.Both));
}

protected override void FillEntityTypes(List<string> entityTypes)
{
// Return the type of entity claim that we are using (only one in this case).
entityTypes.Add(SPClaimEntityTypes.FormsRole);
}

图 4 演示了搜索 wingtip 之后人员选取器的显示方式。

图 4. 在人员选取器中搜索

在人员选取器中搜索

图 5 演示了添加声明并单击"确定"按钮之后输入控件的显示方式。

图 5. 添加声明之后输入控件的显示

添加声明后显示键入控件

从代码角度而言,我们已经完成。但此时该回到本文最初所讨论的问题上。现在,我们可以根据您最喜爱的篮球队是谁来设置权限。在本例中,我已决定其最喜爱球队为 Wingtip Toys 的用户应该向网站提供内容。在本例中,我将此声明添加到"成员"组中。图 6 演示了该情况("Windows 声明"为网站集的名称;请不要混淆)。

图 6. 将声明添加到"成员"组

向 Members 组添加声明

请记住我所说的,不用再关心您是谁或您如何登录。通过示例的方法,我并没有将网站任何位置的权限授予给任何 Windows 用户、Windows 组、基于表单的身份验证用户或基于表单的身份验证角色。我只是将我的 Wingtip Toys 声明添加到"成员"组中,另外将我的 Consolidated Messenger 声明添加到"访问者"组中。

通过验证的方法,图 7 演示了网站集的所有组列表的形式。

图 7. 网站集的组权限列表

网站集的组权限列表

希望这会让您思考一下声明身份验证和声明提供程序的功能到底能有多强大。在本文的最后一部分,我将演示如何实现用户将声明名称输入到输入控件的支持。

第 4 部分:支持名称解析

在本文的前三个部分中,我们已实现了大部分创建端对端声明提供程序所需的支持。在最后一部分中,我将介绍如何在输入控件中实现名称解析支持。

若要添加名称解析支持,我们必须实现 SPClaimProvider 类的以下属性和方法:SupportsResolve 属性和 FillResolve 方法。

幸运的是,我已在第 3 部分:搜索声明中解决了如何实现 SupportsResolve 属性和 FillResolve 方法重载的问题。因此,本节比较短。以下是用于实现在"第 3 部分:搜索声明"中没有介绍的另一个 FillResolve 方法重载的代码。

protected override void FillResolve(Uri context, string[] entityTypes, 
      string resolveInput, 
      List<Microsoft.SharePoint.WebControls.PickerEntity> resolved)
{

// Ensure that People Picker is asking for the type of entity that we 
// return; site collection administrator will not return, for example.
if (!EntityTypesContain(entityTypes, SPClaimEntityTypes.FormsRole))
      return;
 
// Same sort of code as in search, to validate that we have a match.
foreach (string team in ourTeams)
{
      if (team.ToLower() == resolveInput.ToLower())
      {
            // We have a match;, create a matching entity.
            PickerEntity pe = GetPickerEntity(team);

            // Add it to the return list of picker entries.
            resolved.Add(pe);
       }
}
}

如果您已通读到了这里,则此代码块对您来说不算难。如果您觉得不容易理解,请重新阅读第 3 部分:搜索声明。

现在,代码已完成。如果我显示输入控件,输入球队名称,然后单击解析按钮,下面的两个图显示了名称解析之前和之后,输入控件的显示方式。图 8 显示了当在输入控件中输入球队名称时输入控件的显示内容。

图 8. 单击解析之前的输入控件

解析前的键入控件

图 9 演示了单击解析之后的输入控件。

图 9. 解析之后的输入控件

解析后的键入控件

到此为止,我们已全部完成。希望在您开始构建声明身份验证计划时,文本所提供的信息能对您有所帮助。

结论

您可以使用 SharePoint 2010 中的声明提供程序扩充声明并提供名称解析。使用声明身份验证,您可以根据声明来指定权限,而无需知道用户是谁或对其进行身份验证的方式;您只需知道用户的属性。例如,您可以使用一条与员工关联的公司元数据,让声明提供程序对某个其他系统执行查询以找出该特定员工使用的所有不同标识(Windows、基于表单的身份验证、SAP 和 CRM 等),并将某个其他标识符或一组声明映射至该标识。然后将这些声明用于授予对资源的访问权限。

其他资源

有关详细信息,请参阅以下资源: