级别: 中级 Duane O'Brien, PHP 开发人员, 自由撰稿人
2006 年 12 月 19 日 更新 2008 年 2 月 21 日 CakePHP 是一种用 PHP 构建 Web 站点的辅助工具,它稳定、可直接用于生产及快速开发。“使用 CakePHP 快速打造 Web 站点” 系列教程向您展示如何使用 CakePHP 构建在线产品目录。
编辑说明:本系列最初发表于 2006 年和 2007 年。自从本系列发表以来,CakePHP 开发人员对 CakePHP 做了重大修改,因此原来的内容过时了。为了反映这些修改并充实本系列的内容,作者修订了本系列的五个部分,使它与 2008 年 1 月发布的 CakePHP 版本保持一致。
简介
“使用 CakePHP 快速打造 Web 站点” 系列教程适合希望开始使用 CakePHP 轻松构建应用程序的 PHP 应用程序开发人员学习。通过本系列教程,您将了解如何安装和配置 CakePHP 以及有关 Model-View-Controller(MVC)设计、如何在 CakePHP 中检验用户数据、如何使用 CakePHP helper、如何使用 CakePHP 快速建立并运行应用程序的基本知识。听起来好像有很多东西要学习,但不必担心 — CakePHP 会替您完成其中的大部分工作。
本教程假设您已经学习了 第 1 部分 和 第 2 部分,而且仍然保留为那两个教程设置的工作环境。如果还没有安装 CakePHP,则应当先读完第 1 部分和第 2 部分,并按其内容完成操作,然后再继续学习本教程。
您需要熟悉 PHP 编程语言,基本掌握数据库设计并喜欢实战。
系统需求
开始之前,需要具备一个工作环境。CakePHP 的最低服务器需求为:
- 支持会话(并且最好支持
mod_rewrite)的 HTTP 服务器。本教程采用的是支持 mod_rewrite 的 Apache V2.2.4。
- PHP V4.3.2 或更高版本(包括 PHP V5)。本教程采用的是 PHP V5.2.3。
- 受支持的数据库引擎。本教程采用的是 MySQL V5.0.4。
还需要准备好一个数据库以供应用程序使用。本教程将提供在 MySQL 中创建任何必需的表的语法。
下载 CakePHP 的最简单方法是访问 CakeForge.org 并下载最新的稳定版本。本教程采用的是 V1.2.0。还可以直接使用来自 Subversion 的每日构建和拷贝。CakePHP Manual 中有更详细的信息(请参阅 参考资料)。
目前为止的 Tor
在 第 2 部分 的末尾,您获得了另一个通过增强 Tor 实践您的技能的机会。使用 Bake 为经销商生成视图和控制器,然后检验经销商名称是否是惟一的,修复产品 ACL 中的 bug,更改 products 视图使其仅向可以使用 Edit 和 Delete 按钮的用户显示这些按钮。面对如此之多的任务,您是怎样做的?
用 Bake 生成经销商
用 Bake 生成 dealers 控制器和视图应该说是非常简单的。从 /webroot/app 目录中启动 Cake Console。
为了生成控制器,应当在第一个菜单中输入 C 并选择 dealers 控制器。为了生成视图,应当在第一个菜单中输入 V 并再次选择 dealers 控制器。这些步骤实际上与创建 products 控制器和视图所遵循的步骤是相同的。
还必须确保经销商名称是惟一的。实现的方法是在 Dealer 模型中添加一个 beforeValidate 方法,见清单 1。
清单 1. 修改 dealers 中的 add 操作
function beforeValidate() {
if (!$this->id) {
if ($this->findCount(array('Dealer.title' =>
$this->data['Dealer']['title'])) > 0) {
$this->invalidate('title_unique');
return false;
}
}
return true;
}
|
还需要修改经销商的 add 视图,寻找 title_error 错误,就像为用户 register 视图所做的那样。
修复产品 bug
另一项任务是修复 products add 方法中的 bug。正如 第 2 部分 结尾处指出的,任何人都可以添加产品,即使是在用户注销后。要解决此问题,有多种方法。
通过使用访问控制列表(Access Control List,ACL),可以在 products 控制器中创建一个操作,并使用它将对每个经销商 ACO 的 create 访问权授予用户访问请求对象(Access Request Object,ARO)。然后删除此操作,因为不再需要使用它。接下来,修改 dealer add 操作,将经销商 ACO 的 create 权限授予用户的 ARO。最后,在 products add 方法中,进行检查以确保用户具有权限。可能执行如下所示的操作。
清单 2. 在用户的 index 操作中检查用户是否已登录
function add() {
$username = $this->Session->read('user');
if ($username) {
... the rest of your add function goes here
} else {
$this->redirect(array('controller' =>
'users', 'action'=>'login'), null, true);
}
}
|
看上去很熟悉?应该是这样。该检查与在用户 index 操作中查看用户是否已登录的检查相同。如果用户已登录,那么他显然就是一名具有相应权限的用户。可以说,这是一种巧妙的解决方案,它说明存在多种验证用户的方法。
经销商 ACL
根据目前的情况来看,经销商操作完全不受保护。应当修改这些操作,以便只有用户才能实际使用它们。虽然要想完整地说明此问题需要很长的篇幅,无法在此介绍,但可以(也应该)应用更严格的 ACL。请考虑以下规则:
- 任何用户都可以添加或查看经销商。
- 只有创建了经销商的用户才能修改或删除这个经销商。
在应用程序中还可以更进一步,例如:
- 如果用户已经创建了一个经销商,那么他可以将另一个用户添加到经销关系中。
- 经销关系中的任何用户都可以修改为这个经销关系创建的任何产品。
可以使用多种其他方法来完成此任务。体验其中的几种。要勇于动手实践!
products 视图增强
需要完成的最后一项任务是修改 products 视图,以使 Edit 和 Delete 按钮仅显示给可以编辑或删除产品的用户。完成此任务的最简单方法可能是,从 index 视图中完全删除 Edit 和 Delete 按钮。只需根据用户权限检查每个产品,并相应地显示或隐藏按钮即可,但性能可能不理想。
然后,在 view 操作中,可以检查用户是否具有更新或删除产品的权限,并设置用于显示或隐藏视图中的按钮的变量。在控制器的 view 操作中,应当有如下所示的代码。
清单 3. 可能的 view 操作解决方案
if ($this->Acl->check($this->Session->read('user'),
$id.'-'.$product['Product']['title'], 'update'))
{
$this->set('showUpdate', TRUE);
} else {
$this->set('showUpdate', FALSE);
}
if ($this->Acl->check($this->Session->read('user'),
$id.'-'.$product['Product']['title'],
'delete'))
{
$this->set('showDelete', TRUE);
} else {
$this->set('showDelete', FALSE);
} |
另外,在 products/view.ctp 中,需要如下所示的代码:
清单 4. 可能的 products/view.ctp 解决方案
<?php if ($showUpdate) { ?>
<li><?php echo $html->link(... your edit link code ... ); ?> </li>
<?php } ?>
<?php if ($showDelete) { ?>
<li><?php echo $html->link(... your edit link code ... ); ?> </li>
<?php } ?>
|
如果您的解决方案与这些解决方案不完全相同,也不必担心。它们只是作为示例,而非教条。可以下载到目前为止的代码以获得相同的页面。
数据安全性
在处理 Web 应用程序时,数据安全性的重要性是怎么强调也不过分的。在看似安全但充满风险的商业环境中,必须时刻防范黑客、解密高手、脚本小子、个人身份信息盗窃者、垃圾邮件发送者、网络钓鱼者、犯罪者和单纯的捣乱者。尽管多年以来情况一直都是如此,但数据安全性的重要性依然往往被低估。就算编写出人类历史上最美妙、最优雅的 Web 应用程序也没有多大意义。糟糕的数据安全性将会使应用程序崩溃。要了解如何处理不良数据,应当熟悉一些需要面对的基本问题。
保护数据意味着什么
虽然糟糕的数据安全性会导致很多问题,但是所有问题可以归结为一句话:知道您要应付什么。数据安全性并不意味着要去掉所有 HTML — 尽管您可能希望这样。也不意味着去掉所有特殊字符 — 尽管您可能希望这样。基本原则是应用程序应该知道要应付什么风险。
这不是了解您的愿望。也不是了解应用程序正在请求哪些内容。毫无疑问,这也不是接受一切。
SQL 注入
如果用户能够将 SQL 代码直接传递给应用程序,此代码将在一个查询中执行,那么就可能发生 SQL 注入攻击。例如,用户看到了登录屏幕,在该屏幕中输入用户名和密码。password 变量可能会在以下查询中使用。
"select * from users where username = '" + $username + "'
and password = '" + $password + "'"
|
考虑一下,如果用户提交以下密码会发生什么情况:' or '1' = '1。最终的 SQL 语句可能类似以下内容:
"select * from users where username = 'wrestler'
and password = 'secret' or '1' = '1'"
|
不检查 SQL 特有的字符可能会给应用程序带来各种隐患。CakePHP 可以帮助您轻松地解决这个问题。
跨站点脚本
跨站点脚本(Cross-site scripting,XSS)代表了一大类攻击,这些攻击的主要目的是把恶意代码显示给没有疑心的用户。通常采用的形式是恶意 JavaScript,从骚扰用户到从 cookie 中捕获数据,它都能做到。
XSS 攻击的核心是提交用户数据后未恰当过滤的应用程序。例如,假定要构建一个包含论坛的应用程序,但是没有对用户数据执行过滤。通过在论坛中发帖提交的任何内容都可以显示。那么假定我是一个恶意用户,通过在论坛中发帖提交了以下文本:
<script>alert("EXPLETIVES!!!")</script>
|
如果应用程序允许显示此文本,那么查看我帖子的所有人都会收到一个 JavaScript 警告框,向这些人骂脏话。虽然伤害不太大,但您肯定不希望自己的老板看到这种东西。
这是一个无害的 XSS 攻击的简单示例。虽然本例是完全无害的,但 XSS 很可能造成损害。XSS 曾被用于窃取密码、窃取信用卡卡号、伪造新闻等等。保护您自己和您的应用程序免受 XSS 攻击是非常重要的。CakePHP 可以帮助您保护应用程序。
跨站点请求伪造
跨站点请求伪造(Cross-Site Request Forgery,CRSF)可能不像 XSS 一样常见和广为人知,但这并不意味着它不危险。出于演示的目的,我们假定应用程序包含一个论坛,该论坛向主题创建者授予删除主题的权限。在论坛中,已将删除功能实现为仅显示给创建者的按钮,甚至还检验创建者是不是发出请求的人,然后再执行实际的删除操作。通过发布一个名为 action、值为 delete 的字段和一个包含主题的惟一 ID 的 id 字段完成删除操作。查询字符串可能类似于 http://localhost/forum.php?action=delete&id=1729。
现在,假定我们发布一张图片,或者具有将图片指定为签名的能力,并提供指向图片的 URL 作为查询字符串。在 HTML 中,代码大致如下:
<img src="http://localhost/forum.php?action=delete&id=1729">
|
我自己不能直接访问该 URL,因为我不是创建者,应用程序知道这一点。但是,如果主题显示我发布的内容,浏览器就会尝试装入图片,这会请求 http://localhost/forum.php?action=delete&id=1729
— 因为这是创建者发出的请求,所以主题被删除。
这里只非常简单地探讨了 CSRF 及其原理。CakePHP 的 Security 组件可以为您提供保护。
Sanitize
如果希望要求数据符合某些规则,就应该使用 Sanitize 组件。在 CakePHP 中,输入的所有数据都被正确地转义,这会从根本上防止 SQL 注入攻击。除此之外,CakePHP 在 Sanitize 组件中提供了许多用来清理数据的方法。
Sanitize 是 CakePHP 中帮助您处理数据安全问题的类。与第 2 部分中讨论的 ACL 组件不同,只需在控制器的顶部添加一行代码即可包含 Sanitize 组件。例如,如果需要在 products 控制器中使用 Sanitize,则控制器的顶部可能类似于以下内容:
清单 5. 在产品中使用 Sanitize
<?php
App::import('Sanitize');
class ProductsController extends AppController
{
... |
Sanitize 提供了四个方法,可以将各种级别的数据安全应用于用户提交的数据。每个方法都用于不同的目的。
Sanitize paranoid 方法
此方法是可用方法中最严格的一个方法。paranoid 方法将去掉字符串中所有不是字母与数字(a-z、A-Z 或 0-9)的字符。paranoid 方法接受一个输入字符串和一个可选的数组($allowedChars)。$allowedChars 数组可用于传递允许使用的字符。例如,如果要求数据是字母、数字、下划线或点号,则使用以下代码:
$clean = Sanitize::paranoid($your_data, array('_','.')); |
注意:
paranoid 方法将去掉所有空格,除非将 ' ' 作为允许的字符放在 $allowedChars 数组中。
这种数据保护方法被称为白名单(whitelisting)。此方法不是去掉不想要的字符(即黑名单(blacklisting)),而是去掉除明确声明为可接受的字符以外的所有字符。此方法对处理必须遵守特定规则的数据片段(例如用户名、电子邮件地址和密码)很有效,白名单方法可用于几乎所有类型的非二进制数据。
Sanitize html 方法
Sanitize 的 html 方法可以传递两个参数:由 Sanitize 处理的字符串和称为 $remove 的可选布尔标志。
如果 $remove 被设置为 true,则 html 方法将把字符串传递给 PHP 函数 strip_tags,该函数将返回去掉所有 HTML 标记的字符串。例如,strip_tags("<p>Hello</p>", true) 将返回值 Hello。
如果 $remove 为 false 或未设置,则 html 方法将用 HTML 实体替换掉一些字符。具体如下:
-
& 被替换为 &。
-
% 被替换为 %。
-
< 被替换为 <。
-
> 被替换为 >。
-
" 被替换为 "。
-
' 被替换为 '。
-
( 被替换为 (。
-
) 被替换为 )。
-
+ 被替换为 +。
-
- 被替换为 -。
html 方法使用 preg_replace 来执行这些替换操作。
Sanitize 转义方法
转义方法接受一个任意类型的字符串并返回相同字符串的 SQL 安全版本。例如,Sanitize::addslashes("O'Brien") 返回字符串 O\'Brien。
Sanitize clean 方法
clean 方法以一个字符串或数组作为输入,并在递归地 “清理” 后返回同一个字符串或数组。清理过程可以解决许多潜在的数据问题,比如处理反斜杠、空白、HTML、回车等等。
可以在 cake/libs/sanitize.php 中查看这些方法以便更好地掌握它们的作用。但是,绝不能修改基本的 CakePHP 文件。这将使升级到新版本变得困难,而且可能会造成异常的行为。
CakePHP 的 Security 组件
要想使用 CakePHP 的 Security 组件,需要将其添加到控制器的组件中,就像处理 ACL 那样。
var $components = array ('Acl', 'Security');
|
包含 Security 组件后,就会自动执行很多处理,即使还未使用该组件来保护操作:
- 通过使用核心 Security 类,生成身份验证密钥并将其写入会话。身份验证密钥将根据
app/config/core.php 中的设置过期。
- 身份验证密钥在控制器中被设为类变量。
- 如果控制器生成的任意一个视图使用
$form->create() 来创建表单,则表单中将包含一个名为 _Token/key 的隐藏输入字段,其中包含身份验证密钥。当表单被提交时,系统将把此字段的值与会话中的身份验证密钥值相比较,以检验表单提交是否有效。执行这个检验后,系统将生成一个新的身份验证密钥并在会话中设置它。
现在已经包含了 Security 组件,还需要在使用 Security 组件的控制器中创建 beforeFilter 方法。在执行用户调用的任何方法之前,控制器将自动执行这个方法。这是通常执行安全检查的地方。
有两个方法可以用来实现本文所需的功能。第一个方法要求表单使用 POST 方法。第二个方法要求使用一个有效的身份验证密钥。结合使用这两个方法将为应用程序创建强大的安全基础。
requirePost
Security requirePost 方法让 CakePHP 忽略提交给指定操作的任何信息,除非使用的是 POST。向 requirePost 方法传递一个需要保护的控制器操作列表。例如,如果需要使用 requirePost 保护 delete 方法和 add 方法,则 beforeFilter 方法应当如下所示:
function beforeFilter()
{
$this->Security->requirePost('delete', 'add');
}
|
通过要求操作只能使用来自表单 POST 的数据,可以避免攻击者使用查询字符串伪造请求。
requireAuth
Security requireAuth 方法让 CakePHP 用身份验证密钥来检验所有表单提交。仅当通过 POST 提交表单时,才会发生此种检验。
与 requirePost 方法一样,向 requireAuth 传递一个需要保护的控制器操作列表。例如,如果需要使用 requireAuth 保护 delete 方法和 add 方法,则 beforeFilter 方法应当如下所示:
function beforeFilter()
{
$this->Security->requireAuth('delete', 'add');
}
|
要同时使用 requireAuth 和 requirePost,只需在 beforeFilter 方法中指定它们。
清单 6. 指定 requireAuth 和 requirePost
function beforeFilter()
{
$this->Security->requireAuth('delete', 'add');
$this->Security->requirePost('delete', 'add');
}
|
同时使用 requireAuth 和 requirePost 保护操作是一个强大的组合。如果需要为不同的操作指定不同的保护级别,甚至可以混合使用这两个方法。
清单 8. 混合使用 requireAuth 和 requirePost
function beforeFilter()
{
$this->Security->requireAuth('delete');
$this->Security->requirePost('delete', 'add');
}
|
通过要求表单提交包含有效的身份验证密钥,然后才能处理,可以使攻击者更难伪造其他用户提交的表单。结合使用 requireAuth 和 requirePost 是一种非常好的帮助保护应用程序的方法。
浅谈缓存
虽然使用 requireAuth 保护操作有很明显的优点,但是也有一些需要克服的缺点。大多数缺点都属于 “缓存问题”。
每当表单被 requireAuth 评估时,都将生成身份验证密钥。这意味着,如果一个用户提交一个具有已使用过的密钥的表单,则该表单提交操作将被视为无效。在几种情况下会出现这个问题,包括(但不仅限于)使用多个浏览器窗口、使用 Back 按钮返回前一个页面、浏览器缓存、代理服务器缓存等等。您可能会把这些问题看成是 “用户错误”,因此不予理会;但这种想法是不正确的,应该有计划地适当处理无效的表单提交。
提交无效的表单时会发生什么情况?
如果请求被 requirePost 或 requireAuth 拒绝,则应用程序会退出并向用户发送一个 404 页面。要想修改这种处理方式,可以将 Security 组件的 $blackHoleCallback 属性设置为控制器内的一个函数的名称。例如,如果有一个称为 invalid 的操作和一个相应的视图,则可以让 Security 组件将错误的请求发送给 invalid 操作。可以通过将以下行添加到 beforeFilter 方法的开头来完成这个设置:$this->Security->blackHoleCallback='invalid';。
这样就能够控制在用户提交无效请求时的处理方式。用户可能由于合理的原因提交无效请求,例如用户运行应用程序,然后走开了一会儿,当他回来时身份验证密钥已经过期了。
补充功能
现在您知道了如何使用 Sanitize 组件和 Security 组件,让它们在 Tor 中运行。
首先用 Sanitize 保护所有数据。对于产品、用户和经销商,用 Sanitize 保护提交的所有数据。使用哪一个 Sanitize 方法由您决定。接下来,查看控制器及其操作,判断哪些操作需要使用 requireAuth 来保护,而哪些操作需要使用 requirePost 来保护。相应地实现 Security 组件并使用它。最后,为每个控制器创建一个称为 invalid 的操作,并使用这个方法通知用户提交的表单无效。
结束语
CakePHP 的 Sanitize 组件和 Security 组件并非 灵丹妙药。使用这两个组件并不意味着您可以不再考虑应用程序安全性。但是,使用这两个组件可以使您更轻松地处理一些与安全相关常见的任务。通过清理数据并忽略被错误提交的数据,已经为保护应用程序奠定了良好的基础。
第 4 部分 将主要介绍 CakePHP 的 Session 组件,演示三种保存会话数据的方法,并演示 Request Handler 组件如何帮助您管理各种各样的请求(移动浏览器、包含 XML 或 HTML 的请求等等)。
下载 | 描述 | 名字 | 大小 | 下载方法 |
|---|
| 第 3 部分源代码 | os-php-cake3.source.zip | 11KB | HTTP |
|---|
参考资料 学习
获得产品和技术
-
使用 IBM 试用软件 改进您的下一个开放源码开发项目,这些软件可以下载或者通过 DVD 获得。
-
下载 IBM 产品评估版,试用这些来自 DB2®、Lotus®、Rational®、Tivoli® 和 WebSphere® 的应用程序开发工具和中间件产品。
讨论
关于作者  | |  | 当 Oregon Trail 还仅仅是文字的时候,Duane O'Brien 就已经是一名全能的技术人员了。他最喜欢吃的食物是寿司。他还未曾到过月球。 |
对本文的评价
|