web-dev-qa-db-ja.com

PHPを使用してWS-Securityで保護されたWebサービスに接続する

パスワードで保護され、URLがhttpsであるWebサービスに接続しようとしています。スクリプトが要求を行う前に認証する方法がわかりません。サービスを定義するとすぐにリクエストを行うようです。たとえば、私が入れた場合:

$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の組み込みクラスを使用してそれを設定する方法を教えてくれることを望んでいました。 「userName」と「password」をアレイに追加しようとしましたが、それは良くありませんでした。問題は、要求を拒否しているかどうかは言うまでもなく、リモートサイトに到達しているかどうかさえわからないことです。

30
Anthony

問題は、WSDLドキュメントが何らかの形で保護されているようです(基本認証-ダイジェスト認証がSoapClientでサポートされているとは思わないので、この場合は運が悪いでしょう)、したがってSoapClientは読み取りおよび解析できませんサービスの説明。

まず、ブラウザでWSDLの場所を開いて、認証ダイアログが表示されるかどうかを確認する必要があります。認証ダイアログがある場合は、SoapClientがWSDLドキュメントの取得時に必要なログイン資格情報を使用することを確認する必要があります。問題は、SoapClientは、WSDLをフェッチするときではなく、サービスを呼び出すときにクライアントを作成するときに、loginおよびpasswordオプション(および証明書認証を使用する場合は_local_cert_オプション)で指定された資格情報のみを送信することです( ここ )。この問題を解決する方法は2つあります。

  1. SoapClientコンストラクター呼び出しでWSDLのURLにログイン資格情報を追加します

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

    これは最も簡単な解決策であるはずです-しかし、PHP Bug#27777 では、これも機能しないと書かれています(私は試していません)。

  2. HTTPストリームラッパーまたは_ext/curl_を使用してWSDLを手動で取得するか、ブラウザまたはwgetを使用して手動でWSDLを取得し、ディスクに保存して、ローカルWSDLへの参照を使用してSoapClientをインスタンス化します。

    このソリューションは、変更を検出してディスクに新しいバージョンを保存する必要があるため、WSDLドキュメントが変更された場合に問題になる可能性があります。

認証ダイアログが表示されず、ブラウザーでWSDLを読み取ることができる場合、他の考えられるエラー/問題をチェックするために、いくつかの詳細を提供する必要があります。

SoapClientは、サービス自体への呼び出しを発行する前に、サービスの説明文書を読み取るときにすでにチョークしているため、この問題はサービス自体とは関係ありません。

編集:

WSDLファイルをローカルに用意することが最初のステップです。これにより、SoapClientがサービスと通信する方法を知ることができます。 WSDLがサービスの場所から直接提供されるか、別のサーバーから提供されるか、ローカルファイルから読み取られるかは問題ではありません。サービスURLはWSDL内でコーディングされるため、SoapClientは常にサービスエンドポイントの検索場所を認識します。

2番目の問題は、SoapClientWS-Security 仕様をネイティブにサポートしていないことです。つまり、SoapClientを拡張して特定のヘッダーを処理する必要があります。必要な動作を追加する拡張ポイントは SoapClient::__doRequest() になり、サービスエンドポイントに送信する前にXMLペイロードを前処理します。しかし、WS-Securityソリューションを自分で実装するには、特定のWS-Security仕様に関する適切な知識が必要だと思います。 SoapClient::__setSoapHeaders() および適切な SoapHeader sを使用して、WS-Securityヘッダーを作成し、XML要求にパックすることもできますが、これが機能するかどうかは疑問です、カスタムSoapClient拡張を唯一の可能性として残します。

単純なSoapClient拡張子は

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

基本的なWS-Security認証の場合、以下をSOAPヘッダーに追加する必要があります。

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

しかし、上記で述べたように、これを機能させるには、WS-Security仕様と特定のサービスアーキテクチャに関するより多くの知識が必要だと思います。

WS- *仕様範囲全体にエンタープライズグレードのソリューションが必要な場合、およびPHPモジュールをインストールできる場合は、 WSO2 PHP(WSO2 WSF/PHP)のWebサービスフレームワーク

28
Stefan Gehrig

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を使用する必要がある場合、ピーターは 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

34
Chris

パスワードダイジェストセキュリティの場合、次を使用できます。

   /**
    * 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', ...);
17
Alain Tiemblo

既存のsoapclientライブラリを拡張するよりも簡単なソリューションがあります。

ステップ1:2つのクラスを作成して、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);

ステップ3:認証クラスのオブジェクトを作成し、soap varを渡す

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

ステップ4:AuthクラスのオブジェクトからSoapVarを作成する

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

ステップ5:トークンクラスのオブジェクトを作成する

$objWSSEToken = new clsWSSEToken($objSoapVarWSSEAuth);

ステップ6:TokenクラスのオブジェクトからSoapVarを作成する

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

ステップ7:「セキュリティ」ノードのSoapVarを作成する

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

ステップ8:セキュリティsoapvarからヘッダーオブジェクトを作成する

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

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

ステップ9:Soapクライアントのオブジェクトを作成する

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

ステップ10:soapclientオブジェクトのヘッダーを設定する

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

ステップ11:メソッドの最後の呼び出し

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

Alain Tiembloの優れたソリューションを採用しましたが、ダイジェストではなくパスワードを使用しています。

    /**
    * 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));
1
Scott C Wilson

ダイジェストパスワードによるWSセキュア。このコードは私のために働く:

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)]);
0
$client = new SoapClient("some.wsdl", array('login'    => "some_name",
                                            'password' => "some_password"));

PHPドキュメントから

0
Jase Whatson