Cookie学习笔记(二)
(4) 从URL中删除任何结尾斜杠。
$url = rtrim($url, '/\\');
由于子文件夹可能添加额 外的斜杠(/)或反斜杠(\,对于Windows),所以函数需要删除它们。为了执行该任务,可使用rtrim()函数。默认情况下,这个函数会删除字符 串右边的空格。如果提供了要删除的字符列表作为第二个参数,就会代之以删除这些字符。这里要删除的字符应该是/或\。但是,由于PHP中的反斜杠是转义 符,需要使用\\来指示单个反斜杠。因此,简而言之,如果$url以这些字符中的任何一个结尾,rtrim()函数将删除它们。
(5) 添加特定的页面到URL中并完成函数。
$url .= '/' . $page;
return $url;
} // End of absolute_url() function.
最后,将特定的页面名称追加到$url中。在它前面放置一个斜杠,因为任何尾随的斜杠都会在第(4)步中被删除,并且不能把www.example.compage.php作为URL,然后返回该URL。
这些看起来似乎都相当复杂,但它可以非常有效地确保重定向正确工作,而不管在什么服务器上、来自于什么目录,或者脚本是否在运行(只要重定向发生在那个目录内即可)。
(6) 开始创建新函数。
function check_login($dbc, $email = '', $pass = '') {
这个函数将验证登录信息。它带有三个参数:数据库连接(它是必需的)、电子邮件地址(它是可选的)和密码(它也是可选的)。
尽管这个函数可以直接访问$_POST['email']和$_POST['pass'],但是更好的做法是给函数传递这些值,从而使得函数更独立。
(7) 验证电子邮件地址和密码。
$errors = array();
if (empty($email)) {
$errors[] = 'You forgot to enter your email address.';
} else {
$e = mysqli_real_escape_string($dbc, trim($email));
}
if (empty($pass)) {
$errors[] = 'You forgot to enter your password.';
} else {
$p = mysqli_real_escape_string($dbc, trim($pass));
}
这个验证例程类似于注册页面中使用的那个验证例程。如果发生任何问题,都会把它们添加到$errors数组中,最终将把它们用在登录页面上(参见图11-2)。
(8) 如果没有发生错误,就运行数据库查询。
if (empty($errors)) {
$q = "SELECT user_id, first_name FROM users WHERE email='$e' AND pass=SHA1('$p')";
$r = @mysqli_query ($dbc, $q);
这个查询从数据库中选择user_id和first_name值,其中提交的电子邮件地址(来自于表单)匹配存储的电子邮件地址,并且提交的密码的SHA1()版本匹配存储的密码(参见图11-3)。
图11-3 如果用户提交正确的电子邮件地址/密码组合,登录查询就会得到这个结果
(9) 检查查询的结果。
if (mysqli_num_rows($r) == 1) {
$row = mysqli_fetch_array ($r, MYSQLI_ASSOC);
return array(true, $row);
} else {
$errors[] = 'The email address and password entered do not match those on file.';
}
如果查询返回一行,那么登录信息是正确的。然后将把结果获取进$row中。成功登录中的最后一步是把两种信息返回到请求的脚本中:值true和从MySQL获取的数据,其中前者指示登录成功。使用array()函数,可以返回布尔值和$row数组。
如果查询没有返回一行,就会把一条出错消息添加到数组中。它最终将显示在登录页面上(参见图11-4)。
图11-4 如果用户输入了电子邮件地址和密码,但是它们与数据库中存储的值不匹配,就会得到这个结果
(10) 完成开始于第(8)步中的条件语句,并完成函数。
} // End of empty($errors) IF.
return array(false, $errors);
} // End of check_login() function.
该 函数的最后一步是返回值false指示登录失败,以及返回$errors数组,它存储失败的原因。可以把这个return语句放在这里(函数的末尾,而不 是条件语句内)因为如果登录失败,函数将只会执行到这里。如果登录成功,第(9)步中的return行将阻止函数继续执行(一旦函数执行return语 句,它就会停止执行)。
(11) 完成页面。
?>
(12) 将文件另存为login_functions.inc.php,并将其存放在Web目录中(该目录与header.html、footer.html和style.css这几个文件都在includes文件夹中)。
该页面也会使用.inc.php扩展名,指示它是一个可包含的文件并且它包含PHP代码。
ü提示
l 本章中的脚本不包含任何调试代码(如MySQL错误或查询)。如果你有关于这些脚本的任何问题,可以应用第7章中概括的调试技术。
l 可以在header()调用中把name=value对添加到URL中,以把值传递到目标页面:
$url .= '?name=' . urlencode(value);
11.3 使用cookie
cookie是服务器在用户的机器上存储信息的一种方式。利用这种方式,站点可以在访问期间记住或跟踪用户。可以把cookie看作像名字标签一样,你告诉服务器你的名字,它就会给你戴上一个标签。这样,它就可以回过头来查阅名字标签,从而知道你是谁。
有 些人对cookie心存疑虑,因为他们认为cookie允许服务器知道了他们太多的事情。不过,cookie只能用于存储提供给服务器的信息,因此,与大 多数其他在线技术相比,cookie的安全性并不差。不幸的是,许多人仍然误解了这种技术,由于这些误解可能会破坏Web应用程序的功能,所以这就成了一 个问题。
在本节中,你将学习如何设置一个cookie,从存储的cookie中检索信息,改变cookie的设置以及删除一个cookie。
测试cookie
要使用cookie有效地编程,需要能够准确地测试它们是否存在。执行该任务的最佳方式是:当接收到一个cookie时,让Web浏览器询问应该做什么。在这种情况下,每当PHP试图发送一个cookie时,浏览器都会用cookie信息提示你。
不同平台上不同浏览器的不同版本都在不同的位置定义了它们的cookie处理策略。我将快速介绍两个针对流行的Web浏览器的选项。
要 使用Windows XP上的Internet Explorer设置它,可选择“工具”→“Internet选项”。然后单击“隐私”选项卡,接着单击“设置”下面的“高级”按钮。单击选中“覆盖自动 cookie处理”复选框,然后为“第一方Cookie”和“第三方Cookie”选择“提示”。
如果在Windows上使用 Firefox,可选择“工具”→“选项”→“隐私”。在cookie区域中,在“保存到”下拉菜单中选择“每次询问我”。如果在Mac OS X上使用Firefox,其步骤是相同的,但是必须通过选择Firefox→“参数设置”来开始设置步骤。
不幸的是,Mac OS X上的Safari没有cookie提示选项,但是,它允许查看现有的cookie,这仍然是一种有用的调试工具。可以在“Safari参数设置”面板下面找到这个选项。
11.3.1 设置cookie
关 于cookie要理解的最重要的事情是,必须在任何其他信息之前把它们从服务器发送给客户。万一服务器试图在Web浏览器已经接收到HTML(甚至是无关 紧要的空白)之后发送cookie,就会导致一条出错消息,并且不会发送cookie(参见图11-5)。迄今为止,这是最常见的与cookie相关的错 误,但是很容易修复它。
图11-5 在创建cookie时,headers already sent…出错消息实在太常见。要注意出错消息说了些什么,以便找出并修复问题
通过setcookie()函数发送cookie:
setcookie(name, value);
setcookie ('name', 'Nicole');
第二行代码在把cookie发送到浏览器时,带有名字name和值Nicole(参见图11-6)。
图11-6 如果把浏览器设置成在接收cookie时请求许可,则当站点试图发送一个cookie时,将看到一条如本图所示的消息(这是Firefox版本的提示)
通过接着使用setcookie()函数,可以继续把更多的cookie发送给浏览器:
setcookie('ID', 263);
setcookie('email', 'email@example.com');
与在PHP中使用任何变量时一样,在给cookie命名时,不要使用空格或标点符号,但是,要特别注意使用正确的大小写字母。
发送cookie
(1) 在文本编辑器中创建一个新的PHP文档(参见脚本11-3)。
脚本11-3 login.php脚本会在成功登录时创建两个cookie
1 <?php # Script 11.3 - login.php
2
3 // This page processes the login form submission.
4 // Upon successful login, the user is redirected.
5 // Two included files are necessary.
6 // Send NOTHING to the Web browser prior to the setcookie() lines!
7
8 // Check if the form has been submitted:
9 if (isset($_POST['submitted'])) {
10
11 // For processing the login:
12 require_once ('includes/login_functions.inc.php');
13
14 // Need the database connection:
15 require_once ('../mysqli_connect.php');
16
17 // Check the login:
18 list ($check, $data) = check_login($dbc, $_POST['email'], $_POST['pass']);
19
20 if ($check) { // OK!
21
22 // Set the cookies:
23 setcookie ('user_id', $data['user_id']);
24 setcookie ('first_name', $data ['first_name']);
25
26 // Redirect:
27 $url = absolute_url ('loggedin.php');
28 header("Location: $url");
29 exit(); // Quit the script.
30
31 } else { // Unsuccessful!
32
33 // Assign $data to $errors for error reporting
34 // in the login_page.inc.php file.
35 $errors = $data;
36
37 }
38
39 mysqli_close($dbc); // Close the database connection.
40
41 } // End of the main submit conditional.
42
43 // Create the page:
44 include ('includes/login_page.inc.php');
45 ?>
对于这个示例,我将建立一个新的login.php脚本(它将与第8章中的脚本协同工作)。这个脚本还需要在本章开头创建的两个文件。
(2) 验证表单。
if (isset($_POST['submitted'])) {
require_once ('includes/login_functions.inc.php');
require_once ('../mysqli_connect.php');
list ($check, $data) = check_login($dbc, $_POST['email'], $_POST['pass']);
这个脚本将做两件事:处理表单提交和显示表单。这个条件语句检查提交。
在这个条件语句内,脚本必须包含login_functions.inc.php和mysqli_connect.php(它是在第8章中创建的,并且仍然应该位于相对于这个脚本的相同位置)。
在包含这两个文件后,可以调用check_login()函数,将数据库连接(它来自于mysqli_connect.php)以及电子邮件地址和密码(它们都来自于表单)传递给它。
该函数返回两个元素的数组:布尔值和另一个数组(其中包含用户数据或错误)。为了把这些返回的值赋予变量,可使用list()函数。该函数返回的第一个值(布尔值)将被赋予$check,返回的第二个值($row或$errors数组)将被赋予$data。
(3) 如果用户输入了正确的信息,则登录。
if ($check) {
setcookie ('user_id', $data['user_id']);
setcookie ('first_name', $data['first_name']);
$check变量指示登录尝试是否成功。如果它为true,那么$data将包含用户的ID和名字。可以在cookie中使用这两个值。
(4) 把用户重定向到另一个页面。
$url = absolute_url ('loggedin.php');
header("Location: $url");
exit();
使用本章中概括的步骤,首先动态生成重定向URL,并通过absolute_url()函数返回它。要重定向到的特定页面是loggedin.php。然后在header()函数中使用绝对URL,并用exit()终止脚本的执行。
(5) 完成$check条件语句(开始于第(3)步),然后关闭数据库连接。
} else {
$errors = $data;
}
mysqli_close($dbc);
如果$check的值为false,那么$data变量就会存储在check_login()函数内生成的错误。如果是这样,就应该把它们赋予$errors变量,因为这是脚本中显示登录页面(login_page.inc.php)的代码所期待的。
(6) 完成主提交条件语句,并包含登录页面。
}
include
('includes/login_page.inc.php');
?>
这个login.php脚本主要验证登录表单,这是通过调用check_login()函数完成的。login_page. inc.php文件包含登录页面本身,因此只需要包含它即可。
(7) 将文件另存为login.php,存放在Web目录中(在与第8章中的文件相同的目录中),并在Web浏览器中加载这个页面(参见图11-2)。
ü提示
l cookie被限制为总共包含大约4 KB的数据,每个Web浏览器可以记住来自任何一个站点的有限数量的cookie。对于目前的大多数Web浏览器,这个限制是50个cookie(但是, 如果发出50个不同的cookie,你可能想重新考虑如何执行该任务)。
l 因为不同的浏览器将以不同的方式处理cookie,PHP中有几个函数可以在不同的浏览器中生成不同的结果,setcookie()函数是其中之一。一定要在不同平台上的多个浏览器中测试你的Web站点,以确保一致性。
l 如果前两个包含文件向Web浏览器发送任何内容,或者甚至在关闭PHP标签后面具有空白行或空格,你将看到headers already sent错误。如果看到这种错误,可以查看错误中提到的文档和行号(在output started at之后),并修复问题。
11.3.2 访问cookie
要从cookie中检索一个值,只需要把合适的cookie名称用作键来引用$_COOKIE超全局数组(就像利用任何数组一样)。例如,为了从用如下一行代码建立的cookie中检索一个值:
setcookie('username', 'Trout');
将引用$_COOKIE['username']。
在下面的示例中,将以两种方式来访问login.php脚本设置的cookie。首先,将检查用户是否已登录(如果未登录,他们不应该访问这个页面)。接下来,将通过用户的名字来问候他们,他们的名字存储在一个cookie中。
访问cookie
(1) 在文本编辑器中创建一个新的PHP文档(参见脚本11-4)。
脚本11-4 loggedin.php脚本基于一个存储的cookie向用户打印一个问候
1 <?php # Script 11.4 - loggedin.php
2
3 // The user is redirected here from login.php.
4
5 // If no cookie is present, redirect the user:
6 if (!isset($_COOKIE['user_id'])) {
7
8 // Need the functions to create an absolute URL:
9 require_once ('includes/login_functions.inc.php');
10 $url = absolute_url();
11 header("Location: $url");
12 exit(); // Quit the script.
13
14 }
15
16 // Set the page title and include the HTML header:
17 $page_title = 'Logged In!';
18 include ('includes/header.html');
19
20 // Print a customized message:
21 echo "<h1>Logged In!</h1>
22 <p>You are now logged in, {$_COOKIE ['first_name']}!</p>
23 <p><a href=\"logout.php\">Logout</a></p>";
24
25 include ('includes/footer.html');
26 ?>
在用户成功登录后,将把他们重定向到这个页面。它将打印一个特定于用户的问候。
(2) 检查cookie是否存在。
if(!isset($_COOKIE['user_id'])){
因为不希望用户访问这个页面(除非那个用户已经登录),所以应该首先检查是否已经设置了cookie(在login.php中)。
(3) 如果用户未登录,就重定向他们。
require_once ('includes/login_functions.inc.php');
$url = absolute_url();
header("Location: $url");
exit();
}
如果用户没有登录,就会自动把他们重定向到这个主页面。这是一种限制访问内容的简单方式。
(4) 包含页面的标题。
$page_title = 'Logged In!';
include('includes/header.html');
(5) 使用cookie欢迎用户。
echo "<h1>Logged In!</h1>
<p>You are now logged in, {$_COOKIE ['first_name']}!</p>
<p><a href=\"logout.php\"> Logout</a></p>";
为了用名字问候用户,引用了$_COOKIE['first_name']变量(括在花括号内以避免解析错误)。还会打印一个指向注销页面(将在本章后面编写)的链接。
(6) 完成HTML页面。
include('includes/footer.html');
?>
(7) 将文件另存为loggedin.php,存放在Web目录中(在与login.php相同的文件夹中),并通过login.php登录在Web浏览器中测试它(参见图11-7)。
图11-7 如果使用正确的电子邮件地址和密码,则在登录后,会重定向到这里
因为这些示例使用与第8章中相同的数据库,你应该能够使用当时提交的注册用户名和密码来登录。
(8) 要查看设置的cookie(参见图11-8和图11-9),可以针对你的浏览器更改cookie设置并再次测试它。
|
图11-8 user_id cookie的值为1 |
图11-9 first_name cookie的值为Larry(你的可能有所不同) |
ü提示
l 直到重新加载了设置页面(例如,login.php)或者访问了另一个页面(换句话说,你不能设置和访问同一个页面中的cookie),才可以访问cookie。
l 如果用户拒绝一个cookie,或者把他们的Web浏览器设置成不接受它们,则会把用户自动重定向到这个示例中的主页,即使他们成功登录也是如此。由于这个原因,你可能想让用户知道何时需要cookie。
11.3.3 设置cookie参数
尽管只把名称和值这两个参数传递给setcookie()函数就足够了,还应该知道其他可用的参数。该函数还可以带有最多另外5个参数,其中每一个都会改变cookie的定义。
setcookie (name, value, expiration, path, host, secure, httponly);
到期时间 (expiration)参数用于设置cookie存在的精确限定的时间长度,用从新纪元(epoch)(新纪元是1970年1月1日0点)起所经过的秒 数表示。如果没有设置它或者把它的值设置为0,cookie将持续起作用,直到用户关闭了他或她的浏览器。这些cookie被认为在浏览器会话期间一直存 在(图11-8和图11-9也指示了这一点)。
为了设置特定的到期时间,可在当前时刻上增加特定的分钟数或小时数,可以使用time()函数获取当前时刻。下面一行代码将把cookie的到期时间设置为当前时间后30分钟(60秒乘以30分钟):
setcookie(name, value, time()+1800);
路 径(path)和主机(host)这两个参数用于把cookie限制到Web站点内(路径)特定的文件夹,或者限制到特定的主机 (www.example.com或192.168.0.1)。例如,你可以把cookie限制为,仅当用户位于域中的admin文件夹(以及admin 文件夹的子文件夹)内时才存在。
setcookie(name, value, expire, '/admin/');
将路径设置为/将使得cookie在整个域(Web站点)内可见。将域设置为.example.com将使得cookie在整个域和每个子域(www.example.com、admin.example.com、pages.example.com等)内都可见。
安全(secure)值规定只应该通过安全的HTTPS连接来发送cookie。1指示必须使用安全的连接,0则指示标准连接也不错。
setcookie (name, value, expire, path, host, 1);
如果站点使用安全连接,则限制通过安全的HTTPS连接发送cookie比不这样做要安全得多。
最后,PHP 5.2中添加了一个httponly参数。使用一个布尔值指示只能通过HTTP(和HTTPS)访问cookie。强制执行这种限制将使得cookie更安全(阻止一些黑客攻击),但是在编写本书时并非所有的浏览器都支持它。
setcookie (name, value, expire, path, host, secure, TRUE);
与带有参数的所有函数一样,必须按顺序传递setcookie()值。要跳过任何参数,可使用NULL、0或空字符串(不要使用FALSE)。到期时间和安全这两个值都是整数,因此不能用引号括住它们。
为了演示这个信息,我将添加一个到期时间设置到登录cookie中,使得它们只持续存在1个小时。
设置cookie的参数
(1) 在文本编辑器中打开login.php(参见脚本11-3)。
(2) 更改两个setcookie()行,使之包括一个持续60分钟的到期日期(参见脚本11-5)。
setcookie ('user_id', $data['user_id'], time()+3600, '/', '', 0, 0);
setcookie ('first_name', $data ['first_name'], time()+3600, '/', '', 0, 0);
脚本11-5 login.php脚本现在使用了setcookie()函数可以带的每个参数
1 <?php # Script 11.5 - login.php #2
2
3 if (isset($_POST['submitted'])) {
4
5 require_once ('includes/login_functions.inc.php');
6 require_once ('../mysqli_connect.php');
7 list ($check, $data) = check_login($dbc, $_POST['email'], $_POST['pass']);
8
9 if ($check) { // OK!
10
11 // Set the cookies:
12 setcookie ('user_id', $data['user_id'], time()+3600, '/', '', 0, 0);
13 setcookie ('first_name', $data['first_name'], time()+3600, '/', '', 0, 0);
14
15 // Redirect:
16 $url = absolute_url ('loggedin.php');
17 header("Location: $url");
18 exit();
19
20 } else { // Unsuccessful!
21 $errors = $data;
22 }
23
24 mysqli_close($dbc);
25
26 } // End of the main submit conditional.
27
28 include ('includes/login_page.inc.php');
29 ?>
通过把到期日期设置成time() + 3600(60分钟乘以60秒),在设置cookie后,它将持续存在1个小时。在执行这种改变时,显式指定其他cookie参数。
对于最后一个参数,它接受一个布尔值,也可以使用0表示false(PHP将为你处理这种转换)。这样做是一个好主意,因为在任何cookie参数中都使用false可能引发问题。
(3) 保存这个脚本,存放在Web目录中,并通过登录在Web浏览器中测试它(参见图11-10)。
图11-10 对setcookie()参数(比如到期日期和时间)所做的更改将会反映在发送给Web浏览器的cookie中(对比图11-9)
ü提示
l 有些浏览器难以处理没有列出每个参数的cookie。显式指出每个参数,甚至是一个空字符串,对于所有浏览器将得到更可靠的结果。
l 对于cookie的到期时间有一些一般性的指导原则:如果cookie应该持续存在与会话一样长的时间,就不要设置到期时间;如果cookie在用户关闭 并重新打开他或她的浏览器之后应该继续存在,就要把到期时间向前设置几周或几个月的时间;如果cookie可能构成安全风险,就要把到期时间设置成自此开 始1个小时或不到1个小时的时间,以使得在用户离开他或她的浏览器之后,cookie不会继续存在太长的时间。
l 出于安全性考虑,可以在cookie上设置一个5分钟或10分钟的到期时间,并与用户访问的每个新页面一起重发cookie(假定cookie存在)。这 样,cookie持续存在的时间就与用户活动的时间一样长,但它会在用户最后一个动作的5分钟或10分钟后自动死亡。
l 电子商务以及其他与隐私相关的Web应用程序都应该为所有事务(包括cookie)使用一条SSL(Secure Sockets Layer,安全套接字层)连接。
l 要小心对待由目录内的脚本创建的cookie。如果没有指定路径,那么该cookie将只能由同一个目录内的其他脚本使用。
11.3.4 删除cookie
关于使用cookie要理解的最后一件事是如何删除一个cookie。虽然在关闭用户的浏览器时或者在到达到期日期/时间时cookie会自动到期,但是,有时你希望手动删除cookie。例如,在具有登录能力的Web站点内,可能希望在用户注销时删除任何cookie。
尽 管setcookie()函数可以带最多7个参数,但是实际上只有一个参数(cookie名称)是必需的。如果发送一个包含名称但没有值的cookie, 其效果就相当于删除现有的同名cookie。例如,要创建first_namecookie,可以使用下面这一行代码:
setcookie('first_name', 'Tyler');
要删除first_namecookie,可以编写如下代码:
setcookie('first_name');
一种附加的预防措施是,还可以把到期日期设置成过去的某个日期。
setcookie('first_name', '', time()-3600);
为了演示所有这些操作,将给站点添加注销能力。指向注销页面的链接将出现在loggedin.php上。作为一种附加特性,将更改头文件,使得当用户登录时显示Logout链接,并且当用户注销时显示Login链接。
1.删除cookie
(1) 在文本编辑器或IDE中创建一个新的PHP文档(参见脚本11-6)。
脚本11-6 logout.php脚本会删除以前建立的cookie
1 <?php # Script 11.6 - logout.php
2
3 // This page lets the user logout.
4
5 // If no cookie is present, redirect the user:
6 if (!isset($_COOKIE['user_id'])) {
7
8 // Need the functions to create an absolute URL:
9 require_once ('includes/login_functions.inc.php');
10 $url = absolute_url();
11 header("Location: $url");
12 exit(); // Quit the script.
13
14 } else { // Delete the cookies.
15 setcookie ('user_id', '', time()-3600, '/', '', 0, 0);
16 setcookie ('first_name', '', time()-3600, '/', '', 0, 0);
17 }
18
19 // Set the page title and include the HTML header:
20 $page_title = 'Logged Out!';
21 include ('includes/header.html');
22
23 // Print a customized message:
24 echo "<h1>Logged Out!</h1>
25 <p>You are now logged out, {$_COOKIE['first_name']}!</p>";
26
27 include ('includes/footer.html');
28 ?>
(2) 检查user_id cookie是否存在;如果它不存在,就重定向用户。
if (!isset($_COOKIE['user_id'])) {
require_once ('includes/login_functions.inc.php');
$url = absolute_url();
header("Location: $url");
exit();
与loggedin.php页面一样,如果用户还没有登录,这个页面就应该把用户重定向到主页。尝试注销尚未登录的用户没有意义。
(3) 如果cookie存在,则删除它们。
} else {
setcookie ('first_name', '', time()-3600, '/', '', 0, 0);
setcookie ('user_id', '', time()-3600, '/', '', 0, 0);
}
如果用户已经登录,这两个cookie将有效地删除现有的cookie。除了值和到期时间外,其他参数应该具有与创建cookie时相同的值。
(4) 建立PHP页面的提醒。
$page_title = 'Logged Out!';
include ('includes/header.html');
echo "<h1>Logged Out!</h1>
<p>You are now logged out, {$_COOKIE['first_name']}!</p>";
include ('includes/footer.html');
?>
这个页面本身也与loggedin.php页面非常相似。尽管它看起来似乎有些奇怪,但是因为仍然可以引用first_name cookie(刚才在这个脚本中删除了它),考虑以下过程很有意义:
1) 这个页面将被客户请求。
2) 服务器从客户的浏览器读取合适的cookie。
3) 运行页面,并做它的事情(包括发送新的cookie,删除现有的cookie)。
因此,简而言之,在第一次运行这个脚本时,它就可以使用原来的first_name cookie数据。这个页面发送的cookie集(删除的cookie)不能为这个页面所用,因此原来的值仍然有用。
(5) 将文件另存为logout.php,并存放在Web目录中(在与login.php相同的文件夹中)。
2.创建注销链接
(1) 在文本编辑器或IDE中打开header.html(参见脚本8-1)。
(2) 将第五个和最后一个链接更改如下(参见脚本11-7):
<li><?php
if ( (isset($_COOKIE['user_id'])) && (!strpos($_SERVER['PHP_SELF'], 'logout.php')) ) {
echo '<a href="logout.php">Logout</a>';
} else {
echo '<a href="login.php">Login</a>';
}
?></li>
脚本11-7 根据用户的当前状态,header.html文件现在将显示登录或注销链接
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/ xhtml1-strict.dtd">
2 <html xmlns="http://www.w3.org/1999/xhtml">
3 <head">
4 <title><?php echo $page_title; ?></title>
5 <link rel="stylesheet" href="includes/style.css" type="text/css" media="screen" />
6 <meta http-equiv="content-type" content="text/html; charset=utf-8" />
7 </head>
8 <body>
9 <div id="header">
10 <h1>Your Website</h1>
11 <h2>catchy slogan...</h2>
12 </div>
13 <div id="navigation">
14 <ul>
15 <li><a href="index.php">Home Page</a></li>
16 <li><a href="register.php">Register</a></li>
17 <li><a href="view_users.php">View Users</a></li>
18 <li><a href="password.php">Change Password</a></li>
19 <li><?php // Create a login/logout link:
20 if ( (isset($_COOKIE['user_id'])) && (!strpos($_SERVER['PHP_SELF'], 'logout.php')) ) {
21 echo '<a href="logout.php">Logout</a>';
22 } else {
23 echo '<a href="login.php">Login</a>';
24 }
25 ?></li>
26 </ul>
27 </div>
28 <div id="content"><!-- Start of the page-specific content. -->
29 <!-- Script 11.7 - header.html -->
不是在导航区域中建立一个永久的登录链接,而是在用户登录时,让其显示一个Logout链接,或者如果用户没有登录,则显示一个Login链接。根据cookie存在与否来完成上面的条件语句。
由于logout.php脚本一般会显示一个注销链接(因为第一次查看页面时cookie存在),条件语句不得不检查当前页面是否是logout.php脚本。利用strpos()函数可以轻松完成这个任务,用于在一个字符串内发现另一个字符串。
(3) 保存文件,将其存放在Web目录中(放置在includes文件夹内),并在Web浏览器中测试登录/注销过程(参见图11-11、图11-12和图11-13)。
ü提示
l 要查看logout.php脚本中setcookie()调用的结果,可以在浏览器中打开cookie提示(图11-14)。
l 由于在Windows上的Internet Explorer处理cookie的方法中存在一个错误,可能需要把host参数设置成false(不带引号),以便在自己的计算机上开发应用程序时,可以让注销过程工作(即通过localhost)。
l 在删除一个cookie时,应该总是使用与设置cookie相同的参数。如果在创建cookie时设置了主机和路径,则在删除cookie中要再次使用它们。
l 为了讲清楚要点,记住,直到重新加载页面或者访问了另一个页面时,cookie的删除才会生效。换句话说,在那个页面删除了cookie之后,它仍然可供页面使用。
|
图11-11 带有Login链接的主页 |
图11-12 在用户登录后,页面现在具有一个Logout链接 |
|
图11-13 注销后的结果 |
图11-14 这就是在Firefox提示中显示删除cookie的方式 |
11.4 使用会话
使数据可供Web站点上的多个页面使用的另一种方法是使用会话 (session)。会话假定数据存储在服务器上,而不是在Web浏览器中,会话标识符用于定位特定用户的记录(会话数据)。这个会话标识符通常通过 cookie存储在用户的Web浏览器中,但是,敏感数据本身(如用户的ID、姓名等)总是保留在服务器上。
有人可能会问:本来 cookie工作得好好的,为什么还要使用会话?首先,会话更安全,这是由于所有记录的信息都存储在服务器上,并且不会持续不断地在服务器和客户之间来回 发送。其次,可以在会话中存储更多的数据。第三,有些用户拒绝cookie,或者完全关闭它们。虽然会话被设计成与cookie一起工作,但它也可以独立 起作用。
为了演示会话,并把它们与cookie作比较,我将重写以前的脚本集。
会话与cookie
本章具有一些示例,它们使用cookie和会话来完成相同的任务(登录和注销)。显然,它们都能很容易地在PHP中使用,但是,真正的问题是什么时候使用哪一个更合适。
与cookie相比,会话具有以下优点:
l 一般更安全(因为数据保存在服务器上);
l 允许存储更多数据;
l 使用会话时,可以不使用cookie。
与会话相比,cookie则具有以下优点:
l 更容易编程;
l 需要更少的服务器资源。
一般而言,要存储和检索一两份少量的信息,则可使用cookie。不过,对于大部分Web应用程序,都会使用会话。
11.4.1 设置会话变量
关于会话的最重要的规则是,将会使用它们的每个页面首先都必须调用session_start()函数。这个函数告诉PHP开启一个新的会话,或者访问一个现有的会话。必须在把任何内容发送到Web浏览器之前调用这个函数。
第 一次使用session_start()函数时,它会试图发送一个cookie,名称为PHPSESSID(会话名称),和一个类似于 a61f8670baa8e90a30c878df89a2074b(32个十六进制字母,它是会话ID)的值。由于试图发送一个cookie,所以在把 任何数据发送到Web浏览器之前,必须先调用session_start(),这与使用setcookie()和header()这两个函数时的情况一 样。
一旦启动了会话,就可以使用正常的数组语法把值注册到会话中:
$_SESSION['key'] = value;
$_SESSION['name'] = 'Roxanne';
$_SESSION['id'] = 48;
记住这一点后,让我们更新login.php脚本。
开启会话
(1) 在文本编辑器或IDE中打开login.php(参见脚本11-5)。
(2) 用以下几行代码替换setcookie()那几行代码(第12~14行)(参见脚本11-8):
session_start();
$_SESSION['user_id'] = $data['user_id'];
$_SESSION['first_name'] = $data ['first_name'];
脚本11-8 login.php脚本现在使用的是会话,而不是cookie
1 <?php # Script 11.8 - login.php #3
2
3 if (isset($_POST['submitted'])) {
4
5 require_once ('includes/login_functions.inc.php');
6 require_once ('../mysqli_connect.php');
7 list ($check, $data) = check_login($dbc, $_POST['email'], $_POST['pass']);
8
9 if ($check) { // OK!
10
11 // Set the session data:.
12 session_start();
13 $_SESSION['user_id'] = $data['user_id'];
14 $_SESSION['first_name'] = $data['first_name'];
15
16 // Redirect:
17 $url = absolute_url ('loggedin.php');
18 header("Location: $url");
19 exit();
20
21 } else { // Unsuccessful!
22 $errors = $data;
23 }
24
25 mysqli_close($dbc);
26
27 } // End of the main submit conditional.
28
29 include ('includes/login_page.inc.php');
30 ?>
第 一步是开启会话。因为在脚本中这一刻之前没有echo()语句、HTML文件包含,或者甚至是空白,所以现在可以安全地使用 session_start()(尽管也可以把它放在脚本的顶部)。然后,把两个键-值(key-value)对添加到$_SESSION超全局数组中, 以把用户的名字和用户ID注册到会话中。
(3) 将页面另存为login.php,存放在Web目录中,并在Web浏览器中测试它(参见图11-15)。
尽管需要重写loggedin.php以及头部和脚本,你仍然可以测试登录脚本,并查看得到的cookie(参见图11-16)。不过,loggedin.php页面应该把你重定向回主页,因为它仍会检查$_COOKIE变量存在与否。
|
图11-15 登录表单对最终用户保持不变,但是底层的功能现在使用的是会话 |
图11-16 由PHP的session_start()函数创建的这个cookie存储会话ID |
ü提示
l 由于会话通常会发送和读取cookie,应该总是设法在脚本中尽可能早地开启它们。这样做将有助于避免如下问题:试图在已经发送头部(HTML或空白)之后发送cookie。
l 如果你愿意,可以在php.ini文件中把session.auto_start设置为1,从而不必在每个页面上使用session_start()。这样做会加大服务器上的开销,由于这个原因,如果不考虑某些环境因素,就不应该使用它。
l 可以在会话中存储数组(使$_SESSION成为一个多维数组),就像可以存储字符串或数字一样。
11.4.2 访问会话变量
一旦启动了会话,并向其注册了变量,就可以创建将访问这些变量的其他脚本。为了执行该操作,每个脚本首先必须再次使用session_start()来启用会话。
这 个函数将允许当前脚本访问以前启动的会话(如果它可以读取cookie中存储的PHPSESSID值),或者创建一个新的会话(如果它不能读取这个值)。 要理解如果不能找到当前会话ID或者生成了新的会话ID,那么在旧会话ID下存储的所有数据都将不可用。我之所以现在在这里提到这一点,是因为如果你有关 于会话的问题,那么第一个调试步骤是检查会话ID值,看看它是否因页面而异。
假定访问当前会话没有问题,要引用一个会话变量,可使用$_SESSION['var'],就像你引用任何其他数组一样。
访问会话变量
(1) 在文本编辑器或IDE中打开loggedin.php(参见脚本11-4)。
(2) 添加对session_start()函数的调用(参见脚本11-9)。
session_start();
脚本11-9 我更新了loggedin.php脚本,使得它可以引用$_SESSION,而不是引用$_COOKIE(需要在两行上执行更改)
1 <?php # Script 11.9 - loggedin.php #2
2
3 // The user is redirected here from login.php.
4
5 session_start(); // Start the session.
6
7 // If no session value is present, redirect the user:
8 if (!isset($_SESSION['user_id'])) {
9 require_once ('includes/login_functions.inc.php');
10 $url = absolute_url();
11 header("Location: $url");
12 exit();
13 }
14
15 $page_title = 'Logged In!';
16 include ('includes/header.html');
17
18 // Print a customized message:
19 echo "<h1>Logged In!</h1>
20 <p>You are now logged in, {$_SESSION['first_name']}!</p>
21 <p><a href=\"logout.php\">Logout</a></p>";
22
23 include ('includes/footer.html');
24 ?>
设置或访问会话变量的每个PHP脚本都必须使用session_start()函数。必须在包含header.html文件之前以及在把任何内容发送到Web浏览器之前调用这一行代码。
(3) 用$_SESSION替换对$_COOKIE的引用(原文件第6行和第22行)。
if (!isset($_SESSION['user_id'])) {
和
echo "<h1>Logged In!</h1>
<p>You are now logged in, {$_SESSION['first_name']}!</p>
<p><a href=\"logout.php\">Logout</a></p>";
把一个脚本从cookie转换成会话只需要把使用的$_COOKIE更改成$_SESSION(假定使用相同的名称)。
(4) 将文件另存为loggedin.php,存放在Web目录中,并在浏览器中测试它(参见图11-17)。
(5) 在header.html中用$_SESSION替换对$_COOKIE的引用(从脚本11-7到脚本11-10)。
if ( (isset($_SESSION['user_id'])) && (!strpos($_SERVER['PHP_SELF'], 'logout.php')) ) {
图11-17 在登录后,将会把用户重定向到loggedin.php,它会使用存储的会话值通过名字来欢迎用户
脚本11-10 header.html文件现在也会引用$_SESSION,而不是引用$_COOKIE
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/ xhtml1-strict.dtd">
2 <html xmlns="http://www.w3.org/1999/xhtml">
3 <head">
4 <title><?php echo $page_title; ?></title>
5 <link rel="stylesheet" href="includes/style.css" type="text/css" media="screen" />
6 <meta http-equiv="content-type" content="text/html; charset=utf-8" />
7 </head>
8 <body>
9 <div id="header">
10 <h1>Your Website</h1>
11 <h2>catchy slogan...</h2>
12 </div>
13 <div id="navigation">
14 <ul>
15 <li><a href="index.php">Home Page</a></li>
16 <li><a href="register.php">Register</a></li>
17 <li><a href="view_users.php">View Users</a></li>
18 <li><a href="password.php">Change Password</a></li>
19 <li><?php // Create a login/logout link:
20 if ( (isset($_SESSION['user_id'])) && (!strpos($_SERVER['PHP_SELF'], 'logout.php')) ) {
21 echo '<a href="logout.php">Logout</a>';
22 } else {
23 echo '<a href="login.php">Login</a>';
24 }
25 ?></li>
26 </ul>
27 </div>
28 <div id="content"><!-- Start of the page-specific content. -->
29 <!-- Script 11.10 - header.html -->
为了让Login/Logout链接正确地工作(注意图11-17中不正确的链接),必须把头文件内对cookie变量的引用转变成会话。头文件不需要调用session_start()函数,因为它会被调用该函数的页面包括在内。
(6) 保存头文件,存放在Web目录中(在includes文件夹中),并在浏览器中测试它(参见图11-18)。
图11-18 利用为会话进行过更改的头文件,将会显示正确的Login/Logout链接(对比图11-17)
ü提示
l 为了让Login/Logout链接在其他页面(register.php、index.php等)上也能正确工作,将需要在其中的每一个页面上添加session_start()命令。
l 回忆一下我曾经说过的,如果你具有一个应用程序,它看起来似乎不能从一个页面到另一个页面访问会话数据,这可能是由于新的会话是在每个页面上创建的。为了 检查这一点,可以比较会话ID(值的最后几个字符就足够了)来查看它是否相同。你可以在发送会话cookie时通过查看会话cookie来查看会话的 ID,或者使用session_id()函数来执行该操作:
echo session_id();
l 一旦建立了会话变量,就可以使用它们。因此,与使用cookie时不同,可以把一个值赋予$_SESSION['var'],然后在同一个脚本的后面引用$_SESSION['var']。
垃圾收集
关于会话的垃圾收集是指删除会话文件(其中存储了实际的数据)的过程。创建一个销毁会话的注销系统是理想的,但是,并不能保证所有用户都按应该做的那样正式注销。所以PHP包含了一个清理进程。
无 论何时调用session_start()函数,都会引入PHP的垃圾收集,用于检查每个会话最近的修改日期(每当设置或获取变量时都会修改会话)。有两 个设置规定了垃圾收集,它们是:session.gc_maxlifetime和session.gc_ probability。第一个设置用于指定在一个会话持续多少秒不活动之后,可将其看作空闲会话,从而删除。第二个设置确定执行垃圾收集的概率,其取值 范围为1~100。因此,默认设置是,对session_start()的每个调用都有1%的机会调用垃圾收集。如果PHP没有启动清理进程,则会删除任 何超过1440秒未使用的会话。
你可以使用ini_set()函数改变这些设置,尽管要小心谨慎地执行该操作。频率过高或概率太大的垃圾收集可能使服务器陷入困境,并且会不经意地结束较慢用户的会话。
浙公网安备 33010602011771号