Skip to content

专业版鉴权 ​

专业版只接受 /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。

鉴权链路 ​

  1. 接入方服务端构造待签字符串。
  2. 使用 Secret Key 计算 x-signature。
  3. 调用 GET /api/grant/otp_code 获取一次性 code。
  4. 前端调用 AipptAgent.create,传入 auth.appKey 和 auth.code。
  5. iframe 页面内完成凭证兑换、用户校验和服务可用性校验。
  6. 校验通过后加载页面,并通过 APP_READY 通知接入方。

接入方负责获取、传入和刷新 code。前端不应直接处理访问凭证,也不要把拼装版 API 的 token 传给 iframe。

凭证边界 ​

凭证获取接口使用位置适用接入方式
token/api/grant/token拼装版 API 请求 Header拼装版 API
code/api/grant/codeAipptIframe.show 初始化参数预装版 UI
一次性 code/api/grant/otp_codeAipptAgent.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-keyheaderstring是创建账号时生成的 appkey
x-timestampheaderstring是请求时间戳,与计算签名使用的时间戳保持一致
x-signatureheaderstring是根据以下签名规则计算得到的签名
uidquerystring是接入方自己的用户唯一标识
channelquerystring是渠道标识,没有渠道区分时传空值

请求示例 ​

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>'
})

鉴权完成后创建 iframe ​