API接入综述
1.引言
贷款超市用于连接用户和商户,借助于千万级的导流能力,满足商户扩大用户流量,提升品牌知名度的需求。另为加强用户体验,贷款超市打通商户账号体系,统一用户资料、授信、借款、还款流程。 本文档主要面对于需要接入贷款超市的商户系统开发人员。
2.简介
2.1 接口约定
签名机制: 贷款超市通过签名机制来保证通信安全。签名算法为 SHA256withRSA。RSA生成的密钥对请务必使用PKCS#8的格式。商户自行生成一份 RSA 密钥对,将公钥传给贷款超市,贷款超市也会给商户关于贷款超市的合作公钥。即商户有「商户私钥,贷款超市公钥」,贷款超市有「商户公钥,贷款超市私钥」。进行接口交互时,商户使用商户私钥对参数签名,贷款超市使用商户公钥来验证签名,反之亦然。具体语言实现见样例代码。
总体规范:
1. 申请APP_ID, 交换公私钥
2. 所有请求均使用 POST 方法
3. 所有请求均需要设置如下 header 头: `Content-type: application/json; charset=utf-8`
4. 所有请求参数顺序,请保持文档范例的顺序
接口涉及到的金额说明: 所有文档里面凡是金额字段,全部都是以最小单位做交互。例如人民币就是以分为单位,印度币就是以派士为单位。
重要说明: 凡是商户自己数据库里面的还款计划表有任何变更,都必须回调贷款超市更新,调用接口3.1还款计划变更通知,建议使用mysql的触发器来做,或者消息队列。
请求接口:
请求接口如下:
{
"app_id": "",
"params": "",
"sign" : ""
}
params 是由所有业务参数组成一个 string,再编码成 json 字符串,sign 是由appid加 对 params 参数进行json编码, 按照通过上诉签名算法生成的字符串,
对最终生成的签名进行base64编码, 最终将以上的得到的参数(sign)拼装到请求中,发起http请求。
响应请求
{
"code": 0,
"message": "success",
"data": {}
}
code 用于表示错误编码(字典值详见4.1),message 表示用户友好的错误提示,data 才是真正的业务结果参数。
签名样例代码
PHP版本
$private = '-----BEGIN PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKyDAsD34pUzg0JQ
MSd5dRhmoirckyxNQtryVrt6AeLJBKUtuSRZRSQFh6YxFWSI/CFJTm4Y4/35ahsM
hE+ruxAL7LU0oxo7jigmlir8vSxD2EQJeuQSLa+fcFIeOD+zC3dUycU70hJBXctJ
TLZCKcAhXmvYfTLAdiFDpZog0bSJAgMBAAECgYBb2c2HjPRZWHoUvrnNOL/94/eI
NrUtq11jUhCX9Vg3tawBfpzLG7TSfxdAh3XKd502498cg6gwu7GhAUMZYGhSPLzR
X1/NRQwCzYpD4JpiEn5OPkfu/GkF3wUe2x7/NcBwjqttdNHLXzTuZwSi51cKxLG3
L8VlBG+qWOLT4f0QGQJBAN7FmimamESNIiCiBDylf6MEZlO4+dazFMvb0etr4Ke5
hD13yCqwVgmgdRyyXEVXr0fpFkS8yEAI6pfPWhJhNT8CQQDGPkJDPKacn6AMk/0d
3aM7J9HSD3easSgvmheT/lDV0ywpNO9gDHj+SuPXrr9g1QssRO3MeSS3068h8i6W
67w3AkA5nNcKzW11xR4/XEdGO/LvgPLJ0jvi17uYIUh/3w1hsyvNDc5Zo/MMWFAR
VNtiH5Q4P8K/kicj0GqDSl/cAwHXAkAcr2eRKRaGtUZIa6WFi8uRxLpEpf5NgPLq
qGZfAL6bohnSwJkekc90JEwBqoSAs3MRbcvm+WSJwPSnec6qAeRTAkEAl/3FMsj7
E2tTF2gK4k78f0UXk6lFTnihL/bwxtC+EkUrDOAIDC5uDQokoilYN+NqYHK8dqwY
zl0AauMwcchaCw==
-----END PRIVATE KEY-----';
$public = "-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCsgwLA9+KVM4NCUDEneXUYZqIq
3JMsTULa8la7egHiyQSlLbkkWUUkBYemMRVkiPwhSU5uGOP9+WobDIRPq7sQC+y1
NKMaO44oJpYq/L0sQ9hECXrkEi2vn3BSHjg/swt3VMnFO9ISQV3LSUy2QinAIV5r
2H0ywHYhQ6WaING0iQIDAQAB
-----END PUBLIC KEY-----";
// 获取公私钥
$pi_key = openssl_pkey_get_private($private);
$pub_key = openssl_pkey_get_public($public);
$params = [
"username" => "mmomomo",
"orderNo" => "SH20180913111929"
];
// 处理请求参数
$params = getParams($params);
// 生成签名
$sign = sign($params, $private);
// 验签
$result = (bool)openssl_verify($params, base64_decode($sign), $public, OPENSSL_ALGO_SHA256);
if ($result) {
echo "验签成功" . PHP_EOL;
} else {
echo "验签失败" . PHP_EOL;
}
/**
* @param $params
*
* @return string
*/
function getParams($params)
{
$appId = "12345678";
$params = "app_id={$appId}¶ms=" . json_encode($params, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
return $params;
}
function sign($str, $privateKey)
{
//返回16进制小写字母
$pkeyid = openssl_pkey_get_private($privateKey);
// compute signature
openssl_sign(trim($str), $signature, $pkeyid, "sha256WithRSAEncryption");
// free the key from memory
openssl_free_key($pkeyid);
return base64_encode(trim($signature));
}
JAVA版本
import com.alibaba.fastjson.JSON;
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import java.io.IOException;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;
public class Demo {
//RSA签名
//text:待签名的数据,privateKeyData:第三方的私钥
private static byte[] sign(final byte[] text, final byte[] privateKeyData) throws GeneralSecurityException {
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyData);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
Signature signatureChecker = Signature.getInstance("SHA256WITHRSA");
signatureChecker.initSign(privateKey);
signatureChecker.update(text);
return signatureChecker.sign();
}
//RSA验签名检查
//text:待签名数据,signedText:签名值,publicKeyData:开发商公钥
private static boolean verify(final byte[] text, final byte[] signedText, final byte[] publicKeyData) throws GeneralSecurityException {
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyData);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(keySpec);
Signature signatureChecker = Signature.getInstance("SHA256WITHRSA");
signatureChecker.initVerify(publicKey);
signatureChecker.update(text);
return signatureChecker.verify(signedText);
}
public static String buildSignStr(Map<String, String> requestData) {
return "app_id=" + requestData.get("app_id") + "¶ms=" + requestData.get("params");
}
public static void main(String args[]) throws GeneralSecurityException, RuntimeException, IOException {
String privateKey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKyDAsD34pUzg0JQMSd5dRhmoirckyxNQtryVrt6AeLJBKUtuSRZRSQFh6YxFWSI/CFJTm4Y4/35ahsMhE+ruxAL7LU0oxo7jigmlir8vSxD2EQJeuQSLa+fcFIeOD+zC3dUycU70hJBXctJTLZCKcAhXmvYfTLAdiFDpZog0bSJAgMBAAECgYBb2c2HjPRZWHoUvrnNOL/94/eINrUtq11jUhCX9Vg3tawBfpzLG7TSfxdAh3XKd502498cg6gwu7GhAUMZYGhSPLzRX1/NRQwCzYpD4JpiEn5OPkfu/GkF3wUe2x7/NcBwjqttdNHLXzTuZwSi51cKxLG3L8VlBG+qWOLT4f0QGQJBAN7FmimamESNIiCiBDylf6MEZlO4+dazFMvb0etr4Ke5hD13yCqwVgmgdRyyXEVXr0fpFkS8yEAI6pfPWhJhNT8CQQDGPkJDPKacn6AMk/0d3aM7J9HSD3easSgvmheT/lDV0ywpNO9gDHj+SuPXrr9g1QssRO3MeSS3068h8i6W67w3AkA5nNcKzW11xR4/XEdGO/LvgPLJ0jvi17uYIUh/3w1hsyvNDc5Zo/MMWFARVNtiH5Q4P8K/kicj0GqDSl/cAwHXAkAcr2eRKRaGtUZIa6WFi8uRxLpEpf5NgPLqqGZfAL6bohnSwJkekc90JEwBqoSAs3MRbcvm+WSJwPSnec6qAeRTAkEAl/3FMsj7E2tTF2gK4k78f0UXk6lFTnihL/bwxtC+EkUrDOAIDC5uDQokoilYN+NqYHK8dqwYzl0AauMwcchaCw==";
String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCsgwLA9+KVM4NCUDEneXUYZqIq3JMsTULa8la7egHiyQSlLbkkWUUkBYemMRVkiPwhSU5uGOP9+WobDIRPq7sQC+y1NKMaO44oJpYq/L0sQ9hECXrkEi2vn3BSHjg/swt3VMnFO9ISQV3LSUy2QinAIV5r2H0ywHYhQ6WaING0iQIDAQAB";
final Map<String, String> params = new HashMap<String, String>();
params.put("username", "mmomomo");
params.put("orderNo", "SH20180913111929");
Map<String, String> requestData = new HashMap<String, String>() {
{
put("app_id", "12345678");
put("params", JSON.toJSONString(params));
}
};
byte[] requestDataByJson = buildSignStr(requestData).getBytes();
byte[] base64PrivateKey = Base64.decode(privateKey);
byte[] base64PublicKey = Base64.decode(publicKey);
byte[] signBytes = sign(requestDataByJson, base64PrivateKey);
String strSignature = new String(Base64.encode(signBytes));
System.out.println("签名: " + strSignature);
boolean bolVerify = verify(requestDataByJson, signBytes, base64PublicKey);
System.out.println("验签: " + bolVerify);
}
}
GO版本
package main
import (
"bytes"
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"fmt"
)
func main() {
signature := Sign(GetParams())
if Check(GetParams(), signature) {
fmt.Println("验签成功")
} else {
fmt.Println("验签失败")
}
}
func GetParams() string {
params := map[string]interface{}{
"username":"momo",
"orderNo":"SH20180913111929",
}
paramJson, _ := json.Marshal(params)
appId := "12345678"
str := "app_id=" + appId+"¶ms=" + string(paramJson)
return str
}
var privateKey = []byte(`
-----BEGIN PRIVATE KEY-----
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAOXcXDlUfioHYK8x
+PTip8W9bwh9fWmTT9gXQGVo+91UQ0V/uZe1eR/PZrsW3GgZEqA3qavj2xSg/q+i
o8isZ2zm7qrzY0zvuCJ/ZTnqi1aGzlB2oH4CIq0nUtck/LeWS3Z5OQVQLB0FglRq
atZ/feqdBsqgqqwfggMVZI6A6w2PAgMBAAECgYACT7Pxs6LNI2PafyMkuz66o0wB
htmC/UM5RgqUuniF4joG8eDY4K9GHZzH1tQCi/X+/HBNVS43x+Knu3fwJaOolMzc
WagAW8csDXrT9M1XxfXiw/X/rKoit6Z60z+2OPO/XozR2MuP9Z6MYrLHRvD3RySD
rPK7ccogj3IJZ/4iUQJBAPYSb6Th2sb24flS8pxTgHMaBUeoRklnUMFW3UzpjsQV
uVGfr+P3RcqpWGh+9IqhFdMoVDmnUnr4Om712GIOJvcCQQDvInxeS9TqZNzSpVaZ
du7SL4tXEsZrgCpehtzQfEBCfReNTdEa25aseKSBqdpLyO8pgz5IjeFnU6ahifY0
y7ApAkEA2A6R9nzUglQtT6QUH0x2ARo8vpEyvaq4Tjn971U3JFZKtC942bm4jtwo
IwAtqTcNGa1UXpbapdwkOl8kEdyJOwJAQ0cKgmUHQ+KYldLaaFajnhKuOxMXK8tl
IC8FFMrAMXSMGb8Y41uAKonOjoRA3C1ty9oWvcbc8XsBWFU+JWBg8QJBAJ1xeRPh
SdYwi7GHNPQ4JruaYvrnBRK7XqYqHRBSMb8IFpDjWEoKJhr/rDoE/Z9x+pwgLsSM
OsfW8fVMcwILSqg=
-----END PRIVATE KEY-----
`)
var publicKey = []byte(`
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDl3Fw5VH4qB2CvMfj04qfFvW8I
fX1pk0/YF0BlaPvdVENFf7mXtXkfz2a7FtxoGRKgN6mr49sUoP6voqPIrGds5u6q
82NM77gif2U56otWhs5QdqB+AiKtJ1LXJPy3lkt2eTkFUCwdBYJUamrWf33qnQbK
oKqsH4IDFWSOgOsNjwIDAQAB
-----END PUBLIC KEY-----
`)
// 返回 signature
func Sign(params string) string {
messageBytes := bytes.NewBufferString(params)
hash := sha256.New()
hash.Write(messageBytes.Bytes())
digest := hash.Sum(nil)
//pem解码
block, _ := pem.Decode(privateKey)
var rsaPrivateKey interface{}
//x509解码
rsaPrivateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return ""
}
signature, err := rsa.SignPKCS1v15(rand.Reader, rsaPrivateKey.(*rsa.PrivateKey), crypto.SHA256, digest)
if err != nil {
return ""
}
return base64.StdEncoding.EncodeToString(signature)
}
func Check(params, signature string) bool {
messageBytes := bytes.NewBufferString(params)
hash := sha256.New()
hash.Write(messageBytes.Bytes())
digest := hash.Sum(nil)
//pem解码
block, _ := pem.Decode(publicKey)
//x509解码
publicKeyInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return false
}
sig, err := base64.StdEncoding.DecodeString(signature)
if err != nil {
return false
}
//类型断言
publicKey := publicKeyInterface.(*rsa.PublicKey)
err = rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, digest, sig)
if err != nil {
return false
}
return true
}