Demo - PHP
使用 PHP 编程语言编写的请求示例。
RsaUtil
<?php
namespace App\Util;
use Exception;
use OpenSSLAsymmetricKey;
/**
* RsaUtil
*/
class RsaUtil
{
/**
* 公钥
* @throws Exception
*/
static function public_key(string $key_data): OpenSSLAsymmetricKey
{
if (!str_contains($key_data, '-----BEGIN PUBLIC KEY-----') && strrpos($key_data, '-----END PUBLIC KEY-----') === false) {
preg_match_all('/.{1,64}/', $key_data, $matched_key_data);
$key_data = "-----BEGIN PUBLIC KEY-----\n" . (isset($matched_key_data[0]) ? implode("\n", $matched_key_data[0]) : '') . "\n-----END PUBLIC KEY-----";
}
$public_key = openssl_pkey_get_public($key_data);
if ($public_key === false) {
throw new Exception('Invalid public key');
}
return $public_key;
}
/**
* 私钥
* @throws Exception
*/
static function private_key(string $key_data): OpenSSLAsymmetricKey
{
if (!str_contains($key_data, '-----BEGIN PRIVATE KEY-----') && strrpos($key_data, '-----END PRIVATE KEY-----') === false) {
preg_match_all('/.{1,64}/', $key_data, $matched_key_data);
$key_data = "-----BEGIN PRIVATE KEY-----\n" . (isset($matched_key_data[0]) ? implode("\n", $matched_key_data[0]) : '') . "\n-----END PRIVATE KEY-----";
}
$private_key = openssl_pkey_get_private($key_data);
if ($private_key === false) {
throw new Exception('Invalid private key');
}
return $private_key;
}
/**
* 加密
*/
static function encrypt(OpenSSLAsymmetricKey $public_key, string $plains): string
{
$bits = openssl_pkey_get_details($public_key)['bits'];
$data_array = str_split($plains, ($bits / 8) - 11);
$encrypted = [];
foreach ($data_array as $sub_data) {
$sub_encrypted = null;
openssl_public_encrypt($sub_data, $sub_encrypted, $public_key);
$encrypted[] = $sub_encrypted;
}
return base64_encode(implode('', $encrypted));
}
/**
* 解密
*/
static function decrypt(OpenSSLAsymmetricKey $private_key, string $cipher): string
{
$bits = openssl_pkey_get_details($private_key)['bits'];
$data = base64_decode($cipher);
$data_array = str_split($data, $bits / 8);
$decrypted = [];
foreach ($data_array as $sub_data) {
$sub_decrypted = null;
openssl_private_decrypt($sub_data, $sub_decrypted, $private_key);
$decrypted[] = $sub_decrypted;
}
return implode('', $decrypted);
}
/**
* 加签
* @throws Exception
*/
static function sign(OpenSSLAsymmetricKey $private_key, $cipher): string
{
$bool = openssl_sign($cipher, $sign, $private_key, OPENSSL_ALGO_SHA256);
if (!$bool) {
throw new Exception('Sign failed');
}
return base64_encode($sign);
}
/**
* 验签
*/
static function verify(OpenSSLAsymmetricKey $public_key, $cipher, $sign): bool
{
return (bool)openssl_verify($cipher, base64_decode($sign), $public_key, OPENSSL_ALGO_SHA256);
}
}
FireDemo
<?php
namespace Test\Demo;
use App\Util\RsaUtil;
use Exception;
use PHPUnit\Framework\TestCase;
use function PHPUnit\Framework\assertEquals;
/**
* 模拟 agency 向 ice-run 发送请求
*/
class FireDemoTest extends TestCase
{
/**
* 服务商(agency)的编号
* 此处使用 agency_no = 100000000000000000 作为模拟
* 真实请求场景,请咨询 ice-run 的技术支持获取正式的 服务商编号 agency_no
*/
private static string $AGENCY_NO = "100000000000000000";
/**
* 安全算法 RSA
*/
private static string $SECURITY = "RSA";
/**
* ice-run 的 公钥
*/
private static string $PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC78QqLKTkVcwvB2Scm4IoImOXmIthVmx4HHr5Im09aol2r2UlMK3jl4lamdxktvjFswWx41hQBD4tyYFHTY+mQJsRK8Zwm7LZmLJDHcgibIuLxEddln+MI+rWwT+SOdOt7lBvd9PcO4nYr/3Nqkyk8L4PrT+GF7dpVk8iE/VpTwQIDAQAB";
/**
* agency 的 私钥
*/
private static string $PRIVATE_KEY = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAOyC76Y+/2NqkkIVUtAHkbZkBYI2XXkuqE9o4oIDkbyAY04jC5MLa3oVc3uGG7T/Elodo9dIbIxNh7/ir7GQLR/hY+ookHAewurJ1z4Jn5TQeRk3+nYQcX6a6orfUOMffwcn0Jtat8D+cNAu3uCi+N7Ez/yNpItNORYqTa8AkgzrAgMBAAECgYAS/rsfl3ysb+E6RHsnsQvvYZ4dpJ8iPfCPnCVg+sdoI8mV+3ORBkBGCFYDjDRKd5fyO+IuRqdNJ2bpLtwcfy9YchcV/x40GIuAlre6A6qt4Raq+0l4FJufaqgdmSiLxd5zPX79gdMvBqTbdYzCSDGP5Pf3pTqIS3iKvSixFTQBzQJBAO6k6rE6JE/EHrbRjVtZNitDZ8rouCL0s9nGsgIDBSfgOCClO+HlZKcHq6W9ry8j1gOFeGH2rF1yMslA/ZymRL0CQQD9tk/gk9Xs1Eg1if7SLwvNxUP+z96oEqQTF26tSudPRwOO88fnfE4ZZ4heBt1QI+s7IhG39qecsFW269nvKfbHAkEArXmEgUBalQFjslGyB+1Zyyk8keuJrx9ifbRKQdwgK1R6eICkfxlZiXGx/NFeP041jGnBkLTXpzYUZOexc+YJoQJBANR3r87vnxAU+l+zr62O7oCk+XtT0y/HZJYEYpBHEQyn+MfnSXqG89R8iovLjd0GJ4E+173KlrU2SqHEQ57w8pMCQAEoDjFUXpYzBl8e0M9XsSpGY531mbOl8ASnVp4OSyavaCsn56eEkL9TI5q69KrYUEA4PN3BazMWfll2s/wu6G0=";
/**
* ice-run 的 test 环境的 API 网关 的域名地址
* 正式生产环境的域名地址请查阅 ice-run 的技术文档
*/
private static string $DOMAIN = "test-fire-api.ice.run";
/**
* 示例地址,仅用于测试加密解密加签验签流程,无任何实际业务场景
*/
private static string $DEMO = "/demo/api/demo";
/**
* 示例接口 demo
* @throws Exception
*/
function test_demo(): void
{
/**
* 对方公钥
*/
$public_key = RsaUtil::public_key(self::$PUBLIC_KEY);
/**
* 己方私钥
*/
$private_key = RsaUtil::private_key(self::$PRIVATE_KEY);
/**
* 此处使用 `${URL}` 模拟请求地址
*/
$url = "https://" . self::$DOMAIN . self::$DEMO;
echo "request url : " . $url . "\n";
/**
* 请求参数:通常为复杂对象。
* 此处使用 json 对象 模拟,实际场景可以定义数据模型类
* 注意:不可传 null 或 空字符串 或 单值 ( string, number, boolean )
* 如果当前业务接口不需要传递具体参数,可以传空对象参数 `{}`,json 序列化为 `{"param":{}}`
*/
$param = array(
"name" => "!"
);
/**
* 参数明文:参数对象的 json 序列化后的字符串的明文
*/
$param_plains = json_encode($param, JSON_UNESCAPED_UNICODE);
echo "param plains : " . $param_plains . "\n";
/**
* 参数密文:agency 使用 ice-run 公钥 对参数明文加密后得到的密文
*/
$param_cipher = RsaUtil::encrypt($public_key, $param_plains);
echo "param cipher : " . $param_cipher . "\n";
/**
* 请求签名:agency 使用 私钥 对参数密文进行签名
* 当 ice-run 收到请求时,将会使用 agency 公钥 进行验签
*/
$request_sign = RsaUtil::sign($private_key, $param_cipher);
echo "request sign : " . $request_sign . "\n";
/**
* 请求数据 request body
* 此处使用 json 对象 模拟,实际场景建议定义数据模型类
* body 数据的 键 固定为 param ,值为 json 序列化后的字符串 RSA 加密后的密文
*/
$request = array(
"param" => $param_cipher,
);
$request_body = json_encode($request);
echo "request body : " . $request_body . "\n";
/**
* 请求时间,当前世界标准时间的字符串,格式为 RFC-1123 标准,服务器会校验时间差,不允许超过设置的时间间隔(默认为 2 分钟)
*/
$time = gmdate("D, d M Y H:i:s T");
/**
* 链路追踪号(防止重放攻击的请求编号)
* 单位时间内(默认为 1 分钟)不重复的字符串,建议使用 uuid 或者时间戳+随机数。限制长度 = 32。正则表达式 = `^[0-9a-f]{32}$`
*/
$trace = bin2hex(random_bytes(16));
/**
* 请求头
*/
$request_headers = array(
"Content-Type: application/json",
"X-Security: " . self::$SECURITY,
"X-Client: " . self::$AGENCY_NO,
"X-Sign: " . $request_sign,
"X-Time: " . $time,
"X-Trace: " . $trace
);
echo "request headers : " . json_encode($request_headers) . "\n";
/**
* http 客户端,此处使用 php 标准库 curl 扩展 模拟,实际场景建议自行选择 HTTP 客户端组件包
* 发起 HTTP 请求之前,请确认 `${DOMAIN}` 可以正常访问,部分客户端的服务器环境可能需要设置请求代理
*/
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_HTTPHEADER, $request_headers);
curl_setopt($ch, CURLOPT_POSTFIELDS, $request_body);
$response = curl_exec($ch);
if ($response === false) {
$curl_errno = curl_errno($ch);
$curl_error = curl_error($ch);
curl_close($ch);
self::fail("cURL request failed with error ($curl_errno): $curl_error");
}
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$response_status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
/**
* 响应状态码
*/
echo "response status : " . $response_status . "\n";
assertEquals(200, $response_status);
/**
* 响应 header
*/
$headers = substr($response, 0, $header_size);
$headers_array = explode("\r\n", $headers);
$response_headers = [];
foreach ($headers_array as $header) {
$header_parts = explode(": ", $header, 2);
if (count($header_parts) == 2) {
$response_headers[$header_parts[0]] = $header_parts[1];
}
}
echo "response headers : " . json_encode($response_headers) . "\n";
/**
* 响应 header X-Sign
*/
$response_sign = $response_headers["X-Sign"];
echo "response sign : " . $response_sign . "\n";
/**
* 响应 body ,json 格式的字符串
*/
$response_body = substr($response, $header_size);
echo "response body : " . $response_body . "\n";
/**
* 响应数据 response body
* 此处使用 json 对象 模拟,实际场景建议定义数据模型类
* 数据的 键 有 code 和 message 和 data
* 其中 data 为 json 序列化后的字符串 RSA 加密后的密文
*/
$response_object = json_decode($response_body);
/**
* 响应码,默认 000000 表示请求成功
*/
$code = $response_object->{"code"};
assertEquals("000000", $code);
/**
* 响应 数据密文
*/
$data_cipher = $response_object->{"data"};
/**
* 响应验签:ice-run 处理响应时,会使用 ice-run 私钥对 响应密文进行签名
* agency 收到响应后,需要使用 ice-run 公钥进行验签
*/
$verify = RsaUtil::verify($public_key, $data_cipher, $response_sign);
echo "sign verify : " . $verify . "\n";
assertEquals(true, $verify);
/**
* 响应数据明文:ice-run 使用 agency 公钥 对响应数据密文进行解密
*/
$data_plains = RsaUtil::decrypt($private_key, $data_cipher);
echo "data plains : " . $data_plains . "\n";
}
}