由于身份验证错误,Php Soap 请求失败

问题描述 投票:0回答:3

我有点脱离了自己的舒适区。我正在尝试使用 PHP 创建 SOAP 请求,并且我正在使用著名的 KBO API,这是文档:

https://economie.fgov.be/sites/default/files/Files/Entreprises/CBE/Cookbook-CBE-Public-Search-Webservice.pdf

所以基本上一切都很顺利,但我仍然在与身份验证有关的问题上苦苦挣扎,这使我的整个小框架失败了:

<?php

$endpoint = 'https://kbopub-acc.economie.fgov.be/kbopubws110000/services/wsKBOPub';
$wsdl = 'https://kbopub-acc.economie.fgov.be/kbopubws110000/services/wsKBOPub?wsdl';
$username = 'myusername';
$password = 'mypassword';

$timestamp = gmdate('Y-m-d\TH:i:s\Z');
$nonce = base64_encode(random_bytes(16));
$passwordDigest = base64_encode(sha1($nonce . $timestamp . $password, true));
$header = '
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
    <wsse:UsernameToken>
        <wsse:Username>' . $username . '</wsse:Username>
        <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">' . $passwordDigest . '</wsse:Password>
        <wsse:Nonce>' . $nonce . '</wsse:Nonce>
        <wsu:Created xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">' . $timestamp . '</wsu:Created>
    </wsse:UsernameToken>
</wsse:Security>';
$options = array(
    'soap_version' => SOAP_1_1,
    'trace' => true,
    'exceptions' => true,
    'encoding' => 'UTF-8',
    'cache_wsdl' => WSDL_CACHE_NONE,
    'stream_context' => stream_context_create(array(
        'http' => array(
            'header' => 'Authorization: WSSE profile=' . $header,
            'user_agent' => 'PHPSoapClient'
        ),
        'ssl' => array(
            'verify_peer' => false,
            'verify_peer_name' => false
        )
    ))
);
$context = stream_context_create(array(
    'ssl' => array(
        'verify_peer' => false,
        'verify_peer_name' => false,
    ),
));
$options['stream_context'] = $context;
// $options = array(
//     'soap_version' => SOAP_1_1
// );
$client = new SoapClient($wsdl, $options);

// Set up the request parameters
$enterpriseNumber = '0810.002.854';
$request = array('EnterpriseNumber' => $enterpriseNumber);

// Call the SOAP operation with the request parameters
$response = $client->__soapCall('ReadEnterprise', array($request));

// Get the request and response XML
$requestXML = $client->__getLastRequest();
$responseXML = $client->__getLastResponse();

var_dump($response);

我还遵循了 PHP 日志文件中的一些建议,其中编写了使用不同版本的肥皂版本:SOAP_1_1 而不是 SOAP_1_2。

我发现真正具有挑战性的是我无法以任何方式调试它;我不知道如何检查错误消息,或者我的错误日志文件中没有任何提示,它只是提到了安全检查,但没有太多。

有人遇到同样的问题吗?

顺便说一句,我遇到的错误如下:

[14-Feb-2023 17:09:24 UTC] PHP 致命错误:未捕获的 SoapFault 异常:[ns1:SecurityError] 验证消息时遇到安全错误 堆栈跟踪: #0 /Users/mymac/Sites/cboxform/api-call/index.php(55): SoapClient->__soapCall('ReadEnterprise', Array) #1 {主要} 扔在 /Users/mymac/Sites/cboxform/api-call/index.php 第 55 行

TLTR:

总而言之,我需要一个如下所示的请求:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:mes="http://economie.fgov.be/kbopub/webservices/v1/messages" xmlns:dat="http://economie.fgov.be/kbopub/webservices/v1/datamodel">
   <soapenv:Header>
 <mes:RequestContext>
<mes:Id>myid</mes:Id>
<mes:Language>fr</mes:Language>
 </mes:RequestContext>
 </soapenv:Header>
 <soapenv:Body>
 <mes:ReadEnterpriseRequest>
 <dat:EnterpriseNumber>0206231995</dat:EnterpriseNumber>
 </mes:ReadEnterpriseRequest>
 </soapenv:Body>
</soapenv:Envelope>

