Подключение к защищенному веб-сервису WS-Security с помощью PHP
Я пытаюсь подключиться к веб-службе, которая защищена паролем, и url-адрес https. Я не могу понять, как аутентифицироваться до того, как скрипт сделает запрос. Похоже, он делает запрос, как только я определяю службы. Например, если я вставлю:
$client = new SoapClient("https://example.com/WSDL/nameofservice",
array('trace' => 1,)
);
а потом зайдите на сайт в браузере, я получаю:
Fatal error: Uncaught SoapFault exception:
[WSDL] SOAP-ERROR: Parsing WSDL: Couldn't load from
'https://example.com/WSDL/nameofservice' in /path/to/my/script/myscript.php:2
Stack trace: #0 /path/to/my/script/myscript.php(2):
SoapClient->SoapClient('https://example...', Array) #1 {main} thrown in
/path/to/my/script/myscript.php on line 2
если я попытаюсь определить службу как Soap-сервер, например:
$server= new SoapServer("https://example.com/WSDL/nameofservice");
Я:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>WSDL</faultcode>
<faultstring>
SOAP-ERROR: Parsing WSDL:
Couldn't load from 'https://example.com/WSDL/nameofservice'
</faultstring>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Я не попробовал отправить конверт необработанного запроса, чтобы увидеть, что возвращает сервер, но это может быть обходным путем. Но я надеялся, что кто-то скажет мне, как я могу настроить его с помощью встроенных классов php. Я попытался добавить "имя пользователя" и "пароль" в массив, но это было нехорошо. Проблема в том, что я даже не могу сказать, достигаю ли я удаленного сайта вообще, не говоря уже о том, отклоняет ли он запрос.
7 ответов
проблема заключается в том, что документ WSDL каким - то образом защищен (базовая аутентификация-я не думаю, что дайджест-аутентификация поддерживается с SoapClient
, Так что вам не повезло в этом случае) и что SoapClient
поэтому не удается прочитать и проанализировать описание службы.
прежде всего, вы должны попытаться открыть расположение WSDL в вашем браузере, чтобы проверить, если вы представлены диалоговое окно аутентификации. Если есть диалоговое окно аутентификации, вы должны убедиться, что the SoapClient
использует необходимые учетные данные для входа при получении документа WSDL. Проблема в том, что SoapClient
будет только отправлять данные с login
и password
параметры (а также local_cert
опция при использовании аутентификации сертификата) при создании клиента при вызове службы, а не при получении WSDL (см. здесь). Существует два метода преодоления этой проблемы:
-
добавьте учетные данные для входа в url-адрес WSDL на the
SoapClient
вызов конструктора$client = new SoapClient( 'https://' . urlencode($login) . ':' . urlencode($password) . '@example.com/WSDL/nameofservice', array( 'login' => $login, 'password' => $password ) );
это должно быть самое простое решение - но в PHP ошибка #27777 написано, что это тоже не сработает (я этого не пробовал).
-
извлеките WSDL вручную с помощью оболочки потока HTTP или
ext/curl
или вручную через браузер или черезwget
например, сохраните его на диске и создайте экземплярSoapClient
со ссылкой на локальный WSDL.данное решение может быть проблематично, если документ WSDL изменяется, как вы должны обнаружить изменение и сохранить новую версию на диске.
если диалог аутентификации не отображается и вы можете прочитать WSDL в своем браузере, вы должны предоставить дополнительные сведения для проверки других возможных ошибок/проблем.
эта проблема окончательно не связана с самой службой как SoapClient
дроссели уже при чтении документа описания службы перед вызовом сама услуга.
EDIT:
наличие файла WSDL локально является первым шагом - это позволит SoapClient
чтобы знать, как общаться с сервисом. Не имеет значения, обслуживается ли WSDL непосредственно из местоположения службы, с другого сервера или считывается из локального файла - URL-адреса службы кодируются в WSDL so SoapClient
всегда знает, где искать конечную точку службы.
вторая проблема теперь в том, что SoapClient
нет поддержка WS-Security спецификации изначально, что означает, что вы должны расширить SoapClient
для обработки определенных заголовков. Точкой расширения для добавления требуемого поведения будет SoapClient::__doRequest()
который предварительно обрабатывает полезную нагрузку XML перед отправкой ее в конечную точку службы. Но я думаю, что реализация решения WS-Security самостоятельно потребует достойного знания конкретных спецификаций WS-Security. Возможно, заголовки WS-Security также могут быть созданы и упаковано в XML-запрос с помощью SoapClient::__setSoapHeaders()
и соответствующий SoapHeader
s но я сомневаюсь, что это сработает, оставив обычай SoapClient
расширение как единственная возможность.
простой
просто расширьте SoapHeader, чтобы создать аутентификацию компилятора Wsse:
class WsseAuthHeader extends SoapHeader {
private $wss_ns = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd';
function __construct($user, $pass, $ns = null) {
if ($ns) {
$this->wss_ns = $ns;
}
$auth = new stdClass();
$auth->Username = new SoapVar($user, XSD_STRING, NULL, $this->wss_ns, NULL, $this->wss_ns);
$auth->Password = new SoapVar($pass, XSD_STRING, NULL, $this->wss_ns, NULL, $this->wss_ns);
$username_token = new stdClass();
$username_token->UsernameToken = new SoapVar($auth, SOAP_ENC_OBJECT, NULL, $this->wss_ns, 'UsernameToken', $this->wss_ns);
$security_sv = new SoapVar(
new SoapVar($username_token, SOAP_ENC_OBJECT, NULL, $this->wss_ns, 'UsernameToken', $this->wss_ns),
SOAP_ENC_OBJECT, NULL, $this->wss_ns, 'Security', $this->wss_ns);
parent::__construct($this->wss_ns, 'Security', $security_sv, true);
}
}
$wsse_header = new WsseAuthHeader($username, $password);
$x = new SoapClient('{...}', array("trace" => 1, "exception" => 0));
$x->__setSoapHeaders(array($wsse_header));
Если вам нужно использовать ws-security с nonce и меткой времени, Питер опубликовал версию обновления наhttp://php.net/manual/en/soapclient.soapclient.php#114976 из которых он написал, что это сработало для него:
class WsseAuthHeader extends SoapHeader
{
private $wss_ns = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd';
private $wsu_ns = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd';
function __construct($user, $pass)
{
$created = gmdate('Y-m-d\TH:i:s\Z');
$nonce = mt_rand();
$passdigest = base64_encode(pack('H*', sha1(pack('H*', $nonce) . pack('a*', $created) . pack('a*', $pass))));
$auth = new stdClass();
$auth->Username = new SoapVar($user, XSD_STRING, NULL, $this->wss_ns, NULL, $this->wss_ns);
$auth->Password = new SoapVar($pass, XSD_STRING, NULL, $this->wss_ns, NULL, $this->wss_ns);
$auth->Nonce = new SoapVar($passdigest, XSD_STRING, NULL, $this->wss_ns, NULL, $this->wss_ns);
$auth->Created = new SoapVar($created, XSD_STRING, NULL, $this->wss_ns, NULL, $this->wsu_ns);
$username_token = new stdClass();
$username_token->UsernameToken = new SoapVar($auth, SOAP_ENC_OBJECT, NULL, $this->wss_ns, 'UsernameToken', $this->wss_ns);
$security_sv = new SoapVar(
new SoapVar($username_token, SOAP_ENC_OBJECT, NULL, $this->wss_ns, 'UsernameToken', $this->wss_ns),
SOAP_ENC_OBJECT, NULL, $this->wss_ns, 'Security', $this->wss_ns);
parent::__construct($this->wss_ns, 'Security', $security_sv, true);
}
}
сравните также с деталями, приведенными в ответе https://stackoverflow.com/a/18575154/367456
для безопасности дайджеста паролей вы можете использовать следующее:
/**
* This function implements a WS-Security digest authentification for PHP.
*
* @access private
* @param string $user
* @param string $password
* @return SoapHeader
*/
function soapClientWSSecurityHeader($user, $password)
{
// Creating date using yyyy-mm-ddThh:mm:ssZ format
$tm_created = gmdate('Y-m-d\TH:i:s\Z');
$tm_expires = gmdate('Y-m-d\TH:i:s\Z', gmdate('U') + 180); //only necessary if using the timestamp element
// Generating and encoding a random number
$simple_nonce = mt_rand();
$encoded_nonce = base64_encode($simple_nonce);
// Compiling WSS string
$passdigest = base64_encode(sha1($simple_nonce . $tm_created . $password, true));
// Initializing namespaces
$ns_wsse = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd';
$ns_wsu = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd';
$password_type = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest';
$encoding_type = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary';
// Creating WSS identification header using SimpleXML
$root = new SimpleXMLElement('<root/>');
$security = $root->addChild('wsse:Security', null, $ns_wsse);
//the timestamp element is not required by all servers
$timestamp = $security->addChild('wsu:Timestamp', null, $ns_wsu);
$timestamp->addAttribute('wsu:Id', 'Timestamp-28');
$timestamp->addChild('wsu:Created', $tm_created, $ns_wsu);
$timestamp->addChild('wsu:Expires', $tm_expires, $ns_wsu);
$usernameToken = $security->addChild('wsse:UsernameToken', null, $ns_wsse);
$usernameToken->addChild('wsse:Username', $user, $ns_wsse);
$usernameToken->addChild('wsse:Password', $passdigest, $ns_wsse)->addAttribute('Type', $password_type);
$usernameToken->addChild('wsse:Nonce', $encoded_nonce, $ns_wsse)->addAttribute('EncodingType', $encoding_type);
$usernameToken->addChild('wsu:Created', $tm_created, $ns_wsu);
// Recovering XML value from that object
$root->registerXPathNamespace('wsse', $ns_wsse);
$full = $root->xpath('/root/wsse:Security');
$auth = $full[0]->asXML();
return new SoapHeader($ns_wsse, 'Security', new SoapVar($auth, XSD_ANYXML), true);
}
чтобы использовать его с PHP SoapClient, используйте следующий способ:
$client = new SoapClient('http://endpoint');
$client->__setSoapHeaders(soapClientWSSecurityHeader('myUser', 'myPassword'));
// $client->myService(array('param' => 'value', ...);
у меня есть более простое решение, чем расширение существующей библиотеки soapclient.
Step1: создайте два класса для создания структуры для заголовков WSSE
class clsWSSEAuth {
private $Username;
private $Password;
function __construct($username, $password) {
$this->Username=$username;
$this->Password=$password;
}
}
class clsWSSEToken {
private $UsernameToken;
function __construct ($innerVal){
$this->UsernameToken = $innerVal;
}
}
Шаг 2: Создайте переменные Soap для имени пользователя и пароля
$username = 1111;
$password = 1111;
//Check with your provider which security name-space they are using.
$strWSSENS = "http://schemas.xmlsoap.org/ws/2002/07/secext";
$objSoapVarUser = new SoapVar($username, XSD_STRING, NULL, $strWSSENS, NULL, $strWSSENS);
$objSoapVarPass = new SoapVar($password, XSD_STRING, NULL, $strWSSENS, NULL, $strWSSENS);
Step3: создайте объект для класса Auth и передайте в soap var
$objWSSEAuth = new clsWSSEAuth($objSoapVarUser, $objSoapVarPass);
Step4: создайте SoapVar из объекта класса Auth
$objSoapVarWSSEAuth = new SoapVar($objWSSEAuth, SOAP_ENC_OBJECT, NULL, $strWSSENS, 'UsernameToken', $strWSSENS);
Step5: создание объекта для токена Класс!--12-->
$objWSSEToken = new clsWSSEToken($objSoapVarWSSEAuth);
Step6: создайте SoapVar из объекта класса токенов
$objSoapVarWSSEToken = new SoapVar($objWSSEToken, SOAP_ENC_OBJECT, NULL, $strWSSENS, 'UsernameToken', $strWSSENS);
Step7: создайте SoapVar для узла "безопасность"
$objSoapVarHeaderVal=new SoapVar($objSoapVarWSSEToken, SOAP_ENC_OBJECT, NULL, $strWSSENS, 'Security', $strWSSENS);
Step8: создайте объект заголовка из безопасности soapvar
$objSoapVarWSSEHeader = new SoapHeader($strWSSENS, 'Security', $objSoapVarHeaderVal,true, 'http://abce.com');
//Third parameter here makes 'mustUnderstand=1
//Forth parameter generates 'actor="http://abce.com"'
Step9: создайте объект Soap Client
$objClient = new SoapClient($WSDL, $arrOptions);
Step10: установите заголовки для объекта soapclient
$objClient->__setSoapHeaders(array($objSoapVarWSSEHeader));
Шаг 11: окончательный вызов метода
$objResponse = $objClient->__soapCall($strMethod, $requestPayloadString);
$client = new SoapClient("some.wsdl", array('login' => "some_name",
'password' => "some_password"));
Я принял отличное решение, Алена Тьембло, но я использую пароль, а не хэш.
/**
* This function implements a WS-Security authentication for PHP.
*
* @access private
* @param string $user
* @param string $password
* @return SoapHeader
*/
function soapClientWSSecurityHeader($user, $password)
{
// Creating date using yyyy-mm-ddThh:mm:ssZ format
$tm_created = gmdate('Y-m-d\TH:i:s\Z');
$tm_expires = gmdate('Y-m-d\TH:i:s\Z', gmdate('U') + 180); //only necessary if using the timestamp element
// Generating and encoding a random number
$simple_nonce = mt_rand();
$encoded_nonce = base64_encode($simple_nonce);
// Compiling WSS string
$passdigest = base64_encode(sha1($simple_nonce . $tm_created . $password, true));
// Initializing namespaces
$ns_wsse = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd';
$ns_wsu = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd';
$password_type = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText';
$encoding_type = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary';
// Creating WSS identification header using SimpleXML
$root = new SimpleXMLElement('<root/>');
$security = $root->addChild('wsse:Security', null, $ns_wsse);
//the timestamp element is not required by all servers
$timestamp = $security->addChild('wsu:Timestamp', null, $ns_wsu);
$timestamp->addAttribute('wsu:Id', 'Timestamp-28');
$timestamp->addChild('wsu:Created', $tm_created, $ns_wsu);
$timestamp->addChild('wsu:Expires', $tm_expires, $ns_wsu);
$usernameToken = $security->addChild('wsse:UsernameToken', null, $ns_wsse);
$usernameToken->addChild('wsse:Username', $user, $ns_wsse);
$usernameToken->addChild('wsse:Password', $password, $ns_wsse)->addAttribute('Type', $password_type);
$usernameToken->addChild('wsse:Nonce', $encoded_nonce, $ns_wsse)->addAttribute('EncodingType', $encoding_type);
$usernameToken->addChild('wsu:Created', $tm_created, $ns_wsu);
// Recovering XML value from that object
$root->registerXPathNamespace('wsse', $ns_wsse);
$full = $root->xpath('/root/wsse:Security');
$auth = $full[0]->asXML();
return new SoapHeader($ns_wsse, 'Security', new SoapVar($auth, XSD_ANYXML), true);
}
чтобы вызвать его, используйте
$client = new SoapClient('YOUR ENDPOINT');
$userid = "userid";
$password = "password";
$client->__setSoapHeaders(soapClientWSSecurityHeader($userid,$password));
WS Secure с паролем digest. Этот код работает для меня:
class WsseAuthHeader extends SoapHeader {
private $wss_ns = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd';
private $wsu_ns = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd';
private $type_password_digest= 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest';
private $type_password_text= 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText';
private $encoding_type_base64 = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary';
private function authText($user, $pass) {
$auth = new stdClass();
$auth->Username = new SoapVar($user, XSD_STRING, NULL, $this->wss_ns, NULL, $this->wss_ns);
$auth->Password = new SoapVar('<ns2:Password Type="'.$this->type_password_text.'">' . $pass . '</ns2:Password>', XSD_ANYXML );
return $auth;
}
private function authDigest($user, $pass) {
$created = gmdate('Y-m-d\TH:i:s\Z');
$nonce = mt_rand();
$enpass = base64_encode(pack('H*', sha1(pack('H*', $nonce) . pack('a*', $created) . pack('a*', $pass))));
$auth = new stdClass();
$auth->Username = new SoapVar($user, XSD_STRING, NULL, $this->wss_ns, NULL, $this->wss_ns);
$auth->Password = new SoapVar('<ns2:Password Type="'.$this->type_password_digest.'">' . $enpass . '</ns2:Password>', XSD_ANYXML );
$auth->Nonce = new SoapVar('<ns2:Nonce EncodingType="' . $this->encoding_type_base64 . '">' . base64_encode(pack('H*', $nonce)) . '</ns2:Nonce>', XSD_ANYXML);
$auth->Created = new SoapVar($created, XSD_STRING, NULL, $this->wss_ns, NULL, $this->wsu_ns);
return $auth;
}
function __construct($user, $pass, $useDigest=true) {
if ($useDigest) {
$auth = $this->authDigest($user, $pass);
}else{
$auth = $this->authText($user, $pass);
}
$username_token = new stdClass();
$username_token->UsernameToken = new SoapVar($auth, SOAP_ENC_OBJECT, NULL, $this->wss_ns, 'UsernameToken', $this->wss_ns);
$security_sv = new SoapVar(
new SoapVar($username_token, SOAP_ENC_OBJECT, NULL, $this->wss_ns, 'UsernameToken', $this->wss_ns),
SOAP_ENC_OBJECT, NULL, $this->wss_ns, 'Security', $this->wss_ns);
parent::__construct($this->wss_ns, 'Security', $security_sv, true);
}
}
использование:
$client->__setSoapHeaders([new WsseAuthHeader($login, $password)]);