第 1 章 用 PHP 进行 HTTP 认证

PHP 的 HTTP 认证机制仅在 PHP 以 Apache 模块方式运行时才有效,因此该功能不适用于 CGI 版本。在 Apache 模块的 PHP 脚本中,可以用 header() 函数来向客户端浏览器发送“Authentication Required”信息,使其弹出一个用户名/密码输入窗口。当用户输入用户名和密码后,包含有 URL 的 PHP 脚本将会加上预定义变量 PHP_AUTH_USERPHP_AUTH_PWAUTH_TYPE 被再次调用,这三个变量分别被设定为用户名,密码和认证类型。预定义变量保存在 $_SERVER 或者 $HTTP_SERVER_VARS 数组中。支持“Basic”和“Digest”(自 PHP 5.1.0 起)认证方法。请参阅 header() 函数以获取更多信息。

PHP 版本问题:

Autoglobals 全局变量,包括 $_SERVER等,自 PHP » 4.1.0 起有效,$HTTP_SERVER_VARS 从 PHP 3 开始有效。

以下是在页面上强迫客户端认证的脚本范例:

例 1.1. Basic HTTP 认证范例

<?php
 
if (!isset($_SERVER['PHP_AUTH_USER'])) {
   
header('WWW-Authenticate: Basic realm="My Realm"');
   
header('HTTP/1.0 401 Unauthorized');
   echo
'Text to send if user hits Cancel button';
   exit;
 } else {
   echo
"<p>Hello {$_SERVER['PHP_AUTH_USER']}.</p>";
   echo
"<p>You entered {$_SERVER['PHP_AUTH_PW']} as your password.</p>";
 }
?>

例 1.2. Digest HTTP 认证范例

本例演示怎样实现一个简单的 Digest HTTP 认证脚本。更多信息请参考 » RFC 2617

<?php
$realm
= 'Restricted area';

//user => password
$users = array('admin' => 'mypass', 'guest' => 'guest');


if (empty(
$_SERVER['PHP_AUTH_DIGEST'])) {
   
header('HTTP/1.1 401 Unauthorized');
   
header('WWW-Authenticate: Digest realm="'.$realm.
         
'" qop="auth" nonce="'.uniqid().'" opaque="'.md5($realm).'"');

   die(
'Text to send if user hits Cancel button');
}


// analyze the PHP_AUTH_DIGEST variable
if (!($data = http_digest_parse($_SERVER['PHP_AUTH_DIGEST'])) ||
   !isset(
$users[$data['username']]))
   die(
'Wrong Credentials!');


// generate the valid response
$A1 = md5($data['username'] . ':' . $realm . ':' . $users[$data['username']]);
$A2 = md5($_SERVER['REQUEST_METHOD'].':'.$data['uri']);
$valid_response = md5($A1.':'.$data['nonce'].':'.$data['nc'].':'.$data['cnonce'].':'.$data['qop'].':'.$A2);

if (
$data['response'] != $valid_response)
   die(
'Wrong Credentials!');

// ok, valid username & password
echo 'Your are logged in as: ' . $data['username'];


// function to parse the http auth header
function http_digest_parse($txt)
{
   
// protect against missing data
   
$needed_parts = array('nonce'=>1, 'nc'=>1, 'cnonce'=>1, 'qop'=>1, 'username'=>1, 'uri'=>1, 'response'=>1);
   
$data = array();

   
preg_match_all('@(\w+)=([\'"]?)([a-zA-Z0-9=./\_-]+)\2@', $txt, $matches, PREG_SET_ORDER);

   foreach (
$matches as $m) {
       
$data[$m[1]] = $m[3];
       unset(
$needed_parts[$m[1]]);
   }

   return
$needed_parts ? false : $data;
}
?>
  </programlisting>
 </example>
</para>

<note>
 <title>&#20860;&#23481;&#24615;&#38382;&#39064;</title>
 <para>
  &#22312;&#32534;&#20889; HTTP
  &#26631;&#22836;&#20195;&#30721;&#26102;&#35831;&#26684;&#22806;&#23567;&#24515;&#12290;&#20026;&#20102;&#23545;&#25152;&#26377;&#30340;&#23458;&#25143;&#31471;&#20445;&#35777;&#20860;&#23481;&#24615;&#65292;&#20851;&#38190;&#23383;&#8220;Basic&#8221;&#30340;&#31532;&#19968;&#20010;&#23383;&#27597;&#24517;&#39035;&#22823;&#20889;&#20026;&#8220;B&#8221;&#65292;&#20998;&#30028;&#23383;&#31526;&#20018;&#24517;&#39035;&#29992;&#21452;&#24341;&#21495;&#65288;&#19981;&#26159;&#21333;&#24341;&#21495;&#65289;&#24341;&#29992;&#65307;&#24182;&#19988;&#22312;&#26631;&#22836;&#34892;
  <emphasis>HTTP/1.0 401</emphasis> &#20013;&#65292;&#22312; <emphasis>401</emphasis> &#21069;&#24517;&#39035;&#26377;&#19988;&#20165;&#26377;&#19968;&#20010;&#31354;&#26684;&#12290;
 </para>