带有摘要密码的标头,以及将在 300 秒后过期到此端点的时间戳和随机数:

https://kbopub-acc.economie.fgov.be/kbopubws110000/services/wsKBOPub?wsdl

还有用户名。

php authentication soap ws-security
3个回答
1
投票

您用来验证 SOAP 请求的安全令牌似乎存在问题。 请检查

timezone
格式为 UTC
(YYYY-MM-DDTHH:MM:SSZ)
Nonce
应是为每个请求生成的随机值,并且
PasswordDigest
值计算正确,
PasswordDigest
(SHA-1 哈希)和
 的串联值Nonce
,以二进制格式创建
timestamp
password

您也可以尝试打印出

timestamp
nonce
passwordDigest
的值,以便于调试

您可以尝试替换以下代码中的第 20 行吗:

$options = array(
    'soap_version' => SOAP_1_1,
    'trace' => true,
    'exceptions' => true,
    'encoding' => 'UTF-8',
    'cache_wsdl' => WSDL_CACHE_NONE,
    'stream_context' => stream_context_create(array(
        'http' => array(
            'header' => 'Authorization: WSSE ' . $header,
            'user_agent' => 'PHPSoapClient'
        ),
        'ssl' => array(
            'verify_peer' => false,
            'verify_peer_name' => false
        )
    ))
);
$context = stream_context_create(array(
    'ssl' => array(
        'verify_peer' => false,
        'verify_peer_name' => false,
    ),
));

WSSE 标头在其后的空格内使用正确的配置文件值


1
投票

KBO 公共 Web 服务使用

ns1:SecurityError
作为捕获所有异常,这使得很难确定实际原因。

作为第一步,我发现它有效:

这通常在设置语言代码和企业编号后起作用。

但是实际有效负载是多少?

  • 在请求窗口中单击:向模拟服务添加请求(左上角菜单中的第三个按钮)
  • 启动模拟服务。
  • 在请求窗口中选择“http://localhost:8080”或模拟服务正在使用的任何端口。
  • 在模拟服务窗口:转到“OnRequest Script”。
  • 指定
    log.info mockRequest.requestContent
  • 在请求窗口中运行请求。
  • 在模拟服务窗口中:转到脚本日志。
  • 显示实际有效负载。

SoapUI of KBO

