【PHP】使用curl发送json-post请求

*复盘工作中遇到的问题并整理出知识点。

需求

维护并升级一个历史悠久的PHP项目。该项目要求我在数据安全与通信完整性方面,实施严格的加密与签名机制。具体而言,需要使用RSA加解密与外部进行校验以保障敏感数据的传输安全,同时,为了验证数据的完整性和来源的真实性,还要使用hmacSHA256签名技术,避免在近源攻击上留下隐患。

尽管这些接口在业务逻辑上各有千秋,但它们在处理加密与签名流程时却展现出了惊人的共性:变化的仅仅是请求的uri和消息体内容,而加密与签名的核心逻辑则保持了一致性和可复用性,因此复盘整理出下文中API类来完成对项目接口的调用。

*为了安全考虑,对部分内容省略以及脱敏。这意味着你需要对代码进行适当修改然后才可以跑通

实现

  1. 开启php.ini中的扩展

    extension=curl

  2. 配置http请求类
    /**
     * 科技 API
     */
    class API{
        /**
         * 客户端id
         * 用于获取appKey、appSecret、客户端公钥、服务端私钥等
         * @var string
         */
        private static $ClientUuid = "1234a567-b89c-12d3-45e6-fa789b0cde1";
        
        /**
         * 发送请求处理 * 业务
         * @param string $host 示例:http://127.0.0.1:3000
         * @param string $uri 示例:/api/interface/method
         * @param mixed $post_data 消息体对象
         * @param boolean $debug 测试使用
         * @return mixed
         */
        public static function httpRequest($host, $uri, $post_data, $debug = false) {
            $ch = curl_init(); // 初始化cURL对话
            curl_setopt($ch, CURLOPT_URL, $host.$uri); // 设置请求地址
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // 获取的信息以文件流的形式返回
            $headers = [ // 设置请求头
                'key' => Query::getAppKey(self::$ClientUuid), // 这里放你的appKey
                'nounce' => Libs::createUuid(), // 为请求头设置一个随机值
                'signMethod' => 'HmacSHA256', // 签名方法
                'time' => strval(self::timenow()) // 这里是13位数的时间戳字符串
            ];
            // 对敏感信息进行 RSA 加密
            if(isset($post_data['phone'])) { // 如果存在电话
                $post_data['phone'] = RSA::publicEncrypt($post_data['phone']);
            }
            ksort($post_data); // 对一级键值对进行严格排序
            // 设置签名数据
            $e = $headers;
            $e['method'] = "POST";
            $e['uri'] = $uri;
            $e['data'] = $post_data;
            ksort($e); // 严格排序
            // 签名
            $temp = self::hmacSHA256($e, $post_data); // 代码省略,此处对数据进行签名
            $headers['sign'] = $temp['sign']; // 然后将结果放在请求头签名结果里
            if($debug) echo "签名字符串:<br>".$temp['encode']; // 可以人工校验签名串
        
        	// 请求头转义,并设置cURL请求头
            $key = array_keys($headers);
            $value = array_values($headers);
            $r = [
                'Content-Type: application/json' // 这是发送json请求必备的
            ];
            for($i = 0; $i < count($key); $i++){
                array_push($r, $key[$i].":".$value[$i]);
            }
            if($debug) echo "请求头:<br>".json_encode($r, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES)."<br>"; // 可以人工校验请求头
            
            // 设置请求头
            curl_setopt($ch, CURLOPT_HEADER, 0);
            curl_setopt($ch, CURLOPT_HTTPHEADER, $r);
            // SSL验证
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            // POST请求
            curl_setopt($ch, CURLOPT_POST, 1);
            // 请求消息体
            $body = $temp['json']; // 这里可以理解为 请求内容转成json格式
            if($debug) echo "请求体:<br>".$body."<br>";
            curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
            // if($debug) return; /* 测试 */
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
            // 发送请求
            $file_contents = curl_exec($ch);
            // 获取状态码
            $httpCode = curl_getinfo($ch,CURLINFO_HTTP_CODE);
            // 结束请求
            curl_close($ch);
            $response = json_decode($file_contents, true);
            // 输出结果
            echo "响应体:<br>".json_encode($response, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES)."<br>");
            // 返回响应体
            return $response;
        }
        /**
         * 获取当前时间戳,毫秒
         */
        public static function timenow() {
            list($s, $ms) = explode(" ", microtime());
            return (float)sprintf("%.0f", (floatval($s) + floatval($ms))*1000);
        }
    }
    
  3. 开始调用接口
    // 获取班级为242001的男同学的个人信息
    $res = API::httpRequest('http://8.134.251.42:8889', '/api/v1/getStudentInfo', [
    	'class' => '242001',
    	'gender' => 1
    ]);
    

复盘分析

使用cURL发送json-post请求没有一次走通的原因有:

  1. 项目原本没有设置content-type: application/json,与外部对接会发现签名对不上,在php项目中需要为其加上
  2. 关于json_encode空对象转为数组的情况
    // 如果消息体为空,使用json_encode会导致转换时被当成数组
    $data = json_encode([]); // []
    // 如果要强制转为对象,可以这样
    $data = json_encode([], JSON_FORCE_OBJECT); // {}
    // 这个时候如果有非空对象里面有一个空的子对象,需要记得进行特殊处理
    
  3. 关于json_encode处理斜杠和中文转义的问题,可以这么解决
    // 斜杠不进行"\"的转义      JSON_UNESCAPED_SLASHES
    // 中文不进行"\u3455"的转义 JSON_UNESCAPED_UNICODE
    $res = json_encode('世界/你好', JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE);
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值