背景
欢迎与 Bitget 交易所合作,您作为 Broker 在接入我们的 FastApi 时,需要按照我们给出的标准进行接入。在本文档中,我们将为您介绍如何接入到 Bitget 的 FastApi。
接入准备
该功能需要联系您的 Bitget BD(Business Development)进行申请。请联系 BD 并提交以下资料:
| 信息 | 说明 |
|---|
| name | 平台名称 |
| contact | 联系方式 — 邮箱或手机号 |
| ip | 服务器 IP — Broker 平台调用 Bitget API 接口的常用 IP 地址 |
| logo(AI) | 平台 Logo — 文件格式为 Adobe Illustrator |
| logo(PNG) | 平台 Logo — 文件格式为 PNG,像素要求为 64 × 64 |
| callbackUrl | 回调地址 — 用于 Broker 用户信息回传 |
| rsaPublicKey(2048 bit) | 公钥 — 基于 RSA 算法;格式规则为 RSA PKCS8 2048 位 |
申请成功后,BD 会提供以下信息:
| 参数名称 | 参数类型 | 说明 |
|---|
| clientId | String | Broker 平台 ID — 由 Bitget 分配的唯一标识 |
技术实现
下面涉及到的一些属性信息,主要用于对接功能实现。
| 参数名称 | 参数类型 | 提供方 | 描述 |
|---|
| clientId | String | Bitget | Broker 平台 ID — 申请成功后由 BD 提供 |
| callbackUrl | String | Broker 客户 | 回调地址 — 作为 BD 申请材料的一部分提交给 Bitget;用于 Broker 用户信息回传 |
| rsaPublicKey(2048 bit) | String | Broker 客户 | 公钥 — 作为 BD 申请材料的一部分提交给 Bitget;基于 RSA 算法,格式规则为 RSA PKCS8 2048 位 |
| rsaPrivateKey(2048 bit) | String | Broker 客户 | 私钥 — 基于 RSA 算法;格式规则为 RSA PKCS8 2048 位;由 Broker 自行保管,无需提供给 Bitget |
请妥善保管私钥
rsaPrivateKey 必须由 Broker 一方安全保存,切勿提供给 Bitget 或任何第三方。一旦私钥泄露,他人可解密用户的 API Key 信息并伪造签名。
代码示例(Java)
下面是签名和 RSA 加密解密的代码示例,均基于 Java 实现。
工具类
签名以及 RSA 加密/解密都基于下面的工具类实现。
package xxx;
import org.springframework.util.Base64Utils;
import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
public class RsaUtil {
// 使用公钥进行 RSA 加密
public static String encryptRSA(String plaintext, String publicKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, getPublicKey(publicKey));
byte[] encryptedBytes = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
return Base64Utils.encodeToString(encryptedBytes);
}
// 使用私钥进行 RSA 解密
public static String decryptRSA(String encryptedData, String privateKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, getPrivateKey(privateKey));
byte[] encryptedBytes = Base64Utils.decodeFromString(encryptedData);
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
return new String(decryptedBytes, StandardCharsets.UTF_8);
}
/**
* 解码 PublicKey
*
* @param key
* @return
*/
public static PublicKey getPublicKey(String key) throws Exception {
byte[] byteKey = org.apache.commons.codec.binary.Base64.decodeBase64(key);
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(byteKey);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(x509EncodedKeySpec);
}
/**
* 解码 PrivateKey
*
* @param key
* @return
*/
public static PrivateKey getPrivateKey(String key) throws Exception {
byte[] byteKey = org.apache.commons.codec.binary.Base64.decodeBase64(key);
PKCS8EncodedKeySpec x509EncodedKeySpec = new PKCS8EncodedKeySpec(byteKey);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(x509EncodedKeySpec);
}
public static boolean verify(Map<String, String> map, String publicKey, String sign) throws Exception {
String srcData = getSignData(map);
byte[] keyBytes = getPublicKey(publicKey).getEncoded();
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey key = keyFactory.generatePublic(keySpec);
Signature signature = Signature.getInstance("MD5withRSA");
signature.initVerify(key);
signature.update(srcData.getBytes());
return signature.verify(Base64Utils.decodeFromString(sign));
}
/**
* 签名
*
* @param data 待签名数据
* @param privateKey 私钥
* @return 签名
*/
public static String sign(String data, String privateKey) throws Exception {
byte[] keyBytes = getPrivateKey(privateKey).getEncoded();
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey key = keyFactory.generatePrivate(keySpec);
Signature signature = Signature.getInstance("MD5withRSA");
signature.initSign(key);
signature.update(data.getBytes());
return Base64Utils.encodeToString(signature.sign());
}
/**
* 得到签名
*
* @param params 参数集合,不含 secretKey
* @return
*/
public static String getSignData(Map<String, String> params) {
StringBuilder sb = new StringBuilder();
// step1:先对请求参数排序
Set<String> keyset = params.keySet();
TreeSet<String> sortSet = new TreeSet<String>();
sortSet.addAll(keyset);
Iterator<String> it = sortSet.iterator();
// step2:把参数的 key value 链接起来,secretKey 放在最后面,得到要加密的字符串
while (it.hasNext()) {
String key = it.next();
String value = params.get(key);
sb.append(key).append(value);
}
return sb.toString();
}
static String publicKey = "*****";
static String privateKey = "*****";
}
签名使用示例
public static void main(String[] args) throws Exception {
// 生成签名
Map<String, String> map = new HashMap<>();
long l = System.currentTimeMillis();
map.put("timestamp", String.valueOf(l));
map.put("clientId", "1");
map.put("clientUserId", "123");
String sign = RsaUtil.sign(RsaUtil.getSignData(map), RsaUtil.privateKey);
System.out.println(sign);
}
RSA 加密和解密使用示例
public static void main(String[] args) throws Exception {
String xxStr = "hello world";
// encrypt
String encrypted = RsaUtil.encryptRSA(xxStr, RsaUtil.publicKey);
System.out.println("encrypt by publicKey: " + encrypted);
// decrypt
System.out.println("decrypt by privateKey: " + RsaUtil.decryptRSA(encrypted, RsaUtil.privateKey));
}
功能实现
下面是功能实现的具体过程,其中涉及到了接口、参数、签名生成规则等 OAuth 授权流程细节。
整体流程分为三步:Broker 跳转 Bitget 授权页面、Bitget 携带用户 API Key 回调 Broker 接口、Bitget 携带授权结果跳转回 Broker 页面。
1. Broker 跳转 Bitget 授权页面
从 Broker 平台跳转到 Bitget 的授权页面时,需要 Broker 在 URL 中携带以下参数。
Bitget 授权页面链接为 https://www.bitget.com/account/oauth。
| 参数名 | 参数类型 | 描述 |
|---|
| clientUserId | String | Broker 用户 ID — 客户自定义 |
| timestamp | String | 时间戳 — Unix 毫秒格式;请传入当前时间前后 5 分钟内的时间戳,超出范围会被认为无效请求;用户在 Bitget 页面点击授权时会校验该时间戳 |
| clientId | String | Broker 平台 ID |
| sign | String | 签名 — 格式规则为 clientId[]clientUserId[]timestamp[];实际使用请参考代码示例(Java) |
| redirectUrl | String | 重定向地址 — 授权完成后自动跳转的 Broker 平台链接 |
| serialNo | String | 随机序列号 — 1)在回调接口中,Broker 需要通过此参数校验回调接口请求的合法性;2)出于安全考虑,请将 serialNo 与 clientUserId 关联,不要在多个账户间混用;3)出于安全考虑,建议为 serialNo 设置有效期,例如 10 分钟 |
示例 URL(请将参数进行 URL 编码,防止参数在传输过程中转译丢失):
https://www.bitget.com/account/oauth?clientUserId=123×tamp=1691117744470&clientId=1&redirectUrl=https://broker-domain/callback/url&sign=F9zeG9oUYzqwN94kSfoFy...&serialNo=123
补充说明(可选配置)
为帮助提升用户注册转化,Bitget 支持在 OAuth 跳转链接中携带邀请码(Referral Code)。当用户通过 OAuth 跳转至 Bitget 页面后点击注册,系统会自动识别链接中携带的邀请码,并在注册页面自动填充,无需用户手动输入。
配置方式:
- 联系 Broker 运营或 BD,在后台配置邀请链接(长链接),例如
https://www.bitget.com/test?channelCode=abcde&vipCode=1234&languageType=1。
- 从邀请链接中获取邀请码参数,例如
vipCode=1234。
- 将该邀请码参数追加至 Bitget OAuth 跳转链接后,即可完成配置。
https://www.bitget.com/account/oauth?clientUserId=123×tamp=1691117744470&clientId=1&redirectUrl=https://broker-domain/callback/url&sign=F9zeG9oUYzqwN94kSfoFy...&serialNo=123&channelCode=abcde&vipCode=1234
2. Bitget 回调 Broker 接口
一旦用户在页面完成授权,Bitget 将自动请求 Broker 回调接口。
- 请求类型:
POST
- 请求 URL:双方商议
- Content-Type:
application/x-www-form-urlencoded
| 参数名 | 参数类型 | 描述 |
|---|
| openId | String | 加密后的 Bitget user ID |
| userId | String | Bitget user ID — 仅白名单用户返回,非白名单用户返回为空 |
| clientUserId | String | Broker 用户 ID |
| timestamp | String | 时间戳 — Unix 毫秒格式;请验证该时间与当前时间的差值是否超过 5 分钟,如超出范围请视作无效请求 |
| sign | String | 签名 — 请通过 privateKey 解密,解密后可获得 serialNo |
| data | String | 加密后的 API Key 信息 — 请通过 privateKey 解密,解密后可获得用户 apiKey、passphrase 及 secret |
其他说明
- 请使用
privateKey 对 sign 进行解密,并校验结果是否与 serialNo 一致,若不一致则说明请求不合法。
- 请使用
privateKey 对 data 进行解密,解密结果为 JSON 格式的用户 API Key 信息:
{
"apiKey": "bg_xxxxxxxxxx",
"passphrase": "***",
"secret": "xxxxxxxxxxxxxxxxxxxxxxx"
}
请妥善保管 API Key 信息
解密后得到的 apiKey、passphrase、secret 可用于操作用户的 Bitget 账户交易权限,请安全存储(如加密存储),切勿在日志、前端代码或错误信息中暴露这些信息。
Bitget 请求后,Broker 需按照如下格式返回响应参数:
{
"code": "00000",
"msg": "success",
"requestTime": "1671258739383"
}
3. Bitget 跳转回 Broker 页面
授权结果无论成功还是失败,Bitget 都会将下列参数添加至 URL,并跳转到 Broker 页面。
| 参数名 | 参数类型 | 描述 |
|---|
| clientUserId | String | Broker 平台的用户 ID |
| sign | String | 签名 — 通过 publicKey 加密后的 serialNo,需要进行解密 |
| state | String | 授权状态 — 通过 publicKey 加密后的 state,需要进行解密;SUCCESS 表示授权成功,FAIL 表示授权失败 |
示例:
https://broker-domain/callback/url?sign=sahkduhas1237hkNAQGS&clientUserId=123&state=DHJSAshaj
其他说明
- 请使用
privateKey 对 sign 进行解密,并校验结果是否与 serialNo 一致,若不一致则说明请求不合法。
- 建议通过
serialNo 查询 clientUserId,并判断是否与当前 URL 中的 clientUserId 一致,若不一致则说明请求不合法。