</note>

<para>
 &#22312;&#20197;&#19978;&#20363;&#23376;&#20013;&#65292;&#20165;&#20165;&#21482;&#25171;&#21360;&#20986;&#20102; <varname>PHP_AUTH_USER</varname> &#21644;
 <varname>PHP_AUTH_PW</varname>
 &#30340;&#20540;&#65292;&#20294;&#22312;&#23454;&#38469;&#36816;&#29992;&#20013;&#65292;&#21487;&#33021;&#38656;&#35201;&#23545;&#29992;&#25143;&#21517;&#21644;&#23494;&#30721;&#30340;&#21512;&#27861;&#24615;&#36827;&#34892;&#26816;&#26597;&#12290;&#25110;&#35768;&#36827;&#34892;&#25968;&#25454;&#24211;&#30340;&#26597;&#35810;&#65292;&#25110;&#35768;&#20174; dbm &#25991;&#20214;&#20013;&#26816;&#32034;&#12290;
</para>

<para>
 &#27880;&#24847;&#26377;&#20123; Internet Explorer
 &#27983;&#35272;&#22120;&#26412;&#36523;&#26377;&#38382;&#39064;&#12290;&#23427;&#23545;&#26631;&#22836;&#30340;&#39034;&#24207;&#26174;&#24471;&#20284;&#20046;&#26377;&#28857;&#21561;&#27611;&#27714;&#30133;&#12290;&#30446;&#21069;&#30475;&#26469;&#22312;&#21457;&#36865;
 <literal>HTTP/1.0 401</literal> &#20043;&#21069;&#20808;&#21457;&#36865;
 <emphasis>WWW-Authenticate</emphasis> &#26631;&#22836;&#20284;&#20046;&#21487;&#20197;&#35299;&#20915;&#27492;&#38382;&#39064;&#12290;
</para>

<simpara>
 &#33258; PHP 4.3.0
 &#36215;&#65292;&#20026;&#20102;&#38450;&#27490;&#26377;&#20154;&#36890;&#36807;&#32534;&#20889;&#33050;&#26412;&#26469;&#20174;&#29992;&#20256;&#32479;&#22806;&#37096;&#26426;&#21046;&#35748;&#35777;&#30340;&#39029;&#38754;&#19978;&#33719;&#21462;&#23494;&#30721;&#65292;&#24403;&#22806;&#37096;&#35748;&#35777;&#23545;&#29305;&#23450;&#39029;&#38754;&#26377;&#25928;&#65292;&#24182;&#19988;&safemode;&#34987;&#24320;&#21551;&#26102;&#65292;PHP_AUTH
 &#21464;&#37327;&#23558;&#19981;&#20250;&#34987;&#35774;&#32622;&#12290;&#20294;&#26080;&#35770;&#22914;&#20309;&#65292;<varname>REMOTE_USER</varname>
 &#21487;&#20197;&#34987;&#29992;&#26469;&#36776;&#35748;&#22806;&#37096;&#35748;&#35777;&#30340;&#29992;&#25143;&#65292;&#22240;&#27492;&#21487;&#20197;&#29992;
 <varname>$_SERVER['REMOTE_USER']</varname> &#21464;&#37327;&#12290;
</simpara>

<note>
 <title>&#37197;&#32622;&#35828;&#26126;</title>
 <para>
  PHP &#29992;&#26159;&#21542;&#26377; <literal>AuthType</literal> &#25351;&#20196;&#26469;&#21028;&#26029;&#22806;&#37096;&#35748;&#35777;&#26426;&#21046;&#26159;&#21542;&#26377;&#25928;&#12290;
 </para>
</note>

