<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <HTML ><HEAD ><TITLE >用 PHP 进行 HTTP 认证</TITLE ><META NAME="GENERATOR" CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK REL="HOME" TITLE="PHP 手册" HREF="index.html"><LINK REL="UP" TITLE="特点" HREF="features.html"><LINK REL="PREVIOUS" TITLE="特点" HREF="features.html"><LINK REL="NEXT" TITLE="Cookie" HREF="features.cookies.html"><META HTTP-EQUIV="Content-type" CONTENT="text/html; charset=UTF-8"></HEAD ><BODY CLASS="chapter" BGCOLOR="#FFFFFF" TEXT="#000000" LINK="#0000FF" VLINK="#840084" ALINK="#0000FF" ><DIV CLASS="NAVHEADER" ><TABLE SUMMARY="Header navigation table" WIDTH="100%" BORDER="0" CELLPADDING="0" CELLSPACING="0" ><TR ><TH COLSPAN="3" ALIGN="center" >PHP 手册</TH ></TR ><TR ><TD WIDTH="10%" ALIGN="left" VALIGN="bottom" ><A HREF="features.html" ACCESSKEY="P" >上一页</A ></TD ><TD WIDTH="80%" ALIGN="center" VALIGN="bottom" ></TD ><TD WIDTH="10%" ALIGN="right" VALIGN="bottom" ><A HREF="features.cookies.html" ACCESSKEY="N" >下一页</A ></TD ></TR ></TABLE ><HR ALIGN="LEFT" WIDTH="100%"></DIV ><DIV CLASS="chapter" ><H1 ><A NAME="features.http-auth" >章 34. 用 PHP 进行 HTTP 认证</A ></H1 ><P > PHP 的 <ACRONYM CLASS="acronym" >HTTP</ACRONYM > 认证机制仅在 PHP 以 Apache 模块方式运行时才有效,因此该功能不适用于 CGI 版本。在 Apache 模块的 PHP 脚本中,可以用 <A HREF="function.header.html" ><B CLASS="function" >header()</B ></A > 函数来向客户端浏览器发送“Authentication Required”信息,使其弹出一个用户名/密码输入窗口。当用户输入用户名和密码后,包含有 URL 的 PHP 脚本将会加上<A HREF="reserved.variables.html" >预定义变量</A > <CODE CLASS="varname" >PHP_AUTH_USER</CODE >,<CODE CLASS="varname" >PHP_AUTH_PW</CODE > 和 <CODE CLASS="varname" >AUTH_TYPE</CODE > 被再次调用,这三个变量分别被设定为用户名,密码和认证类型。预定义变量保存在 <A HREF="reserved.variables.html#reserved.variables.server" >$_SERVER</A > 或者 <CODE CLASS="varname" >$HTTP_SERVER_VARS</CODE > 数组中。支持“Basic”和“Digest”(自 PHP 5.1.0 起)认证方法。请参阅 <A HREF="function.header.html" ><B CLASS="function" >header()</B ></A > 函数以获取更多信息。 </P ><DIV CLASS="note" ><BLOCKQUOTE CLASS="note" ><P ><B >PHP 版本问题: </B > <A HREF="language.variables.predefined.html#language.variables.superglobals" >Autoglobals</A > 全局变量,包括 <A HREF="reserved.variables.html#reserved.variables.server" >$_SERVER</A >等,自 PHP <A HREF="http://www.php.net/releases/4_1_0.php" TARGET="_top" >4.1.0</A > 起有效,<CODE CLASS="varname" >$HTTP_SERVER_VARS</CODE > 从 PHP 3 开始有效。 </P ></BLOCKQUOTE ></DIV ><P > 以下是在页面上强迫客户端认证的脚本范例: </P ><P > <TABLE WIDTH="100%" BORDER="0" CELLPADDING="0" CELLSPACING="0" CLASS="EXAMPLE" ><TR ><TD ><DIV CLASS="example" ><A NAME="AEN6932" ></A ><P ><B >例 34-1. Basic HTTP 认证范例</B ></P ><TABLE BORDER="0" BGCOLOR="#E0E0E0" CELLPADDING="5" ><TR ><TD ><code><font color="#000000"> <font color="#0000BB"><?php<br /> </font><font color="#007700">if (!isset(</font><font color="#0000BB">$_SERVER</font><font color="#007700">[</font><font color="#DD0000">'PHP_AUTH_USER'</font><font color="#007700">])) {<br /> </font><font color="#0000BB">header</font><font color="#007700">(</font><font color="#DD0000">'WWW-Authenticate: Basic realm="My Realm"'</font><font color="#007700">);<br /> </font><font color="#0000BB">header</font><font color="#007700">(</font><font color="#DD0000">'HTTP/1.0 401 Unauthorized'</font><font color="#007700">);<br /> echo </font><font color="#DD0000">'Text to send if user hits Cancel button'</font><font color="#007700">;<br /> exit;<br /> } else {<br /> echo </font><font color="#DD0000">"<p>Hello </font><font color="#007700">{</font><font color="#DD0000">$_SERVER</font><font color="#007700">[</font><font color="#DD0000">'PHP_AUTH_USER'</font><font color="#007700">]}</font><font color="#DD0000">.</p>"</font><font color="#007700">;<br /> echo </font><font color="#DD0000">"<p>You entered </font><font color="#007700">{</font><font color="#DD0000">$_SERVER</font><font color="#007700">[</font><font color="#DD0000">'PHP_AUTH_PW'</font><font color="#007700">]}</font><font color="#DD0000"> as your password.</p>"</font><font color="#007700">;<br /> }<br /></font><font color="#0000BB">?></font> </font> </code></TD ></TR ></TABLE ></DIV ></TD ></TR ></TABLE > </P ><P > <TABLE WIDTH="100%" BORDER="0" CELLPADDING="0" CELLSPACING="0" CLASS="EXAMPLE" ><TR ><TD ><DIV CLASS="example" ><A NAME="AEN6936" ></A ><P ><B >例 34-2. Digest HTTP 认证范例</B ></P ><P > 本例演示怎样实现一个简单的 Digest HTTP 认证脚本。更多信息请参考 <A HREF="http://www.faqs.org/rfcs/rfc2617" TARGET="_top" >RFC 2617</A >。 </P ><TABLE BORDER="0" BGCOLOR="#E0E0E0" CELLPADDING="5" ><TR ><TD ><code><font color="#000000"> <font color="#0000BB"><?php<br />$realm </font><font color="#007700">= </font><font color="#DD0000">'Restricted area'</font><font color="#007700">;<br /><br /></font><font color="#FF8000">//user => password<br /></font><font color="#0000BB">$users </font><font color="#007700">= array(</font><font color="#DD0000">'admin' </font><font color="#007700">=> </font><font color="#DD0000">'mypass'</font><font color="#007700">, </font><font color="#DD0000">'guest' </font><font color="#007700">=> </font><font color="#DD0000">'guest'</font><font color="#007700">);<br /><br /><br />if (empty(</font><font color="#0000BB">$_SERVER</font><font color="#007700">[</font><font color="#DD0000">'PHP_AUTH_DIGEST'</font><font color="#007700">])) {<br /> </font><font color="#0000BB">header</font><font color="#007700">(</font><font color="#DD0000">'HTTP/1.1 401 Unauthorized'</font><font color="#007700">);<br /> </font><font color="#0000BB">header</font><font color="#007700">(</font><font color="#DD0000">'WWW-Authenticate: Digest realm="'</font><font color="#007700">.</font><font color="#0000BB">$realm</font><font color="#007700">.<br /> </font><font color="#DD0000">'" qop="auth" nonce="'</font><font color="#007700">.</font><font color="#0000BB">uniqid</font><font color="#007700">().</font><font color="#DD0000">'" opaque="'</font><font color="#007700">.</font><font color="#0000BB">md5</font><font color="#007700">(</font><font color="#0000BB">$realm</font><font color="#007700">).</font><font color="#DD0000">'"'</font><font color="#007700">);<br /><br /> die(</font><font color="#DD0000">'Text to send if user hits Cancel button'</font><font color="#007700">);<br />}<br /><br /><br /></font><font color="#FF8000">// analyze the PHP_AUTH_DIGEST variable<br /></font><font color="#007700">if (!(</font><font color="#0000BB">$data </font><font color="#007700">= </font><font color="#0000BB">http_digest_parse</font><font color="#007700">(</font><font color="#0000BB">$_SERVER</font><font color="#007700">[</font><font color="#DD0000">'PHP_AUTH_DIGEST'</font><font color="#007700">])) ||<br /> !isset(</font><font color="#0000BB">$users</font><font color="#007700">[</font><font color="#0000BB">$data</font><font color="#007700">[</font><font color="#DD0000">'username'</font><font color="#007700">]]))<br /> die(</font><font color="#DD0000">'Wrong Credentials!'</font><font color="#007700">);<br /><br /><br /></font><font color="#FF8000">// generate the valid response<br /></font><font color="#0000BB">$A1 </font><font color="#007700">= </font><font color="#0000BB">md5</font><font color="#007700">(</font><font color="#0000BB">$data</font><font color="#007700">[</font><font color="#DD0000">'username'</font><font color="#007700">] . </font><font color="#DD0000">':' </font><font color="#007700">. </font><font color="#0000BB">$realm </font><font color="#007700">. </font><font color="#DD0000">':' </font><font color="#007700">. </font><font color="#0000BB">$users</font><font color="#007700">[</font><font color="#0000BB">$data</font><font color="#007700">[</font><font color="#DD0000">'username'</font><font color="#007700">]]);<br /></font><font color="#0000BB">$A2 </font><font color="#007700">= </font><font color="#0000BB">md5</font><font color="#007700">(</font><font color="#0000BB">$_SERVER</font><font color="#007700">[</font><font color="#DD0000">'REQUEST_METHOD'</font><font color="#007700">].</font><font color="#DD0000">':'</font><font color="#007700">.</font><font color="#0000BB">$data</font><font color="#007700">[</font><font color="#DD0000">'uri'</font><font color="#007700">]);<br /></font><font color="#0000BB">$valid_response </font><font color="#007700">= </font><font color="#0000BB">md5</font><font color="#007700">(</font><font color="#0000BB">$A1</font><font color="#007700">.</font><font color="#DD0000">':'</font><font color="#007700">.</font><font color="#0000BB">$data</font><font color="#007700">[</font><font color="#DD0000">'nonce'</font><font color="#007700">].</font><font color="#DD0000">':'</font><font color="#007700">.</font><font color="#0000BB">$data</font><font color="#007700">[</font><font color="#DD0000">'nc'</font><font color="#007700">].</font><font color="#DD0000">':'</font><font color="#007700">.</font><font color="#0000BB">$data</font><font color="#007700">[</font><font color="#DD0000">'cnonce'</font><font color="#007700">].</font><font color="#DD0000">':'</font><font color="#007700">.</font><font color="#0000BB">$data</font><font color="#007700">[</font><font color="#DD0000">'qop'</font><font color="#007700">].</font><font color="#DD0000">':'</font><font color="#007700">.</font><font color="#0000BB">$A2</font><font color="#007700">);<br /><br />if (</font><font color="#0000BB">$data</font><font color="#007700">[</font><font color="#DD0000">'response'</font><font color="#007700">] != </font><font color="#0000BB">$valid_response</font><font color="#007700">)<br /> die(</font><font color="#DD0000">'Wrong Credentials!'</font><font color="#007700">);<br /><br /></font><font color="#FF8000">// ok, valid username & password<br /></font><font color="#007700">echo </font><font color="#DD0000">'Your are logged in as: ' </font><font color="#007700">. </font><font color="#0000BB">$data</font><font color="#007700">[</font><font color="#DD0000">'username'</font><font color="#007700">];<br /><br /><br /></font><font color="#FF8000">// function to parse the http auth header<br /></font><font color="#007700">function </font><font color="#0000BB">http_digest_parse</font><font color="#007700">(</font><font color="#0000BB">$txt</font><font color="#007700">)<br />{<br /> </font><font color="#FF8000">// protect against missing data<br /> </font><font color="#0000BB">$needed_parts </font><font color="#007700">= array(</font><font color="#DD0000">'nonce'</font><font color="#007700">=></font><font color="#0000BB">1</font><font color="#007700">, </font><font color="#DD0000">'nc'</font><font color="#007700">=></font><font color="#0000BB">1</font><font color="#007700">, </font><font color="#DD0000">'cnonce'</font><font color="#007700">=></font><font color="#0000BB">1</font><font color="#007700">, </font><font color="#DD0000">'qop'</font><font color="#007700">=></font><font color="#0000BB">1</font><font color="#007700">, </font><font color="#DD0000">'username'</font><font color="#007700">=></font><font color="#0000BB">1</font><font color="#007700">, </font><font color="#DD0000">'uri'</font><font color="#007700">=></font><font color="#0000BB">1</font><font color="#007700">, </font><font color="#DD0000">'response'</font><font color="#007700">=></font><font color="#0000BB">1</font><font color="#007700">);<br /> </font><font color="#0000BB">$data </font><font color="#007700">= array();<br /><br /> </font><font color="#0000BB">preg_match_all</font><font color="#007700">(</font><font color="#DD0000">'@(\w+)=([\'"]?)([a-zA-Z0-9=./\_-]+)\2@'</font><font color="#007700">, </font><font color="#0000BB">$txt</font><font color="#007700">, </font><font color="#0000BB">$matches</font><font color="#007700">, </font><font color="#0000BB">PREG_SET_ORDER</font><font color="#007700">);<br /><br /> foreach (</font><font color="#0000BB">$matches </font><font color="#007700">as </font><font color="#0000BB">$m</font><font color="#007700">) {<br /> </font><font color="#0000BB">$data</font><font color="#007700">[</font><font color="#0000BB">$m</font><font color="#007700">[</font><font color="#0000BB">1</font><font color="#007700">]] = </font><font color="#0000BB">$m</font><font color="#007700">[</font><font color="#0000BB">3</font><font color="#007700">];<br /> unset(</font><font color="#0000BB">$needed_parts</font><font color="#007700">[</font><font color="#0000BB">$m</font><font color="#007700">[</font><font color="#0000BB">1</font><font color="#007700">]]);<br /> }<br /><br /> return </font><font color="#0000BB">$needed_parts </font><font color="#007700">? </font><font color="#0000BB">false </font><font color="#007700">: </font><font color="#0000BB">$data</font><font color="#007700">;<br />}<br /></font><font color="#0000BB">?><br /></font> </programlisting><br /> </example><br /> </para><br /><br /> <note><br /> <title>兼容性问题</title><br /> <para><br /> 在编写 HTTP<br /> 标头代码时请格外小心。为了对所有的客户端保证兼容性,关键字“Basic”的第一个字母必须大写为“B”,分界字符串必须用双引号(不是单引号)引用;并且在标头行<br /> <emphasis>HTTP/1.0 401</emphasis> 中,在 <emphasis>401</emphasis> 前必须有且仅有一个空格。<br /> </para><br /> </note><br /><br /> <para><br /> 在以上例子中,仅仅只打印出了 <varname>PHP_AUTH_USER</varname> 和<br /> <varname>PHP_AUTH_PW</varname><br /> 的值,但在实际运用中,可能需要对用户名和密码的合法性进行检查。或许进行数据库的查询,或许从 dbm 文件中检索。<br /> </para><br /><br /> <para><br /> 注意有些 Internet Explorer<br /> 浏览器本身有问题。它对标头的顺序显得似乎有点吹毛求疵。目前看来在发送<br /> <literal>HTTP/1.0 401</literal> 之前先发送<br /> <emphasis>WWW-Authenticate</emphasis> 标头似乎可以解决此问题。<br /> </para><br /><br /> <simpara><br /> 自 PHP 4.3.0<br /> 起,为了防止有人通过编写脚本来从用传统外部机制认证的页面上获取密码,当外部认证对特定页面有效,并且&safemode;被开启时,PHP_AUTH<br /> 变量将不会被设置。但无论如何,<varname>REMOTE_USER</varname><br /> 可以被用来辨认外部认证的用户,因此可以用<br /> <varname>$_SERVER['REMOTE_USER']</varname> 变量。<br /> </simpara><br /><br /> <note><br /> <title>配置说明</title><br /> <para><br /> PHP 用是否有 <literal>AuthType</literal> 指令来判断外部认证机制是否有效。<br /> </para><br /> </note><br /><br /> <simpara><br /> 注意,这仍然不能防止有人通过未认证的 URL 来从同一服务器上认证的 URL 上偷取密码。<br /> </simpara><br /> <simpara><br /> Netscape Navigator 和 Internet Explorer 浏览器都会在收到 401<br /> 的服务端返回信息时清空所有的本地浏览器整个域的 Windows<br /> 认证缓存。这能够有效的注销一个用户,并迫使他们重新输入他们的用户名和密码。有些人用这种方法来使登录状态“过期”,或者作为“注销”按钮的响应行为。<br /> </simpara><br /> <para><br /> <example><br /> <title>强迫重新输入用户名和密码的 HTTP 认证的范例</title><br /> <programlisting role="php"><br /><![CDATA[<br /><font color="#0000BB"><?php<br /> </font><font color="#007700">function </font><font color="#0000BB">authenticate</font><font color="#007700">() {<br /> </font><font color="#0000BB">header</font><font color="#007700">(</font><font color="#DD0000">'WWW-Authenticate: Basic realm="Test Authentication System"'</font><font color="#007700">);<br /> </font><font color="#0000BB">header</font><font color="#007700">(</font><font color="#DD0000">'HTTP/1.0 401 Unauthorized'</font><font color="#007700">);<br /> echo </font><font color="#DD0000">"You must enter a valid login ID and password to access this resource\n"</font><font color="#007700">;<br /> exit;<br /> }<br /><br /> if (!isset(</font><font color="#0000BB">$_SERVER</font><font color="#007700">[</font><font color="#DD0000">'PHP_AUTH_USER'</font><font color="#007700">]) ||<br /> (</font><font color="#0000BB">$_POST</font><font color="#007700">[</font><font color="#DD0000">'SeenBefore'</font><font color="#007700">] == </font><font color="#0000BB">1 </font><font color="#007700">&& </font><font color="#0000BB">$_POST</font><font color="#007700">[</font><font color="#DD0000">'OldAuth'</font><font color="#007700">] == </font><font color="#0000BB">$_SERVER</font><font color="#007700">[</font><font color="#DD0000">'PHP_AUTH_USER'</font><font color="#007700">])) {<br /> </font><font color="#0000BB">authenticate</font><font color="#007700">();<br /> }<br /> else {<br /> echo </font><font color="#DD0000">"<p>Welcome: </font><font color="#007700">{</font><font color="#DD0000">$_SERVER</font><font color="#007700">[</font><font color="#DD0000">'PHP_AUTH_USER'</font><font color="#007700">]}</font><font color="#DD0000"><br />"</font><font color="#007700">;<br /> echo </font><font color="#DD0000">"Old: </font><font color="#007700">{</font><font color="#DD0000">$_REQUEST</font><font color="#007700">[</font><font color="#DD0000">'OldAuth'</font><font color="#007700">]}</font><font color="#DD0000">"</font><font color="#007700">;<br /> echo </font><font color="#DD0000">"<form action='</font><font color="#007700">{</font><font color="#DD0000">$_SERVER</font><font color="#007700">[</font><font color="#DD0000">'PHP_SELF'</font><font color="#007700">]}</font><font color="#DD0000">' METHOD='post'></font><font color="#007700">\n</font><font color="#DD0000">"</font><font color="#007700">;<br /> echo </font><font color="#DD0000">"<input type='hidden' name='SeenBefore' value='1' />\n"</font><font color="#007700">;<br /> echo </font><font color="#DD0000">"<input type='hidden' name='OldAuth' value='</font><font color="#007700">{</font><font color="#DD0000">$_SERVER</font><font color="#007700">[</font><font color="#DD0000">'PHP_AUTH_USER'</font><font color="#007700">]}</font><font color="#DD0000">' /></font><font color="#007700">\n</font><font color="#DD0000">"</font><font color="#007700">;<br /> echo </font><font color="#DD0000">"<input type='submit' value='Re Authenticate' />\n"</font><font color="#007700">;<br /> echo </font><font color="#DD0000">"</form></p>\n"</font><font color="#007700">;<br /> }</font> </font> </code></TD ></TR ></TABLE ></DIV ></TD ></TR ></TABLE > </P ><P > 该行为对于 HTTP 的 Basic 认证标准来说并不是必须的,因此不能依靠这种方法。对 Lynx 浏览器的测试表明 Lynx 在收到 401 的服务端返回信息时不会清空认证文件,因此只要对认证文件的检查要求没有变化,只要用户点击“后退”按钮,再点击“前进”按钮,其原有资源仍然能够被访问。不过,用户可以通过按“_”键来清空他们的认证信息。 </P ><P > 同时请注意,在 PHP 4.3.3 之前,由于微软 IIS 的限制,HTTP 认证无法工作在 IIS 服务器的 CGI 模式下。为了能够使其在 PHP 4.3.3 以上版本能够工作,需要编辑 IIS 的设置“目录安全”。点击“编辑”并且只选择“匿名访问”,其它所有的复选框都应该留空。 </P ><P > 另一个限制是在 IIS 的 ISAPI 模式下使用 PHP 4 的时候,无法使用 <TT CLASS="literal" >PHP_AUTH_*</TT > 变量,而只能使用 <TT CLASS="literal" >HTTP_AUTHORIZATION</TT >。例如,考虑如下代码:<TT CLASS="literal" >list($user, $pw) = explode(':', base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6)));</TT >。 </P ><DIV CLASS="note" ><BLOCKQUOTE CLASS="note" ><P ><B >IIS 注意事项: </B > 要 HTTP 认证能够在 IIS 下工作,PHP 配置选项 <A HREF="ini.core.html#ini.cgi.rfc2616-headers" >cgi.rfc2616_headers</A > 必须设置成 <TT CLASS="literal" >0</TT >(默认值)。 </P ></BLOCKQUOTE ></DIV ><DIV CLASS="note" ><BLOCKQUOTE CLASS="note" ><P ><B >注意: </B > 如果<A HREF="features.safe-mode.html#ini.safe-mode" >安全模式</A >被激活,脚本的 UID 会被加到 <TT CLASS="literal" >WWW-Authenticate</TT > 标头的 <TT CLASS="literal" >realm</TT > 部分。 </P ></BLOCKQUOTE ></DIV ></DIV ><DIV CLASS="NAVFOOTER" ><HR ALIGN="LEFT" WIDTH="100%"><TABLE SUMMARY="Footer navigation table" WIDTH="100%" BORDER="0" CELLPADDING="0" CELLSPACING="0" ><TR ><TD WIDTH="33%" ALIGN="left" VALIGN="top" ><A HREF="features.html" ACCESSKEY="P" >上一页</A ></TD ><TD WIDTH="34%" ALIGN="center" VALIGN="top" ><A HREF="index.html" ACCESSKEY="H" >起始页</A ></TD ><TD WIDTH="33%" ALIGN="right" VALIGN="top" ><A HREF="features.cookies.html" ACCESSKEY="N" >下一页</A ></TD ></TR ><TR ><TD WIDTH="33%" ALIGN="left" VALIGN="top" >特点</TD ><TD WIDTH="34%" ALIGN="center" VALIGN="top" ><A HREF="features.html" ACCESSKEY="U" >上一级</A ></TD ><TD WIDTH="33%" ALIGN="right" VALIGN="top" >Cookie</TD ></TR ></TABLE ></DIV ></BODY ></HTML >