Appearance
V1 认证与安全
本章说明卖家应用如何通过系统浏览器完成 Sugumart 注册或登录、接收 API Token,以及 Token 在卖家完成 KYC 审核和支付绑定后的激活流程。
业务 OpenAPI 均需要 Token,并按 Token 绑定的卖家店铺隔离数据。Web、移动端和 ISV 应用使用 Authorization Code + PKCE(S256)流程;机密客户端还必须使用 client_secret 在服务端兑换 Token。app_id 是公开的客户端标识,不能代替 client_secret。
1.1 Token
Token 是调用 Sugumart OpenAPI 的访问凭证。卖家系统调用所有业务接口时都必须携带 Token;Token 可能过期、被撤销或因其他安全原因失效,调用方不得假设 Token 永久有效。
http
Authorization: Bearer <token>当前 Access Token 默认有效期为 90 天,Refresh Token Family 从首次授权起的绝对有效期为 120 天。应用后端可在 Access Token 到期前使用 Refresh Token 换取一组新凭证;平台每次刷新都会轮换 Refresh Token,旧值立即失效,但刷新不会延长该 Family 的 120 天绝对期限。Token 会因自然过期、主动撤销、检测到 Refresh Token 重放、卖家修改密码或账号状态变化而失效。
Token 状态
| 状态 | 可否调用业务 API | 说明 | 应用处理 |
|---|---|---|---|
| 未激活 | 否 | 已完成注册或登录并取得 Token,但卖家尚未完成 KYC 审核和支付绑定 | 引导卖家登录 Sugumart Seller 完成初始化,之后使用原 Token 重试 |
| 已激活 | 是 | KYC 审核和支付绑定均已完成 | 正常调用业务 API |
| 已暂停 | 否 | 店铺被封禁或主动暂停,API 返回 shop_suspended | 停止业务调用并联系 Sugumart 客服处理 |
| Access Token 过期 | 否 | Access Token 已超过 expires_in | 应用后端使用当前 Refresh Token 刷新,成功后原子替换整组凭证 |
| 授权失效 | 否 | Refresh Token 过期、被撤销、发生重放或账号凭证变化 | 清除本地凭证,重新发起浏览器认证 |
取得 Token 不代表 Token 已激活。应用必须将“认证成功”和“卖家初始化完成”作为两个独立状态处理。
1.2 客户端凭证
开放应用由 Sugumart Admin 创建,取得公开的 app_id 与仅显示一次的 client_secret。Secret 只能存放在应用后端或密钥管理系统中,不得进入浏览器、移动应用包、源码或日志。Secret 轮换后旧值立即失效。
| 用途 | 说明 |
|---|---|
| 用户来源统计 | 统计各合作伙伴带来的注册用户数量 |
| Webhook 自动配置 | 通过携带 App ID 的注册链接注册新用户时,系统根据 App ID 自动配置对应的 Webhook URL |
app_id 仍用于来源统计和 Webhook 自动配置,但客户端认证由 client_secret 完成。
client_secret 不得发送到浏览器认证页面。浏览器阶段只发送 app_id、PKCE challenge、redirect_uri 和 state。
1.3 认证流程
1.3.1 流程概述
认证分为两个阶段:
- 获取 Token:卖家应用打开系统浏览器,由卖家注册或登录 Sugumart,应用后端收到授权码后兑换 Token。
- 激活 Token:卖家登录 Sugumart Seller,完成 KYC 审核和支付绑定,Token 才能调用业务 API。
mermaid
flowchart LR
A["1. 应用打开系统浏览器<br/>携带 app_id + PKCE"] --> B["2. 卖家注册或登录邮箱账号"]
B --> C["3. Sugumart 生成 Token"]
C --> D["4. 浏览器重定向至应用回调"]
D --> E["5. Token 未激活<br/>业务 API 返回初始化提示"]
E --> F["6. 卖家登录网站<br/>完成 KYC + 支付绑定"]
F --> G["7. Token 激活<br/>可正常调用业务 API"]1.3.2 桌面应用认证时序
mermaid
sequenceDiagram
participant App as 卖家应用
participant Browser as 系统浏览器
participant Server as Sugumart 服务器
Note over App: 启动 127.0.0.1 本地 HTTP 回调服务
App->>Browser: 打开注册/登录 URL<br/>携带 app_id、redirect_uri、state、PKCE challenge
Browser->>Server: 请求注册/登录页面
Server-->>Browser: 显示邮箱注册或登录页面
Browser->>Server: 卖家完成注册或登录
Server-->>Server: 创建或确认卖家账号并生成 Token
Server-->>Browser: 302 重定向至应用回调<br/>携带 code、state
Browser-->>App: 应用收到一次性授权码
App->>Server: 后端携带 code、client_secret、code_verifier 换 Token
Server-->>App: 返回 access_token、refresh_token 与 shop_id
App-->>Browser: 显示“认证成功,可返回应用”页面
Note over App: 此时 Token 可能仍处于未激活状态
App->>Server: 调用业务 API
Server-->>App: 未初始化:返回初始化提示
Note over Server: 卖家在网站完成 KYC 与支付绑定
App->>Server: 使用原 Token 再次调用业务 API
Server-->>App: 正常返回业务数据1.3.3 步骤详解
步骤 1:准备服务端回调
应用必须使用 Admin 已登记的 HTTP 或 HTTPS 服务端回调地址。生产环境建议始终使用 HTTPS;HTTP 主要用于 localhost 本地联调。浏览器、桌面和移动客户端不得持有 client_secret,应由其服务端/BFF 发起授权、保存 PKCE verifier 并兑换 Token。
步骤 2:打开系统浏览器
应用生成高强度、一次性的 state 和 PKCE code_verifier,计算 SHA-256 Base64URL code_challenge 后打开系统浏览器。
python
import secrets
import urllib.parse
import webbrowser
params = {
"redirect_uri": "http://127.0.0.1:19527/callback",
"state": secrets.token_urlsafe(32),
"app_id": "sgm_app_xxxxxxxxxxxxxxxx",
"code_challenge": "<BASE64URL_SHA256_CODE_VERIFIER>",
"code_challenge_method": "S256",
}
auth_url = "https://auth.sugumart.com/register?" + urllib.parse.urlencode(params)
webbrowser.open(auth_url)注册链接 Query 参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
redirect_uri | string | 是 | 已登记的 HTTP 或 HTTPS 回调地址;本地调试可使用 http://localhost:{port}/callback |
state | string | 是 | 应用生成的高熵一次性随机值,用于关联请求并防止 CSRF |
app_id | string | 是 | Sugumart 分配的公开客户端标识 |
code_challenge | string | 是 | 43 字符 Base64URL SHA-256 PKCE challenge |
code_challenge_method | string | 是 | 固定为 S256 |
校验规则:
state为 16–512 个 URL-safe 字符。- 必须携带有效
app_id,且redirect_uri与后台登记的 HTTP 或 HTTPS 地址规范化后完全一致。 - 回调 URL 不得包含 fragment、内嵌账号密码,或预先携带
token、shop_id、state。 - 已停用的 App ID 不得发起新授权;停用不影响已经签发的 Token。
步骤 3:卖家注册或登录
- 未登录的卖家在浏览器中使用邮箱和密码注册或登录。
- 已登录的卖家可按页面提示继续授权流程。
- 应用不得收集、代理或嵌入保存卖家的 Sugumart 密码。
Sugumart 认证站点使用独立的 7 天浏览器会话。再次打开认证链接时,已登录卖家仍需在页面明确点击“授权并返回”;退出或修改密码会使该浏览器会话失效。浏览器会话不是 OpenAPI Token,不能用于调用业务接口。
步骤 4:接收回调
认证成功后,Sugumart 将浏览器重定向到请求中的 redirect_uri:
text
https://erp.example.com/callback?code=sgm_code_xxxxx&state=yyy回调 Query 参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
code | string | 是 | 5 分钟有效、仅可使用一次的授权码 |
state | string | 是 | 必须与发起认证时保存的值完全一致 |
应用必须先校验 state,校验成功后才能接受并保存 token 和 shop_id。state 缺失、不匹配、已使用或已过期时,应拒绝回调并重新发起认证。
步骤 5:服务端兑换并保存 Token
http
POST https://auth.sugumart.com/api/auth/token
Content-Type: application/json
{
"grant_type": "authorization_code",
"code": "sgm_code_xxxxx",
"redirect_uri": "https://erp.example.com/callback",
"client_id": "sgm_app_xxxxxxxxxxxxxxxx",
"client_secret": "sgm_secret_xxxxxxxxxxxxxxxx",
"code_verifier": "<ORIGINAL_PKCE_VERIFIER>"
}成功响应包含 access_token、固定值 Bearer 的 token_type、expires_in、refresh_token、refresh_token_expires_in 和 shop_id:
json
{
"access_token": "sgm_tok_xxxxx",
"token_type": "Bearer",
"expires_in": 7776000,
"refresh_token": "sgm_rt_xxxxx",
"refresh_token_expires_in": 10368000,
"shop_id": "82dbf488-3467-4825-afc4-31dbc91c115c"
}expires_in 和 refresh_token_expires_in 均为从响应时刻起计算的秒数。首次兑换时当前默认值分别为 90 天(7,776,000 秒)和 120 天(10,368,000 秒);刷新响应中的 refresh_token_expires_in 是该 Token Family 距离绝对到期时间的剩余秒数,不会重新变为 120 天。运行策略可能调整,调用方必须以响应为准。授权码、回调地址、客户端凭证或 PKCE 任一不匹配时均不得签发 Token。
| 字段 | 类型 | 说明 |
|---|---|---|
access_token | string | sgm_tok_ 开头的 Bearer Access Token |
token_type | string | 固定为 Bearer |
expires_in | number | Access Token 从响应时刻起的有效秒数 |
refresh_token | string | sgm_rt_ 开头、仅发送给应用后端的单次 Refresh Token |
refresh_token_expires_in | number | Refresh Token 从响应时刻起的有效秒数 |
shop_id | string | Token 唯一绑定的 Sugumart 店铺 UUID |
应用应将 Access Token 与 Refresh Token 保存到服务端密钥管理系统或操作系统安全凭证存储中。不得写入日志、崩溃报告、分析事件、URL 历史或明文配置文件;Refresh Token 不得发送给 OpenAPI 资源接口。
浏览器收到本地回调响应后,可显示“认证成功,请返回应用”页面。应用不应依赖脚本一定能够关闭系统浏览器窗口。
步骤 6:完成卖家初始化
新取得的 Token 可能处于未激活状态。卖家必须登录 https://seller.sugumart.com/,完成:
- KYC 身份审核;
- Payoneer 支付绑定。
初始化完成前调用业务 API,平台返回 403 shop_not_initialized:
json
{
"success": false,
"code": "shop_not_initialized",
"message": "卖家初始化还未完成,请登录 https://seller.sugumart.com/ 完成初始化",
"data": {
"required_steps": ["kyc_verification", "payment_binding"],
"kyc_status": "not_submitted",
"payoneer_bound": false,
"init_url": "https://seller.sugumart.com/"
},
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}调用方应显示该提示并提供可操作的网站入口,不应将其当作可自动重试的临时故障。卖家完成初始化后,可继续使用原 Token 调用 API。
1.4 调用 API
| Header | 类型 | 必填 | 说明 |
|---|---|---|---|
Content-Type | string | 是 | application/json;文件直传对象存储时按上传接口返回值设置 |
Authorization | string | 是 | Bearer {token} |
X-SGM-Request-Id | string | 否 | 推荐使用 UUID v4;用于请求追踪及调用方幂等控制 |
1.5 刷新与撤销
1.5.1 刷新 Token
应用后端使用当前 Refresh Token 和 Client credentials 请求同一个 Token 端点:
http
POST https://auth.sugumart.com/api/auth/token
Content-Type: application/json
{
"grant_type": "refresh_token",
"refresh_token": "sgm_rt_xxxxx",
"client_id": "sgm_app_xxxxxxxxxxxxxxxx",
"client_secret": "sgm_secret_xxxxxxxxxxxxxxxx"
}成功响应与授权码兑换响应相同,并同时返回新的 Access Token 与新的 Refresh Token。客户端必须在一次原子持久化操作中替换整组凭证,旧 Refresh Token 在成功刷新后立即失效。
平台采用 Refresh Token Rotation。若已经消费过的 Refresh Token 再次出现,平台将其判定为重放,撤销该次浏览器授权产生的整个 Token Family,并返回 400 invalid_grant。客户端必须串行刷新,同一店铺不得并发使用同一个 Refresh Token。
1.5.2 主动撤销
解绑店铺或退出集成时,应用后端应先撤销 Refresh Token,再删除本地凭证:
http
POST https://auth.sugumart.com/api/auth/revoke
Content-Type: application/json
{
"token": "sgm_rt_xxxxx",
"client_id": "sgm_app_xxxxxxxxxxxxxxxx",
"client_secret": "sgm_secret_xxxxxxxxxxxxxxxx"
}成功返回 { "success": true }。为避免泄露 Token 是否存在,凭证合法时,未知或已经撤销的 Refresh Token 同样返回成功。撤销作用于整个 Token Family;该 Family 签发的 Access Token 在下一次资源请求验证时同步失效。
1.5.3 Auth 错误响应
/api/auth/token 与 /api/auth/revoke 不使用业务 OpenAPI 信封,错误直接返回:
json
{
"error": "Refresh Token 无效",
"errorCode": "invalid_grant"
}| HTTP | errorCode | 发生条件 | 调用方动作 |
|---|---|---|---|
| 400 | invalid_token_request | Token 请求字段缺失、格式错误或不支持的 grant_type | 修正请求,不重试原请求 |
| 400 | invalid_grant | 授权码、Refresh Token、PKCE 或 Token Family 无效;也用于检测到重放 | 清除整组本地凭证,重新浏览器认证 |
| 400 | invalid_revoke_request | 撤销请求字段缺失或格式错误 | 修正请求 |
| 401 | invalid_client | App 不存在、已停用或 Client Secret 不匹配 | 检查 App 状态和服务端 Secret |
Auth 成功和错误响应均携带 Cache-Control: no-store。
1.5.4 Token 失效机制
- 自然过期:Token 嵌入创建时间,服务端按当前有效期策略判断是否过期;有效期可能动态调整,客户端不得硬编码固定天数。
- 修改密码:卖家修改 Sugumart 登录密码后,既有 Token 全部失效。
- Refresh Token 轮换:刷新成功后旧 Refresh Token 立即失效。
- Refresh Token 重放:撤销整个 Token Family,必须重新浏览器认证。
- 主动撤销:解绑时撤销整个 Token Family。
- 重新认证:刷新返回
400 invalid_grant后,清除本地凭证并重新走浏览器认证流程。
1.6 各客户端回调方式
| 客户端类型 | 回调方式 | 示例 |
|---|---|---|
| Web 应用 | 服务端 HTTP 或 HTTPS 回调;生产环境建议 HTTPS | https://erp.example.com/callback |
| 本地调试 | 本地 HTTP 服务端回调 | http://localhost:4000/api/stores/sugumart/callback |
| 桌面或移动应用 | 由配套 BFF 提供回调与 Token 保管 | https://api.erp.example.com/oauth/callback |
完整的本地测试服务见 Authorization Code + PKCE 示例。
1.7 认证端点汇总
| 端点 | 方法 | URL | 说明 |
|---|---|---|---|
| 注册/登录 | GET | https://auth.sugumart.com/register | 引导卖家完成注册或登录 |
| 兑换 Token | POST | https://auth.sugumart.com/api/auth/token | 机密客户端使用授权码、Client Secret 与 PKCE verifier 兑换 Token |
| 刷新 Token | POST | https://auth.sugumart.com/api/auth/token | 机密客户端使用当前 Refresh Token 与 Client credentials 轮换整组 Token |
| 撤销 Token | POST | https://auth.sugumart.com/api/auth/revoke | 撤销 Refresh Token 所属的整个 Token Family |
| Token 信息 | GET | https://openapi.sugumart.com/auth/tokeninfo | 查看当前 Token 状态及关联店铺 |
Token 信息接口使用标准 Bearer Token。成功响应:
json
{
"success": true,
"data": {
"token": {
"issued_at": "2026-08-10T08:00:00.000Z",
"expires_at": "2026-11-08T08:00:00.000Z",
"app_id": "sgm_app_z6GmGbbA-HpX9m5uUf99dL1T"
},
"shop": {
"id": "82dbf488-3467-4825-afc4-31dbc91c115c",
"name": "杉木生活选物",
"status": "draft",
"kyc_status": "draft",
"payoneer_bound": false,
"initialized": false
}
}
}当前默认 Access Token 有效期为 90 天,Refresh Token Family 的绝对有效期为首次授权后的 120 天,轮换不会延长。平台可调整运行策略;调用方不得硬编码固定期限,应以 Token 响应的秒数和 Token 信息中的 expires_at 为准。
1.8 安全要求
- 每次认证都生成新的
state和code_verifier;授权码与 state 均为一次性且短时有效。 client_secret只允许从服务端 Token 交换请求发送,不得传给 Auth Web。- Refresh Token 只允许存放在应用后端,并且只能发送到 Auth 服务的 Token 或撤销端点。
- 刷新成功后必须原子替换 Access Token 与 Refresh Token,不得继续使用旧 Refresh Token。
- 回调只接受发起认证时记录的路径、端口和当前待处理
state,拒绝任何意外参数或重复回调。 - Token 属于敏感凭证。不得通过工单、即时通信、截图或日志传递完整 Token。
- 业务请求仅发送到 Sugumart 官方 HTTPS API 地址;不得把 Token 转发给第三方服务。
- Access Token 到期时最多执行一次串行刷新;刷新失败时不得无限循环重试。
- 卸载应用、退出账号或解除店铺绑定时,应先调用撤销端点,再删除本地保存的 Token。
1.9 常见失败与处理
| 场景 | 应用处理 |
|---|---|
| 用户取消注册或登录 | 停止等待并允许用户重新发起认证 |
| 本地端口不可用 | 选择新的可用端口,使用新的 redirect_uri 和 state 重新打开浏览器 |
state 缺失或不匹配 | 拒绝回调,不保存任何凭证,重新发起认证 |
| 回调超时 | 关闭本地监听,作废本次 state,允许用户重试 |
| Token 未激活 | 引导卖家完成 KYC 与 Payoneer 绑定,完成后使用原 Token 重试 |
| Access Token 临近到期或已过期 | 使用当前 Refresh Token 刷新,并原子替换返回的整组凭证 |
400 invalid_grant | 清除本地 Access Token 与 Refresh Token,重新执行浏览器认证流程 |
| 检测到本地并发刷新 | 只允许一个请求刷新,其余请求等待并复用刷新结果 |