# 接入指南
# 基础概念解释
1、公共参数: 公共请求参数是指每个接口都需要使用到的请求参数,与业务无关;
2、业务参数: 根据调用API服务接口的需求所传递的参数;
3、签名算法: 签名算法是指数字签名的算法。数字签名(又称公钥数字签名、电子签章)是一种类似写在纸上的普通的物理签名,但是使用了公钥加密领域的技术实现,用于鉴别数字信息的方法。一套数字签名通常定义两种互补的运算,一个用于签名,另一个用于验证。
4、表单: 在网页中主要负责数据采集功能。一个表单有三个基本组成部分: 表单标签:这里面包含了处理表单数据所用CGI程序的URL以及数据提交到服务器的方法。 表单域:包含了文本框、密码框、隐藏域、多行文本框、复选框、单选框、下拉选择框和文件上传框等。 表单按钮:包括提交按钮、复位按钮和一般按钮;用于将数据传送到服务器上的CGI脚本或者取消输入,还可以用表单按钮来控制其他定义了处理脚本的处理工作。
# API签名算法
认证平台的 API 是基于 HTTP(S) 协议来调用的,开发者可以直接使用我们提供的SDK(包含了请求的封装,签名加密,响应解释等)来调用, 以下主要是针对自行封装 HTTP(S) 请求进行API调用的签名算法进行详细解说。API调用除了必须包含公共参数外,API本身业务级的参数
,每个API的业务级参数请参考API文档说明。
# 签名算法原理
为了防止 API 调用过程中被恶意篡改,调用任何一个 API 都需要携带签名,服务端会根据请求参数,对签名进行验证,签名不合法的请求将会被拒绝。目前支持的签名算法:HMAC-SHA256(signMethod=HMAC-SHA256),签名大体过程如下:
- 对所有 API 请求参数(包括公共参数和
业务参数
,但除去sign参数),根据参数名称的ASCII码表的顺序排序,将排序好的参数名和参数值拼接在一起。 - 拼接好的字符串和密钥分别按照UTF-8编码,用编码后的密钥字符流结合HmacSHA256算法对编码后的参数字符流进行摘要。
- 将摘要后的字符流转换为
十六进制大写字符串
,即得到签名值。
# JAVA 签名示例代码
P1验签例子:
请求示例:
package cn.unitid.matrix.pkiservice.test;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.*;
public class computeSignatureTest {
/**密钥*/
private static String secretKey = "xxx";
private static String charset = "UTF-8";
private static final String ALGORITHM = "HmacSHA256";
private static final Object LOCK = new Object();
private static Mac macInstance;
public static void main(String[] args) {
Map<String, String> paramMap = new HashMap<String, String>(10);
//公共参数
paramMap.put("appKey", "xxx");
paramMap.put("v", "1");
paramMap.put("method", "sign/verify/p1");
paramMap.put("format", "JSON");
paramMap.put("t",getTime());
paramMap.put("nonce", getNonce());
//具体接口参数
paramMap.put("sign", "CERT");
paramMap.put("signItemValue", "xxx");
paramMap.put("dataType", "ORIGINAL");
paramMap.put("data", "xxx");
paramMap.put("signature", "xxx");
paramMap.put("signatureAlgorithm", "SHA1withRSA");
paramMap.put("returnCert", "true");
//获取待签名内容,排序
String signContent = getSignatureContent(paramMap);
System.out.println("待签名内容:" + signContent);
//计算签名
String sign = computeSignature(secretKey, signContent, charset);
System.out.println("签名后:" + sign);
}
/**
* 格式化时间
*/
private static String getTime() {
return String.valueOf(System.currentTimeMillis());
}
/**
* 生成随机数
*/
private static String getNonce(){
Random random = new Random();
return String.valueOf(random.nextInt(1000000000));
}
/**
* 将参数按key值排序
*/
public static String getSignatureContent(Map<String, String> paramMap) {
Collection<String> keySet = paramMap.keySet();
//签名内容
StringBuilder content = new StringBuilder();
//所有的键值
List<String> keys = new ArrayList<String>(keySet);
//排序
Collections.sort(keys);
//循环赋值
for (String key : keys) {
String value = paramMap.get(key);
if (isNotEmpty(key) && isNotEmpty(value)) {
content.append(key).append(value);
}
}
return content.toString();
}
/**
*字符串非空校验
*/
private static boolean isNotEmpty(String str) {
return str != null && str.length() != 0;
}
/**
* 计算签名值
*/
private static String computeSignature(String key, String data, String charset) {
try {
byte[] signData = sign(key.getBytes(charset), data.getBytes(charset));
return byte2hex(signData);
} catch (UnsupportedEncodingException ex) {
throw new RuntimeException("不支持的算法: " + charset, ex);
}
}
/**
* 把字节流转换为十六进制表示方式。
*/
private static String byte2hex(byte[] bytes) {
StringBuilder sign = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(bytes[i] & 0xFF);
if (hex.length() == 1) {
sign.append("0");
}
sign.append(hex.toUpperCase());
}
return sign.toString();
}
/**
* 使用HMAC加密
*/
private static byte[] sign(byte[] key, byte[] data) {
try {
//因为Mac类的getInstance()方法的调用时一个同步方法,可能被阻塞,所以使用原型模式来提高可靠性
if (macInstance == null) {
synchronized (LOCK) {
if (macInstance == null) {
macInstance = Mac.getInstance(ALGORITHM);
}
}
}
Mac mac;
try {
mac = (Mac) macInstance.clone();
} catch (CloneNotSupportedException e) {
//如果不可复制,创建一个新的Mac对象
mac = Mac.getInstance(ALGORITHM);
}
mac.init(new SecretKeySpec(key, ALGORITHM));
return mac.doFinal(data);
} catch (NoSuchAlgorithmException ex) {
throw new RuntimeException("不支持的算法: " + ALGORITHM, ex);
} catch (InvalidKeyException ex) {
throw new RuntimeException("非法key: " + key, ex);
}
}
}
详细示例代码请参见 SDK 源代码。
# 调用示例
1、设置参数值
method = "sign/verify/p1"
data = 签名数据
signature = MEUCIH2eIxHO3UVWDkY8Du3h+ptUx387aOgtpIOlu1ET3jxiAiEAhLUL+ofXwHHUVXEix+kcKSBfCGFyi42EVMlmJcXdrWU=
dataType = ORIGINAL
format = "JSON"
nonce = V2Yx5zNt1r
signatureAlgorithm = SHA1withRSA
t = 1668496549088
signItemValue = MIICszCCAligAw****
v = 1
returnCert = true
signType = CERT
appKey = ODRp4fQmiQiVytrk
2、排序
appKey = ODRp4fQmiQiVytrk
data = 签名数据
dataType = ORIGINAL
format = "JSON"
method = "sign/verify/p1"
nonce = V2Yx5zNt1r
returnCert = true
signItemValue = MIICszCCAligAw****
signType = CERT
signature = MEUCIH2eIxHO3UVWDkY8Du3h+ptUx387aOgtpIOlu1ET3jxiAiEAhLUL+ofXwHHUVXEix+kcKSBfCGFyi42EVMlmJcXdrWU=
signatureAlgorithm = SHA1withRSA
t = 1668496549088
v = 1
3、拼接参数名与参数值
appKeyODRp4fQmiQiVytrkdata签名数据dataTypeORIGINALformatJSONmethodsign/verify/p1nonceV2Yx5zNt1rreturnCerttruesignItemValueMIICszCCAligAwIBAgINAO2Cjz/tEqZSVvI/eDAKBggqgRzPVQGDdTBJMQswCQYDVQQGEwJDTjESMBAGA1UECAwJ5rGf6IuP55yBMRIwEAYDVQQHDAnljZfkuqzluIIxEjAQBgNVBAMMCXRlc3RTTTJDQTAeFw0yMjAzMTgwNTQwMTlaFw0yMzAzMTgwNTQwMTlaMIGjMQswCQYDVQQGEwJDTjEPMA0GA1UECAwG5rGf6IuPMQ8wDQYDVQQHDAbljZfkuqwxFTATBgNVBAoMDOaVsOWtl+iupOivgTERMA8GA1UECwwI5pm65oWnQ0ExMDAuBgNVBC0MJzFmZWM0ODA3MjlhYTQ0MjJiODk0YzhiMTM2YjFkNDU0MjAyMjAzNzEWMBQGA1UEAwwNd3d3LnF3ZmRjLmNvbTBZMBMGByqGSM49AgEGCCqBHM9VAYItA0IABFjoFlPaMPGM7OAKEYpwwsJWKjr5w0qy32hLc/z4r065nPWJyZl+VkP/8EF0mRSrvjJz3tlR9OMiaDjjHwxW86KjgckwgcYwCQYDVR0TBAIwADAdBgNVHQ4EFgQU0HDEQ8QjtsYHokinKQvAK7z+FoEwSwYDVR0lAQH/BEEwPwYIKwYBBQUHAwEGCCsGAQUFBwMCBggrBgEFBQcDBAYIKwYBBQUHAwgGCisGAQQBgjcKAwMGCWCGSAGG+EIEATAOBgNVHQ8BAf8EBAMCBsAwHAYDVR0RBBUwE4IRd3d3Lnh1amkudGVzdC5jb20wHwYDVR0jBBgwFoAUE3GRQ7iygyrxFixllDZ6SqWNJicwCgYIKoEcz1UBg3UDSQAwRgIhAIi2sGHKO+KkMX9MxC2c+u/p82t4SxqEjbDk96zluqmfAiEA00lg6iFIc03pofZbsA5uiA6vznSLKaru1A+nGqO78Uw=signTypeCERTsignatureMEUCIH2eIxHO3UVWDkY8Du3h+ptUx387aOgtpIOlu1ET3jxiAiEAhLUL+ofXwHHUVXEix+kcKSBfCGFyi42EVMlmJcXdrWU=signatureAlgorithmSHA1withRSAt1668496549088v1
4、生成签名
假设 secretKey 为 111111,则签名结果为:
F384EB51EFF959BF0AA7BA2C7F4759BD9D0F0D6ADE95E24F235CE7B4945DE1B2
# API调用协议
接口支持HTTP,HTTPS GET/POST请求,所有接口需在请求中加入公共参数,请求及返回结果都使用 UTF-8 字符集进行编码。
组装 HTTP(S) 请求
将所有参数名和参数值采用UTF-8进行 URL 编码(参数顺序可随意,但必须要包括签名参数),然后通过GET
或POST
发起请求,如:
https://api.spiderid.cn/openapi/svs/v1/sign/verify/p1?method=sign%2Fverify%2Fp1&t=1668497130259&v=1&sign=858BB6FAECEC3B52CBEDD9391743DB8A5E68A7EF75650C0F45F61406C2FD88E2&appKey=ODRp4fQmiQiVytrk&nonce=AqqqG1qWmt&format=JSON
# 注意事项
1、所有的请求和响应数据编码皆为UTF-8格式,URL 里的所有参数名和参数值请做 URL 编码。如果请求的 Content-Type 是 application/x-www-form-urlencoded,则 HTTP Body 体里的所有参数值也做 URL 编码;如果是 multipart/form-data 格式,每个表单字段的参数值无需编码, 但每个表单字段的 charset 部分需要指定为UTF-8。
2、参数名与参数值拼装起来的 URL 长度小于 1024 个字符时,可以用 GET 发起请求;参数类型含 byte[] 类型或拼装好的请求 URL 过长时,必须用 POST 发起请求。所有 API 都可以用 POST 发起请求,某些 API 只支持 POST 请求
。
3、POST请求请务必将业务参数放入请求Body中
# 公共参数
公共请求参数是指每个接口都需要使用到的请求参数,务必以url参数形式传入
名称 | 类型 | 是否必须 | 描述 |
---|---|---|---|
appKey | String | 是 | 身份标识 |
sign | String | 是 | 签名结果串,请参看API签名机制 |
method | String | 是 | 服务方法/API接口名称 |
format | String | 是 | 返回值的类型,默认JSON |
t | String | 是 | 时间戳 |
nonce | String | 是 | 唯一随机数,同样的值,10分钟内只能被使用一次 |
v | String | 是 | API 版本号,目前版本是1 |
请求示例:
GET:
https://api.spiderid.cn/openapi/svs/v1/sign/verify/p1?
&appKey=XXX
&sign=XXX
&method=XXX
&format=JSON
&t=时间戳
&nonce=随机数
&v=1
&<[具体接口特有的请求参数]>
POST:
https://api.spiderid.cn/openapi/svs/v1/sign/verify/p1?
&appKey=XXX
&sign=XXX
&method=XXX
&format=JSON
&t=时间戳
&nonce=随机数
&v=1
<[具体接口特有的请求参数]>
# 响应参数
调用 API 服务后返回数据采用统一格式,code
为0
,请求成功,其他为失败,这时没有data结果信息
名称 | 类型 | 描述 |
---|---|---|
code | Integer | 状态码,0 请求成功,其他为失败,具体见 API错误码说明 |
message | String | 状态码的描述 |
data | Object | 结果信息,code为0时出现,具体看各个接口说明 |
# 成功示例
JSON示例
{
"code": 0,
"data": {
"original": "签名数据",
"cert": "MIICszCCAligAw******LKaru1A+nGqO78Uw=",
"message": "success",
"verification": 0
},
"requestId": "f100637335f7c9e77c0001f2d50c",
"message": "success"
}
# 失败示例
JSON示例
{
"code": 10008,
"message": "App不存在或状态异常"
}
# API错误码说明
# 错误码解释
返回码 | 系统错误 | 备注 |
---|---|---|
0 | 成功 | |
10001 | 系统错误 | |
10002 | 服务异常,请联系客服 | |
10003 | 系统繁忙,请稍候再试 | |
10005 | 租户不存在 | |
10006 | 租户密钥错误 | |
10007 | 该APP不属于此租户 | |
10008 | 此租户已被禁用 | |
10010 | 请求重复 | |
10011 | 请求过期 | |
10012 | 请求方法不存在 | |
10021 | App不存在 | |
10022 | App状态异常 | |
10023 | App被限流 | |
10024 | App签名错误 | |
10031 | 暂不支持此方法 | |
10032 | 没有访问此接口权限 | |
10100 | 参数校验异常 | (如:请求参数(name)不合法,请参考API文档) |
10101 | 加密卡引擎加密失败 | |
10102 | 加密卡引擎暂不支持此算法 | |
10103 | 加密卡引擎解密失败 | |
10104 | 加密卡签名验证失败 | |
10105 | 加密卡暂不支持此摘要算法 | |
10106 | 加密卡签名操作失败 | |
10107 | 加密卡导入sm2加密密钥对异常 | |
10108 | 通过生成随机数方式生成密钥对异常 | |
10109 | 加密卡引擎生成密钥对失败 | |
10110 | 加密卡异常 | |
10111 | 文件引擎加密失败 | |
10112 | 文件引擎暂不支持此算法 | |
10113 | 文件引擎解密失败 | |
10114 | 文件签名验证失败 | |
10115 | 文件暂不支持此摘要算法 | |
10116 | 文件签名操作失败 | |
10117 | 文件导入sm2加密密钥对异常 | |
10119 | 文件加密引擎生成密钥对失败 | |
10120 | 文件引擎生成摘要失败 | |
10121 | 修改密钥对数据失败 | |
10122 | 不支持的加密引擎类型 | |
10123 | 删除密钥对失败 | |
10124 | 同步指定索引位置的SM2密钥信息失败 | |
10125 | 同步指定索引位置的RSA密钥信息失败 | |
10126 | 同步指定索引位置的SM4密钥信息失败 | |
10127 | 生成密钥对失败 | |
10201 | 证书内容不合法 | |
10202 | 证书密钥信息不合法 | |
10203 | 暂不支持该种算法证书 | |
10204 | 签名操作失败 | |
10205 | 签名验证失败 | |
10206 | 证书注销列表(CRL)内容不合法 | |
10207 | 证书增强密钥用法读取失败 | |
10208 | RSA加密操作失败 | |
10209 | RSA解密操作失败 | |
10210 | SM2加密操作失败 | |
10211 | SM2解密操作失败 | |
10212 | SM4加密操作失败 | |
10213 | SM4解密操作失败 | |
10300 | 时间戳异常 | |
10301 | 该证书不符合时间戳证书规范 | |
10302 | 密钥对不存在 | |
10400 | 证书不存在 | |
10401 | 服务方法不支持RSA算法 | |
10402 | 服务方法不支持SM2算法 |