IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope: Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  SOA and Web services  >

基于规则的访问控制

使用授权框架来提高安全性和简化编程

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 中级

Barry Brachman (brachman@dss.ca), 总裁, Distributed Systems Software, Inc.

2007 年 4 月 24 日

尽管 Web 服务器能够为应用程序执行用户身份验证和粗粒度的授权检查,但是 Web 服务和面向服务的体系结构(Service-Oriented Architecture,SOA)开发人员通常必须编写自定义代码来限制对某些系统功能的访问,或者基于用户的标识来自定义行为或外观。在应用程序中嵌入授权检查很不灵活,易于出错,并且增加了复杂性。如果授权检查是数据驱动的而不是由程序逻辑实现的,结果会怎么样呢?通过重用某个授权框架,脚本和编译后的程序可以更小、更简单、更安全,并且可以减少应用程序开发时间和工作量。

引言

您也许熟悉操作系统提供的访问控制功能。它通常对系统调用和通过文件系统指定的资源的操作请求执行授权检查。程序员通常不必测试运行程序的用户是否有权读取数据文件和设置系统时间。操作系统将负责此任务,并通过一个返回值让程序知道是允许还是拒绝此操作。例如,UNIX® 强制实施通过文件所有者 ID、组 ID 和与实际或有效用户标识相关的权限来建立的要求。尽管此设计非常简单,但它可能不够,从而导致某些类 UNIX 系统通过访问控制列表和其他机制对其进行扩展。其他操作系统则使用了更精细的安全模型。而且,该安全模型通常适用于系统已知的文件对象和用户帐户(出现在 /etc/passwd 或 NIS 中的文件对象和用户帐户中)。

对于某些基于服务器的应用程序,此模型是足够的。服务器最初使用增强的特权来执行,因此它能执行任何操作。当代表某个特定用户执行操作时,它实际上就变成了该用户。或者,服务器可以使用系统调用来构建安全模型,从而将操作限制到允许该用户执行的操作。在执行该操作前,程序必须测试授权:当前用户是否允许执行该操作?





回页首


Web 服务的授权检查

对于所负责的资源和操作系统无法识别的用户帐户,程序有时必须强制实施它们自己的访问控制要求。一个初步的示例是 Apache Web 服务器,可以配置它授予或拒绝针对其某个资源的 HTTP 请求。专用密码文件中的条目将创建 Web 服务器帐户,这些帐户完全独立于操作系统已知的帐户。同样,专门创建了供 Web 服务器使用的组成员资格列表。Apache 管理员可以将这些名称与服务器的 Require、Allow 和 Deny 指令一起用于描述谁能够(或不能)访问某个资源。由于操作系统在这方面帮不上什么忙,所以 Apache 程序员实现了他们自己的授权子系统。

尽管可以统一 Web 服务器和操作系统的用户列表,但通常出于安全性、性能和实现目的考虑而将它们保持分离。

诸如 Apache 等 Web 服务器提供的授权类型可以称为粗粒度 访问控制,因为它只提供了一个外部安全层。授权检查的结果将确定是否应该允许该请求。如果对某个 Web 服务的访问被拒绝,例如该 Web 服务不是由该 Web 服务器执行,则该 Web 服务甚至从来不会看到该请求。

然而,许多基于服务器的应用程序(包括基于 Web 的应用程序)所需的授权测试功能远远超出了此范畴。请考虑这样一个 Web 服务应用程序,它可由任何人执行,但是对不同的用户授予不同的能力和权限。也许最简单的示例就是其中仅允许某些用户能够访问管理功能的应用程序——所有其他用户不仅无法执行这些功能,他们甚至不应该能够看到这些功能(这些功能不应该在菜单上列出,或者至少是不可选择的)。另一个常见示例是应用程序的每个用户都有各自的配置文件,这些配置文件只能由其所有者(也许还包括应用程序的管理员)修改。通常,应用程序必须限制特定用户对其数据的访问。此类授权测试可以称为细粒度 访问控制,因为运行应用程序将其应用于该程序所使用的几乎任何类型的资源。

在某些情况下,有点创造力的程序员可能应用某些小伎俩来绕过这些问题。例如,应用程序可能使用 URI 来指定其资源,配置这些 URI 的 Web 服务器访问控制,然后发出 HTTP 请求来确定某个用户受否获得授权。此方法将利用 Web 服务器的授权检查机制,但是并不实用。

