星期二, 十月 02, 2007

翻译:PHP和Session终极指南 IV

原 文:PHP and Sessions, the ultimate guide!
原文作者:Deian Motov
发布日期:Sunday, 30 September 2007
译 者:石头希迁

表单(Form)和函数

这是另一个例子,稍微复杂了一点点:


<?php

if (!isset($_POST['email'])) {
// if form has not been submitted
// display form
// if cookie already exists, pre-fill form field with cookie value
?>
<html>
<head></head>
<body>

<form action="<?php echo $_SERVER['PHP_SELF']?>" method="post">
Enter your email address: <input type="text" name="email" value="<?php echo $_COOKIE['email']; ?>">
<input type="submit" name="submit">
<?php
// also calculate the time since the last submission
if ($_COOKIE['lastsave']) {
$days = round((time() - $_COOKIE['lastsave']) / 86400);
echo "$days day(s) since last submission";
}
?>
</form>

</body>
</html>
<?php
}
else {
// if form has been submitted
// set cookies with form value and timestamp
// both cookies expire after 30 days
if (!empty($_POST['email'])) {
setcookie("email", $_POST['email'], mktime()+(86400*30), "/");
setcookie("lastsave", time(), mktime()+(86400*30), "/");
echo "Your email address has been recorded.";
}
else {
echo "ERROR: Please enter your email address!";
}
}
?>
</body>
</html>
在这个例子中,输入的表单的值被保存到一个叫做email的cookie中,在接下来的访问中请求中,这个cookie可以自动填写表单,在需要用户输入用户名和密码的时候,这个技术被很多网站频繁使用。帮助用户在登录界面自动填写上次成功登录的用户名,这样也帮助用户少敲几次键盘。


这个例子也演示了如何对一个域使用多个cookie,这需要调用setcookie多次。在上面的例子中,登录的时间被保存为第二个cookie,用来计算两次登录间的时间间隔。

要从客户端删除一个cookie,可以以同样的语法调用setcookie() ,而将过期时间设定为一个已经过去的时间,这将导致客户端自动删除cookie。下面是例子:

<?php

// delete cookie
setcookie("lastsave", NULL, mktime() - 3600, "/");

?>
如果需要知道更多关于cookie和setcookie()函数,可以参阅链接
http://www.php.net/manual/en/features.cookies.php
http://www.php.net/manual/en/function.setcookie.php.

访问授权


如我在教程的最开始说的,cookie和sessions是两种使得数据“长久”存于客户端的方法。一个session保持一次会话的时的数据,而cookie可以保存任何你需要它保存的时间。记住这些,让我们继续看看如何同时使用它们两者。

这个应用是一个简单的用户授权系统。这个系统中,一些页面只能显示给成功登录的用户,任何没有提供有效密码的用户都无法看到那些“特殊”的页面。用户和用户密码被保存在一个MySQL数据库表中。PHP使用这个表来进行有效的用户认证,以确定用户是否有权访问。

假设用户的MySQL数据表看起如下:

+-------+---------------------------------------------------+
| name | pass |
+-------+---------------------------------------------------+
| sue | 9565d44fd0fe4db59f073eea1db70f3ea258e10b |
| harry | 6e74234b8b552685113b53c7bff0f386c8cef8cf |
| louis | 6817dda51b64b8190029490d2811a4d9cb9cd432 |
| sam | bd17f8243e771a57cfbb06aa9a82bbf09fd2d90b |
| james | 792ec9b44d432c947ac6775b2b52326e9d08512f |
+-------+---------------------------------------------------+
每一个用户有一个用户名和一个有SHA1()函数处理后的密码,下面是使用PHP来做这个艰巨工作的相关代码:
<?php

