天天看点

PHP cURL模拟登录HTTPS无验证码的WebServices

近期使用PHP的SoapClient调用.net的WebServices始终行不通,然而又没有对方技术人员支持的情况,尝试着 WSDL 模式和 non-WSDL 模式都无果,在 new SoapClient时直接会报错,在网上找了一个服务是可以正常调用的,说明运行环境正常:

$wsdlUrl = "http://www.webxml.com.cn/WebServices/IpAddressSearchWebService.asmx?wsdl";
$client = new SoapClient($wsdlUrl, $array('encoding' => 'utf-8'));
//$client = new SoapClient(null, array('encoding' => 'utf-8', 'uri'=> $uri, 'location' => $wsdlUrl, 'trace' => true));
           

但我这边调用的服务是 HTTPS 带登录验证的服务,连实例化时都过不去,于是改用POST方式尝试着。

对方给的 WebServices URL直接访问的是跳登录页了,登录之后访问 WebServices 是正常的,则先使用cURL的模拟登录然后携带登录返回的cookie请求WebServices,请求的参数中有一个是将XML转为 base64Binary 传输。

传输的编码 Content-Type:application/x-www-form-urlencoded

所以在这里POST的参数为字符串,如 param1=value1&param2=value2

如果使用数组,则 Content-Type 会变为表单上传的类型。

以下用代码和注释说明:

<?php
/**
 * CurlSoap
 * 
 * 参考:
 * https://github.com/Zjmainstay/php-curl
 * https://segmentfault.com/a/1190000003808546
 */
class CurlSoap 
{
    /**
     * @var string 服务地址,测试 https://localhost/Service.asmx/HelloWorld
     */
    public $wsdl = 'https://localhost/Service.asmx/VisitorData';
    /**
     * @var string 验证地址
     */
    public $checkUrl = 'https://localhost/login.form';
    /**
     * @var string 如果失败尝试次数
     */
    protected $testTry = 3;
    protected $isCheck;
    protected $cookieArr;
    private $supplyCode;
    private $username;
    private $password;
    
    
    public function __construct()
    {
        $this->cookieArr = array(array(),array());
        $this->supplyCode = '';
        $this->username = 'username';
        $this->password = 'password';
        //先检测是否已登录
        if(!$this->isCheck){
            $this->checking();
        }
    }
    
    
    /**
     * 请求服务
     * 
     */
    public function postdata()
    {
        
        try {
            //将XML转为 base64Binary , xml模板由服务方提供
            $xmlStr = $this->getXml();
            //先将XML转为 base64 string
            $baseEncode = base64_encode($xmlStr);
            //再转为 byte 数组
            $base64BinaryArr = $this->getBytes($baseEncode);
            //将byte拼接为POST 的字符串参数时, 因对方不需要 &bytes[]= 可以接受到所有参数
            $bytesParam = implode('&bytes=', $base64BinaryArr);
            
            $param = array(
                'url' => $this->wsdl,
                'cookie' => $this->cookieArr[1], //携带登录后的cookie访问
                'header' => array(
                    'Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
                    'Content-Type:application/x-www-form-urlencoded'
                ),
                'data' => 'supplyCode='. $this->supplyCode .'&bytes='. $bytesParam
            );
            
            $res = $this->curlPost($param);
            if($res){
                $xmlObj = simplexml_load_string($res);
                // print_r($xmlObj); exit;
                if(strtoupper($xmlObj->CODE) == 'S'){
                    return true;
                }
            }
            
        } catch (Exception $e) {
            // libxml_get_last_error();
        }
        return false;
    }
    
    
    protected function getXml()
    {
        $xml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
            <msg ver="1.0">
              <meta>
                <field name="SOURCE" val="" />
              </meta>
              <head>
                <field name="SEND" val="'. date('Y-m-d H:i:s') .'" type="System.DateTime" />
              </head>
              <body>
            ';
        
        $xmlTpl = '<vo name="VISITOR">
              <field name="ID" val="%d" type="System.Int32" />
              <field name="NAME" val="%s" type="System.String" />
            </vo>
            ';
        $data = array(
            array('id' => 1, 'user_name' => 'test'),
        );
        foreach ($data as $val){
            $xml .= sprintf($xmlTpl, $val['id'], trim($val['user_name']) );
        }
        
        $xml .= '</body>
            </msg>';
        
        //格式化xml
        $xml = preg_replace('/\s{2,}/', '', $xml);
        $xml = str_replace('><', ">\n<", $xml);
        return $xml;
    }
    
    
    /**
     * 登录
     * 
     */
    private function checking()
    {
        $param = array(
            'url' => $this->checkUrl,
            'nobody' => 1,
            'header' => array(
                'Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
                'Content-Type:application/x-www-form-urlencoded'
            ),
            'data' => 'username='. $this->username .'&password='. $this->password
        );
        //多次尝试登陆
        for($i=0; $i < $this->testTry; $i++){
            $resData = $this->curlPost($param);
            if(preg_match('@HTTP/1.1\s*200\s*[email protected]', $resData)){
                $this->isCheck = true;
                //登录成功 保存Cookie
                preg_match_all('|Set-Cookie: (.*);|U', $resData, $this->cookieArr);
                break;
            }
        }
        
    }
    
    
    protected function curlPost($param)
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $param['url']);
        // 对认证证书来源的检查, false 表示阻止对证书的合法性的检查
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        // 从证书中检查SSL加密算法是否存在
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);//关闭直接输出
        curl_setopt($ch,CURLOPT_POST,1);//使用post提交数据
        curl_setopt($ch,CURLOPT_POSTFIELDS, $param['data']);//设置 post提交的数据, 区别数组与字符串
        curl_setopt($ch,CURLOPT_USERAGENT,'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36');//设置用户代理
        
        //Boolean
        if(isset($param['nobody'])){
            //curl_setopt($ch, CURLOPT_NOBODY, 1); //不需要HTML实体
            curl_setopt($ch, CURLOPT_HEADER, 1); //是否头文件信息
        }
        /**
         * Array
         * array('Content-Type:application/x-www-form-urlencoded', 'Referer:https://localhost')
         */
        if(isset($param['header'])){
            curl_setopt($ch,CURLOPT_HTTPHEADER, $param['header']);//设置头信息
        }
        /**
         * 携带Cookie访问
         * array(0=>'SID=abcde', 1=>'ppid=123')
         */
        if(isset($param['cookie'])){
            $cookies = implode('; ', $param['cookie']);
            curl_setopt($ch, CURLOPT_COOKIE,  $cookies);
        }
        // curl_setopt($ch, CURLOPT_SSLVERSION, 3); //设定SSL版本
        //保存Cookie
        //$cookie_file = '/tmp/rqwebservices.cookie'; //tempnam('/tmp','cookie');
        // curl_setopt($ch,CURLOPT_COOKIEJAR,$cookie_file);//设置cookie的保存目录
        $data = curl_exec($ch);
        if (curl_errno($ch)) {
            return false;
        }
        curl_close($ch);
        return $data;
    }
    
    
    /**
     * 将字节数组转化为String类型的数据 
     * @param array $bytes
     * @return string
     */
    public function toStr($bytes) { 
        $str = ''; 
        foreach($bytes as $ch) { 
            $str .= chr($ch); 
        }
        return $str; 
    } 
    
    
    /**
     * 转换一个String字符串为byte数组
     * @param string $string
     * @return array
     */
    public function getBytes($string) {
        $bytes = array();
        for($i = 0; $i < strlen($string); $i++){
            $bytes[] = ord($string[$i]);
        }
        return $bytes;
    }
}