PHP在Windows 7下通过公司NTLM代理时CURLAUTH_NTLM认证失败,无法解析外部网页
解决PHP通过NTLM代理访问外网的问题
我之前也碰到过一模一样的NTLM代理坑,浏览器能用但PHP死活连不上,折腾了好几天才摸清楚门道,给你分享几个靠谱的解决思路和代码实现:
一、用cURL内置NTLM支持(最省心的方案)
其实很多人不知道,cURL原生就支持NTLM认证,根本不用自己手动写哈希逻辑!你试试下面的代码,关键是几个curl选项的配置:
<?php // 替换成你的实际参数 $targetUrl = "https://你要解析的外部网页地址"; $proxy = "http://公司代理服务器地址:端口"; $proxyUser = "公司域\\你的用户名"; // 注意格式:域+反斜杠+用户名,比如CORP\\zhangsan $proxyPass = "你的登录密码"; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $targetUrl); curl_setopt($ch, CURLOPT_PROXY, $proxy); curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_NTLM); // 核心:开启NTLM认证 curl_setopt($ch, CURLOPT_PROXYUSERPWD, "$proxyUser:$proxyPass"); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // 自动跳转 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 把响应存到变量而非直接输出 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 临时关闭HTTPS证书验证(生产环境建议配置可信证书) curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); $response = curl_exec($ch); if(curl_errno($ch)){ echo "cURL出错啦: " . curl_error($ch); } else { // 这里开始解析网页,比如用DOMDocument $dom = new DOMDocument(); @$dom->loadHTML($response); // 用@忽略HTML格式不规范的警告 // 示例:提取所有<h1>标签内容 $titles = $dom->getElementsByTagName('h1'); foreach($titles as $title){ echo "页面标题:" . $title->nodeValue . "<br>"; } } curl_close($ch); ?>
注意:先确认你的PHP环境里cURL扩展是启用的(看php.ini里
extension=curl有没有被注释),Windows下的PHP包基本都自带支持NTLM的cURL版本。如果还是不行,重点检查代理地址、端口和用户名格式——NTLM必须带域,本地用户可以用.\用户名。
二、手动模拟NTLM认证流程(cURL方案失效时用)
如果你确实需要复刻浏览器的NTLM步骤,核心是两次请求的交互:
- 第一次发普通请求,代理返回
401 Unauthorized,并在响应头里给你一个NTLM随机挑战码 - 用你的凭证生成响应哈希,带上
Authorization: NTLM 响应值发第二次请求
下面是简化版的手动实现(需要PHP安装mbstring和hash扩展):
<?php function generateNtlmResponse($domain, $username, $password, $challenge) { // 第一步:生成NTLMv1哈希(MD4加密Unicode编码的密码) $unicodePwd = mb_convert_encoding($password, 'UTF-16LE'); $ntlmHash = hash('md4', $unicodePwd, true); // 第二步:处理服务器返回的挑战码 $challengeBytes = base64_decode($challenge); // 生成响应哈希(简化版,完整NTLMv1还需要客户端随机数,大部分代理场景够用) $response = hash('md5', $ntlmHash . substr($challengeBytes, 24, 8), true); // 构建NTLM响应包(简化结构,适配多数代理) $ntlmResponse = "NTLMSSP\x00\x03\x00\x00\x00" . str_repeat("\x00", 24) . $response . str_repeat("\x00", 16); return base64_encode($ntlmResponse); } // 替换成你的参数 $targetUrl = "https://目标网页地址"; $proxy = "http://代理地址:端口"; $domain = "公司域"; $username = "你的用户名"; $password = "你的密码"; // 第一次请求:获取NTLM挑战码 $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $targetUrl); curl_setopt($ch, CURLOPT_PROXY, $proxy); curl_setopt($ch, CURLOPT_HEADER, true); // 保留响应头 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); $firstResponse = curl_exec($ch); curl_close($ch); // 从响应头里提取挑战码 preg_match('/WWW-Authenticate: NTLM (.*)/i', $firstResponse, $matches); if(!isset($matches[1])){ die("没拿到NTLM挑战码,可能代理不支持NTLM?"); } $challenge = $matches[1]; // 生成NTLM响应 $ntlmAuth = generateNtlmResponse($domain, $username, $password, $challenge); // 第二次请求:带上NTLM认证头 $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $targetUrl); curl_setopt($ch, CURLOPT_PROXY, $proxy); curl_setopt($ch, CURLOPT_HTTPHEADER, [ "Authorization: NTLM $ntlmAuth" ]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); $finalResponse = curl_exec($ch); if(curl_errno($ch)){ echo "出错了: " . curl_error($ch); } else { // 解析网页逻辑 $dom = new DOMDocument(); @$dom->loadHTML($finalResponse); echo "网页内容获取成功,总长度:" . strlen($finalResponse); } curl_close($ch); ?>
提醒:手动实现只支持NTLMv1,如果你的代理要求NTLMv2,需要更复杂的HMAC-MD5计算,优先推荐用cURL的内置方案,它已经兼容所有NTLM版本了。
三、常见排查点
- 检查
php.ini里extension=curl是否启用,修改后重启Web服务器 - 确认代理服务器允许PHP所在的机器访问(有些公司代理会限制IP或用户组)
- 如果是IIS运行PHP,试试把应用池身份改成有权限过代理的域用户
- 用命令行测试:
curl -x 代理地址:端口 -U 域\用户名:密码 https://目标地址,如果命令行能通,说明代码配置有问题;如果命令行也不通,检查凭证或网络权限
内容的提问来源于stack exchange,提问作者yo3hcv




