### PHP 4 完全中文手册

用户认证

在商业 Web 网站上，常常会需要用户的帐号及密码，也就是身份确认的步骤。早期的 NCSA httpd 服务器并没有提供这项用户确认的功能，Webmaster 只能用手工打造一个身份确认的 CGI 程序。

自 CERN httpd 之后的 Web 服务器大部份都提供了用户身份确认的功能。尽管每套 Web 服务器的配置都不太相同，但总起来说大同小异。

以下就是 Apache 服务器上的用户身份确认的配置。

<Directory /home/MyMember>

AuthType Basic

AuthName MyMember

AuthUserFile /usr/local/MyMember.txt

Options Includes ExecCGI

<Limit GET POST>

require valid-user

</Limit>

</Directory>

在这个例子中，当用户在看 MyMember 目录下所有的文件，包括图片文件及其它各式文件时，都需要用户的帐号密码确认。而用户的帐号及密码文件都存在于 /usr/local/MyMember.txt 之中。

这个帐号密码文件 /usr/local/MyMember.txt 的样子可能如下例。其中冒号前的字符串是用户帐号，冒号之后的字符串是经过不可还原加密的密码，编码一般都是使用传统的 DES 编码，密码的头两个字是类似种子的字符 (salt)，本例中都是 3P。每行代表一位用户。当然 Web master 要自行控制帐号重复的情形。比较特殊的是在 Win32 系统上架 Apache 的情形，冒号后的密码不可加密，因为 Win32 没有提供这方面的编码 API，因此用户密码以明码的方式存在。

john1234:3PWudBlJMiwro

queenwan:3PFNVLNPN9W0M

noname00:3PEsXaJx5pk7E

wilson49:3PjoWb0EnaG22

rootboot:3PIt0snI6.84E

sun_moon:3PvymMeNOc.x.

nobody38:3PbskPKwV94hw

在 Apache 1.3.6 版上，可以用 ~apache/bin/htpasswd 来产生单笔的帐号及密码，但对于需要大量资料的商业网站，可能需要自行编写程序进行处理。UNIX 上需要调用 crypt() 来处理编码。

在一切都配置好了之后，连接时就会在浏览器出现查核密码的窗口，如下图就是 SEEDNet 的 MySEED 网站的用户查核机制。在输入了帐号及密码后，浏览器会将它用 BASE64 编码后，传到服务器端。当然 BASE64 只是编码不是加密，因此在网络上这种传输的安全性仍然不高，还是有可能被中间的骇客截下，再将 BASE64 还原，这也是整个用户认证中最美中不足的地方，或许日后支持摘要认证 (Digest) 及使用 MD5 编码后，可以解决这种问题。之后每一页仍然需要帐号及密码，只不过浏览器会帮你主动送出，不用再输入帐号密码了。这方面浏览器会保留到被关闭为止，下次重新打开浏览器时仍需再输入一次。

在用户数量少时，使用上述的方法轻松又省事。但是在用户有数万人，甚至数十万人时，会发生整个服务器的效率都被搜寻帐号密码而拖垮，可能读取一页需要数十秒到数分钟。这种情形再使用服务器提供的密码查核机制就不太明智了。在 Netscape Enterprise Server 上可能可以使用 NSAPI 来开发自己的查核方式，在 IIS 上也可以用 ISAPI 过滤器开发。写 C/C++ 程序调用 NSAPI/ISAPI 总是很累，在 PHP 上有了另外的选择，这也是本节的主题。

PHP 的 HTTP 相关函数库提供了 header() 的函数。许多 Web 服务器与客户端的互动，都可以使用这个函数来变戏法。例如在某个 PHP 页面最开始处，也就是第一行或第二行，加入以下的程序，可以将用户重定向到作者的网页。

<?php header("Location: http://wilson.gs"); exit; ?>

当然，在上述程序之后的 HTML 文字或者是 PHP 程序都永远不会出现在用户端了。

同样的道理，我们就用 header() 来变用户认证的把戏。可以在 PHP 的最开头送出字符串到用户端，就会在用户端出现如上图所示的窗口。

