soapclient net ejemplo consume php web-services soap ws-security

net - Conexión al servicio web protegido con WS-Security con PHP



ws security ejemplo (7)

Estoy intentando conectarme a un servicio web que está protegido con contraseña y la url es https. No puedo averiguar cómo autenticar antes de que el script haga una solicitud. Parece que hace una solicitud tan pronto como defino el servicio. Por ejemplo, si pongo:

$client = new SoapClient("https://example.com/WSDL/nameofservice", array(''trace'' => 1,) );

y luego ir al sitio en el navegador, obtengo:

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

Si intento definir el servicio como un servidor de Soap, como:

$server= new SoapServer("https://example.com/WSDL/nameofservice");

Yo obtengo:

<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>

Todavía no he intentado enviar un sobre sin procesar para ver qué devuelve el servidor, pero eso puede ser una solución. Pero esperaba que alguien pudiera decirme cómo puedo configurarlo usando las clases incorporadas de php. Intenté agregar "userName" y "password" a la matriz, pero eso no fue bueno. El problema es que ni siquiera puedo decir si estoy llegando al sitio remoto, y mucho menos si está rechazando la solicitud.


Adopté la excelente solución de Alain Tiemblo, pero uso la contraseña en lugar de un resumen.

/** * 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); }

Para llamarlo, use

$client = new SoapClient(''YOUR ENDPOINT''); $userid = "userid"; $password = "password"; $client->__setSoapHeaders(soapClientWSSecurityHeader($userid,$password));


El problema parece ser que el documento WSDL está de alguna manera protegido (autenticación básica: no creo que la compatibilidad con SoapClient sea ​​compatible con SoapClient , por lo que no tendrías suerte en este caso) y que el SoapClient no puede leer ni analizar la descripción del servicio.

En primer lugar, debe intentar abrir la ubicación WSDL en su navegador para comprobar si se le presenta un cuadro de diálogo de autenticación. Si hay un cuadro de diálogo de autenticación, debe asegurarse de que SoapClient utiliza las credenciales de inicio de sesión necesarias para recuperar el documento WSDL. El problema es que SoapClient solo enviará las credenciales proporcionadas con las opciones de login y password (así como la opción local_cert al usar la autenticación de certificado) al crear el cliente al invocar el servicio, no al recuperar el WSDL (ver here ). Hay dos métodos para superar este problema:

  1. Agregue las credenciales de inicio de sesión a la url WSDL en la SoapClient constructor SoapClient

    $client = new SoapClient( ''https://'' . urlencode($login) . '':'' . urlencode($password) . ''@example.com/WSDL/nameofservice'', array( ''login'' => $login, ''password'' => $password ) );

    Esta debería ser la solución más simple, pero en PHP here está escrito que esto tampoco funcionará (no lo he intentado).

  2. Obtenga el WSDL de forma manual utilizando el derivador HTTP stream o ext/curl o manualmente a través de su navegador o vía wget por ejemplo, almacénelo en el disco e SoapClient el SoapClient con una referencia al WSDL local.

    Esta solución puede ser problemática si el documento WSDL cambia ya que debe detectar el cambio y almacenar la nueva versión en el disco.

Si no se muestra un cuadro de diálogo de autenticación y puede leer el WSDL en su navegador, debe proporcionar más detalles para verificar otros posibles errores / problemas.

Este problema definitivamente no está relacionado con el servicio en sí, ya que SoapClient ahoga ya la lectura del documento de SoapClient del servicio antes de emitir una llamada al servicio en sí.

EDITAR:

Tener el archivo WSDL localmente es un primer paso: esto permitirá que SoapClient sepa cómo comunicarse con el servicio. No importa si el WSDL se sirve directamente desde la ubicación del servicio, desde otro servidor o se lee desde un archivo local: las URL de servicio están codificadas dentro del WSDL, por lo que SoapClient siempre sabe dónde buscar el punto final del servicio.

El segundo problema ahora es que SoapClient no tiene soporte para las especificaciones de WS-Security forma nativa, lo que significa que debe extender SoapClient para manejar los encabezados específicos. Un punto de extensión para agregar el comportamiento requerido sería SoapClient::__doRequest() que procesa previamente la carga XML antes de enviarla al punto final del servicio. Pero creo que la implementación de la solución de WS-Security requerirá un conocimiento decente de las especificaciones específicas de WS-Security. Quizás los encabezados WS-Security también se pueden crear y empaquetar en la solicitud XML utilizando SoapClient::__setSoapHeaders() y el SoapHeader apropiado, pero dudo que esto funcione, dejando la extensión SoapClient personalizada como la única posibilidad.

Una simple extensión de SoapClient sería

class My_SoapClient extends SoapClient { protected function __doRequest($request, $location, $action, $version) { /* * $request is a XML string representation of the SOAP request * that can e.g. be loaded into a DomDocument to make it modifiable. */ $domRequest = new DOMDocument(); $domRequest->loadXML($request); // modify XML using the DOM API, e.g. get the <s:Header>-tag // and add your custom headers $xp = new DOMXPath($domRequest); $xp->registerNamespace(''s'', ''http://www.w3.org/2003/05/soap-envelope''); // fails if no <s:Header> is found - error checking needed $header = $xp->query(''/s:Envelope/s:Header'')->item(0); // now add your custom header $usernameToken = $domRequest->createElementNS(''http://schemas.xmlsoap.org/ws/2002/07/secext'', ''wsse:UsernameToken''); $username = $domRequest->createElementNS(''http://schemas.xmlsoap.org/ws/2002/07/secext'', ''wsse:Username'', ''userid''); $password = $domRequest->createElementNS(''http://schemas.xmlsoap.org/ws/2002/07/secext'', ''wsse:Password'', ''password''); $usernameToken->appendChild($username); $usernameToken->appendChild($password); $header->appendChild($usernameToken); $request = $domRequest->saveXML(); return parent::__doRequest($request, $location, $action, $version); } }

