SecondMeSecondMe API
认证

OAuth2 集成指南

OAuth2 允许第三方应用在用户授权后访问其 MindVerse 数据

OAuth2 允许第三方应用在用户授权后访问其 MindVerse 数据。本指南介绍如何实现标准的授权码流程。

概述

SecondMe API 使用标准的 OAuth2 授权码流程 (Authorization Code Flow):

  1. 用户被重定向到 MindVerse 授权页面
  2. 用户确认授权
  3. MindVerse 返回授权码到你的应用
  4. 你的应用用授权码换取 Access Token
  5. 获取并保存 appScopedUserId
  6. 使用 Access Token 调用 API
  7. 在用户撤销授权时接收 webhook 通知

授权流程时序图

下图展示了完整的 OAuth2 授权码流程:

前提条件

在开始之前,你需要:

  1. SecondMe Developer Console 注册应用
  2. 获取 client_idclient_secret
  3. 配置回调 URL (Redirect URI)

令牌类型和有效期

令牌类型前缀有效期
Authorization Codelba_ac_5 分钟
Access Tokenlba_at_7 天
Refresh Tokenlba_rt_365 天

授权流程

步骤 1: 发起授权请求

引导用户访问 SecondMe 授权页面进行登录和授权。

将用户重定向到 SecondMe 授权页面:

https://go.second-me.cn/oauth/?client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URI&response_type=code&state=RANDOM_STATE

授权 URL 参数:

参数类型必需说明
client_idstring应用的 Client ID
redirect_uristring授权后的回调 URL,必须与应用配置一致
response_typestring固定值 code
statestringCSRF 保护参数,建议使用随机字符串

前端示例代码:

// 构建授权 URL
function buildAuthorizationUrl() {
  const params = new URLSearchParams({
    client_id: 'your_client_id',
    redirect_uri: 'https://your-app.com/callback',
    response_type: 'code',
    state: generateRandomState()  // 生成随机 state 并存储用于验证
  });
  return `https://go.second-me.cn/oauth/?${params.toString()}`;
}

// 发起授权 - 方式1: 直接跳转
window.location.href = buildAuthorizationUrl();

// 发起授权 - 方式2: 新窗口打开
window.open(buildAuthorizationUrl(), '_blank');

用户在 SecondMe 完成登录和授权后,会被重定向回你的 redirect_uri,URL 中包含授权码:

https://your-app.com/callback?code=lba_ac_xxxxx...&state=your_state

重要: 收到回调后,务必验证 state 参数与发起请求时存储的值一致,以防止 CSRF 攻击。

步骤 2: 用授权码换取 Token

收到授权码后,在服务端用授权码换取 Access Token:

curl -X POST "https://api.mindverse.com/gate/lab/api/oauth/token/code" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=lba_ac_xxxxx..." \
  -d "redirect_uri=https://your-app.com/callback" \
  -d "client_id=your_client_id" \
  -d "client_secret=your_client_secret"

注意: 必须使用 application/x-www-form-urlencoded 格式发送请求体,不能使用 JSON 格式(application/json)。使用错误的格式会导致 Field required 验证错误。

请求参数:

参数类型必需说明
grant_typestring固定值 authorization_code
codestring步骤 1 获取的授权码
redirect_uristring必须与步骤 1 中的值一致
client_idstring应用的 Client ID
client_secretstring应用的 Client Secret

成功响应:

{
  "code": 0,
  "data": {
    "accessToken": "lba_at_xxxxx...",
    "refreshToken": "lba_rt_xxxxx...",
    "tokenType": "Bearer",
    "expiresIn": 7200,
    "scope": ["userinfo", "chat.read", "chat.write"]
  }
}

步骤 3: 获取并保存 appScopedUserId

在 Access Token 换取成功后,建议立即调用 GET /api/auth/me 获取当前授权关系对应的 appScopedUserId,并保存到你自己的用户绑定关系中。