if (isset($_POST['name']) || isset($_POST['pass'])) {
// form submitted
// check for required values
if (empty($_POST['name'])) {
die ("ERROR: Please enter username!");
}
if (empty($_POST['pass'])) {
die ("ERROR: Please enter password!");
}

// set server access variables
$host = "localhost";
$user = "test";
$pass = "test";
$db = "db2";

// open connection
$connection = mysql_connect($host, $user, $pass) or die ("Unable to connect!");

// select database
mysql_select_db($db) or die ("Unable to select database!");

// create query
$query = "SELECT * FROM users WHERE name = '" . $_POST['name'] . "' AND pass = SHA1('" . $_POST['pass'] . "')";

// execute query
$result = mysql_query($query) or die ("Error in query: $query. " . mysql_error());

// see if any rows were returned
if (mysql_num_rows($result) == 1) {
// if a row was returned
// authentication was successful
// create session and set cookie with username
session_start();
$_SESSION['auth'] = 1;
setcookie("username", $_POST['name'], time()+(84600*30));
echo "Access granted!";
}
else {
// no result
// authentication failed
echo "ERROR: Incorrect username or password!";
}

// free result set memory
mysql_free_result($result);

// close connection
mysql_close($connection);
}
else {
// no submission
// display login form
?>
<html>
<head></head>
<body>
<center>
<form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>">
Username <input type="text" name="name" value="<?php echo $_COOKIE['username']; ?>">
<p />
Password <input type="password" name="pass">
<p />
<input type="submit" name="submit" value="Log In">
</center>
</body>
</html>
<?php
}

?>
这里,在登录框输入的值将会被集成进入MySQL SELECT查询语句,对用户表执行相关查询。如果用户名和密码都符合,会返回单一的一行数据,表示授权成功,如果不是,即没有获得任何数据,则授权失败。(译注:这个脚本存在被SQL注入攻击的可能性,请不要直接使用)

假设授权成功了,就会初始化一次会话,$_SESSION['auth'] 将会创建,并被赋值为BooleanTrue。用户名将会被保存到一个cookie中,以备下次之需。cookie会被保存30天,会被用来在下次登录时自动填写登录框的用户名。

当然,仅仅上面这些还不够。上面的脚本执行了用户的授权,初始化了session,设置了cookie,但是,在每次访问有限制的页面时也要进行安全检查。没有此安全检查,用户可以通过直接输入相关URL来绕过登录界面。

因为上面脚本中session变量$_SESSION['auth'] 只在用户认证完成了以后才有效,因此可以在每个限制页面脚本的前面先检查此变量是否存在。下面是代码:

<?php

// start session
session_start();
if (!$_SESSION['auth'] == 1) {
// check if authentication was performed
// else die with error
die ("ERROR: Unauthorized access!");
}
else {
?>
<html>
<head></head>
<body>
This is a secure page. You can only see this if $_SESSION['auth'] = 1
</body>
</html>
<?php
}

?>
这是一个安全页面,只在$_SESSION['auth'] = 1的情况下,才可以显示。

相当的简洁,对吧?只有授权用户才可以看到这个页面,因为只有他们的客户端才会拥有一个 $_SESSION['auth'] 变量有效的会话。其他用户只能看到一个error消息。

(--The End--)

星期一, 十月 01, 2007

翻译:PHP和Session终极指南 III

原 文:PHP and Sessions, the ultimate guide!
原文作者:Deian Motov
发布日期:Sunday, 30 September 2007
译 者:石头希迁

游戏规则
session使用in-memory cookie,这就解释了为什么只在创建这个cookie的浏览器没有关闭的时候这个cookie才是可得的。一旦这个客户端浏览器关闭了,浏览器占用的内存就会释放,交还给系统,也销毁了会话的cookie。如果想要cookie生存时期更长,可以使用PHP的内建cookie函数将cookie写入用户的磁盘文件,需要的时候再读出此数据。

在我们开始使用cookie前,有以下问题需要注意:
  1. 因为cookie是用来记录你在某个特点的域的活动的,因此只有创建cookie的域才能读它;
  2. 一个域最多只能设置20个cookie,而且每个cookie最大尺寸不能超过4 KB;
  3. 一个cookie通常处理6个属性,其中只有第一个是强制的,它们是:
  • name:cookie的名字
  • value:cookie的值
  • expires:cookie过期的日期和时间
  • path:域(damain)中能够访问cookie的顶极目录
  • domain:指定的域
  • secure:一个boolean标志,用来确定这个cookie是否只能够通过安全HTTP连接来传输