Para una autenticación básica de WS-Security, deberá agregar lo siguiente al encabezado SOAP:

<wsse:UsernameToken> <wsse:Username>userid</wsse:Username> <wsse:Password>password</wsse:Password> </wsse:UsernameToken>

Pero como dije antes: creo que se necesita mucho más conocimiento sobre la especificación de WS-Security y la arquitectura de servicio dada para que funcione.

Si necesita una solución de nivel empresarial para todo el rango de especificación WS- * y si puede instalar módulos PHP, debe consultar el Marco de servicios web de WSO2 para PHP (WSO2 WSF / PHP)


Para una seguridad de resumen de contraseña, puede usar lo siguiente:

/** * 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); }

Para usarlo con PHP SoapClient, use esta forma:

$client = new SoapClient(''http://endpoint''); $client->__setSoapHeaders(soapClientWSSecurityHeader(''myUser'', ''myPassword'')); // $client->myService(array(''param'' => ''value'', ...);


Simplemente extienda el SoapHeader para crear una autenticación compilada 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));

Si necesita usar ws-security con un nonce y una marca de tiempo, Peter ha publicado una versión de actualización en http://php.net/manual/en/soapclient.soapclient.php#114976 de la que escribió que sí funcionaba para él:

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); } }

compare también con los detalles dados en la respuesta https://.com/a/18575154/367456


Tengo una solución más simple que ampliar la biblioteca de soapclient existente.

Paso 1: crea dos clases para crear una estructura para los encabezados de 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; } }

Paso 2: crea variables de jabón para nombre de usuario y contraseña

$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);

Paso 3: crea un objeto para la clase de autenticación y pasa una varilla de jabón

$objWSSEAuth = new clsWSSEAuth($objSoapVarUser, $objSoapVarPass);

Paso 4: crea SoapVar fuera del objeto de la clase Auth

$objSoapVarWSSEAuth = new SoapVar($objWSSEAuth, SOAP_ENC_OBJECT, NULL, $strWSSENS, ''UsernameToken'', $strWSSENS);

Paso 5: Crear objeto para la clase Token

$objWSSEToken = new clsWSSEToken($objSoapVarWSSEAuth);

Paso 6: crea SoapVar fuera del objeto de la clase Token

$objSoapVarWSSEToken = new SoapVar($objWSSEToken, SOAP_ENC_OBJECT, NULL, $strWSSENS, ''UsernameToken'', $strWSSENS);

Paso 7: Crear SoapVar para el nodo ''Seguridad''

$objSoapVarHeaderVal=new SoapVar($objSoapVarWSSEToken, SOAP_ENC_OBJECT, NULL, $strWSSENS, ''Security'', $strWSSENS);

Paso 8: Crear objeto de encabezado fuera de seguridad soapvar

$objSoapVarWSSEHeader = new SoapHeader($strWSSENS, ''Security'', $objSoapVarHeaderVal,true, ''http://abce.com''); //Third parameter here makes ''mustUnderstand=1 //Forth parameter generates ''actor="http://abce.com"''

Paso9: crea el objeto de Soap Client

$objClient = new SoapClient($WSDL, $arrOptions);

Paso 10: establecer encabezados para el objeto soapclient

$objClient->__setSoapHeaders(array($objSoapVarWSSEHeader));

Paso 11: Llamada final al método

$objResponse = $objClient->__soapCall($strMethod, $requestPayloadString);


WS Secure con contraseña de resumen Este código funciona para mí:

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); } }

Utilizar:

$client->__setSoapHeaders([new WsseAuthHeader($login, $password)]);


$client = new SoapClient("some.wsdl", array(''login'' => "some_name", ''password'' => "some_password"));

De la documentación php