<?php Header("WWW-Authenticate: Basic realm=\"Member\""); Header("HTTP/1.0 401 Unauthorized"); ?>

在程序中字符串 realm=\"Member\" 中的 Member 字样出现在图中，当然若使用中文字符取代，浏览器端也会出现中文字符，如上面的 MySEED 图。若 Web 网站用户还有其它语种的文字，如英文或日文，送出中文的 realm 字符串似乎就不太合适。无论如何，这都要视网站的性质及用户定位而定。

当然这还是很粗糙，因为除了送出窗口后，就没有下文了，帐号输入正确也好，输入错误也罢，都不会有任何的结果。我们需要更进一步的程序来处理。

在后端的使用认证上，考虑使用数据库作为储存帐号及密码的后端，在这种架构可以容纳许多的用户，管它一万个用户还是十万个用户。若您的站已有数十万个用户帐号，那么恭喜您，您的站算是世界级的大站了。MySQL 是个不错的选择，许多网站，甚至是商业化的网站都用它来做后端的数据库。当然您要架真正的商业网站，钱不是问题的话，那可以使用口碑最广的 Oracle 数据库系列。

要在 PHP 中使用任何数据库，都要先将数据库的服务器端及客户端配置好，之后才编译 PHP 及 Apache 系统。

准备好 MySQL 及 PHP 之后，先在 MySQL 中加入新的数据库，本例是加入 mymember，用别的名字当然也可以。MySQL 要加入数据库 (Database) 很容易，只要在 MySQL 存放 Database 的地方 mkdir 就可以了。例如在 UNIX Shell 下打

hahaha:/usr/local/mysql/data# mkdir mymember

在建立了数据库之后，尚需要建立数据表格 (Table) 方能使用。配置的表格如下，可以将它储在 /tmp/memberauth.sql 中

CREATE TABLE MemberAuth ( Serial mediumint(9) NOT NULL auto_increment, Username char(8) NOT NULL, Password char(8) NOT NULL, Enable char(1) DEFAULT '0' NOT NULL,

PRIMARY KEY (Serial)

);

文件 memberauth.sql

先看看 memberauth.sql 的这些字段。Serial 是个自动增加的整数字段，每输入一条记录，就会自动+1，这当然不能是空的字段，于是就用 NOT NULL 了。第二个字段是 Username，代表用户的帐号，为了统一以及适应各系统起见，配置成八个字，当然这个字段也不能是空的。Password 是第三个字段，为用户的密码。第四个字段 Enable 做为帐号是否有效的标志，设计上用 0 表示无用，1 表示可用，日后还可加入其它值做不同的用途。

设计好了数据表之后，要将数据表加入数据库中。由于常要使用 MySQL 数据库，可以到 http://www.phpwizard.net/phpMyAdmin 下载 phpMyAdmin，使用浏览器操作及管理 MySQL，轻松又方便。若使用这套 phpMyAdmin 可以在它的用户界面上输入 memberauth.sql 加入 MySQL 中。或者也可以在 UNIX Shell 下输入下式，也是有同样效果。

mysql mymember < /tmp/memberauth.sql

在准备好了之后，在 memberauth 数据表中输入用户帐号及密码。当然还是使用 phpMyAdmin 方便，用 mysql 程序就要一条条用 INSERT 操作了。

接着进入了设计函数的阶段。

<?php //--------------------------- // 用户认证函数 auth.inc // Author: Wilson Peng // Copyright (C) 1999
//---------------------------
$error401 = "/home/phpdocs/error/401.php";
if ($PHP_AUTH_PW=="") {
Header("WWW-Authenticate: Basic realm=\"超金卡会员\"");
Header("HTTP/1.0 401 Unauthorized");
include($error401);
exit;
} else {
$db_id = mysql_pconnect("localhost", "myid", "mypw");
$result = mysql_db_query("mymember","select password, enable from MemberAuth where username='$PHP_AUTH_USER'");
$row = mysql_fetch_array($result);
$MemberPasswd = $row[0];
$MemberEnable = $row[1];
if ($MemberEnable==0) {
echo "您的帐号被停用了";
exit;
}
if ($PHP_AUTH_PW!=$MemberPasswd) {
Header("WWW-Authenticate: Basic realm=\"超金卡会员\"");
Header("HTTP/1.0 401 Unauthorized");
include($error401);
exit;
}
}
?>