请注意,密码摘要只能使用一次。在后续使用时,它还将返回包罗万象的 ns1:SecurityError。工作 SQL 实现的示例如下(如 https://forums.invantive.com/t/uitlezen-kbo-public-search-api-geeft-foutmelding-ns1-securityerror/3424/2 所示):

--
-- Tested according to http://www.herongyang.com/Web-Services/WS-Security-Validate-Password-Digest-String.html
-- using:
--  l_created := '2014-06-21T12:43:21.791Z';
--  l_password := 'iLoveDogs';
--  l_username := 'herong';
--
declare
  l_nonce                blob;
  l_created              varchar2;
  l_expires              varchar2;
  l_wsse_created         varchar2;
  l_password_digest      varchar2;
  l_xml                  varchar2;
  l_out_contents_char    varchar2;
  l_out_http_status_code int32;
  l_created_date         datetime;
  l_expires_date         datetime;
  --
  l_enterprise_number    varchar2 := '0670979187';
  l_username             varchar2 := 'wsot9999';
  l_password             varchar2 := 'secret';
  l_language_code        varchar2 := 'nl';
  l_msg_id               varchar2 := to_char(newid());
begin
  l_created_date := sysdateutc;
  --
  -- Default maximum offset of wss4j is 300 seconds.
  --
  l_expires_date := l_created_date + 300/86400;
  --
  l_created := to_char(l_created_date, 'YYYY-MM-DD"T"HH24:MI:SS.FF3"Z"');
  l_expires := to_char(l_expires_date, 'YYYY-MM-DD"T"HH24:MI:SS.FF3"Z"');
  l_wsse_created := to_char(l_created_date, 'YYYY-MM-DD"T"HH24:MI:SS.FF3"Z"');
  --
  l_nonce:=random_blob(24);
  --
  -- Calculate digested password.
  --
  l_password_digest := base64_encode(hex_to_blob(sha1(l_nonce || to_binary(l_wsse_created) || to_binary(l_password))));
  --
  l_xml := '<soapenv:Envelope xmlns:dat="http://economie.fgov.be/kbopub/webservices/v1/datamodel" xmlns:mes="http://economie.fgov.be/kbopub/webservices/v1/messages" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">';
  l_xml := l_xml || chr(13) || chr(10) || '  <soapenv:Header>';
  l_xml := l_xml || chr(13) || chr(10) || '    <wsse:Security soapenv:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">';
  l_xml := l_xml || chr(13) || chr(10) || '      <wsu:Timestamp wsu:Id="TS-A4DC6AF47AFDBFB025168519485171276">';
  l_xml := l_xml || chr(13) || chr(10) || '        <wsu:Created>' || l_created || '</wsu:Created>';
  l_xml := l_xml || chr(13) || chr(10) || '        <wsu:Expires>' || l_expires || '</wsu:Expires>';
  l_xml := l_xml || chr(13) || chr(10) || '      </wsu:Timestamp>';
  l_xml := l_xml || chr(13) || chr(10) || '      <wsse:UsernameToken wsu:Id="UsernameToken-A4DC6AF47AFDBFB025168519485171275">';
  l_xml := l_xml || chr(13) || chr(10) || '        <wsse:Username>' || xmlencode(l_username) || '</wsse:Username>';
  l_xml := l_xml || chr(13) || chr(10) || '        <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">' || xmlencode(l_password_digest) || '</wsse:Password>';
  l_xml := l_xml || chr(13) || chr(10) || '        <wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">' || xmlencode(l_nonce) || '</wsse:Nonce>';
  l_xml := l_xml || chr(13) || chr(10) || '        <wsu:Created>' || l_wsse_created || '</wsu:Created>';
  l_xml := l_xml || chr(13) || chr(10) || '      </wsse:UsernameToken>';
  l_xml := l_xml || chr(13) || chr(10) || '    </wsse:Security>';
  l_xml := l_xml || chr(13) || chr(10) || '    <mes:RequestContext>';
  l_xml := l_xml || chr(13) || chr(10) || '      <mes:Id>' || xmlencode(l_msg_id) || '</mes:Id>';
  l_xml := l_xml || chr(13) || chr(10) || '      <mes:Language>' || xmlencode(l_language_code) || '</mes:Language>';
  l_xml := l_xml || chr(13) || chr(10) || '    </mes:RequestContext>';
  l_xml := l_xml || chr(13) || chr(10) || '  </soapenv:Header>';
  l_xml := l_xml || chr(13) || chr(10) || '  <soapenv:Body>';
  l_xml := l_xml || chr(13) || chr(10) || '    <mes:ReadEnterpriseRequest>';
  l_xml := l_xml || chr(13) || chr(10) || '      <dat:EnterpriseNumber>' || xmlencode(l_enterprise_number) || '</dat:EnterpriseNumber>';
  l_xml := l_xml || chr(13) || chr(10) || '    </mes:ReadEnterpriseRequest>';
  l_xml := l_xml || chr(13) || chr(10) || '  </soapenv:Body>';
  l_xml := l_xml || chr(13) || chr(10) || '</soapenv:Envelope>';
  --
  select htp.CONTENTS_CHAR
  ,      htp.http_status_code
  into   l_out_contents_char
  ,      l_out_http_status_code
  from   HTTPDownload@DataDictionary
         ( url => 'https://kbopub-acc.economie.fgov.be/kbopubws110000/services/wsKBOPub'
         , acceptMimeType => '*/*'
         , contentType => 'text/xml;charset=UTF-8'
         , method => 'POST'
         , textPayload => l_xml
         , ignoreWebError => false
         ) htp
  ;
  dbms_output.put_line(l_out_contents_char);
end;

0
投票

我在使用食谱连接到https://kbopub-acc.economie.fgov.be/kbopubws110000/services/wsKBOPub时遇到了同样的错误:

PHP 致命错误:未捕获的 SoapFault 异常:[ns1:SecurityError] 验证 /var/www/html/soap.php:39 中的消息时遇到安全错误

不知道出了什么问题

<?php
    $wsdl = 'https://kbopub-acc.economie.fgov.be/kbopubws110000/services/wsKBOPub?wsdl';
    $username = 'user';
    $password = 'pass';
    $client = new WSSoapClient($wsdl);
    $client->setUsernameToken($username, $password);
    $response = $client->__soapCall('ReadEnterprise', ['EnterpriseNumber' => '0810002854']);
    var_dump($response);
    
    class WSSoapClient extends \SoapClient
    {
        private $nonce;
    
        private $timestamp;
    
        private $timestampExpires;
    
        private $username;
    
        private $password;
    
        public function setUsernameToken($username, $password)
        {
            $this->username = $username;
            $this->password = $password;
        }
    
        public function __soapCall(
            $function_name,
            $arguments,
            $options = null,
            $input_headers = null,
            &$output_headers = null
        ) {
            $headers = [$this->generateWSSecurityHeader(), $this->generateRequestContextHeader()];
    
            return parent::__soapCall(
                $function_name,
                $arguments,
                $options,
                $headers
            );
        }
    
        private function generatePasswordDigest()
        {
            $this->nonce = mt_rand();
            $currentTime = time();
            $this->timestamp = gmdate("Y-m-d\TH:i:s", $currentTime) . 'Z';
            $this->timestampExpires = gmdate("Y-m-d\TH:i:s", $currentTime + 300) . 'Z';
    
            return base64_encode(sha1($this->nonce . $this->timestamp . $this->password));
        }
    
        private function generateWSSecurityHeader()
        {
            $passwordDigest = $this->generatePasswordDigest();
    
            $xml = '<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
        <wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
            <wsu:Created>' . $this->timestamp . '</wsu:Created>
            <wsu:Expires>' . $this->timestampExpires . '</wsu:Expires>
        </wsu:Timestamp>
        <wsse:UsernameToken>
            <wsse:Username>' . $this->username . '</wsse:Username>
            <wsse:Password>' . $passwordDigest . '</wsse:Password>
            <wsse:Nonce>' . base64_encode($this->nonce) . '</wsse:Nonce>
            <wsu:Created xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">' . $this->timestamp . '</wsu:Created>
        </wsse:UsernameToken>
    </wsse:Security>';
    
            return new \SoapHeader(
                'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd',
                'Security',
                new \SoapVar($xml, XSD_ANYXML),
                true
            );
        }
    
        private function generateRequestContextHeader()
        {
            $xml = '<mes:RequestContext xmlns:mes="http://economie.fgov.be/KBOpub/webservices/v1/messages">
        <mes:Id>' . $this->getUuid() . '</mes:Id>
        <mes:Language>fr</mes:Language>
    </mes:RequestContext>';
    
            return new SoapHeader(
                'http://economie.fgov.be/KBOpub/webservices/v1/messages',
                'RequestContext',
                new SoapVar($xml, XSD_ANYXML),
                true
            );
        }
    
        private function getUuid()
        {
            $uuid = [
                'time_low' => 0,
                'time_mid' => 0,
                'time_hi' => 0,
                'clock_seq_hi' => 0,
                'clock_seq_low' => 0,
                'node' => []
            ];
    
            $uuid['time_low'] = mt_rand(0, 0xffff) + (mt_rand(0, 0xffff) << 16);
            $uuid['time_mid'] = mt_rand(0, 0xffff);
            $uuid['time_hi'] = (4 << 12) | (mt_rand(0, 0x1000));
            $uuid['clock_seq_hi'] = (1 << 7) | (mt_rand(0, 128));
            $uuid['clock_seq_low'] = mt_rand(0, 255);
    
            for ($i = 0; $i < 6; $i++) {
                $uuid['node'][$i] = mt_rand(0, 255);
            }
    
            $uuid = sprintf(
                '%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x',
                $uuid['time_low'],
                $uuid['time_mid'],
                $uuid['time_hi'],
                $uuid['clock_seq_hi'],
                $uuid['clock_seq_low'],
                $uuid['node'][0],
                $uuid['node'][1],
                $uuid['node'][2],
                $uuid['node'][3],
                $uuid['node'][4],
                $uuid['node'][5]
            );
    
            return $uuid;
        }
    }
© www.soinside.com 2019 - 2024. All rights reserved.