Question

I'm trying to implement WSSE authentication on a Web Services API. I followed this tutorial on symfony official site. I send requests with SoapUI, with security headers :

POST http://dev.sellerphp.com/app_dev.php/wsapi/soap/ordersfollowup HTTP/1.0
Accept-Encoding: gzip,deflate
Content-Type: text/xml;charset=UTF-8
SOAPAction: "http://dev.sellerphp.com/app_dev.php/wsapi/soap/ordersfollowup"
Content-Length: 1520
Host: dev.sellerphp.com
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.1.1 (java 1.5)

<soapenv:Envelope xmlns:ord="https://dev.sellerphp.com/app_dev.php/wsapi/soap/ordersfollowup?wsdl" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsap="http://wsapi.sellerphp.com/">
   <soapenv:Header>
       <wsse:Security soap: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">
           <wsu:Timestamp wsu:Id="TS-14">
               <wsu:Created>2012-10-10T09:36:10Z</wsu:Created>
               <wsu:Expires>2012-10-10T09:52:50Z</wsu:Expires>
           </wsu:Timestamp>
           <wsse:UsernameToken wsu:Id="UsernameToken-13">
               <wsse:Username>myUsername</wsse:Username>
               <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">hb5AJ7CT2tMSQymSsxwvc8J/xoI=</wsse:Password> 
               <wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">PEo//Z/yGF5/HCW6rkCuwQ==</wsse:Nonce>
               <wsu:Created>2012-10-10T09:36:10.485Z</wsu:Created>
           </wsse:UsernameToken>
        </wsse:Security>
   </soapenv:Header>
   <soapenv:Body>
      <wsap:setTracking>
         <input>
            <!--1 to 10 repetitions:-->
            <OrderItemTracking>
               <order-item-id>?</order-item-id>
               <tracking-number>?</tracking-number>
            </OrderItemTracking>
         </input>
      </wsap:setTracking>
   </soapenv:Body>
</soapenv:Envelope>

On the server-side, the WsseListener class doesn't read SOAP headers... The example only involves that headers are defined on the Request level, but they are only embedded in SOAP envelope :

 public function handle(GetResponseEvent $event)
   {
      $request = $event->getRequest();

      $wsseRegex = '/UsernameToken Username="([^"]+)", PasswordDigest="([^"]+)", Nonce="([^"]+)", Created="([^"]+)"/';
      if (!$request->headers->has('x-wsse') || 1 !== preg_match($wsseRegex, $request->headers->get('x-wsse'), $matches))
      {
         return;
      }

      // ########### It never reached this line, never found any headers...

      var_dump($matches);exit;

      $token = new WsseUserToken();
      $token->setUser($matches[1]);

      $token->digest = $matches[2];
      $token->nonce = $matches[3];
      $token->created = $matches[4];

      try
      {
         $authToken = $this->authenticationManager->authenticate($token);

         $this->securityContext->setToken($authToken);
      }
      catch (AuthenticationException $failed)
      {
         // ... you might log something here
         // To deny the authentication clear the token. This will redirect to the login page.
         // $this->securityContext->setToken(null);
         // return;
         // Deny authentication with a '403 Forbidden' HTTP response
         $response = new Response();
         $response->setStatusCode(403);
         $event->setResponse($response);
      }
   }

I don't know how to read soap:Header headers from the listener. Can you help me ?

Was it helpful?

Solution

I finally admitted that samples provided by Symfony implies that WSSE headers are brought by HTTP layer, not by the SOAP envelope. So I made a workaround to read SOAP headers :

The WsseListener handle method :

   public function handle(GetResponseEvent $event)
   {
      $request = $event->getRequest();

      $decodedHeaders = WsseHeadersDecoder::getHeaders($request);

      if (!$decodedHeaders || !is_array($decodedHeaders))
      {
         return;
      }

      $token = new WsseUserToken();
      $token->setUser($decodedHeaders['username']);

      $token->digest = $decodedHeaders['passwordDigest'];
      $token->nonce = $decodedHeaders['nonce'];
      $token->created = $decodedHeaders['created'];

      // ...
   }

WsseHeadersDecoder::getHeaders method :

   static public function getHeaders(Request $request)
   {
      //HTTP headers (as described here : http://symfony.com/doc/2.0/cookbook/security/custom_authentication_provider.html#the-listener
      if ($request->headers->has('x-wsse'))
      {
         $wsseRegex = '/UsernameToken Username="([^"]+)", PasswordDigest="([^"]+)", Nonce="([^"]+)", Created="([^"]+)"/';
         if (1 !== preg_match($wsseRegex, $request->headers->get('x-wsse'), $matches))
         {
            return false;
         }
         else
         {
            $username = $matches[1];
            $passwordDigest = $matches[2];
            $nonce = $matches[3];
            $created = $matches[4];
         }
      }
      //Classic SOAP headers
      else
      {
         //PéCé: Clear XML namespace prefixes to handle with SimpleXML
         $decodedRequest = preg_replace("/(<\/?)([-\w]+):([^>]*>)/", "$1$3", $request->getContent());
         $xmlRequest = simplexml_load_string($decodedRequest);

         if (
            !isset($xmlRequest->Header->Security->UsernameToken->Username)
            || !isset($xmlRequest->Header->Security->UsernameToken->Password)
            || !isset($xmlRequest->Header->Security->UsernameToken->Nonce)
            || !isset($xmlRequest->Header->Security->UsernameToken->Created)
         )
         {
            return false;
         }
         else
         {
            $username = (string) $xmlRequest->Header->Security->UsernameToken->Username;
            $passwordDigest = (string) $xmlRequest->Header->Security->UsernameToken->Password;
            $nonce = (string) $xmlRequest->Header->Security->UsernameToken->Nonce;
            $created = (string) $xmlRequest->Header->Security->UsernameToken->Created;
         }
      }

      return array (
              'username' => $username, 'passwordDigest' => $passwordDigest, 'nonce' => $nonce, 'created' => $created
      );
   }
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top