会话处理
当向 Web 站点发出请求时,该请求完全独立于之前发出的每个请求。为了实现用户功能,特定的 Web 客户机需要重复访问一些惟一的、可能需要保密的信息。这在 Web 应用程序中被称为会话处理(session handling),而正确处理会话的细微之处令很多开发人员头疼。
会话处理的基本原理
在所有编程语言中,会话处理的基本原理都是相同的。在首次访问 Web 站点时,客户机被授予一个惟一的 ID,这个 ID 通常存储在 cookie 中。在服务器端,以某种方式存储一个变量数组,该数组与惟一 ID 相对应。对于同一个客户机发出的任何后续请求,这个惟一 ID 会随请求一同发送,服务器根据这个 ID 把数组装入内存。这些变量被称为客户机的会话变量(session variable)。在 PHP 中,它们被存储在 $_SESSION 全局变量中。
在使用 CakePHP 时,只需使用 $_SESSION 全局变量即可访问会话信息。但是,CakePHP 附带自己的会话处理对象,正如我们稍后将要看到的,这个会话处理对象有很多优点。
Tor 中的会话处理
不用费多大气力就可以实现会话。当用户登录时,UsersController 对象存储用户名,并在后续请求期间将其与客户机自动关联起来。
每个 Controller 对象(继承自 AppController 的任何对象)都自动具有对 Session 组件的访问权。回想一下:组件是一个与控制器相关联的类(允许附加功能),很像是为视图提供附加功能的 helper。在第 2 部分中添加 ACL 时,使用了 ACL 组件。需要将以下行添加到 ProductsController 类和 UsersController 类的开头:var $components = array('Acl');。
虽然会话处理在 CakePHP 中是一个组件,但它是每个控制器都包含的标准组件,因此无需将其添加到组件列表中。
要使用 Session 组件,只需访问控制器中的会话实例变量。会话处理程序的两大关键功能就是设置变量和获取变量,这些功能是通过调用 read 方法和 write 方法来实现的。在用户登录时,使用清单 7 中的代码把当前客户机的用户名写入会话中。这段代码位于 UsersController 类中。
清单 7. 使用 write 方法存储用户名
function login()
{
...
$this->Session->write('user', $this->data['User']['username']);
...
{
|
write 方法接受两个参数。第一个参数是指定的键,第二个参数是分配给该键的值。在本例中,键为 user,而值是输入的用户名。稍后将访问该用户名(见清单 8)以根据 ACL 检查是否可以查看或编辑产品。
清单 8. 使用 read 方法访问用户名
function view($id) {
...
if ($this->Acl->check($this->Session->read('user'),
$id.'-'.$product['Product']['title'], 'read'))
{
...
}
}
|
read 方法接受一个参数 — 需要访问的键 — 并返回存储的值。
存储会话
所有这些做法对于 Web 开发人员来说应当都不算是什么新闻,但是会话处理对于小型应用程序来说通常只是一个小问题。如果应用程序要求在会话中存储敏感的用户信息,例如密码或信用卡信息,则存储数据的位置和方法就极其重要了。
在 CakePHP 中,会话存储是由一行来控制的:Configure::write('Session.save', 'php');。这一行(位于 /app/config/core.php 文件中)告诉 CakePHP 应该如何存储会话。有三个有效值:php(使用 php.ini 文件中的设置)、cake(将会话文件保存在 CakePHP 的 tmp 目录中)和 database(使用 CakePHP 的数据库会话)。
存储会话:cake
设置此值时,将把会话作为文件存储在应用程序文件夹中。CakePHP 在 /app/tmp/session 提供了一个文件夹,其中包含诸如 sess_50bfa744a2ab2c98df808f70c893704c 之类的文件。在这些文件中,各个会话变量被存储为明文而不进行加密。
Apache Web 服务器有一个名为安全模式的设置,它限制 Apache 可以访问的文件夹。通常,Apache 只可以读取站点的文档根目录中的内容,但是一些库文件例外。在本系列中,在安装 CakePHP 时将整个目录放在 Apache 的文档根目录中(/webroot 目录)。这肯定是一种比较轻松的安装方式,但是不太安全,原因如下:通过将会话处理设置为 cake,将把会话变量存储到一个目录中,浏览器可以直接访问这个目录。如果站点遭受到某种攻击,攻击者可以让 Apache 返回任意文件,则每个用户的会话数据都容易受到攻击(尽管这个问题可能不是您最担心的)。CakePHP 为保护这些会话文件采取了很好的措施(即使它们在文档根中),但是在理想情况下让 app/webroot 目录成为文档根目录会更安全。这会使 CakePHP 会话文件离线。
必须注意,您无法控制对于这些会话文件的权限。它们将拥有与授予 PHP 的权限完全相同的用户权限和组权限。在某些系统中,这也可能是安全漏洞。
优点:
- 会话变量被存储在 CakePHP 中,因而整个应用程序都位于同一位置。
- 可以用文本浏览器读取会话文件,可能用于调试(可能没用)。
- 启动和访问会话不要求使用数据库连接(可能没有帮助,因为在 CakePHP 中访问模型将启动一个 PHP 会话)。
缺点:
- 会话文件被存储在文档根目录中,可能会在 Web 服务器受到危及时也遭到波及。
- 会话文件拥有与 PHP 相同的权限。
- 不共享一个文件系统的负载平衡的 Web 服务器不能共享对会话文件的访问权,导致会话被神秘地删除,除非使用粘性会话(sticky session)。
存储会话:database
如果要存储的会话信息需要更高的安全级别,或更有力的权限控制,则使用数据库会话会更好。通过将 Session_save 设置为 database,告诉 CakePHP 在应用程序的其余部分中,把所有序列化的变量信息存储到数据库的表中。
在通过 CakePHP 使用数据库会话之前,必须先创建表。您应当放心大胆地在 Tor 中进行尝试。默认情况下,表名为 cake_sessions,不过此名称可以在 app/config/core.php 文件中更改。为了使用数据库会话存储,需要取消 Session.table 和 Session.database 的 Configure::write 行的注释标志。这个表的模式存储在 app/config/sql/sessions.sql 中。CakePHP V1.2.0.x 提供的模式如下:
清单 9. 创建表 cake_sessions
CREATE TABLE cake_sessions (
id varchar(255) NOT NULL default '',
data text,
expires int(11) default NULL,
PRIMARY KEY (id)
);
|
在创建了表并把 Session.save 设置为 database 之后,请尝试在 /users/login 登录到 Tor 中。登录后,检查数据库中是否出现了您的会话。应当会出现一行类似如下所示的内容:
id data expires
50bfa744a2ab2c98df808f70c893704c Config|a:3:{s:4:"rand";i:94427... 1164661678
|
把会话存储到数据库中将直接解决前面列出的三个缺点。第一,Apache 通常对数据库没有直接访问权,所以 Web 服务器遭到入侵时不会暴露会话信息。第二,可以为应用程序明确设置对数据库的访问权限,甚至可以将访问限定于特定的主机。第三,负载平衡的服务器共享对会话表的访问权,而无需您做任何额外的工作。
对于大多数大型应用程序,数据库会话是很合适的,因为它们提供最大程度的安全性而只需投入最少的精力。
优点:
- 易于设置 — 只要求在数据库中添加一个附加表。
- Web 服务器遭到入侵时可能不会危及会话的安全性。
- 可以在负载平衡的服务器之间轻松地共享会话。
缺点:
- 使用数据库存储会话会增加数据库的开销。
- 会话仍以明文的形式存储在数据库中;数据库备份可能会导致敏感数据被长时间存储。
- 根据数据库设置方式的不同,应用程序与数据库之间的通信可能不安全。如果数据库不是位于本地主机上,或者不是通过安全通道(例如 VPN)进行通信,则通信很可能会受到危及。
存储会话:php
存储会话的最后一种方法是使用 PHP 设置的会话处理方法。默认情况下,PHP 将把其会话写到类似于 Session.save 的 cake 设置的文件中。一个主要差别是,会话变量不是保存在文档根目录中,而通常是保存在文件系统中其他地方的一个临时目录中。与将会话存储在站点所在的同一个目录中相比,这可能更好,也可能不好。
通过把会话设置为通过 PHP 存储,可以更好地控制会话。PHP 允许重写一些会话处理程序函数,从本质上将存储会话的方法重写为您选择的方法。例如,通过一个定制的安全通道将会话存储在单独的数据库中,或者您能想到的任何 “疯狂” 的方法。
如果需要更改 PHP 采用的会话处理方法,可以使用函数 session_set_save_handler,该函数可以控制会话回调函数。向此函数传递用来打开、关闭、读取和写入会话的函数的名称。深入讨论 session_set_save_handler 函数超出了本教程的讨论范围(请参阅 参考资料)。
清单 10. 重新定义 PHP 存储会话的方法
function open($save_path, $session_name)
{
global $sess_save_path;
$sess_save_path = $save_path;
return(true);
}
function read($id)
{
global $sess_save_path;
$sess_file = "$sess_save_path/sess_$id";
return (string) @file_get_contents($sess_file);
}
...
session_set_save_handler("open", "close", "read", "write", "destroy", "gc");
|
会话处理函数由 PHP 函数 session_start() 重新定义的。使用此方法有以下优点。
优点:
- 极大的灵活性 —— 可以使用 PHP 支持的任何存储方法。
- 如果不重写会话处理函数,则会话将按照与服务器上所有其他应用程序一样的方法来存储。
缺点:
- 由于 PHP 默认设置为把会话存储在临时目录中,因此您要面对 “存储会话:
'cake'” 中提到的一些缺点。
选择会话存储方法并不总是很容易。在大多数情况下,让 PHP 处理会话就可以了。但是从根本上说,应该根据应用程序的具体情况做出决策。
在下一节中,我们将讨论 Request Handler 组件,以及如何使用它在应用程序中添加 Ajax 功能。
|