阿里云短信SDK在CodeIgniter中无法直接使用,因CI 3.x不兼容Composer自动加载,且其cURL封装不支持阿里云RPC接口所需的签名头(如x-acs-signature-nonce等),必须手动实现HMAC-SHA1签名逻辑,且AccessKeySecret后须显式加“&”字符。

阿里云短信 SDK 在 CodeIgniter 中无法直接使用的原因
阿里云官方 PHP SDK 依赖 guzzlehttp/guzzle 7.x+ 和 PSR-4 自动加载,而老版本 CodeIgniter(尤其是 3.x)默认不兼容 Composer 的自动加载规范,且其 curl 封装层(CI_Curl 或手动 curl_init())不处理阿里云要求的完整签名头(如 x-acs-signature-nonce、x-acs-date、Authorization 等),直接调用会返回 InvalidAccessKeyId.NotFound 或 SignatureDoesNotMatch。
更关键的是:阿里云短信(即 Alibaba Cloud SMS,产品 code Dysmsapi)实际走的是 RPC 风格的 HTTPS 接口,不是 RESTful,必须严格按文档拼接请求参数、排序、HMAC-SHA1 签名、Base64 编码 —— 官方 SDK 做了这些,但 CodeIgniter 项目若拒绝引入完整 SDK(比如因部署环境限制或历史包袱),就得自己实现签名逻辑。
CodeIgniter 3.x 中手写阿里云 RPC 签名算法的关键步骤
签名本质是把请求参数(含公共参数 + 业务参数)按字典序排序后拼成字符串,再用 AccessKeySecret + “&”(注意是字面量 & 符号)做 HMAC-SHA1,最后 Base64 编码。不是对整个 URL 签名,也不是对 JSON body 签名。
-
AccessKeySecret后必须显式加一个&字符(哪怕你没用 AccessKeyId),这是阿里云硬性要求,漏掉必报SignatureDoesNotMatch - 所有参数键名必须小写(如
action、format、version),值要 UTF-8 编码后做rawurlencode()(不是urlencode()),空格变%20,而非+ - 待签名字符串格式为:
GET&POST(HTTP 方法大写) +%2F(URL 编码后的/) +%3F(URL 编码后的?) +sorted_query_string(已排序且 key/value 全部编码过的参数串) - 最终
Authorization头格式为:acs <code>AccessKeyId:base64_encoded_signature,注意中间有空格
示例片段(放在 application/libraries/AlibabaCloudSms.php 中):
$params = [
'Action' => 'SendSms',
'Format' => 'JSON',
'Version' => '2017-05-25',
'AccessKeyId' => 'your_key_id',
'SignatureMethod' => 'HMAC-SHA1',
'SignatureNonce' => uniqid(),
'SignatureVersion' => '1.0',
'Timestamp' => date('c'),
'RegionId' => 'cn-hangzhou',
'PhoneNumbers' => '+8613800138000',
'SignName' => '你的签名',
'TemplateCode' => 'SMS_123456789',
'TemplateParam' => json_encode(['code' => '1234'], JSON_UNESCAPED_UNICODE)
];
// 1. 参数编码 + 排序
$encoded_params = [];
foreach ($params as $k => $v) {
$encoded_params[rawurlencode($k)] = rawurlencode((string)$v);
}
ksort($encoded_params);
// 2. 拼接待签名字符串
$canonicalized_query = http_build_query($encoded_params, '', '&', PHP_QUERY_RFC3986);
$string_to_sign = 'GET&%2F&' . rawurlencode($canonicalized_query);
// 3. 计算签名
$secret = $this->access_key_secret . '&';
$signature = base64_encode(hash_hmac('sha1', $string_to_sign, $secret, true));
$auth_header = 'acs ' . $this->access_key_id . ':' . $signature;
在 CodeIgniter 中发起请求时容易忽略的 HTTP 头和参数细节
即使签名正确,缺一个头或错一个参数也会失败。阿里云 RPC 接口对请求头极其敏感,尤其在 CodeIgniter 3.x 的 curl 封装中容易被覆盖或遗漏。
- 必须设置
Content-Type: application/x-www-form-urlencoded(即使 GET 请求也要设,否则部分网关返回 400) -
Accept: application/json要显式带上,否则可能返回 XML 格式错误信息(难解析) -
Host头必须是https://dysmsapi.aliyuncs.com对应的域名(不能省略端口,也不能带http://),推荐用curl_setopt($ch, CURLOPT_HTTPHEADER, [...])显式控制 - 务必禁用
CURLOPT_FOLLOWLOCATION,阿里云不支持重定向,开启会导致签名失效 - 使用
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE)仅限测试;生产环境必须保留TRUE并确保系统 CA 证书路径正确(CodeIgniter 默认不配置CURLOPT_CAINFO)
调试签名失败最有效的三步定位法
遇到 SignatureDoesNotMatch 不要盲目改代码,先比对原始请求数据。
- 用
curl -v手动构造一次最小请求(含所有头和参数),确认能通 —— 这是基准线 - 在 CodeIgniter 中用
curl_setopt($ch, CURLOPT_HEADERFUNCTION, function($ch, $header) { error_log($header); return strlen($header); })把实际发出的请求头全打出来,和上一步对比差异 - 把 PHP 中生成的
$string_to_sign和$secret单独error_log()出来,在本地用 Python/Node.js 再算一次签名,验证是否一致(排除时区、换行、空格等隐形字符问题)
最常被忽略的是:CodeIgniter 的 config/config.php 中若设置了 $config['charset'] = 'UTF-8',但控制器里用 json_encode() 时没加 JSON_UNESCAPED_UNICODE,导致中文被转成 \u4f60,签名时编码结果就变了。

