大多数编程语言(包括 Perl)在这方面都无计可施。它们在基础系统所提供的安全性之上提供一个简单的安全层。Java™ 语言包括一个授权框架,但是对 Perl 或 C/C++ 程序员来说当然没有任何帮助。

程序员面对的挑战是双重的:首先,这些应用程序的用户不需要在基础系统上拥有帐户;其次,对象的类型和访问对象的方式可能与操作系统安全模型所旨在处理的内容差别相当大。结果,程序员被迫编写大量的代码来支持特定于应用程序的授权测试,有时还必须以不同的语言重新实现本质上相同的框架。诸如 Oracle 和 MySQL 等数据库系统管理它们自己的用户帐户、角色和系统及对象级别的操作权限。





回页首


细粒度授权检查

细粒度访问控制不只是授予或拒绝执行程序或读取数据文件的权限。请考虑一个实现为 CGI 程序的 Wiki 服务器。它维护一个网页集合,每个用户一个网页。任何人都拥有对任何网页的读取访问权限。任何用户都能添加或管理他们自己的网页内容,但是不能添加或管理其他用户的网页内容。指派为管理员的任何用户都能更新和管理任何页面(例如,删除攻击性或非法内容)。在向所有者或管理员显示网页时,所有操作都应该可以通过菜单或链接来选择;其他用户不应该看到这些操作,或者这些操作对他们应该是不可选择的。此外,应用程序必须确保任何人都能看到某个网页,但是只有其所有者或管理员才能执行受限制的操作,例如更新页面的内容。

当前实践是在应用程序中嵌入授权逻辑。在整个代码中的适当地方,程序员编写程序逻辑来测试当前用户是否有权执行某个操作。

在构造菜单时,可能出现与清单 1 类似的代码。


清单 1. 构建允许操作的菜单——不正确的方法
                
for (op = 0; op < n_operations; op++) {
  if (op == CREATE_OP && is_admin(current_user))
      add_operation_to_menu(menu, op);
  else if ((op == UPDATE_OP || op == DELETE_OP)
    && (is_owner(current_page, current_user) || is_admin(current_user)))
      add_operation_to_menu(menu, op);
  else
      add_operation_to_menu(menu, op);
}

虽然清单 1 初看起来似乎是正确的,但它实际上错误百出。在发现问题之前,该代码居然通过了本文档的前几次修订,这说明在编写此类测试时,犯下粗心的错误是多么容易。清单 2 是正确的:


清单 2. 构建允许操作的菜单——正确的方法
                
for (op = 0; op < n_operations; op++) {
  if (op == CREATE_OP && is_admin(current_user))
      add_operation_to_menu(menu, op);
  else if ((op == UPDATE_OP || op == DELETE_OP)
    && (is_owner(current_page, current_user) || is_admin(current_user)))
      add_operation_to_menu(menu, op);
  else if (op != CREATE_OP && op != UPDATE_OP && op != DELETE_OP)
      add_operation_to_menu(menu, op);
}

在该代码中的不同点,也许是在分派操作的地方或每个操作的开头,分别执行了一个相似的测试。


清单 3. 测试受限制的操作
                
if (op == CREATE_OP && !is_admin(current_user))
  raise_exception("Operation not permitted")
else if ((op == UPDATE_OP || op == DELETE_OP)
  && !(is_owner(current_page, current_user) || is_admin(current_user)))
    raise_exception("Operation not permitted")

两个代码片段所执行的授权检查紧密相关,尽管它们可能出现在程序的不同部分。在更复杂的情况下,授权测试可能要复杂得多。例如,如果以后增强应用程序以允许组成员(由页面的所有者定义)执行以前受限制的操作,则程序员需要定位、检查并且可能修改所有执行了这些检查的位置处的代码片段(显然远不止是两处)。未能做出适当的更改可能导致意外或恶意的应用程序使用。





回页首


基于规则的授权检查

为帮助减轻程序员的负担和改进应用程序安全性,我们建议使用专门设计用于授权检查的框架。与程序员使用数学、加密、网络和数据库函数库而不是每次都实现那些例程一样,为什么不利用一个为仅与手边任务偶然相关的问题提供大部分解决方案的包呢?