关于cookie的更多的信息可以从Netscape得到,他们是cookie最初的发明者。访问连接 http://wp.netscape.com/newsref/std/cookie_spec.html (译注:原文的网址链接不上)可以看到Netscape的cookie标准。

需要记住的是,因为cookie是存储在用户的硬盘里的,开发者仅仅永远微弱的控制能力。如果用户在其浏览器中关掉了cookie,则开发者设置的cookie是无法保存在客户端的。因此,每个开发者都要避免过分依赖cookie,设计一套在无法从用户端获得cookie数据时的后备方案。

有了上面这些警告和说明,让我们来看看一些简单用PHP处理cookie的例子吧。

又见老友


PHP提供了很唯一一个cookie操作函数:setcookie()。这个函数允许开发者读写cookie文件。下面的例子给出了演示。
<?php

if (!isset($_COOKIE['visited'])) {
// if a cookie does not exist
// set it
setcookie("visited", "1", mktime()+86400, "/") or die("Could not set cookie");
echo "This is your first visit here today.";
}
else {
// if a cookie already exists
echo "Nice to see you again, old friend!";
}

?>
想知道它是如何工作的,请求上面的网页多次。第一次,因为cookie还没有被设置,网页将会显示第一个消息。在接下来的访问中,因为cookie已经被设置,客户端将会被人出来,这样,网页将会第二条消息。注意,即使你关闭了你的浏览器,当你在打开浏览器再访问这个页面时,上面说道的那些还是会工作----这和上面提到的session的工作例子很不同。

setcookie()函数接收六个参数,cookie的名字、它的值、过期的日期、域、服务器端能个使设置cookie有效的顶极路径和指定安全状态的Boolean值。只有namevalue是必需的(译注:value可以为缺省的值)----虽然上面的例子中,指定了top-level目录,以及通过mktime()函数设置了过期日期(一天)。mktime()和上面提到的time()函数类似。

Cookie值是由客户端自动送到服务器的PHP脚本的,将会转化为key-value对,填充到$_COOKIE变量,$_COOKIE是一个superglobal数组,和$_SESSION类似。如上面的例子所示,其值可以通过通常的数组访问方法得到。注意,同在sessions中一样,setcookie()也必须在脚本有任何输出之前调用,否则,会出现如下的警告:
Warning: Cannot add header information - headers already sent by (output started at ... )

翻译:PHP和Session终极指南 II

原 文:PHP and Sessions, the ultimate guide!
原文作者:Deian Motov
发布日期:Sunday, 30 September 2007
译 者:石头希迁

记得我

这里是另外一个例子,这个例子要求你登录,然后你的用户名和会话开始时间都作为session变量存储。这些信息被用来显示session开始的总时间。

<?php

// initialize a session
session_start();
?>
<html>
<head></head>
<body>

<?php
if (!isset($_SESSION['name']) && !isset($_POST['name'])) {
// if no data, print the form
?>
<form action="<?php echo $_SERVER['PHP_SELF']?>" method="post">
<input type="text" name="name">
<input type="submit" name="submit" value="Enter your name">
</form>
<?php
}
else if (!isset($_SESSION['name']) && isset($_POST['name'])) {
// if a session does not exist but the form has been submitted
// check to see if the form has all required values
// create a new session
if (!empty($_POST['name'])) {
$_SESSION['name'] = $_POST['name'];
$_SESSION['start'] = time();
echo "Welcome, " . $_POST['name'] . ". A new session has been activated for you. Click <a href=" . $_SERVER['PHP_SELF'] . ">here</a> to refresh the page.";
}
else {
echo "ERROR: Please enter your name!";
}
}
else if (isset($_SESSION['name'])) {
// if a previous session exists
// calculate elapsed time since session start and now
echo "Welcome back, " . $_SESSION['name'] . ". This session was activated " . round((time() - $_SESSION['start']) / 60) . " minute(s) ago. Click <a href=" . $_SERVER['PHP_SELF'] . ">here</a> to refresh the page.";
}
?>
</body>
</html>

