专业版鉴权
专业版只接受 /api/grant/otp_code 返回的一次性 code。服务端负责签名和获取,前端在 AipptAgent.create 的 auth.code 中使用。
一次性 code 的使用范围
| 问题 | 结论 |
|---|---|
这个 code 用在哪里 | 只用于 AipptAgent.create 初始化或 refreshAuth 认证刷新 |
谁来获取 code | 接入方服务端使用 AK/SK 签名请求 /api/grant/otp_code |
| 有效期和使用次数 | 默认 5 分钟、单次使用,过期或消费后需要重新获取 |
是否可以复用拼装版 API 的 token | 不可以,拼装版 API 的 token 只用于 API 请求 |
是否可以复用预装版 UI 的 /api/grant/code | 不可以,专业版使用 /api/grant/otp_code |
获取 code 前检查
| 检查项 | 要求 |
|---|---|
| 开放平台凭证 | 已获取 App Key 和 Secret Key |
| 用户标识 | 已确定接入方用户唯一标识 uid |
| 获取位置 | code 必须由接入方服务端获取 |
| 使用位置 | code 只用于 AipptAgent.create |
注意
获取接口路径仍是 /api/grant/otp_code,但响应字段和 SDK 入参字段都是 code。
鉴权链路
- 接入方服务端构造待签字符串。
- 使用
Secret Key计算x-signature。 - 调用
GET /api/grant/otp_code获取一次性code。 - 前端调用
AipptAgent.create,传入auth.appKey和auth.code。 - iframe 页面内完成凭证兑换、用户校验和服务可用性校验。
- 校验通过后加载页面,并通过
APP_READY通知接入方。
接入方负责获取、传入和刷新 code。前端不应直接处理访问凭证,也不要把拼装版 API 的 token 传给 iframe。
凭证边界
| 凭证 | 获取接口 | 使用位置 | 适用接入方式 |
|---|---|---|---|
token | /api/grant/token | 拼装版 API 请求 Header | 拼装版 API |
code | /api/grant/code | AipptIframe.show 初始化参数 | 预装版 UI |
一次性 code | /api/grant/otp_code | AipptAgent.create 初始化或 refreshAuth | 专业版 |
建议接入方服务端为不同接入方式拆分授权方法,避免把三类凭证混用到同一个前端字段中。
签名规则
| 字段 | 取值 |
|---|---|
| 请求方法 | GET |
| 签名路径 | /api/grant/otp_code/ |
| 待签字符串 | GET@/api/grant/otp_code/@{timestamp} |
| 签名算法 | HmacSHA1 后进行 Base64 编码 |
Node.js 服务端完整示例
ts
import CryptoJS from 'crypto-js'
const API_ORIGIN = 'https://co.aippt.cn'
const apiKey = process.env.AIPPT_API_KEY!
const apiSecret = process.env.AIPPT_API_SECRET!
function createSignature(path: string, timestamp: number) {
const apiUri = path.endsWith('/') ? path : `${path}/`
const stringToSign = `GET@${apiUri}@${timestamp}`
const hash = CryptoJS.HmacSHA1(stringToSign, apiSecret)
return CryptoJS.enc.Base64.stringify(hash)
}
export async function getSlidePlatformCode(uid: string, channel = '') {
const path = '/api/grant/otp_code'
const timestamp = Math.floor(Date.now() / 1000)
const signature = createSignature(path, timestamp)
const url = new URL(path, API_ORIGIN)
url.searchParams.set('uid', uid)
url.searchParams.set('channel', channel)
const response = await fetch(url, {
method: 'GET',
headers: {
'x-api-key': apiKey,
'x-timestamp': String(timestamp),
'x-signature': signature
}
})
const result = await response.json()
if (!response.ok || result.code !== 0) {
throw new Error(result.msg || `获取 code 失败:${response.status}`)
}
return {
code: result.data.code as string,
expiresIn: result.data.time_expire as number,
apiKey: result.data.api_key as string,
uid: result.data.uid as string
}
}其他语言签名片段
以下示例只负责生成 x-signature。实际请求时还需要同时传入 x-api-key 和同一个 timestamp。
Java
java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
String apiSecret = "<apiSecret>";
long timestamp = System.currentTimeMillis() / 1000;
String stringToSign = "GET@/api/grant/otp_code/@" + timestamp;
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(new SecretKeySpec(apiSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA1"));
String signature = Base64.getEncoder().encodeToString(
mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8))
);Python
python
import base64
import hashlib
import hmac
import time
api_secret = "<apiSecret>"
timestamp = int(time.time())
string_to_sign = f"GET@/api/grant/otp_code/@{timestamp}"
signature = base64.b64encode(
hmac.new(api_secret.encode(), string_to_sign.encode(), hashlib.sha1).digest()
).decode()Go
go
package main
import (
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"fmt"
"time"
)
func main() {
apiSecret := "<apiSecret>"
timestamp := time.Now().Unix()
stringToSign := fmt.Sprintf("GET@/api/grant/otp_code/@%d", timestamp)
mac := hmac.New(sha1.New, []byte(apiSecret))
mac.Write([]byte(stringToSign))
signature := base64.StdEncoding.EncodeToString(mac.Sum(nil))
fmt.Println(signature)
}PHP
php
<?php
$apiSecret = '<apiSecret>';
$timestamp = time();
$stringToSign = 'GET@/api/grant/otp_code/@' . $timestamp;
$signature = base64_encode(hash_hmac('sha1', $stringToSign, $apiSecret, true));
?>请求 Header 中的 x-timestamp 必须与待签字符串里的 {timestamp} 保持一致。
获取 code
请求参数
| 参数 | 位置 | 类型 | 是否必传 | 说明 |
|---|---|---|---|---|
x-api-key | header | string | 是 | 创建账号时生成的 appkey |
x-timestamp | header | string | 是 | 请求时间戳,与计算签名使用的时间戳保持一致 |
x-signature | header | string | 是 | 根据以下签名规则计算得到的签名 |
uid | query | string | 是 | 接入方自己的用户唯一标识 |
channel | query | string | 是 | 渠道标识,没有渠道区分时传空值 |
请求示例
bash
curl --location 'https://co.aippt.cn/api/grant/otp_code?uid=1&channel=' \
--header 'x-api-key: <apiKey>' \
--header 'x-timestamp: 1696821929' \
--header 'x-signature: <signature>'<signature> 必须按以下签名规则实时计算,不要复用示例值。
响应示例
json
{
"code": 0,
"data": {
"api_key": "api_key",
"uid": "1",
"code": "xxxx",
"time_expire": 300
},
"msg": "ok"
}前端使用方式
ts
const ppt = await AipptAgent.create({
auth: {
appKey: '<apiKey>',
code: '<code>'
},
mount: {
container: document.getElementById('slide-root')!
},
iframe: {
parentOrigin: window.location.origin
},
events: {
onReady(event) {
console.log('专业版 ready', event.payload)
},
onError(error) {
console.log('专业版 error', error)
}
}
})过期与刷新
| 场景 | 建议处理 |
|---|---|
code 未使用但已超过 5 分钟 | 重新向接入方服务端申请新的 code。 |
code 已被消费 | 不要复用旧值,重新申请新的 code。 |
| 页面长时间停留 | 根据 APP_READY.auth.refreshRequiredAt 提前申请新的 code。 |
| iframe 请求刷新认证 | 收到 AUTH_REFRESH_REQUIRED 后,调用 ppt.refreshAuth({ code })。 |
认证刷新示例:
ts
await ppt.refreshAuth({
code: '<freshCode>'
})