天天看點

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