web-dev-qa-db-fra.com

Connexion au service Web protégé WS-Security avec PHP

J'essaie de me connecter à un service Web protégé par mot de passe et dont l'adresse URL est https. Je n'arrive pas à comprendre comment s'authentifier avant que le script ne fasse une demande. Il semble que cela fasse une demande dès que je définis le service. Par exemple, si je mets dans:

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

et puis allez sur le site sur le navigateur, je reçois:

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 j'essaie de définir le service en tant que serveur Soap, procédez comme suit:

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

Je reçois:

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

Je n'ai pas encore essayé d'envoyer une enveloppe de requête brute pour voir ce que le serveur renvoie, mais cela peut constituer une solution de contournement. Mais j'espérais que quelqu'un pourrait me dire comment je pourrais le configurer en utilisant les classes intégrées php. J'ai essayé d'ajouter "userName" et "password" au tableau, mais ce n'était pas bon. Le problème est que je ne peux même pas dire si j'atteins le site distant, et encore moins s'il refuse la demande.

30
Anthony

Le problème semble être que le document WSDL est en quelque sorte protégé (authentification de base - je ne pense pas que l’authentification Digest est prise en charge avec SoapClient, vous n’auriez donc pas de chance dans ce cas) et que SoapClient ne peut donc pas lire et analyser. la description du service. 

Tout d’abord, vous devriez essayer d’ouvrir l’emplacement WSDL dans votre navigateur pour vérifier si une boîte de dialogue d’authentification vous est présentée. S'il existe une boîte de dialogue d'authentification, vous devez vous assurer que la variable SoapClient utilise les informations d'identification de connexion requises lors de la récupération du document WSDL. Le problème est que SoapClient n'enverra que les informations d'identification fournies avec les options login et password (ainsi que l'option local_cert lors de l'utilisation de l'authentification par certificat) lors de la création du client lors de l'appel du service, et non lors de l'extraction du WSDL (voir ici ). Il existe deux méthodes pour résoudre ce problème:

  1. Ajoutez les informations d'identification de connexion à l'URL WSDL lors de l'appel du constructeur SoapClient

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

    Cela devrait être la solution la plus simple - mais dans PHP Bug # 27777 il est écrit que cela ne fonctionnera pas non plus (je n'ai pas essayé cela).

  2. Récupérez le WSDL manuellement à l'aide de l'encapsuleur de flux HTTP ou de ext/curl ou manuellement via votre navigateur ou via wget par exemple, stockez-le sur le disque et instanciez la SoapClient avec une référence au WSDL local. 

    Cette solution peut poser problème si le document WSDL est modifié, car vous devez le détecter et stocker la nouvelle version sur le disque.

Si aucune boîte de dialogue d'authentification ne s'affiche et que vous pouvez lire le WSDL dans votre navigateur, vous devez fournir des informations supplémentaires pour rechercher d'autres erreurs/problèmes éventuels.

Ce problème n’est définitivement pas lié au service lui-même car SoapClient étouffe déjà lors de la lecture du document de description de service avant d’appeler le service lui-même.

MODIFIER:

Avoir le fichier WSDL en local est une première étape - cela permettra à la SoapClient de savoir comment communiquer avec le service. Peu importe que le WSDL soit directement servi à partir de l'emplacement du service, d'un autre serveur ou lu à partir d'un fichier local: les URL du service sont codées dans le WSDL. SoapClient sait toujours où chercher le point de terminaison du service.

Le deuxième problème est que SoapClient ne prend pas en charge nativement les spécifications WS-Security , ce qui signifie que vous devez étendre SoapClient pour gérer les en-têtes spécifiques. Un point d’extension auquel ajouter le comportement requis serait SoapClient::__doRequest() qui pré-traite la charge XML avant de l’envoyer au point de terminaison du service. Mais je pense que pour implémenter vous-même la solution WS-Security, vous devrez bien connaître les spécifications spécifiques de WS-Security. Peut-être que des en-têtes WS-Security peuvent également être créés et intégrés dans la requête XML en utilisant SoapClient::__setSoapHeaders() et le SoapHeader s approprié, mais je doute que cela fonctionne, en laissant l’extension personnalisée SoapClient comme seule possibilité.

Une simple extension SoapClient serait

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

Pour une authentification WS-Security de base, vous devez ajouter les éléments suivants à l'en-tête SOAP:

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

Mais comme je l’ai dit plus haut: je pense qu’il faut beaucoup plus de connaissances sur la spécification WS-Security et l’architecture de service proposée pour que cela fonctionne.

Si vous avez besoin d'une solution d'entreprise pour toute la gamme de spécifications WS- * et si vous pouvez installer des modules PHP, vous devriez consulter le WSO2 Web Services Framework pour PHP (WSO2 WSF/PHP)

28
Stefan Gehrig

Il suffit simplement d’étendre SoapHeader pour créer une authentification par compilateur 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 vous devez utiliser ws-security avec un nonce et un horodatage, Peter a publié une version de mise à jour sur http://php.net/manual/en/soapclient.soapclient.php#114976 dont il a écrit ce qui suit: cela a fonctionné pour lui:

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

comparer également avec les détails donnés dans la réponse https://stackoverflow.com/a/18575154/367456

33
Chris

Pour une sécurité de résumé du mot de passe, vous pouvez utiliser les éléments suivants:

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

Pour l'utiliser avec PHP SoapClient, utilisez cette méthode:

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

J'ai une solution plus simple que d'étendre la bibliothèque existante de soapclient.

Étape 1: créez deux classes pour créer une structure pour les en-têtes 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;
    }
}

Étape 2: Créer des variables de savon pour le nom d'utilisateur et le mot de passe

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

Étape 3: Créez un objet pour la classe d’authentification et transmettez-la dans soap var

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

Étape 4: Créer SoapVar à partir d'un objet de la classe Auth

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

Étape 5: Créer un objet pour la classe de jeton

$objWSSEToken = new clsWSSEToken($objSoapVarWSSEAuth);

Étape 6: Créer SoapVar à partir d'un objet de la classe Token

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

Étape 7: Créer SoapVar pour le nœud 'Sécurité'

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

Etape 8: Créer un objet d’en-tête à partir de la soapvar de sécurité

$objSoapVarWSSEHeader = new SoapHeader($strWSSENS, 'Security', $objSoapVarHeaderVal,true, 'http://abce.com');

//Third parameter here makes 'mustUnderstand=1
//Forth parameter generates 'actor="http://abce.com"'

Étape 9: Créer un objet de client de savon

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

Étape 10: Définir les en-têtes pour l'objet soapclient

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

Étape 11: appel final à la méthode

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

J'ai adopté l'excellente solution d'Alain Tiemblo, mais j'utilise le mot de passe plutôt qu'un résumé.

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

Pour l'appeler, utilisez 

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

WS Secure avec mot de passe condensé. Ce code fonctionne pour moi:

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

Utilisation:

 $client->__setSoapHeaders([new WsseAuthHeader($login, $password)]);
0
Robert Haliullov
$client = new SoapClient("some.wsdl", array('login'    => "some_name",
                                            'password' => "some_password"));

À partir de la documentation php

0
Jase Whatson