代码下载位置: OfficeSpace2008_02.exe (209 KB)
Browse the Code Online
目录
外部安全主体和 SPUser 对象
添加已通过身份验证的用户和外部用户
使用权限级别
WSS 组
标识、提升和模拟
安全对象
结束语
您可能已经对使用 Windows® 和 ASP.NET 的安全性进行安全编程的基础有所了解,但您对 Windows SharePoint® Services 3.0 (WSS) 增加的安全保护又了解多少呢?在本期的 Office Space 专栏中,我将重点介绍 WSS 引入的一些新的安全术语和概念,并为您展现一个使用 WSS 对象模型实现安全编程的新世界。
建议您下载本专栏附带的示例项目,并按照本专栏其他部分提供的代码执行操作。该项目已配置为在构建过程完成之后运行一个批处理文件,该批处理文件会将所有的项目组件编译成一个 WSS 解决方案包,并在本地 WSS 服务器场中安装该包。在 建立项目并安装解决方案之后,您可以浏览任意网站集,并启用针对网站集的名为“Security Demo”的功能。然后您就可以通过“网站操作”菜单导航到自定义应用程序页,这些页面通过一些代码演示了 WSS 安全编程技术。
外部安全主体和 SPUser 对象
大多数安全模型都是基于安全主体的。每个安全主体均表示一个用户或一个组。用户拥有帐户,并通过这些帐户进行身份验证。身份验证完成后,每位用户将获得一个身份标识。当用户使用 Windows 帐户进行身份验证时,您可以使用 System.Security 命名空间中的 Microsoft® .NET Framework 安全类来检索身份标识,该标识回指到特定的 Windows 帐户并允许您查看该用户的登录名:
WindowsIdentity identity = WindowsIdentity.GetCurrent();
string WindowsLogin = identity.Name;
使用 WindowsIdentity,您可以动态地创建一个 WindowsPrincipal,该对象允许您通过测试查看当前用户是属于 Active Directory® 组还是本地 Windows 组,如下所示:
WindowsIdentity identity = WindowsIdentity.GetCurrent();
WindowsPrincipal principal = new WindowsPrincipal(identity);
if( principal.IsInRole(@"LITWAREINC\AllFTE") ){
// perform operation allowed for fulltime employees
}
ASP.NET 同时支持 Windows 身份验证和基于表单的身份验证 (FBA)。ASP.NET 中的 User 对象通过基于 IPrincipal 接口(而非 WindowsPrincipal 类)进行建模的方式摆脱了对 Windows 帐户的依赖性。ASP.NET 运行库根据当前用户是使用 Windows 帐户还是使用 FBA 帐户进行身份验证,来动态创建不同类型的 IPrincipal 对象:
IPrincipal AspUser = HttpContext.Current.User;
string AspUserName = AspUser.Identity.Name;
ASP.NET 的 User 对象还提供了使用 IsInRole 方法检查用户是否属于特殊角色的方式。对于 Windows 用户,IsInRole 方法让您能够查看当前用户是否为 Active Directory 组的成员。如果您使用的是 ASP.NET 角色提供程序附带的 FBA 帐户,也可以使用 IsInRole 方法来检查是否已将 FBA 用户添加到特定的 ASP.NET 角色:
IPrincipal AspUser = HttpContext.Current.User;
if(AspUser.IsInRole("Site Administrators") {
// perform privileged operation
}
身份验证操作会生成某种形式的回执,系统在运行时使用该回执来表示用户身份,以及对组或角色中的成员资格进行跟踪。如果用户使用 Windows 帐户进行身份验证,则身份验证的回执为 Windows 安全令牌。如果用户使用 FBA 帐户进行身份验证,则身份验证的回执为由 ASP.NET 运行库和特定身份验证提供程序创建的一个 HTTP Cookie。
了解 WSS 不支持用户身份验证这一点是十分重要的。但 WSS 可以利用各种 ASP.NET 身份验证提供程序提供的底层身份验证组件。WSS 对于网站安全保护的价值在于它能够对授权和访问控制进行配置。WSS 能够在网站集范围内对外部安全主体(例如,Windows 用户、FBA 用户、Windows 组和 ASP.NET 角色)进行跟踪。借助 WSS,您还可以配置分配给这些外部主体的权限,实际上是授予用户访问 WSS 安全对象(例如网站、列表、项和文档)的权限。
请注意,网站集在配置授权和访问控制时发挥了极其重要的作用。在跟踪外部主体和配置权限时,WSS 将每个网站集视为其彼此独立的孤岛。WSS 的高级设计有意将每个网站集隔离开来,以便一个网站集中的用户安全设置不会影响到其他网站集中的权限或访问控制策略。
WSS 对象模型使用 SPUser 对象来表示外部安全主体。您可以通过当前的 SPWeb 对象检索当前用户的 SPUser 对象:
SPWeb site = SPContext.Current.Web;
SPUser user = site.CurrentUser;
string DisplayName = user.Name;
string Login = user.LoginName;
string EMail = user.Email;
string User Notes = user.Notes;
SPUser 对象公开了外部安全主体的各种属性,例如,登录名、显示名称和电子邮件地址。将外部主体添加到网站时,通常可以从底层用户存储库(例如 Active Directory 域)中检索到这些属性。SPUser 对象还提供了用于跟踪特定于 WSS 的元数据的属性,例如“注释”字段。
WSS 将外部用户、组和角色的配置文件数据保留在隐藏列表(称为“用户信息列表”)中。WSS 每次提供新的网站集时,会在首要站点中自动创建“用户信息列表”作为隐藏列表。然后,当首次为主体分配权限时,或主体首次通过安全检查访问安全对象时,WSS 会为每个外部主体添加一个新的配置文件。请注意,“用户信息列表”中存储的用户配置文件不能跨网站集进行扩展 - 用户更新一个网站集中的配置文件设置后,不会对其他网站集中该用户的配置文件设置造成更改。
另一个易混淆的地方就是 SPUser 对象通常并不总是代表实际用户。SPUser 对象也可以代表 Active Directory 组和 ASP.NET 角色。WSS 不仅跟踪“用户信息列表”中每种外部主体的配置文件,而且还跟踪外部用户的配置文件数据。
SharePoint 安全模型的许多编程方面的内容都是通过 SPWeb 对象在网站级别公开。如果您要查看哪些用户是当前网站的成员,就会发现这一点。一个 SPWeb 对象可公开三种不同的用户集合,如以下代码段所示:
SPWeb site = SPContext.Current.Web;
SPUserCollection c1 = site.Users;
SPUserCollection c2 = site.AllUsers;
SPUserCollection c3 = site.SiteUsers;
Users 集合是三个集合中包含成员最少的集合。该集合包含了当前网站中所有已显式分配了权限的外部主体。
AllUsers 集合包括 Users 集合中的所有成员,以及通过组或角色成员资格使用隐式权限访问过网站中的对象的外部用户。例如,假定名为 Brian 的用户(登录名 LITWAREINC\BrianC)从未被显式授予访问某个网站和查看特定列表的权限。但他也许仍可以查看列表,因为他所属的 Active Directory 组已被配置了列表查看权限。当 Brian 首次访问网站或其中任一对象(比如,使用隐式权限查看一个列表)时,他会被添加为 AllUsers 集合的成员,但不会被添加为 Users 集合的成员。
SiteUsers 集合是包含了当前网站集中每个 AllUsers 集合的成员的一个聚合。该集合的成员包括所有已分配了对网站集中所有对象的访问权限的外部主体,以及所有已被授予使用隐式权限访问网站集中所有对象的权限的外部用户。
添加已通过身份验证的用户和外部用户
那么,如何为使用 Active Directory 帐户通过身份验证的用户创建新的 WSS 用户配置文件?如果您需要创建一个自定义用户界面组件,让标准用户或网站集所有者能够从 Active Directory 域中选择用户或组,您确实应该了解一下 PeoplePicker 控件的用法(参见图 1)。这是 WSS 附带的一个便捷、可重用的控件类型。您可以使用如下的控件标记将此控件添加到自定义应用程序页或 User 控件:
图 1PeoplePicker 控件(单击该图像获得较大视图)
<SharePoint:PeopleEditor
ID="pickerPrincipal"
AllowEmpty="false"
ValidatorEnabled="true"
MultiSelect="false"
SelectionSet="User, SecGroup, DL"
Width="280px"
runat="server" />
在此示例中,我对 PeoplePicker 控件进行了配置,为 SelectSet 属性分配了 User、SecGroup 和 DL 三个值。通过配置这些 SelectSet 设置,该控件可以让用户根据 Active Directory 选择和解析用户、组或通讯组列表。
您可以通过编程方式访问 PeoplePicker 控件属性,以便在用户使用该控件选择一个或多个安全主体后检索基本帐户的相关登录帐户名。然后,您可以编写代码来实际将这些主体添加为网站成员并为其配置访问权限。
现在,我来介绍如何将外部用户或组添加为网站成员。简单了解 WSS 对象模型之后,您可能会认为只需将外部安全主体直接添加到某个 SPUser 集合(例如 SiteUsers)即可:
SPWeb site = SPContext.Current.Web;
site.SiteUsers.Add(@"LITWAREINC\BrianC",
"Brian Cox",
"Notes about Brian Cox");
site.SiteUsers.Add(@"LITWAREINC\AllFTE",
"All Full-time Employees",
"Notes about FTE DL");
尽管这种方法确实能够在“用户信息列表”中为外部主体创建配置文件,但由于无法分配权限,因此对安全性几乎没有作用。相比之下,添加新的外部安全主体的一个更好的方式是要对权限进行分配,以便用户有权访问当前网站。但首先您需要学习如何创建和分配权限级别。
使用权限级别
权限级别是在网站范围内定义的权限命名集。WSS 内建了四个权限级别:Read(读取)、Contribute(参与讨论)、Design(设计)和 Full Access(完全访问)。如果需要在级别设置上更为精细,可以使用 WSS 对象模型或通过网站集所有者能够访问的标准 WSS 管理页,创建属于自己的自定义权限级别。
权限级别有时称为角色,在 WSS 对象模型中通常使用 SPRoleDefinition 对象表示这些权限级别。您可以使用 SPRoleAssignment 对象为外部用户或组分配权限级别。例如,此处我将内置的 Contribute 权限级别分配给登录名为 LITWAREINC\BrianC 的 Windows 用户:
SPWeb site = SPContext.Current.Web;
SPRoleDefinition role = site.RoleDefinitions["Contribute"];
SPRoleAssignment roleAssignment;
roleAssignment = new SPRoleAssignment(@"LITWAREINC\BrianC",
"Brian Cox",
"Notes about Brian Cox");
roleAssignment.RoleDefinitionBindings.Add(role);
site.RoleAssignments.Add(roleAssignment);
使用这种方法,您不必将用户添加到某一 SPUser 集合中,因为在网站内首次为外部用户或组分配权限时 WSS 已自动完成这一操作。您刚才看到的代码会在“用户信息列表”中创建一个用户配置文件(如果原来不存在),并将该用户添加为当前网站的 Users 集合的成员。
WSS 组
WSS 安全模型不仅将外部安全主体表示为 SPUser 对象,而且还提供了 WSS 组,以方便网站集范围内的权限配置。例如,您可以在网站集中为诸如 Site Members(网站成员)、Content Manager(内容管理员)和 Site Administrators(站点管理员)的特定用户角色设计一系列 WSS 组。完成此项操作之后,您只需为 WSS 组分配权限级别即可配置网站的安全设置,而无需将权限级别直接分配给 SPUser 对象。
创建 WSS 组明显的好处是有助于消除添加或删除新的外部用户和组时重新配置权限的需要。您只需创建网站时配置权限,即可做到一劳永逸。之后,只需向 WSS 组中添加或从其中删除外部用户和组即可。WSS 组采用与 Active Directory 组完全相同的设计原则,二者的主要区别在于 WSS 组仅在单个网站集范围内定义和存在。
WSS 对象模型中将 WSS 组表示为 SPGroup 对象。SPWeb 对象公开两个 SPGroup 对象集合,分别为 Groups 和 SiteGroups。Groups 集合包括当前网站中所有已直接分配权限的 WSS 组,而 SiteGroups 集合则是 Groups 集合的超集,其中包括所有在当前网站集内创建的 WSS 组。
如果您希望创建一个新的 WSS 组,您应该调用 SiteGroups 集合公开的 Add 方法,然后在目标网站内为新的 WSS 组分配一个或多个权限级别。图 2 所示为新建一个名为 Site Members 的 WSS 组并在当前网站内为其分配内置 Contribute 权限级别的示例。
Figure2创建一个新的 WSS 组
SPWeb site = SPContext.Current.Web;
SPUser currentUser = site.CurrentUser;
// create new group
site.SiteGroups.Add("Site Members", currentUser, currentUser,
"Site Group created at " + DateTime.Now.ToString());
// assign permission level to new group
SPGroup NewGroup = site.SiteGroups["Site Members"];
SPRoleAssignment roleAssignment = new SPRoleAssignment(NewGroup);
SPRoleDefinition permLevel = site.RoleDefinitions["Contribute"];
roleAssignment.RoleDefinitionBindings.Add(permLevel);
site.RoleAssignments.Add(roleAssignment);
新的 WSS 组创建完毕后,将外部用户和组添加为成员就变得轻而易举了。由一个 SPGroup 对象提供一个 AddUser 方法,该方法接受一个 SPUser 对象,然后您就可以添加外部用户和组了:
SPWeb site = SPContext.Current.Web;
SPUser currentUser = site.CurrentUser;
SPGroup group = site.SiteGroups["Site Members"];
SPUser user1 = site.SiteUsers[@"LITWAREINC\BrianC"];
SPUser user2 = site.SiteUsers[@"LITWAREINC\AllFTE"];
group.AddUser(user1);
group.AddUser(user2);
标识、提升和模拟
WSS Web 应用程序的工作进程是通过 IIS 应用程序池进行控制的。Web 应用程序的工作进程标识可保证您对进程的关注,您可以通过 SharePoint 管理中心应用程序配置这些标识。您应根据域帐户(例如 LITWAREINC\SP_WorkerProcess)配置 Web 应用程序的工作进程标识,而不应根据本地帐户(例如 NETWORK SERVICE)进行配置。
请注意,Web 应用程序的工作进程标识必须是一个拥有特权的 Windows 帐户,该账户已配置了 SQL Server 权限,能够对一个或多个内容数据库进行读写操作。运行 SharePoint 管理中心网站的 Web 应用程序的工作进程标识必须具有更多特权,因为它需要拥有对场的配置数据库进行读写操作的权限。
当 Web 部件或自定义应用程序页的代码响应用户请求开始执行时,该代码不会以托管 Web 应用程序的工作进程标识执行。而是由 WSS 通过模拟将 Windows 安全上下文切换到其他 Windows 帐户。实际上,如果您查看 WSS Web 应用程序的 web.config 文件,会看到以下项:
<configuration>
<system.web>
<identity impersonate="true" />
</system.web>
</configuration>
如果是为已使用 Windows 帐户进行身份验证的用户执行请求,则该请求会模拟当前用户的 Windows 标识。但是,同样的方法并不适用于 FBA 用户,因为 FBA 身份验证不会创建 Windows 安全令牌,而且也没有 Windows 标识。因此,使用 FBA 身份验证的用户的请求会模拟已经配置为匿名访问的 Windows 帐户的身份标识。IIS 中为此帐户默认分配的是 IUSER_MACHINENAME 帐户,但您可以(并且通常应该)重新配置此帐户以指向域帐户。
现在,让我们回过头来看一看更高级别的 WSS 安全编程。WSS 安全模型通常会强制要求开发人员对 Windows 标识和 WSS 用户标识加以区分。但是在一个请求中,如果当前 Windows 标识和当前 WSS 用户标识都指向同一 Windows 登录名时,则上述情况可能不太明显。而在使用 FBA 的情况下,事情就会变得更加复杂。例如,WSS 用户标识可能指向名为 Andrew 的 FBA 用户,而基本 Windows 标识则基于 IUSER_MACHINENAME 帐户。当您的代码尝试访问 WSS 对象时,WSS 会使用用户的 WSS 标识运行访问检查。而当您的代码尝试访问 WSS 之外的外部对象(例如 Windows 操作系统维护的文件)时,操作系统会使用 Windows 标识(代码正以此标识执行)运行访问检查。
有时候要执行代码,您需要使用比当前用户权限更高的权限。例如,我们可以假设这样一个情景:在处理一个仅拥有只读权限的用户的请求时,您的代码必须向一个列表写入数据。而默认情况下,您的代码要以与当前用户相同的权限来运行。这时,您可以调用 SPSecurity 类的 RunWithElevatedPrivileges 方法,提升代码的安全上下文。请注意,调用 RunWithElevatedPrivileges 将同时提升 WSS 用户标识和 Windows 标识。
现在想像这样一种情形:用户通过登录名 LITWAREINC\BrianC 使用 Windows 帐户进行身份验证。调用 RunWithElevatedPrivileges 会将 WSS 用户标识提升为 SHAREPOINT\System 帐户。SHAREPOINT\System 帐户内置于 WSS 运行库,在 WSS 授权模型中拥有完全的权限。调用 RunWithElevatedPrivileges 还将切换执行代码的 Windows 标识,从而该代码以当前 Web 应用程序的工作进程标识运行:
// BEFORE ELEVATION
// WSS User identity = LITWAREINC\BrianC
// Windows identity = LITWAREINC\BrianC
SPSecurity.RunWithElevatedPrivileges(delegate() {
// AFTER ELEVATION
// WSS User identity = SHAREPOINT\System
// Windows identity = LITWAREINC\SP_WorkerProcess
});
在某些情景中,您可能会选择在尝试访问 Windows 文件系统或 SQL Server 数据库中的文件之前调用 RunWithElevatedPrivileges 方法,以便更改当前调用的 Windows 标识。另请注意,如果您从 Windows 标识切换到进程标识(例如 LITWAREINC\SP_WorkerProcess),则可以不必在 Active Directory 环境下对委托进行配置。当您的自定义 Web 部件能够使用 Windows 集成身份验证访问远程 SQL Server 数据库中的数据时,这项功能会非常有价值。
另外还会出现其他情景,您可能需要通过调用 RunWithElevatedPrivileges 方法将 WSS 用户标识提升为 SHAREPOINT\System,以便您的代码能够执行在当前用户权限下不允许的操作。一旦代码能够以 SHAREPOINT\System 身份运行,您就可以在 WSS 授权子系统中几乎随心所欲地执行任意操作。
调用 RunWithElevatedPrivileges 提升为 SHAREPOINT\System 帐户也有其相对棘手的一方面。例如,设想您调用 RunWithElevatedPrivileges,然后尝试通过 SPContext.Current 属性访问当前网站集或网站中的对象。您也许想不到代码会出错,但事实可能出乎您的意料:
SPSecurity.RunWithElevatedPrivileges(delegate() {
SPSite siteCollection = SPContext.Current.Site;
// next line fails if current user is Contributor
string siteCollectionOwner = siteCollection.Owner;
});
为什么将 WSS 用户标识提升为 SHAREPOINT\System 之后本示例代码会执行失败?这与 SPSite 对象的创建时间有关。SPSite 对象及其子对象 SPWeb 的权限实际上并不依赖于当前 WSS 用户标识。而是依赖于创建 SPSite 对象时的 WSS 用户标识。在这里,由于可通过 SPContext.Current 进行访问的 SPSite 对象是在之前的请求中创建的,在该对象创建时,您的代码还无法切换其 WSS 用户标识。
因此,您需要使用一点技巧,在调用 RunWithElevatedPrivileges 并将 WSS 用户标识提升为 SHAREPOINT\System 之后,创建一个新的 SPSite 对象:
SPSecurity.RunWithElevatedPrivileges(delegate() {
using (SPSite elevatedSiteCollection = new SPSite(this.Site.ID)) {
using (SPWeb elevatedSite =
elevatedSiteCollection.OpenWeb(this.Web.ID)) {
// access elevatedSiteCollection and
//elevatedSite as SHAREPOINT\System
}
}
});
这样就可以打开网站集及其中的网站,以便您的代码能以 SHAREPOINT\System 身份访问对象。
您可能还发现有必要模拟特定的 WSS 用户标识。这种方式在为事件处理程序或自定义工作流模板编写代码时(这种情况下代码默认以 SHAREPOINT\System 身份运行)非常常见。例如,您可能希望在创建新对象之前模拟特定 WSS 用户标识,以便 WSS 用户能被识别为新对象的所有者。
text-align: left; line-h