我们建议使用某个基于角色的(数据驱动的)授权框架,它能够:

  • 从命令行调用,因此它几乎可由任何脚本调用或由任何程序执行。
  • 提供一个简单的 C 语言 API,因此能够直接调用或作为许多脚本语言扩展来集成。
  • 使用通用的用户命名语法,因此它能够与大多数身份验证方法互操作。
  • 支持可应用于任何资源的规则。
  • 将访问控制规则表示为简单的 XML 文档。
  • 提供变量、表达式和控制流,以促进更复杂的决策制定。
  • 包括用于测试访问控制要求的丰富功能集,例如用户的 IP 地址、时间和日期,或用户的名称是否出现在给定的列表中。

除了不必实现该框架提供的功能这个明显优点外,它还有其他许多优点:

  • 应用程序使用的规则可由具备适当权限的任何用户更改,而不必更改甚至重新编译应用程序。可以将安全策略的管理委托给非程序员或不熟悉应用程序实现语言的人。
  • 如果修改了规则,则整个应用程序或相关应用程序套件中的所有实例都将自动使用修订后的规则——无需更改代码,所有实例都会得到同步。
  • 由于规则独立于任何编程语言,不同的应用程序可以使用相同的规则。
  • 应用于框架的改进和错误修复可以为使用该框架的应用程序带来好处——无需修改或重新编译。

当然,还是存在一些缺陷。除了学习开销外,框架中的错误可能被许多使用该框架的所有应用程序继承。以这种方式执行的授权检查将比内联代码慢一点,尤其是在通过单独的进程来调用的时候。





回页首


DACScheck:一个授权框架

如果这些意见推动了 dacscheck 的设计和实现,那很好,但这不是它的初衷。相反,Apache 授权模块的一个功能丰富的替换导致了 DACS 的开发,这是一个轻量级单点登录系统。授权框架可以通用化并由几乎任何应用程序(无论是否基于 Web)采用,这个认识是很久以后才出现的。

为了演示如何使用 dacscheck,让我们回顾一下清单 2 中提供的用于构建菜单的伪代码,这次是使用 Perl。


清单 4. 从 Perl 中使用 DACScheckl
                 use DACScheck.pm;

# Tell dacscheck which rules to use
dacscheck_rules("/usr/local/wiki_app/acls");

# Build a list of operations for the menu.
for ($op = 0; $op < $n_operations; $op++) {
  # The object of interest, which is used to locate the appropriate rule,
  # is arbitrarily named "/<username>/menu".
  my $object = "/$ENV{'DOCUMENT_ROOT'}/menu";
  my $result = dacscheck_cgi($object);

  # Access to the object is granted only if the returned value is one.
  if ($result == 1) {
      add_operation_to_menu($menu, $op);
  }
}

Perl 模块 DACScheck.pm 提供了一个简化的 dacscheck 接口,其中包括 dacscheck_cgi()。除了命名对象外,该函数还告诉 dacscheck 它运行在一个 CGI 上下文中,并且应该使用 REMOTE_USER 的值作为发出请求的用户标识。如果用户已经过身份验证,则 Apache 会自动设置 REMOTE_USER

dacscheck 管理的每个资源都有一个对应的规则。尽管规则可以存储并以各种方法进行访问,但是每个规则通常存储在普通文本文件中,并且相关资源的规则一般保存在一个公共根目录下。规则表示为 XML 文档,其中可以包括类似于 C 的自由格式表达式。表达式可以引用在执行上下文中实例化的变量(包括环境变量和 Web 服务参数),以及面向字符串操作和高级标识测试的广泛内置函数。为了使此功能保持简单,没有使用诸如工具命令语言(Tool Command Language,Tcl)等现有的扩展语言。更复杂的测试可以通过单独的程序来执行,甚至是通过 HTTP 来调用的程序。规则还可以指向另一个规则,此功能允许管理员委托规则的职责。

下面让我们了解一下可能的规则。规则告诉 dacscheck 如何决定是允许还是拒绝请求。我们假设用户已经登录,因此已经设置了 REMOTE_USER。我们还假设 OP 是一个用于选择操作的参数,PAGE 是一个用于标识 Wiki 页面所有者的参数。


清单 5. 示例 dacscheck 规则文件
                
