You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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步骤,核心是两次请求的交互:

  1. 第一次发普通请求,代理返回401 Unauthorized,并在响应头里给你一个NTLM随机挑战码
  2. 用你的凭证生成响应哈希,带上Authorization: NTLM 响应值发第二次请求

下面是简化版的手动实现(需要PHP安装mbstringhash扩展):

<?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.iniextension=curl是否启用,修改后重启Web服务器
  • 确认代理服务器允许PHP所在的机器访问(有些公司代理会限制IP或用户组)
  • 如果是IIS运行PHP,试试把应用池身份改成有权限过代理的域用户
  • 用命令行测试:curl -x 代理地址:端口 -U 域\用户名:密码 https://目标地址,如果命令行能通,说明代码配置有问题;如果命令行也不通,检查凭证或网络权限

内容的提问来源于stack exchange,提问作者yo3hcv

火山引擎 最新活动