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:
Agregue las credenciales de inicio de sesión a la url WSDL en la
SoapClient
constructorSoapClient
$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).
Obtenga el WSDL de forma manual utilizando el derivador HTTP stream o
ext/curl
o manualmente a través de su navegador o víawget
por ejemplo, almacénelo en el disco eSoapClient
elSoapClient
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"));