11.3 Cookie会话技术
Cookie是一组“键值对”信息,该信息由WEB服务器的PHP程序生成,最终保存到浏览器内存甚至保存到浏览器端硬盘文件中。
11.3.1 浏览器的Cookie设置
一台主机中可以安装多种浏览器,每种浏览器默认情况下开启了Cookie,用户可以对浏览器进行设置决定是否开启Cookie。以IE浏览器为例,Cookie的设置方法如下。
打开IE浏览器后,单击“工具”菜单中的“Internet选项”选项,选择“隐私”选项卡,在“设置”区域拖动“滚动滑块”即可修改IE浏览器的Cookie配置。用户通常需要将“滚动滑块”拖动至“中”或“中高”,这样既可以保护隐私,又开启了Cookie,不影响某些网络功能的使用。
11.3.2 Cookie的工作原理
Cookie的工作原理如图11-2所示。
浏览器向某WEB服务器中的某页面(page1)发出第一次请求,page1页面接收到请求后创建Cookie,然后仅仅向浏览器返回一个Cookie响应头信息(Cookie响应头信息格式如图11-2所示)。Cookie 响应头信息中不包含任何需要显示的数据,只包含一些“键值对”信息。此时浏览器与WEB服务器之间完成了第一次请求与第一次响应。
浏览器接收到page1页面的Cookie响应头信息后,将根据自己Cookie的设置以及Cookie信息的过期时间,决定将Cookie信息以“键值对”的方式保存在浏览器端主机硬盘中,还是保存在浏览器进程使用的主机内存中。
当浏览器再次向同一 WEB 服务器其他页面(page2)发送第二次请求时,浏览器首先判断Cookie 信息是否失效,如果没有失效,浏览器会自动地将浏览器端的 Cookie 信息放入到第二次请求头中(Cookie请求头信息格式如图11-2所示)。page2页面接收第二次请求中的Cookie信息,从而实现同一WEB服务器内不同页面间的参数传递。
Cookie响应头“键值对”信息说明如下。
(1)Cookie响应头信息中的Set-Cookie关键字定义了该响应中包含了Cookie信息。
(2)name:指定Cookie的标记名称,为字符串类型数据。Cookie的标记名称由程序员定义,并由page1页面发送给浏览器,然后在浏览器端生成“键值对”信息中的“键”。
(3)value:指定 Cookie 的值,为字符串类型数据。Cookie 的值由程序员定义,并由 page1页面发送给浏览器,然后在浏览器端生成“键值对”信息中的“值”。
(4)expire:指定 Cookie 的过期时间,单位为秒,通常为整数数据,该整数数据是个 UNIX时间戳,即从UNIX纪元开始的秒数。
(5)path:指定 Cookie 在 WEB 服务器的有效路径。设定此值后,只有当浏览器访问 WEB服务器中有效路径下的页面时,浏览器才向请求头信息中加入Cookie信息。通过设置Cookie的有效路径,可以实现同一个WEB服务器下同一应用程序之间Cookie信息的安全性。
(6)domain:指定Cookie的有效域名。设定此值后,只有当浏览器访问该域名下的页面时,浏览器才会向请求头信息中放入 Cookie 信息。通过设置 Cookie 的有效路径,可以实现同一个WEB服务器下不同应用程序之间Cookie信息的安全性。
(7)secure:指定Cookie 信息通过HTTP 还是HTTPS 加入请求头中,取值范围为TRUE 或FALSE。默认值为FALSE,表示Cookie只有使用HTTP连接WEB服务器时,才将Cookie信息加入请求头中;值为TRUE时,表示Cookie只有使用HTTPS连接WEB服务器时,才将Cookie信息加入请求头中。
对于某个WEB服务器而言,该服务器的PHP程序关心的仅仅是Cookie的name属性和value属性,其他属性如path、domain和expire等仅仅是告诉浏览器如何处理这些Cookie。由于Cookie是将个人信息存放于浏览器端,因此在某种程度上降低了 WEB 服务器存储压力。但为此也增加了某些安全隐患,尤其是在一台计算机多个用户使用的场合下,容易将某个用户的个人信息暴露给其他用户。
只有浏览器端存在Cookie,并且该Cookie没有过期,domain和path匹配的情况下,该Cookie才有效,否则该Cookie失效。Cookie有效时,浏览器请求访问WEB服务器的其他页面时,才会将Cookie信息放入到请求头中。浏览器不会将Cookie信息放入到其他WEB服务器的请求头中,从而实现了Cookie信息跨服务器的安全性。
11.3.3 Cookie分类
按照Cookie的过期时间可以将Cookie分为会话Cookie和持久Cookie。
1.会话Cookie
图11-2中WEB服务器中的page1页面创建Cookie时,如果page1页面没有指定Cookie的过期时间或设置的Cookie过期时间为过去的时间(小于当前时间的UNIX时间戳),该Cookie为会话Cookie。会话Cookie的信息保存在当前浏览器进程使用的内存中,会话Cookie只在当前浏览器进程有效,关闭当前浏览器进程后,会话Cookie的信息从浏览器进程使用的内存中消失,会话Cookie将失效。会话Cookie的典型应用是实现Session会话技术。
2.持久Cookie
图11-2中WEB服务器中的page1页面创建Cookie时,如果page1页面指定Cookie的过期时间为未来的某个时间(大于当前时间的UNIX时间戳),并且浏览器开启了Cookie设置,该Cookie为持久 Cookie。持久 Cookie 的信息保存在浏览器端硬盘文件中,保存在浏览器端硬盘文件中的持久Cookie信息将一直有效,直到出现下面3种情况。
(1)当前时间的UNIX时间戳等于Cookie的过期时间。
(2)浏览器用户手动删除该持久Cookie。
(3)浏览器上的Cookie太多,超过了浏览器所允许的范围,浏览器将自动删除某些Cookie。因此若要实现两个单独的浏览器进程之间的信息共享,可以选择使用持久Cookie实现。持久Cookie 的典型应用是在 Cookie 的有效期内,打开浏览器时,浏览器自动填入用户名信息和密码信息,方便用户登录系统。
11.3.4 PHP使用Cookie的步骤
各种浏览器接收到Cookie响应头信息后,处理细节未必相同,这里以IE浏览器为例,阐述PHP使用Cookie的步骤(见图11-3)。
(1)浏览器第一次请求访问WEB服务器中的page1页面时,page1程序通过调用setcookie()函数或 header("Set-Cookie:name=value")函数创建 Cookie,产生 Cookie响应头信息,随着服务器的第一次响应将Cookie信息发送给浏览器。
(2)浏览器接收到含有Cookie响应头信息的响应后,判断该Cookie是会话Cookie还是持久Cookie。若是会话 Cookie,则将 Cookie 信息保存在浏览器进程中;若是持久 Cookie,浏览器会自动创建文本文件永久保存Cookie信息。
(3)当浏览器第二次请求该WEB服务器的page2页面时,浏览器判断Cookie是否过期以及domain和path是否匹配,然后再将Cookie信息封装到第二次请求中,向page2页面发出请求。page2页面通过使用预定义变量$_COOKIE访问到第二次请求头中的Cookie信息,从而实现page1页面与page2页面间的参数传递。
(4)浏览器关闭后,会话Cookie从内存中删除,而持久Cookie将保存在浏览器主机硬盘中。
WEB 服务器每次为浏览器返回一个响应(HTML 网页)的时候,这个响应由响应头信息和正文两部分组成。响应头中包含了一些“控制”信息,用于控制浏览器接下来的操作;正文部分包括可视的HTML数据、图片等信息。响应头总应该比正文部分先到达浏览器,以告知浏览器接下来的操作,WEB服务器若向浏览器发送正文信息,那么服务器应该首先发送响应头信息,以便控制浏览器如何处理正文信息。由于PHP的header()函数、setcookie()函数以及session_start()函数向响应中加入Cookie响应头信息,因此调用这些函数前不能有任何HTML数据的输出(包括空格),否则会提示“header already sent”错误信息。
防止提示“header already sent”错误信息的方法是设置 php.ini 文件中的配置选项output_buffering,将output_buffering设置为On。启用output_buffering后,在WEB服务器返回响应前,WEB服务器将所有响应头信息缓存到WEB服务器的缓存中,只有PHP程序中的所有语句执行完毕后,才将缓存中的响应头信息和内存中的执行结果(正文部分)发送给浏览器,避免了“header already sent”错误信息的出现。
11.3.5 创建Cookie
PHP提供的setcookie()函数是创建Cookie的最简单方法。
setcookie()函数的语法格式:
bool setcookie(string name[[[[, string value], int expire], string path],string domain], int secure])
函数功能:setcookie()函数中除了 name 参数外,其他参数都是可选的。setcookie()函数成功创建Cookie则返回TRUE,否则返回FALSE。例如程序create_cookie.php如下,该程序的运行结果如图11-4所示。
<?php
setcookie("myCookie", "Value of MyCookie");
setcookie("withExpire","expire in 1 hour",time()+60); //60秒=1分钟
setcookie("fullCookie","full cookie value",time()+60,"","",FALSE);
echo (time()+60);
?>
程序create_cookie.php说明:通常使用time()函数或mktime()函数加上秒数设定Cookie的过期时间。以time()函数为例,time()函数的语法格式为:int time ( void ),其功能是返回自从UNIX纪元(格林威治时间1970年1月1日00:00:00)到当前时间的秒数,也叫UNIX时间戳。
如果 IE 浏览器开启了 Cookie,浏览器接收到WEB服务器的响应后,会在浏览器端的主机硬盘中创建一个文件保存所有持久Cookie信息。以笔者的IE浏览器为例,在笔者主机的“C:\Documents and Settings\Administrator\Cookies”目录下创建一个administrator@11[2].txt文本文件,文件内容如图11-5所示。
从图11-5中可以得知程序create_cookie.php创建了名字分别为withExpire和fullCookie的两个持久Cookie。而myCookie由于没有指定过期时间,仅仅是一个会话Cookie,会话Cookie并没有保存到文本文件中,关闭IE浏览器后,myCookie将立即失效,而withExpire和fullCookie在关闭浏览器的1min后失效。
程序create_cookie.php产生的withExpire和fullCookie的两个持久Cookie的有效路径为当前WEB服务器根目录下的“11”目录。
任何PHP程序产生的Cookie“键值对”中的“值”默认经urlencode()函数处理,例如程序create_cookie.php中将空格" "转换为加号“+”。
Cookie是HTTP协议头的一部分,用于浏览器和WEB服务器之间传递信息,建议在任何HTML内容输出到浏览器之前调用setcookie()函数(也不要有空格或空行),否则setcookie()函数可能创建Cookie失败。
11.3.6 预定义变量$_COOKIE
浏览器端产生Cookie信息后,再次向其他PHP页面发送请求时,WEB服务器会自动地收集请求头中的Cookie信息,并将这些Cookie信息解析到预定义变量$_COOKIE中。通过$_COOKIE可以读取所有通过HTTP请求传递的Cookie信息,$_COOKIE是一个全局数组,该数组中的每个元素的“键”为Cookie的标记名称,数组中每个元素的“值”为Cookie的值。例如,如下程序read_cookie.php的功能是创建并读取浏览器中的Cookie信息。
<?php
$time = time()+3600;
setcookie("name","victor",$time);
setcookie("password","1234567",$time);
if(isset($_COOKIE["name"])){
$name = $_COOKIE["name"];
echo $name;
echo "<br/>";
}else{
echo "HTTP请求头中没有名字为name的Cookie<br/>";
}
if(isset($_COOKIE["password"])){
$password = $_COOKIE["password"];
echo $password;
}else{
echo "HTTP请求头中没有名字为password的Cookie<br/>";
}
?>
打开浏览器后,第一次访问read_cookie.php的运行结果如图11-6所示。
此时在浏览器端主机“C:\Documents and Settings\Administrator\Cookies”目录下创建一个 administrator@11[3].txt文本文件,文件内容如图11-7所示。
刷新 read_cookie.php 页面或打开新的浏览器再次访问 read_cookie.php 页面后的运行结果如图11-8所示。读者可以自己分析两次访问同一个read_cookie.php页面产生不同结果的原因。程序read_cookie.php 将用户名和密码信息保存在了文本文件中,这无疑泄露了用户的隐私,有效的做法是使用md5加密算法将密码数据加密后保存在Cookie文本文件中。
11.3.7 删除浏览器端的Cookie
浏览器端的 Cookie 可由浏览器用户根据需要手动删除,这可能给用户带来不好的用户体验。较好的做法是程序员开发PHP程序,让浏览器用户选择性地删除浏览器端的Cookie。使用PHP程序删除浏览器端的Cookie主要有两种方法:一种是将Cookie的值设置为空,另一种是将Cookie的有效时间设为过去的时间。不管使用哪种方法,浏览器接收到这样的 Cookie 响应头信息后,将自动删除浏览器端硬盘中的文本文件或内存中的 Cookie 信息。例如如下程序 destroy_cookie.php 的功能是将read_cookie.php程序创建的Cookie全部删除,使用浏览器访问该页面后,浏览器端“C:\Documents andSettings\Administrator\Cookies”目录中的administrator@11[3].txt文本文件将被删除。
<?php
$time = time()-3600;
setcookie("name","");
setcookie("password","1234567",$time);
?>
11.3.8 新闻发布系统用户管理功能的实现(一)
使用Cookie技术可以为“新闻发布系统”添加新的功能:将用户名和密码信息保存到浏览器端的Cookie文本文件中,下次打开登录页面时,无需再次输入用户名和密码。具体步骤如下。
(1)在“C:\wamp\www\news”目录下创建 login.php 程序,该程序实现的功能依次是:检查浏览器端是否存在有关用户登录的Cookie信息,若存在,将浏览器端Cookie中的用户名和密码信息取出;为浏览器用户提供登录表单,并填写Cookie中的用户名和密码信息。login.php页面中的代码如下。
<?php
$name = "";
if(isset($_COOKIE["name"])){
$name = $_COOKIE["name"];
}
$password = "";
if(isset($_COOKIE["password"])){
$password = $_COOKIE["password"];
}
?>
<form action="login_process.php" method="post">
用户名:<input type="text" name="name" size="11" value="<?php echo $name?>" /><br/>
密 码 :<input type="password" name="password" size="11" value="<?php echo $password?>"/><br/>
<input type="checkbox" name="expire" value="3600" checked/>Cookie保存1小时<br/>
<input type="submit" value="登录" />
</form>
(2)在“C:\wamp\www\news”目录下创建login_process.php文件,login_process.php程序实现的功能依次如下。
① 采集login.php页面中浏览器用户输入的用户名信息或自动填入的用户名信息。
② Cookie 文本文件中保存了账户信息,为了保证用户的账户信息安全地保存在浏览器端的Cookie文本文件中,Cookie文本文件中的密码是经md5加密后的数据。因此若从Cookie文本文件中获取密码信息,直接使用即可;若从login.php页面中的密码框中获取密码信息,需要将该密码md5加密。
③ 检查浏览器用户是否选择“Cookie 保存 1 小时”复选框。若没有选择该复选框, login_process.php程序调用setcookie()函数删除Cookie文本文件。
④ 再次使用 md5()函数加密密码,这是由于数据库 users 表中保存的是“admin”两次 md5加密后的数据。
⑤ 构造SQL语句,查询users表中是否存在该账户信息。若不存在,login_process.php程序将页面重定向到登录页面login.php,并传递“password_error”消息;若存在,检查浏览器用户是否选择“Cookie保存1小时”复选框,若选择了该复选框,login_process.php程序负责调用setcookie()函数将用户名和密码信息放置到Cookie文本文件中,然后将页面重定向到登录页面login.php,并传递“password_right”消息。
程序login_process.php代码如下。
<?php
include_once("functions/database.php");
$name = $_POST["name"];
if(isset($_COOKIE["password"])){
$first_password = $_COOKIE["password"];
}else{
$first_password = md5($_POST["password"]);
}
if(empty($_POST["expire"])){
setcookie("name",$name,time()-1);
setcookie("password",$first_password,time()-1);
}
$password = md5($first_password);
$sql = "select * from users where name='$name' and password ='$password'";
get_connection();
$result_set = mysql_query($sql);
if(mysql_num_rows($result_set)>0){
if(isset($_POST["expire"])){
$expire = time()+intval($_POST["expire"]);
setcookie("name",$name,$expire);
setcookie("password",$first_password,$expire);
}
header("Location:login.php?login_message=password_right");
}else{
header("Location:login.php?login_message=password_error");
}
close_connection();
?>
程序login_process.php的代码说明如下。
① 保存在数据库 users 表中的 password 字段值为 md5 两次加密后的数据,因此程序login_process.php需要使用两次md5加密算法将浏览器用户输入的密码信息加密,以便匹配数据库users表中的password字段值。
② 使用 GET 或 POST 提交方式提交的网页数据,全部封装为字符串类型提交到 WEB 服务器,因此必要时需要PHP程序使用数据类型转换函数进行类型转换。例如程序login_process.php调用intval()函数将字符串"3600"转换为整数3600。
③ 将login.php 程序修改为如下代码,使login.php 页面接收login_process.php 传递的查询字符串数据(字体加粗代码为新增代码,其他代码不变)。
<?php
if(isset($_GET["login_message"])){
if($_GET["login_message"]=="password_error"){
echo "密码错误,重新登录!<br/>";
}else if($_GET["login_message"]=="password_right"){
echo "登录成功!<br/>";
}
}
$name = "";
if(isset($_COOKIE["name"])){
$name = $_COOKIE["name"];
}
……
至此,通过使用Cookie实现了浏览器用户不必重新输入用户名和密码登录“新闻发布系统”的功能。
11.3.9 Cookie数组
使用setCookie()函数可以创建Cookie数组,语法格式如下:
setcookie(string name[下标], string value, int expire, string path,string domain, int secure)
创建Cookie数组时的name参数使用了下标,下标可以为整数或字符串,但下标两边不能用引号。此时可以为标记名称为name的Cookie设置多个Cookie值,这些Cookie值使用下标区分。
下面两个PHP程序,程序cookie_array.php负责创建Cookie数组,程序cookie_list.php负责读取Cookie数组中的值。
程序cookie_array.php
<?php
setcookie("name[1]","name1");
setcookie("name[2]","name2");
setcookie("name[one]","name one");
setcookie("name[two]","name two");
header("Location:cookie_list.php");
?>
程序cookie_list.php
<?php
foreach($_COOKIE["name"] as $key => $value){
echo $key,"=>",$value,"<br/>";
}
?>
创建Cookie数组时,不能直接使用setcookie()函数将数组作为该函数的第二个参数value,因为setcookie()函数的第二个参数value要求是一个字符串的值。