Ant*_*ony 31 php ws-security soap web-services
我正在尝试连接到受密码保护且网址为https的Web服务.在脚本发出请求之前,我无法弄清楚如何进行身份验证.它似乎在我定义服务时立即发出请求.例如,如果我投入:
$client = new SoapClient("https://example.com/WSDL/nameofservice",
array('trace' => 1,)
);
Run Code Online (Sandbox Code Playgroud)
然后在浏览器上访问该站点,我得到:
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
Run Code Online (Sandbox Code Playgroud)
如果我尝试将服务定义为Soap服务器,例如:
$server= new SoapServer("https://example.com/WSDL/nameofservice");
Run Code Online (Sandbox Code Playgroud)
我明白了:
<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>
Run Code Online (Sandbox Code Playgroud)
我还没有尝试发送原始请求信封,看看服务器返回什么,但这可能是一种解决方法.但是我希望有人可以告诉我如何使用php内置类来设置它.我尝试将"userName"和"password"添加到数组中,但这并不好.问题是我甚至无法判断我是否到达了远程站点,更不用说它是否拒绝了请求.
小智 34
只需扩展SoapHeader即可创建Wsse compilant身份验证:
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));
Run Code Online (Sandbox Code Playgroud)
如果你需要使用带有nonce和时间戳的ws-security,Peter已经在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);
}
}
Run Code Online (Sandbox Code Playgroud)
与答案/sf/answers/1300260811/中给出的详细信息进行比较
Ste*_*rig 29
问题似乎是WSDL文档受到某种程度的保护(基本身份验证 - 我不认为支持摘要身份验证SoapClient,因此在这种情况下你会运气不好)SoapClient因此无法读取和解析服务描述.
首先,您应该尝试在浏览器中打开WSDL位置,以检查是否显示了身份验证对话框.如果存在身份验证对话框,则必须确保SoapClient在检索WSDL文档时使用所需的登录凭据.问题是,在调用服务时,SoapClient只会在创建客户端时发送带有login和password选项(以及local_cert使用证书身份验证时的选项)的凭据,而不是在获取WSDL时(请参阅此处).有两种方法可以解决这个问题:
在SoapClient构造函数调用上将登录凭据添加到WSDL URL
$client = new SoapClient(
'https://' . urlencode($login) . ':' . urlencode($password) . '@example.com/WSDL/nameofservice',
array(
'login' => $login,
'password' => $password
)
);
Run Code Online (Sandbox Code Playgroud)
这应该是最简单的解决方案 - 但是在PHP Bug#27777中写道,这也不起作用(我没试过).
使用HTTP流包装器手动获取WSDL,ext/curl或者通过浏览器手动获取WSDL,或者通过wget例如将其存储在磁盘上并SoapClient使用对本地WSDL的引用来实例化.
如果WSDL文档发生更改,则此解决方案可能会出现问题,因为您必须检测更改并将新版本存储在磁盘上.
如果未显示身份验证对话框,并且您可以在浏览器中阅读WSDL,则应提供更多详细信息以检查其他可能的错误/问题.
SoapClient在发出对服务本身的调用之前,这个问题明确地与服务本身无关,因为它已经在读取服务描述文档时发生了扼流.
编辑:
在本地拥有WSDL文件是第一步 - 这将允许SoapClient知道如何与服务进行通信.无论WSDL是直接从服务位置,从另一个服务器提供还是从本地文件读取都无关紧要 - 服务URL在WSDL中编码,因此SoapClient始终知道在哪里查找服务端点.
现在的第二个问题是本机SoapClient不支持WS-Security规范,这意味着您必须扩展SoapClient以处理特定的头.添加所需行为的扩展点将是在将SoapClient::__doRequest()XML有效负载发送到服务端点之前对其进行预处理.但我认为自己实施WS-Security解决方案需要对特定的WS-Security规范有一定的了解.也许WS-Security标头也可以通过使用SoapClient::__setSoapHeaders()和适当的SoapHeaders 创建并打包到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);
}
}
Run Code Online (Sandbox Code Playgroud)
对于基本的WS-Security身份验证,您必须将以下内容添加到SOAP标头:
<wsse:UsernameToken>
<wsse:Username>userid</wsse:Username>
<wsse:Password>password</wsse:Password>
</wsse:UsernameToken>
Run Code Online (Sandbox Code Playgroud)
但正如我上面所说:我认为需要更多关于WS-Security规范和给定服务架构的知识才能实现这一点.
如果您需要针对整个WS-*规范范围的企业级解决方案,并且如果您可以安装PHP模块,那么您应该查看适用于PHP的WSO2 Web服务框架(WSO2 WSF/PHP)
Ala*_*blo 18
对于密码摘要安全性,您可以使用以下内容:
/**
* 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);
}
Run Code Online (Sandbox Code Playgroud)
要与PHP SoapClient一起使用,请使用以下方法:
$client = new SoapClient('http://endpoint');
$client->__setSoapHeaders(soapClientWSSecurityHeader('myUser', 'myPassword'));
// $client->myService(array('param' => 'value', ...);
Run Code Online (Sandbox Code Playgroud)
小智 7
我有比扩展现有soapclient库更简单的解决方案.
步骤1:创建两个类以创建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;
}
}
Run Code Online (Sandbox Code Playgroud)
第二步:为UserName和Password创建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);
Run Code Online (Sandbox Code Playgroud)
第3步:为Auth类创建对象并传入soap var
$objWSSEAuth = new clsWSSEAuth($objSoapVarUser, $objSoapVarPass);
Run Code Online (Sandbox Code Playgroud)
Step4:从Auth类的对象中创建SoapVar
$objSoapVarWSSEAuth = new SoapVar($objWSSEAuth, SOAP_ENC_OBJECT, NULL, $strWSSENS, 'UsernameToken', $strWSSENS);
Run Code Online (Sandbox Code Playgroud)
步骤5:为Token Class创建对象
$objWSSEToken = new clsWSSEToken($objSoapVarWSSEAuth);
Run Code Online (Sandbox Code Playgroud)
Step6:从Token类的对象中创建SoapVar
$objSoapVarWSSEToken = new SoapVar($objWSSEToken, SOAP_ENC_OBJECT, NULL, $strWSSENS, 'UsernameToken', $strWSSENS);
Run Code Online (Sandbox Code Playgroud)
步骤7:为"安全"节点创建SoapVar
$objSoapVarHeaderVal=new SoapVar($objSoapVarWSSEToken, SOAP_ENC_OBJECT, NULL, $strWSSENS, 'Security', $strWSSENS);
Run Code Online (Sandbox Code Playgroud)
步骤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"'
Run Code Online (Sandbox Code Playgroud)
Step9:创建Soap Client的对象
$objClient = new SoapClient($WSDL, $arrOptions);
Run Code Online (Sandbox Code Playgroud)
步骤10:为soapclient对象设置标头
$objClient->__setSoapHeaders(array($objSoapVarWSSEHeader));
Run Code Online (Sandbox Code Playgroud)
第11步:最后调用方法
$objResponse = $objClient->__soapCall($strMethod, $requestPayloadString);
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
73462 次 |
| 最近记录: |