当用户后续在 SecondMe 侧主动取消对你应用的授权时,平台发送的 authorization.revoked webhook 将只携带 appScopedUserId 作为授权关系标识。你的服务端需要依靠这个字段定位本地用户,并撤销本地登录态、账号绑定或访问权限。

curl -X GET "https://api.mindverse.com/gate/lab/api/auth/me" \
  -H "Authorization: Bearer lba_at_xxxxx..."

成功响应:

{
  "code": 0,
  "data": {
    "userId": "u_xxxxx...",
    "name": "张三",
    "email": "zhangsan@example.com",
    "avatar": "https://example.com/avatar.png",
    "bio": "个人简介",
    "appScopedUserId": "asu_xxxxx..."
  }
}

字段说明:

字段类型说明
userIdstring平台内部用户 ID
appScopedUserIdstring当前用户在你的应用维度下的稳定授权标识

重要: appScopedUserId 在同一个应用内稳定,但不同应用之间不可比较。它不能替代平台内部 userId,也不应该暴露给其他应用作为通用用户标识。

步骤 4: 使用 Access Token

在 API 请求中使用 Access Token:

curl -X GET "https://api.mindverse.com/gate/lab/api/secondme/user/info" \
  -H "Authorization: Bearer lba_at_xxxxx..."

步骤 5: 处理授权撤销 webhook

当用户在 SecondMe 中主动取消对你的应用授权后,平台会向你在应用配置中填写的 webhook 地址发送 authorization.revoked 事件。

配置 webhook

在应用配置页中,你需要配置:

  • Authorization revoked webhook URL
  • Webhook secret

Webhook secret 只会在创建或更新时明文返回一次。平台只保存加密后的版本,你的服务端需要安全保存这份 secret,用于后续验签。

如果控制台提供“发送测试”能力,你可以用它立即验证当前填写的 URL、secret、验签和接收逻辑是否正确,而不会影响真实授权关系。

事件定义

Headers:

Header说明
Content-Type固定为 application/json
X-SecondMe-Event-Id事件唯一 ID
X-SecondMe-TimestampUnix 秒级时间戳
X-SecondMe-SignatureHMAC-SHA256 十六进制小写签名

Payload:

{
  "eventId": "evt_xxx",
  "eventType": "authorization.revoked",
  "occurredAt": "2026-04-13T14:30:00Z",
  "appId": "app_xxx",
  "appScopedUserId": "asu_xxx",
  "reason": "user_revoked"
}
字段类型说明
eventIdstring事件唯一 ID,可用于幂等去重
eventTypestring固定为 authorization.revoked
occurredAtstring事件发生时间,ISO 8601 格式
appIdstring触发事件的应用 ID
appScopedUserIdstring用于定位你本地用户绑定关系的授权标识
reasonstring第一版固定为 user_revoked

测试投递时,eventType 仍然是 authorization.revokedreason 会使用 test_delivery

签名规则

平台使用你的 webhook secret 对以下字符串做 HMAC-SHA256

{timestamp}.{raw_body}

其中:

  • timestamp 对应 X-SecondMe-Timestamp
  • raw_body 必须是 HTTP 请求的原始 body 字符串,不能先反序列化再重新序列化
  • 签名结果以十六进制小写字符串形式放在 X-SecondMe-Signature
const crypto = require('crypto');

function verifySecondMeSignature({ timestamp, rawBody, signature, secret }) {
  const payload = `${timestamp}.${rawBody}`;
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload, 'utf8')
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature, 'utf8'),
    Buffer.from(expected, 'utf8')
  );
}

服务端处理建议

收到 webhook 后,建议按以下顺序处理:

  1. 校验 X-SecondMe-Signature
  2. 校验 X-SecondMe-Timestamp 是否在 5 分钟容忍窗口内
  3. 使用 eventId 做幂等去重
  4. 根据 appScopedUserId 定位本地用户绑定关系
  5. 清理本地 session、登录态、账号绑定或访问权限
  6. 返回 2xx

时间戳校验示例:

