SOAP(简单对象访问协议)是一种基于XML的协议,用于在网络上交换结构化信息,广泛用于企业级Web服务对接场景。PHP提供了内置的SOAP扩展,可以方便地实现SOAP服务的调用,当服务需要WS-Security安全规范时,还需要额外处理安全头信息。

环境准备
首先要确保PHP环境已经安装了SOAP扩展,可以通过phpinfo()函数查看是否存在SOAP模块。如果没有安装,在Linux环境下可以通过包管理器安装,比如Ubuntu系统执行以下命令:
sudo apt-get install php-soap sudo systemctl restart php-fpm sudo systemctl restart nginx
安装完成后重启服务,再次查看phpinfo确认SOAP扩展已启用。
基础SOAP服务调用
PHP的SoapClient类是实现SOAP调用的核心类,基础调用只需要指定WSDL地址即可。以下是一个简单的调用示例:
<?php
// 初始化SoapClient,传入WSDL地址
$wsdl = "http://ipipp.com/soap/service?wsdl";
$client = new SoapClient($wsdl, [
'trace' => 1, // 开启追踪,方便调试请求和响应
'exceptions' => true, // 开启异常抛出
]);
// 调用SOAP服务的方法,假设服务有getUserInfo方法,需要传入用户ID参数
$params = [
'userId' => 1001
];
$result = $client->__soapCall('getUserInfo', [$params]);
// 输出结果
echo "<pre>";
print_r($result);
echo "</pre>";
// 调试:输出请求和响应的XML内容
echo "请求XML:\n" . $client->__getLastRequest() . "\n";
echo "响应XML:\n" . $client->__getLastResponse() . "\n";
?>上面的代码中,trace参数设置为true后可以获取最后一次请求的XML和响应XML,方便排查调用问题。__soapCall方法的第一个参数是要调用的SOAP方法名,第二个参数是方法的参数数组。
自定义SOAP请求头
有些SOAP服务需要自定义请求头,比如传递认证信息。可以通过SoapHeader类来构造请求头,再传递给SoapClient。示例如下:
<?php
$wsdl = "http://ipipp.com/soap/service?wsdl";
$client = new SoapClient($wsdl, [
'trace' => 1,
'exceptions' => true,
]);
// 构造自定义请求头,命名空间为http://ipipp.com/soap/header,头名称为AuthHeader
$headerParams = [
'username' => 'test_user',
'token' => 'abc123xyz'
];
$header = new SoapHeader('http://ipipp.com/soap/header', 'AuthHeader', $headerParams);
// 设置请求头
$client->__setSoapHeaders($header);
// 调用服务方法
$result = $client->__soapCall('getOrderList', [['status' => 1]]);
print_r($result);
?>WS-Security实现
WS-Security是Web服务安全规范,用于在SOAP消息中添加签名、加密、时间戳等安全信息。PHP原生SoapClient没有直接支持WS-Security,需要手动构造安全头。以下是一个实现WS-Security UsernameToken的示例:
<?php
class WsseSoapClient extends SoapClient {
private $username;
private $password;
public function __construct($wsdl, $options = []) {
parent::__construct($wsdl, $options);
$this->username = $options['username'] ?? '';
$this->password = $options['password'] ?? '';
}
// 重写__doRequest方法,手动添加WS-Security头
public function __doRequest($request, $location, $action, $version, $one_way = 0) {
// 解析原始SOAP请求XML
$dom = new DOMDocument();
$dom->loadXML($request);
// 创建WS-Security相关节点
$wsseNs = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd';
$wsuNs = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd';
// 创建Security节点
$security = $dom->createElementNS($wsseNs, 'wsse:Security');
$security->setAttribute('soap:mustUnderstand', '1');
// 创建UsernameToken节点
$usernameToken = $dom->createElementNS($wsseNs, 'wsse:UsernameToken');
// 创建Username节点
$usernameNode = $dom->createElementNS($wsseNs, 'wsse:Username', $this->username);
$usernameToken->appendChild($usernameNode);
// 创建Password节点,密码类型为密码文本
$passwordNode = $dom->createElementNS($wsseNs, 'wsse:Password', $this->password);
$passwordNode->setAttribute('Type', 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText');
$usernameToken->appendChild($passwordNode);
// 创建Nonce节点(随机数)
$nonce = base64_encode(random_bytes(16));
$nonceNode = $dom->createElementNS($wsseNs, 'wsse:Nonce', $nonce);
$nonceNode->setAttribute('EncodingType', 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary');
$usernameToken->appendChild($nonceNode);
// 创建Created节点(时间戳)
$created = gmdate('Y-m-d\TH:i:s\Z');
$createdNode = $dom->createElementNS($wsuNs, 'wsu:Created', $created);
$usernameToken->appendChild($createdNode);
// 将UsernameToken添加到Security节点
$security->appendChild($usernameToken);
// 找到SOAP Header节点,如果没有则创建
$soapNs = 'http://schemas.xmlsoap.org/soap/envelope/';
$headerNode = $dom->getElementsByTagNameNS($soapNs, 'Header')->item(0);
if (!$headerNode) {
$envelope = $dom->getElementsByTagNameNS($soapNs, 'Envelope')->item(0);
$headerNode = $dom->createElementNS($soapNs, 'soap:Header');
$envelope->insertBefore($headerNode, $envelope->firstChild);
}
// 将Security节点添加到Header
$headerNode->appendChild($security);
// 获取修改后的XML
$newRequest = $dom->saveXML();
// 调用父类方法发送请求
return parent::__doRequest($newRequest, $location, $action, $version, $one_way);
}
}
// 使用自定义的WsseSoapClient调用服务
$wsdl = "http://ipipp.com/soap/secure_service?wsdl";
$client = new WsseSoapClient($wsdl, [
'trace' => 1,
'exceptions' => true,
'username' => 'secure_user',
'password' => 'secure_pass_123'
]);
// 调用服务方法
$result = $client->__soapCall('querySecureData', [['queryKey' => 'test_key']]);
echo "<pre>";
print_r($result);
echo "</pre>";
?>上面的代码通过继承SoapClient类,重写了__doRequest方法,在发送请求前手动构造WS-Security的UsernameToken安全头,满足需要WS-Security认证的SOAP服务调用需求。如果需要更复杂的签名或加密逻辑,可以在此基础上扩展对应的XML节点构造逻辑。
常见问题排查
- 如果调用时出现SOAP-ERROR: Parsing WSDL: Couldn't load from错误,先检查WSDL地址是否可访问,网络是否通畅,防火墙是否拦截了请求。
- 如果返回结果为空或者报错,开启
trace选项,查看__getLastRequest和__getLastResponse的内容,对比服务要求的XML格式排查问题。 - WS-Security相关报错时,检查命名空间是否正确,各节点的属性是否符合服务要求的WS-Security规范版本。
PHPSOAPWS_SecurityWeb服务修改时间:2026-06-05 15:38:21