在这个例子中,session变量的出现和不出现决定了在三个可能出现的显示结果中到底显示哪一个。会话的开始时间通过time()函数被记录在$_SESSION['start']中,time()函数返回从1970年1月1号0时开始到当前时间的所有秒数。在最后阶段,存储在$_SESSION['start'] 中值和最近的time()返回值比较,来获得近似的花掉的时间。

需要重点强调一下,session_start()必须在脚本产生输出之前调用(如果您没有使用PHP的输出缓冲特性,可以阅读http://www.php.net/manual/en/ref.outcontrol.php)。这是因为PHP session内部使用in-memory cookies来保存session数据,而且cookie创建的头部必须在所有输出之前传输给客户端的浏览器。如果你在反问使用了session的页面看到如下的错误提示:
Warning: Cannot send session cache limiter - headers already sent (output started at ...)
这通常是因为在什么地方,以某种方式,一些输出在session_start()之前就传输到浏览器了。即使是在PHP中围绕session_start()的标签之外一个回车、一个空格字符都会产生这样的错误,因此要小心。

如前面提到的,每一次会话都有一个唯一的session ID,PHP用它来跟踪不同的客户端。这个session ID 是一长串字符,这串字符串自动的被PHP在一个页面到另外一个页面之间传递,以维护连续的会话。想看看它的样子,可以使用session_id()函数,如下面的例子:

<?php

// initialize a session
session_start();

// print session ID
echo "I'm tracking you with session ID " . session_id();

?>

当用户关闭了客户端的浏览器将会销毁了session,$_SESSION 数组中所有的变量将被刷新。也可以显式的销毁一个session----比如,当用户登出----通过调用session_destroy()函数。如下面的例子:


<?php

// initialize a session
session_start();

// then destroy it
session_destroy();

?>

你是否怀疑是不是弄错了----对,当你要调用session_destroy()来销毁一个session,你必须首先调用session_start()重新创建它。

记住$_SESSIONsuperglobal,这样你可以在一个函数里面和函数外面使用它,并不需要先声明其是global变量。如下面的例子:

<?php

// initialize a session
session_start();

// this function checks the value of a session variable
// and returns true or false
function isAdmin() {
if ($_SESSION['name'] == 'admin') {
return true;
}
else {
return false;
}
}

// set a value for $_SESSION['name']
$_SESSION['name'] = "guessme";
// call a function which uses a session variable
// returns false here
echo isAdmin()."
";

// set a new value for $_SESSION['name']
$_SESSION['name'] = "admin";
// call a function which uses a session variable
// returns true here
echo isAdmin()."
";

?>

你可以通过这个链接
http://www.php.net/manual/en/ref.session.php
了解更多关于session和处理session的函数。

(To be continued...)

翻译:PHP和Session终极指南 I

原 文:PHP and Sessions, the ultimate guide!
原文作者:Deian Motov
发布日期:
Sunday, 30 September 2007
译 者:石头希迁

一点耐心
现在,你已经用PHP连接了MySQL和SQLite,你可能觉得,你已经知道了使用PHP编程需要知道的一切了。或者,你甚至在考虑,用不着再访问Zend.com,也可以放弃这里的系列文章,去看一些更加豪华和更酷的...

Uh-uh,大错特错。

你要明白,虽然内建的数据库支持使得PHP编程更加容易,但这决不是PHP如此流行的唯一原因。方便的XML API和异常处理机制(PHP 5)、模块插件、内建的session管理等等也只是众多使得PHP流行的部分功能而已。PHP的这些内容将会在这一系列里面深入的讨论,希望你能自己找到它们,并且能个逗留稍微长点儿的时间。好了,现在,闭上眼睛,深呼吸,然后,继续你的阅读和发现----这个教程的主题是Sessions和cookies。

演出开始了
也许你曾听说:“HTTP是一个无状态的协议,Internet是一个无状态的开发环境”。

不?Hmmm。或者,并不全对。

用简单的话说,这句话的意思是,HTTP,即超文本传输协议,这个作为Web基石的协议并不能保留每个连接到Web服务器的客户端的标识,这样,对待每一个对Web页面的请求都被视为唯一和独立的连接,无论怎样,都和这个连接之前的连接没有任何关系。这种无状态的环境在你漫无目的的上网冲浪工作得非常好,可是,对于依赖于之前访问和数据的网站,它又产生一些很让人头疼的问题。最典型的例子是在线购物车----在无状态的环境,当访问者从一个页面跳到另一个页面时,系统保留你的访问踪迹,和采购单是很困难的。

很明显, 需要一个方法来维护状态,允许跟踪和维护一些客户端的连接数据。这样,cookies就出现了,cookies允许网站保存一些用户相关的信息在用户的系统中,并可以在任何需要的时候访问。一个cookie是一个简单的文件,其中保存了一些变量-值对,以及其链接到的域。当一个用户访问一个特定的域时,cookie中的值就会被读出并导入到服务器的环境里,这样,开发者就可以读,修改和使用这些值来实现他们的目的。将用户一次访问的数据带入到下一次访问,cookie是一个很方便的方法。

令一个常用的实现是使用session来保存连接相关的数据,这种session数据在用户访问中被保留在服务器端,并在最后销毁。每个session被赋予一个ID(每个session拥有一个唯一的ID),
在PHP中,
这个ID是PHP自动产生的。此session ID保存在两个位置:在客户端,使用一个临时的cookie,而在服务器端,使用平面文件或者数据库。session ID为每一次每一次接受到的请求赋予了一个名字,开发者可以标识出哪一个客户端开始了哪一次请求,可以在session变量中跟踪和维护用户相关的数据(变量-值对在会话期间都是可访问的,可以保存数值的和文字的信息)。

Sessions和cookies以这种方式绕过了HTTP协议的无状态约定,而且,被广泛使用在大量的最新的网站上用以维护个人和商业事务的信息。典型的,人们通常使用session来保存单次访问过程中的一些信息,而使用cookie来保存多次访问的数据。

PHP从PHP3.0开始支持cookies,内建的session管理从PHP4.o开始。所以这些特性都是缺省开启的,因此不必专门去激活这些特性。下面,看看你的第一个session。

第一个Session
用于演示session如何工作的第一个例子是点击计数程序。这是一个简单的计数器,但用户第一次访问的时候,这个计数器初始化,在用户重新连接这个网页的时候会增加。计数变量存储在一个session里面,这意味着,如果你访问之后访问了其他的站点,然后又回来,这个计数器的值会恢复(只要你没有通过关闭浏览器销毁这个session)。

看看代码:

<?php

// initialize a session
session_start();

// increment a session counter
$_SESSION['counter']++;

// print value
echo "You have viewed this page " . $_SESSION['counter'] . " times";

?>
想看看它是如何工作,通过浏览器,请求上面那段脚本,你会注意到,伴随着每次请求,计数器增加了1。如果你打开两个浏览器,每一个都请求这个脚本,PHP会为每个浏览器维护独立的计数器,它们各自增长。Session ID是用来标识不同的客户端的请求的,并为每个独立的会话创建保存好的之前访问的环境。这也意味着,在同一个会话中,用户反问了其他的网站,然后再头来再访问上面的脚本,只要你没有关闭你的浏览器,便可以恢复之前的session并为你重新创建。

PHP中每一个session通过
session_start()开始。这个函数检查session是否已经存在,要么恢复它(如果它存在的话),要么创建一个新的(如果它不存在的话)。session变量可以通过对$_SESSION superglobal数组添加key-value对来注册。可以在会话期的任何时候通过标准的数值标识来访问。上面的例子中,counter被加入$_SESSION数组,第一次被创建的时候为0,同一个会话中,每次对这个脚本的再请求都会重新获得counter,并增加1。

如果上面的例子并没有如我说的工作,请检查php.ini中session.save_path变量是否指向你系统的临时目录。这个变量缺省被硬编码为/tmp如果你使用windows系统,你需要编辑它为C:\Windows\temp(你系统的临时目录)。

(To be continued...)