<acl_rule>
  <services>
    <service url_expr="/${Args::PAGE}/menu"/>
  </services>

  <rule order="allow,deny">
  <precondition>
    <predicate>
     ${Args::OP} eq "CREATE_OP"
    </predicate>
  </precondition>

  <allow>
     dacs_admin()
  </allow>
  </rule>

  <rule order="allow,deny">
  <precondition>
    <predicate>
     ${Args::OP} eq "UPDATE_OP" or ${Args::OP} eq "DELETE_OP"
    </predicate>
  </precondition>

  <allow>
     dacs_admin() or ${Args::PAGE} eq ${Env::REMOTE_USER}
  </allow>
  </rule>

  <rule order="allow,deny">
  <allow>
     user("any")
  </allow>
  </rule>

 </acl_rule>

此规则非常详细,一方面是为了便于解释,另一方面是因为以这样的方式表示安全策略,一旦您熟悉了语法,要理解和更改它将是非常容易的。

service 元素告诉 dacscheck 该规则适用于哪个资源。dacscheck_cgi() 的参数将针对此字符串进行匹配。

此访问控制规则包括三个 rule 元素:仅当 OP 参数为 CREATE_OP 时才会选择第一个元素;仅当 OP 参数为 UPDATE_OPDELETE_OP 时才会选择第二个元素;仅当这两个条件都不成立时才会选择第三个元素。order 属性的用途与 Apache 的 Order 指令相同:它确定缺省行为和计算 allowdeny 元素的顺序。

在该示例中,仅当 dacs_admin() 函数返回“True”时,第一个 rule 才会授予访问权限。该函数查找用户的标识。第二个规则向管理员和 Wiki 页面所有者授予访问权限。第三个规则向任何人授予访问权限,因为没有请求受限制的操作。

如果需要更改 Wiki 的访问控制策略,您只需更改某个规则。更改会立即生效并应用于对该规则的所有引用。如果管理员需要拒绝来自特定 IP 地址的请求对某个页面的访问,可以添加类似如下的规则:


清单 6. 用于拒绝访问的附加规则子句
                
<rule order="allow,deny">
  <precondition>
    <predicate>
      from("10.0.0/24")
    </predicate>
  </precondition>
</rule>

allow,deny 排序缺省拒绝访问,因此来自该范围中某个 IP地址的任何请求都将被拒绝。

可以在许多情况下应用这种形式的授权检查。例如,ftp 守护进程 (ftpd) 就可得益于此功能。可以对其进行扩展,以采用以下规则:这些规则基于用户标识或可由规则测试的上下文信息来允许和拒绝操作(putgetcd,等等)。例如,可能仅在某段时间内允许上载操作,或者可能将对某个目录的访问限制到特定用户或来自某些 IP 地址的用户。





回页首


结束语

共享本文...

digg 请 Digg 该故事
del.icio.us 发布到 del.icio.us
Slashdot Slashdot 一下!

灵活的自定义框架可以提高安全性,并为程序员做许多“繁重”的工作。脚本和编译后的程序可以更小和更简单,并且可以减少应用程序开发时间和工作量。虽然仍然存在某些未解决的问题,但是 dacscheck 已经被证明是个非常有用的工具。尽管人们很少注意到它的效率,但是典型的 dacscheck 使用对性能不会有显著的影响。

dacscheck 有一个姊妹程序 dacsauth,后者所做的身份验证工作与 dacscheck 所做的身份验证工作大致相同。它允许程序员利用现有的身份验证方法,而不是必须在每当某个新应用程序需要自己的用户列表时重新实现它们。

另一个相关程序是 dacstransform,此实用程序用于执行基于规则的文档转换。使用与 dacscheck 相同的规则,它可以编辑、插入和替换标记文档中的文本。每个转换都依赖于一个在运行时进行评估的规则,从而允许从为特定用户或上下文定制的标记文档生成新文档。



参考资料

学习

讨论


关于作者

Barry Brachman 是软件开发和咨询公司 Distributed Systems Software 的创始人和总裁。他拥有广泛的计算经验,从 UNIX 内核、编译器、分布式系统和协议验证,到性能评估、PKI和 X.500/LDAP。Barry 曾发表过有关操作系统和计算机网络的演讲。他拥有英属哥伦比亚大学的理学硕士和计算机科学博士学位。




对本文的评价










回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款