起因
因为公司的产品要上华为Saas平台,所以需要去对接华为云Saas,但华为云那边只提供Java类型的代码,所以这一次的对接还是有一定的挑战难度的
对接流程
1、查看官方文档
2、对接流程复述
首先从华为云那边请求我们服务器的接口,我们这边先校验accessToken是否为华为云发出,校验成功后,我们服务器这边在header中添加Body-Sign,并返回华为云规定的接口返回值
3、PHP对接代码提供
官方只提供Java版本代码,demo只能去申请工单或发送邮件到华为云运营获取
<?php
/**
* Date: 2019/12/11
* Time: 16:28
*/
use think\Exception;
/**
* 华为云信息解密
* Class HwDataDecrypt
* @package app\common\Service
*/
class HwDataDecrypt
{
/**
* 解密手机号码或邮箱
* @param $key 秘钥
* @param $str 密文
* @param $encrypt_length 加密长度
* @return null 解密结果
*/
public function decryptMobilePhoneOrEMail( $key, $str, $encrypt_length)
{
$iv = substr( $str, 0, 16);
$str = substr( $str, 16);
$key = substr(openssl_digest(openssl_digest($key, 'sha1', true), 'sha1', true), 0, 16);
$result = null;
try
{
$result = $this->decryptAESCBCEncode( $str, $key, $iv, $encrypt_length);
}
catch (Exception $e)
{
//TODO:异常处理
}
return $result;
}
/**
* 对资源开通后,返回的用户名和密码进行加密
* @param $key 秘钥
* @param $string 原文
* @param $encrypt_length 加密长度
* @return mixed
*/
public function generateSaaSUsernameOrPwd( $key, $string, $encrypt_length)
{
$iv = $this->getRandomChars( 16);
$key = substr(sha1(sha1($key, true), true), 0, 16);
$after_encrypt_string = '';
if( !empty($key) && !empty($string))
{
$after_encrypt_string = $this->encrypt_pass( $string, $key, $iv);
}
return $iv.$after_encrypt_string;
}
/**
* 华为云signature加密
* 这里必须修改hash_hmac的最后一个raw_output的参数,修改成true,输出原始二进制数据
* @param $key
* @param $data
* @return string
*/
public function getSignature( $key, $data)
{
//HMAC-SHA256加密
return base64_encode(hash_hmac("sha256", $data, $key, true)); //
}
//生成随机值
function getRandomChars( $length)
{
$random_chars = '';
for ( $i =0; $i < $length; $i++)
{
//字母和数字中随机
$random_chars .= chr( mt_rand( 33, 126));
}
return $random_chars;
}
/**
* 解密AES CBC
* @param $content 原文
* @param $key 秘钥
* @param $iv 盐值
* @param $encrypt_type 解密结果
* @return null|string
*/
function decryptAESCBCEncode( $content, $key, $iv, $encrypt_type)
{
if ( empty( $content) || empty($key) || empty($iv) )
{
return null;
}
//return $this->decryptAESCBC( $content, $key, $iv, $encrypt_type);
/*if( $encrypt_type == 1)
{
$cipher = MCRYPT_RIJNDAEL_256;
}
else
{
$cipher = MCRYPT_RIJNDAEL_128;
}*/
return $this->decrypt_pass( $content, $key, $iv);
}
/**
* AES解密
* @param $sStr
* @param $sKey
* @param $iv
* @param string $cipher
* @param string $mode
* @return bool|string
*/
function decrypt_pass($sStr, $sKey, $iv, $cipher = MCRYPT_RIJNDAEL_128, $mode = MCRYPT_MODE_CBC)
{
$decrypted = mcrypt_decrypt( $cipher, $sKey, base64_decode($sStr), $mode, $iv);
$decrypted = $this->pkcs5_unpad($decrypted);
return $decrypted;
}
/**
* 格式化参数格式化成url参数
* @param $data
* @return mixed
*/
public function ToUrlParams($data)
{
$buff = "";
foreach ($data as $k => $v)
{
if($k != "authToken" && $v != "" && !is_array($v)){
$buff .= $k . "=" . $v . "&";
}
}
$buff = trim($buff, "&");
return $buff;
}
/**
* 生成accessToken
* @param $data
* @param $key
* @return mixed|string
*/
function MakeSignString2($data, $key)
{
//签名步骤一:按字典序排序参数
ksort($data);
$string = $this->ToUrlParams($data);
//根据要求排序
$string = base64_encode(hash_hmac("sha256", $string, $key));
return $string;
}
/**
* 生成signature
* @param $data
* @param $key
* @return mixed|string
*/
function MakeSignString($data, $key)
{
//签名步骤一:按字典序排序参数
//ksort($data);
//$string = $this->ToUrlParams($data);
//根据要求排序
$string = base64_encode(hash_hmac("sha256", $data, $key));
return $string;
}
/**
*
* @param $data
* @param $key
* @param $iv
* @param string $cipher
* @param string $mode
* @return string
*/
function encrypt_pass( $data, $key, $iv, $cipher = MCRYPT_RIJNDAEL_128, $mode = MCRYPT_MODE_CBC)
{
$result = '';
$size = mcrypt_get_block_size( $cipher, $mode);
$padded_data = $this->pkcs5_pad($data, $size);
$encrypt_data = mcrypt_encrypt( $cipher, $key, $padded_data, $mode, $iv);
$result = base64_encode($encrypt_data);
return $result;
}
//填充
function pkcs5_pad ($text, $blocksize)
{
$pad = $blocksize - (strlen($text) % $blocksize);
return $text . str_repeat(chr($pad), $pad);
}
function pkcs5_unpad($text)
{
$end = substr($text, -1);
$last = ord($end);
$len = strlen($text) - $last;
if (substr($text, $len) == str_repeat($end, $last))
{
return substr($text, 0, $len);
}
return false;
}
}
以上方法分别对应华为云官方文档中的 ISV Server对响应消息体进行签名、ISV Server对资源开通后的用户名和密码加密以及ISV Server解密手机号和邮箱
4、对接中所遇到的坑
1、我们这边让与我们对接的华为云商务提供技术支持,但对方没有提供,最后还是提交了华为云的工单,由客服出来回答
2、华为云的客服不指出我这边的错误在哪,回答的内容有点生硬太固定化
3、一开始理解错流程,去请求华为云文档中所提供的URL
4、在对返回值进行验签的时候,虽然华为云技术说要进行二进制对其,但因为对函数的不理解,那天晚上没做到,第二天才去翻查PHP的官方文档,查到有这么一种二进制输出的内容【默认是false】
5、返回值验签,华为云的文档中没有标注好,说明是我们返回给华为云的值进行验签,对于我这样的初级程序员来说会有这么一种操作误解,以为是获取header的值进行验签
6、华为云的新购商品接口描述中,在响应参数instanceId的备注中(建议使用请求参数bussinessId作为instanceId。)这句话中,bussinessId是错的,多了一个s,应改为businessId
总结
在进行华为云的SaaS对接中,没有好好的阅读华为云的文档,没有细细品读内容,造成一早上的测试浪费。另外就是对PHP所提供的的加密参数的不理解,若不是那天去翻查文档,那我还陷入找bug的循环中。总的来说还是自己的不细心,不认真所造成的一些问题,希望写出来,可以帮助到大家。