签名方法
本节讲述在调用API时如何生成数字签名,为了保护用户的财产安全,调用API时需要进行签名(Signature)验证
用户可以在飞鱼云短信平台查看访问凭证:
- AppKey: 用于标识API调用者身份,相当于用户名。
- AppSecret: 用于验证API调用者的身份,相当于密码。
警告
- 用户必须妥善保管凭证,不泄露给他人,否则将造成财产安全隐患。如发现泄漏,请立即禁用该凭证。
签名生成过程
API支持 GET 和 POST 请求。对于 GET 方法,支持 Content-Type: application/x-www-form-urlencoded 格式。对于 POST 方式,只支持 Content-Type: application/json 格式,Content-Type: multipart/form-data 只有特定接口支持。
下面详细解释签名计算过程。
1.拼接待签名字符串
按如下伪代码格式拼接规范待签名串(SignatureStr):
String signStr =
RequestURI + '\n' +
RequestTimestamp + '\n' +
RequestQueryString + '\n' +
HashedRequestPayload
字段名称 | 解释 |
---|---|
RequestURI | URI 参数。此示例取值为 /rest/sms/v3/signature/queryStatus。 |
RequestTimestamp | 请求时间戳,即请求头部的公共参数 X-FZ-Timestamp 取值。此示例取值为 1713100791403。 |
RequestQueryString | 发起 HTTP 请求 URL 中的查询字符串,对于 POST 请求,固定为空字符串"",对于 GET 请求,则为 URL 中问号(?)后面的字符串内容,例如:limit=10&id=1。 注意:RequestQueryString 需要参考 RFC3986 进行 URLEncode 编码(特殊字符编码后需大写字母),字符集 UTF-8。推荐使用编程语言标准库进行编码。 |
HashedRequestPayload | 请求正文(payload,即 body,此示例为 {"signIdSet":[123239,123240]})的哈希值,计算伪代码为 Lowercase(HexEncode(Hash.SHA256(RequestPayload))),即对 HTTP 请求正文做 SHA256 哈希,然后十六进制编码,最后编码串转换成小写字母。对于 GET 请求,RequestPayload 固定为空字符串。此示例计算结果是 dfb249a560bd4452e1674a77cb41c7e07bc90b72f951b4bc8bce9f62b514f7af。 |
注意
1.Timestamp 必须是当前系统时间,且需确保系统时间和标准时间是同步的,如果相差超过五分钟则必定失败。如果长时间不和标准时间同步,可能运行一段时间后,请求失败,返回签名过期错误。
根据以上规则,示例中得到的规范请求串如下:
/rest/sms/v3/signature/queryStatus
1713100791403
dfb249a560bd4452e1674a77cb41c7e07bc90b72f951b4bc8bce9f62b514f7af
2.计算签名
计算签名,伪代码如下:
//先计算加密的密钥 key
String appSecret = "04f229cbba734e22af3f1151a73f8f5d";
byte[] secretKey = HmacUtils.hmacSHA256(appSecret.getBytes(StandardCharsets.UTF_8), timestamp);
String signature = HmacUtils.hmacSHA256Hex(secretKey, signStr);
此示例计算的签名为:0165ab701a71e6ee8907f5282785fae549ed032d67c08fc8a478935fecd15159。
3.拼接 Authorization
伪代码如下:
String authorization = String.format("%s credential=%s,signature=%s", "HmacSHA256", AppKey, signature);
根据以上规则,示例中得到的值为:
HmacSHA256 credential=1kl3pY,signature=0165ab701a71e6ee8907f5282785fae549ed032d67c08fc8a478935fecd15159
最终完整的调用消息如下:
POST https://api.ffrcs.cn/rest/sms/v3/signature/queryStatus
Authorization: HmacSHA256 credential=1kl3pY,signature=783752015052014ccd08b4d43997063e68f02d0efd32d5fa1758a9c87d4a589c
Content-Type: application/json; charset=utf-8
Host: api.ffrcs.cn
X-FZ-Timestamp: 1713100791403
{"signIdSet":[123239,123240]}
签名演示
为了更清楚地解释签名过程,下面以实际编程语言为例,将上述的签名过程完整实现。
import org.bouncycastle.util.encoders.Hex;
import org.apache.commons.codec.digest.DigestUtils;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
public class FeiyusmsApiDemo {
public static byte[] hmacSHA256(byte[] key, String message) throws Exception {
Mac hmacSha256 = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(key, "HmacSHA256");
hmacSha256.init(secret_key);
return hmacSha256.doFinal(message.getBytes(StandardCharsets.UTF_8));
}
public static String hmacSHA256Hex(String key, String message) throws Exception {
byte[] bytes = key.getBytes(StandardCharsets.UTF_8);
return hmacSHA256Hex(bytes, message);
}
public static String hmacSHA256Hex(byte[] key, String message) throws Exception {
byte[] data = hmacSHA256(key, message);
return Hex.toHexString(data);
}
public static void main(String[] args) throws Exception {
String host = "api.ffrcs.cn";
//1.拼接请求串
String canonicalUri = "/rest/sms/v3/signature/queryStatus";
String requestTimestamp = Long.toString(System.currentTimeMillis());
String canonicalQueryString = "";
byte[] requestPayload = "{\"signIdSet\":[123239,123240]}\"".getBytes(StandardCharsets.UTF_8);
String hashedRequestPayload = DigestUtils.sha256Hex(requestPayload);
String signStr = String.format("%s\n%s\n%s\n%s", canonicalUri, requestTimestamp,
canonicalQueryString, hashedRequestPayload);
//2.计算签名
String secretId = "1kl3pY");
String secretKey = "04f229cbb*****1a73f8f5d";
byte[] secretString = HmacUtils.hmacSHA256(secretKey.getBytes(StandardCharsets.UTF_8), requestTimestamp);
String signature = HmacUtils.hmacSHA256Hex(secretString, signStr);
//3.生成 Authorization
String authorization = String.format("%s credential=%s,signature=%s", clientConfig.getSignatureMethod()
, secretId, signature);
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", authorization);
headers.put("Host", host);
headers.put("X-FZ-Timestamp", requestTimestamp);
}
}