Copyright (C) 1999, Wilson Peng

要使用这个 auth.inc，要在每个 PHP 的第一行加入
<? require("auth.inc"); ?> 。在加入本程序的 PHP 文件都会检查帐号密码，图片等就不会检查，比起使用 Web 服务器功能的某目录下全都检查，PHP 显得有弹性多了。

$error401 = "/home/phpdocs/error/401.php";

这行表示在用户按下取消，或检查失败时，要显示给用户看的文件。

if ($PHP_AUTH_PW=="") {
Header("WWW-Authenticate: Basic realm=\"超金卡会员\"");
Header("HTTP/1.0 401 Unauthorized");
include($error401);
exit;
} else {

到 else 之前，若没有传入密码，则送出输入密码的窗口。其中的 $PHP_AUTH_USER、$PHP_AUTH_PW 是 PHP 中特殊的变量，分别代表用户确认的帐号及密码。上面的程序也是利用这两个变量来处理用户认证。

$db_id = mysql_pconnect("localhost", "myid", "mypw");
$result = mysql_db_query("mymember","select password, enable from MemberAuth where username='$PHP_AUTH_USER'");

$row = mysql_fetch_array($result);
$MemberPasswd = $row[0];
$MemberEnable = $row[1];

若用户有输入帐号及密码，则向数据库查询。同时查核该用户是否仍可使用。

if ($MemberEnable==0) {
echo "您的帐号被停用了";
exit;
}

上四行程序为帐号被停用的情形。

if ($PHP_AUTH_PW!=$MemberPasswd) {
Header("WWW-Authenticate: Basic realm=\"超金卡会员\"");
Header("HTTP/1.0 401 Unauthorized");
include($error401);
exit;
}

密码错误则再次向用户要求输入帐号及密码。

在实际使用时，可以视需要加入的网页再加入 auth.inc 这个文件，就不用连看张图形也要查一次密码，无端消耗服务器和用户两端的资源。当然，和 MySQL 的连系上，可以使用 mysql_pconnect() 一直和 MySQL 服务器连接。或是使用 mysql_connect() 每次重新连接，用这个函数要记得早点使用 mysql_close() 将数据库关闭。下面的程序 auth1.inc 是另一版本的认证程序，就是打开连接后马上关闭，释放资源的例子。

<?php //--------------------------- // 用户认证函数-1 auth1.inc // Author: Wilson Peng // Copyright (C) 1999
//---------------------------
$error401 = "/home/phpdocs/error/401.php";
if ($PHP_AUTH_PW=="") {
Header("WWW-Authenticate: Basic realm=\"超金卡会员\"");
Header("HTTP/1.0 401 Unauthorized");
include($error401);
exit;
} else {
$db_id = mysql_connect("localhost", "myid", "mypw");
$result = mysql_db_query("mymember","select password, enable from MemberAuth where username='$PHP_AUTH_USER'");
$row = mysql_fetch_array($result);
$MemberPasswd = $row[0];
$MemberEnable = $row[1];
mysql_close($db_id);
if ($MemberEnable==0) {
echo "您的帐号被停用了";
exit;
}
if ($PHP_AUTH_PW!=$MemberPasswd) {
Header("WWW-Authenticate: Basic realm=\"超金卡会员\"");
Header("HTTP/1.0 401 Unauthorized");
include($error401);
exit;
}
}
?>

Copyright (C) 1999, Wilson Peng

在实际应用时，可以在数据库中加入更多功能，如用户分组 (CUG) 的功能；或是加入时间字段，可做到期检查。其中的变化，就看设计者的巧思了。