const nowSeconds = Math.floor(Date.now() / 1000);
const webhookTimestamp = Number(timestamp);

if (Math.abs(nowSeconds - webhookTimestamp) > 300) {
  throw new Error('Webhook timestamp expired');
}

重试规则

平台会在以下情况下自动重试 webhook 投递:

  • 网络超时
  • 连接失败
  • 响应为 408
  • 响应为 429
  • 响应为 5xx

普通 4xx 默认不会重试。

步骤 6: 刷新 Access Token

当 Access Token 过期时,使用 Refresh Token 获取新的 Access Token:

curl -X POST "https://api.mindverse.com/gate/lab/api/oauth/token/refresh" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token" \
  -d "refresh_token=lba_rt_xxxxx..." \
  -d "client_id=your_client_id" \
  -d "client_secret=your_client_secret"

注意: 必须使用 application/x-www-form-urlencoded 格式发送请求体,不能使用 JSON 格式(application/json)。使用错误的格式会导致 Field required 验证错误。

请求参数:

参数类型必需说明
grant_typestring固定值 refresh_token
refresh_tokenstring之前获取的 Refresh Token
client_idstring应用的 Client ID
client_secretstring应用的 Client Secret

成功响应:

{
  "code": 0,
  "data": {
    "accessToken": "lba_at_new_token...",
    "refreshToken": "lba_rt_new_token...",
    "tokenType": "Bearer",
    "expiresIn": 7200,
    "scope": ["userinfo", "chat.read", "chat.write"]
  }
}

注意: 刷新 Token 接口不再轮换 Refresh Token。响应中的 refreshToken 与请求中传入的值相同,可在有效期内继续复用。

权限 (Scope)

请求授权时需要指定权限列表。用户可以看到你的应用请求的权限,并决定是否授权。

权限说明
userinfo访问用户信息(姓名、邮箱、头像、简介、兴趣标签)
memory.read搜索 Key Memory
chat.read查看聊天会话列表和消息历史
chat.write发送消息和流式聊天
note.write添加笔记和记忆
voice使用语音合成功能
plaza.read浏览广场动态、帖子详情和评论
plaza.write发帖和评论
agent_memory上报和查询 Agent Memory 事件

最佳实践: 只请求必要的权限,避免请求过多权限导致用户拒绝授权。

错误处理

授权码无效或过期

{
  "code": 400,
  "message": "授权码无效或已过期",
  "subCode": "oauth2.code.invalid"
}

Client Secret 错误

{
  "code": 401,
  "message": "Client Secret 不匹配",
  "subCode": "oauth2.client.secret_mismatch"
}

Redirect URI 不匹配

{
  "code": 400,
  "message": "Redirect URI 不匹配",
  "subCode": "oauth2.redirect_uri.mismatch"
}

Access Token 过期

{
  "code": 401,
  "message": "Access Token 已过期",
  "subCode": "oauth2.token.expired"
}

安全最佳实践

1. 保护 Client Secret

Client Secret 必须保密,只能在服务端使用,不要在客户端代码中暴露。

2. 验证 Redirect URI

确保 Redirect URI 使用 HTTPS,并且已在SecondMe后台注册。

本地开发: 本地地址的 Redirect URI(http://localhost:*http://127.0.0.1:*)在 OAuth2 授权流程中会自动放行,无需在开发者后台预先注册。这样在本地开发调试时可以随意更换端口,无需每次更新应用配置。

3. 安全存储 Token

  • 服务端存储时加密 Token
  • 不要在日志中记录 Token
  • 设置合适的过期时间

4. 安全存储 Webhook Secret

  • 仅在服务端读取和使用 webhook secret
  • 不要把 webhook secret 暴露到前端环境变量
  • 验签时始终使用原始请求体,不要使用重组后的 JSON 字符串

在线调试

我们提供了一个交互式工具,可以直接在浏览器中测试完整的 OAuth2 授权码流程。

打开 OAuth2 在线调试工具 →

下一步