接口功能介绍
AI应用中心登录接入是为了实现用户便捷安全自动登录AI空间的合作类AI应用。开发者将AI应用接入后上架AI空间,即可实现用户自动登录AI应用功能。
架构流程
接口约束
无
请求参数
请求头header参数
无
请求body参数
参数 是否必填 参数类型 说明 ticket 是 String 有效期是60秒,用于第三方服务回调AI应用后台,查询用户信息 source 是 String 来源类型,表明该请求从那一方请求,
向服务方申请提供
响应参数
参数 参数类型 说明 auid String 对用户手机号码的加密 name String 用户名称 bssResourceId String BSS系统资源id
枚举参数
无
请求示例
curl https://eaichat.ctyun.cn/ai/portal/v1/app/queryUserInfoByTicket?ticket=xxxxxx&source=xxx
响应示例一
{
"resultCode": 0,
"resultMsg": "success",
"data": {"auid":"xxxxxxxxxx","name":"张三"}
}
响应示例二
{
"resultCode": 10002,
"resultMsg": "ticket 无效",
"data": null
}
测试环境URL
https://eai-test.bgzs.site:1443/ai/portal/v1/app/queryUserInfoByTicket(外网访问,请提供公网出口IP,配置白名单)
生产环境URL
# 示例
wget https://eaichat.ctyun.cn/ai/portal/v1/app/queryUserInfoByTicket?ticket=111&source=techexxx
接口验签
对ai/portal/v1/app/queryUserInfoByTicket 请求接口URL进行参数验签
请求头需带
key | value | col3 |
---|---|---|
YL-Signature | <string> | |
YL-Timestamp | <uint64> | 毫秒时间戳 |
YL-Random | <string> | 8位随机数,例如Cq8s9vqi |
YL-3rd-Appcode | <string> | ak 管理员提供 |
第三方应用的签名生成由下面几项决定:
- URL参数params(目前只支持验签URL参数,body参数不验)
- 时间戳YL-Timestamp
- 随机数YL-Random(由客户端随机生成,用于区分极短时间内发起的相同参数请求)
- 第三方应用标识码YL-3rd-Appcode
- 本地预埋的sk
签名YL-Signature生成方法:
1.将请求参数键值对中的key按照字母升序排序,而后将排好序的参数以“&”为分隔符进行拼接,产生一个字符串;
2.在步骤1字符串的基础上,再按顺序将sk、YL-Timestamp、YL-Random、YL-3rd-Appcode以“&”为分隔符拼接在末尾;
3.基于单向哈希算法SHA256,应用密钥sk对步骤2的字符串生成签名。
例如:SHA256(param1=123¶m2=456&sk×tamp&random&appcode)
参数 参数类型 参数说明 ak String 用于请求参数签名的ak
向服务方申请提供
sk String 用于请求参数签名的私钥,私发
Java签名算法例子
public class OpenApiUtil {
private static final String KEY_SEPARATOR = "&";
/** ak 由管理员提供 */
private static final String HTTP_HEADER_YL_APPCODE = "YL-3rd-Appcode";
/** 时间戳 单位毫秒 */
private static final String HTTP_HEADER_YL_TIMESTAMP = "YL-Timestamp";
/** 8位随机数 */
private static final String HTTP_HEADER_YL_RANDOM = "YL-Random";
/** 签名 */
private static final String HTTP_HEADER_YL_SIGNATURE = "YL-Signature";
/**
* 组装验签头部
* @param appKey
* @param appSecret
* @param paramMap
* @return
*/
public static Map<String, String> buildHeaders(String appKey, String appSecret, Map<String, String[]> paramMap) {
if (appKey == null || appSecret == null) {
return null;
}
// 组装前置条件
Long timestamp = System.currentTimeMillis();
String random = RandomStringUtils.randomAlphanumeric(8);
// 计算签名
String signature = buildSignature(appKey, appSecret, random, timestamp, paramMap);
// 组装HTTP头部
return buildHeaders(appKey, random, timestamp, signature);
}
/**
* 签名
* @param appKey
* @param appSecret
* @param random
* @param timestamp
* @param paramMap
* @return
*/
private static String buildSignature(String appKey, String appSecret, String random, Long timestamp, Map<String, String[]> paramMap) {
if (appKey == null || appSecret == null || random == null || timestamp == null) {
return null;
}
// 组成签名前原串
StringBuilder sb = new StringBuilder();
if (paramMap != null && paramMap.size() > 0) {
List<String> keys = new ArrayList<>(paramMap.keySet());
// 参数排序 (ASCII 升序)
Collections.sort(keys);
for (String key : keys) {
String val = paramMap.get(key)[0];
sb.append(key).append("=").append(val).append(KEY_SEPARATOR);
}
}
sb.append(appSecret).append(KEY_SEPARATOR)
.append(timestamp).append(KEY_SEPARATOR)
.append(random).append(KEY_SEPARATOR)
.append(appKey);
//System.out.println("text=\n"+sb);
return sha256(sb.toString().getBytes(StandardCharsets.UTF_8));
}
/**
* 组装验签头部
* @param appKey
* @param random
* @param timestamp
* @param signature
* @return
*/
private static Map<String, String> buildHeaders(String appKey, String random, Long timestamp, String signature) {
Map<String, String> headers = new HashMap<>(4);
if (appKey != null) {
headers.put(HTTP_HEADER_YL_APPCODE, appKey);
}
if (random != null) {
headers.put(HTTP_HEADER_YL_RANDOM, random);
}
if (timestamp != null) {
headers.put(HTTP_HEADER_YL_TIMESTAMP, ""+timestamp);
}
if (signature != null) {
headers.put(HTTP_HEADER_YL_SIGNATURE, signature);
}
return headers;
}
// 哈希部分逻辑
private static final char[] LOWER_HEX_DIGITS =
new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
private static final String MD5_ALGORITHM_NAME = "MD5";
private static final String SHA256_ALGORITHM_NAME = "SHA-256";
public static String md5(byte[] data) {
return encodeAsHexString(MD5_ALGORITHM_NAME, data);
}
public static String sha256(byte[] data) {
return encodeAsHexString(SHA256_ALGORITHM_NAME, data);
}
/**
* 使用 {@code algorithmName} 加密算法编码 {@code data}
* @param algorithmName 算法名
* @param data 被编码的字节数组
* @return {@code data} 编码后的小写 16 进制形式字符串
* @throw IllegalStateException 当找不到 {@code algorithmName} 对应算法
*/
private static String encodeAsHexString(String algorithmName, byte[] data) {
try {
byte[] hash = MessageDigest.getInstance(algorithmName).digest(data);
return new String(encodeHex(hash));
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("MessageDigest 找不到算法:" + algorithmName, e);
}
}
private static char[] encodeHex(byte[] data) {
int l = data.length;
char[] arr = new char[l << 1];
int i = 0;
for(int j = 0; i < l; ++i) {
arr[j++] = LOWER_HEX_DIGITS[(240 & data[i]) >>> 4];
arr[j++] = LOWER_HEX_DIGITS[15 & data[i]];
}
return arr;
}
public static void main(String[] args) {
String ak = "ak";
String sk = "sk";
Map<String, String[]> paramMap = new HashMap<>(2);
paramMap.put("param2", new String[]{"456","789"});
paramMap.put("param1", new String[]{"123"});
Map<String, String> header = buildHeaders(ak, sk, paramMap);
System.out.println("header="+header);
}