<simpara>
 &#27880;&#24847;&#65292;&#36825;&#20173;&#28982;&#19981;&#33021;&#38450;&#27490;&#26377;&#20154;&#36890;&#36807;&#26410;&#35748;&#35777;&#30340; URL &#26469;&#20174;&#21516;&#19968;&#26381;&#21153;&#22120;&#19978;&#35748;&#35777;&#30340; URL &#19978;&#20599;&#21462;&#23494;&#30721;&#12290;
</simpara>
<simpara>
 Netscape Navigator &#21644; Internet Explorer &#27983;&#35272;&#22120;&#37117;&#20250;&#22312;&#25910;&#21040; 401
 &#30340;&#26381;&#21153;&#31471;&#36820;&#22238;&#20449;&#24687;&#26102;&#28165;&#31354;&#25152;&#26377;&#30340;&#26412;&#22320;&#27983;&#35272;&#22120;&#25972;&#20010;&#22495;&#30340; Windows
 &#35748;&#35777;&#32531;&#23384;&#12290;&#36825;&#33021;&#22815;&#26377;&#25928;&#30340;&#27880;&#38144;&#19968;&#20010;&#29992;&#25143;&#65292;&#24182;&#36843;&#20351;&#20182;&#20204;&#37325;&#26032;&#36755;&#20837;&#20182;&#20204;&#30340;&#29992;&#25143;&#21517;&#21644;&#23494;&#30721;&#12290;&#26377;&#20123;&#20154;&#29992;&#36825;&#31181;&#26041;&#27861;&#26469;&#20351;&#30331;&#24405;&#29366;&#24577;&#8220;&#36807;&#26399;&#8221;&#65292;&#25110;&#32773;&#20316;&#20026;&#8220;&#27880;&#38144;&#8221;&#25353;&#38062;&#30340;&#21709;&#24212;&#34892;&#20026;&#12290;
</simpara>
<para>
 <example>
   <title>&#24378;&#36843;&#37325;&#26032;&#36755;&#20837;&#29992;&#25143;&#21517;&#21644;&#23494;&#30721;&#30340; HTTP &#35748;&#35777;&#30340;&#33539;&#20363;</title>
   <programlisting role="php">
<![CDATA[
<?php
 
function authenticate() {
   
header('WWW-Authenticate: Basic realm="Test Authentication System"');
   
header('HTTP/1.0 401 Unauthorized');
   echo
"You must enter a valid login ID and password to access this resource\n";
   exit;
 }

 if (!isset(
$_SERVER['PHP_AUTH_USER']) ||
     (
$_POST['SeenBefore'] == 1 && $_POST['OldAuth'] == $_SERVER['PHP_AUTH_USER'])) {
 
authenticate();
 }
 else {
  echo
"<p>Welcome: {$_SERVER['PHP_AUTH_USER']}<br />";
  echo
"Old: {$_REQUEST['OldAuth']}";
  echo
"<form action='{$_SERVER['PHP_SELF']}' METHOD='post'>\n";
  echo
"<input type='hidden' name='SeenBefore' value='1' />\n";
  echo
"<input type='hidden' name='OldAuth' value='{$_SERVER['PHP_AUTH_USER']}' />\n";
  echo
"<input type='submit' value='Re Authenticate' />\n";
  echo
"</form></p>\n";
 }

该行为对于 HTTP 的 Basic 认证标准来说并不是必须的,因此不能依靠这种方法。对 Lynx 浏览器的测试表明 Lynx 在收到 401 的服务端返回信息时不会清空认证文件,因此只要对认证文件的检查要求没有变化,只要用户点击“后退”按钮,再点击“前进”按钮,其原有资源仍然能够被访问。不过,用户可以通过按“_”键来清空他们的认证信息。

同时请注意,在 PHP 4.3.3 之前,由于微软 IIS 的限制,HTTP 认证无法工作在 IIS 服务器的 CGI 模式下。为了能够使其在 PHP 4.3.3 以上版本能够工作,需要编辑 IIS 的设置“目录安全”。点击“编辑”并且只选择“匿名访问”,其它所有的复选框都应该留空。

另一个限制是在 IIS 的 ISAPI 模式下使用 PHP 4 的时候,无法使用 PHP_AUTH_* 变量,而只能使用 HTTP_AUTHORIZATION。例如,考虑如下代码:list($user, $pw) = explode(':', base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6)));

IIS 注意事项:

要 HTTP 认证能够在 IIS 下工作,PHP 配置选项 cgi.rfc2616_headers 必须设置成 0(默认值)。

注意:

如果安全模式被激活,脚本的 UID 会被加到 WWW-Authenticate 标头的 realm 部分。