Appearance
V1 认证与安全
本章说明公开应用与私有应用如何取得 API Token,以及 Token 在卖家完成 KYC 审核后的激活流程。
业务 OpenAPI 均需要 Token,并按 Token 绑定的卖家店铺隔离数据。公开应用使用 Authorization Code + PKCE(S256)流程;私有应用使用 Client Credentials。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 还会因授权卖家修改密码或账号状态变化而失效;私有应用 Token 不绑定卖家密码,停用私有应用会立即撤销其全部 Token Family。
Token 状态
| 状态 | 可否调用业务 API | 说明 | 应用处理 |
|---|---|---|---|
| 未激活 | 否 | 已完成注册或登录并取得 Token,但卖家尚未完成 KYC 审核 | 引导卖家登录 Sugumart Seller 完成 KYC,之后使用原 Token 重试 |
| 已激活 | 是 | KYC 审核已完成;Payoneer 可未绑定 | 正常调用业务 API |
| 已暂停 | 否 | 店铺被封禁或主动暂停,API 返回 shop_suspended | 停止业务调用并联系 Sugumart 客服处理 |
| Access Token 过期 | 否 | Access Token 已超过 expires_in | 应用后端使用当前 Refresh Token 刷新,成功后原子替换整组凭证 |
| 授权失效 | 否 | Refresh Token 过期、被撤销、发生重放或账号凭证变化 | 清除本地凭证,重新发起浏览器认证 |
取得 Token 不代表 Token 已激活。应用必须将“认证成功”和“卖家初始化完成”作为两个独立状态处理。
1.2 客户端凭证
开放应用取得公开的 app_id 与仅显示一次的 client_secret。公开应用由 Sugumart Admin 创建;私有应用可由 Admin 代建,也可由店铺 owner 在 Seller 后台创建,同一店铺最多一个私有应用。Secret 只能存放在应用后端或密钥管理系统中,不得进入浏览器、移动应用包、源码或日志。Secret 轮换后旧值立即失效。
| 类型 | 店铺范围 | Token 来源 | 浏览器授权 |
|---|---|---|---|
公开应用 shared | 可由多个店铺分别授权 | Authorization Code + PKCE | 需要 |
私有应用 private | 创建时固定绑定唯一店铺 | Client Credentials | 不需要 |
| 用途 | 说明 |
|---|---|
| 用户来源统计 | 统计各合作伙伴带来的注册用户数量 |
| Webhook 自动配置 | 通过携带 App ID 的注册链接注册新用户时,系统根据 App ID 自动配置对应的 Webhook URL |
app_id 仍用于来源统计和 Webhook 自动配置,但客户端认证由 client_secret 完成。
client_secret 不得发送到浏览器认证页面。浏览器阶段只发送 app_id、PKCE challenge、redirect_uri 和 state。
私有应用没有 redirect_uri,也不得把 shop_id 作为 Token 请求参数。平台始终从私有应用的登记关系取得绑定店铺,调用方无法通过请求切换 tenant。
1.3 认证流程
1.3.1 流程概述
认证分为两个阶段:
- 获取 Token:卖家应用打开系统浏览器,由卖家注册或登录 Sugumart,应用后端收到授权码后兑换 Token。
- 激活 Token:卖家登录 Sugumart Seller,完成 KYC 审核后,Token 即可调用业务 API;Payoneer 绑定不作为 OpenAPI 激活条件。
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。
这里的
redirect_uri是授权回调 URL:用户在浏览器完成登录和授权后,Sugumart 把code和state重定向到该地址。它不是订单、商品或物流的业务通知 URL;业务通知需通过POST /openapi/v1/shop/callback另行设置。多店铺集成应为每个本地店铺分配可唯一识别的授权回调路径,并将每个完整 URL 登记到开放应用白名单。
步骤 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
{
"success": true,
"data": {
"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 支付绑定状态仍可作为店铺资料展示,但不阻断 OpenAPI 业务接口。
初始化完成前调用业务 API,平台返回 403 shop_not_initialized:
json
{
"success": false,
"code": "shop_not_initialized",
"message": "卖家初始化还未完成,请登录 https://seller.sugumart.com/ 完成初始化",
"data": {
"required_steps": ["kyc_verification"],
"kyc_status": "not_submitted",
"payoneer_bound": false,
"init_url": "https://seller.sugumart.com/"
},
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}调用方应显示该提示并提供可操作的网站入口,不应将其当作可自动重试的临时故障。卖家完成 KYC 审核后,可继续使用原 Token 调用 API。
1.3.4 私有应用 Client Credentials
私有应用固定绑定创建时选择的店铺,不需要打开浏览器、准备 PKCE 或接收授权回调。应用后端直接使用 Client credentials 请求 Token:
http
POST https://auth.sugumart.com/api/auth/token
Content-Type: application/json
{
"grant_type": "client_credentials",
"client_id": "sgm_app_xxxxxxxxxxxxxxxx",
"client_secret": "sgm_secret_xxxxxxxxxxxxxxxx"
}| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
grant_type | string | 是 | 固定为 client_credentials |
client_id | string | 是 | 私有应用的公开 App ID |
client_secret | string | 是 | 仅存放在调用方服务端的 Client Secret |
成功响应与 Authorization Code 兑换完全相同,包含 access_token、refresh_token 和固定绑定的 shop_id。请求不接受 shop_id、redirect_uri、授权码或 PKCE 字段。公开应用使用此 grant、或私有应用尝试浏览器授权时,返回 400 unauthorized_client。
私有应用刷新仍使用 1.5.1 刷新 Token 的请求格式。停用私有应用会立即撤销其全部 Token Family;重新启用后必须重新使用 client_credentials 取得 Token。
可运行的测试环境接入代码见私有应用 Client Credentials 示例。
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, "data": { "revoked": true } }。为避免泄露 Token 是否存在,凭证合法时,未知或已经撤销的 Refresh Token 同样返回成功。撤销作用于整个 Token Family;该 Family 签发的 Access Token 在下一次资源请求验证时同步失效。
1.5.3 Auth 错误响应
/api/auth/*、/auth/tokeninfo 与 /openapi/v1/* 使用相同的 V1 错误信封。程序以 code 分支,结构化辅助信息放在 data:
json
{
"success": false,
"code": "invalid_grant",
"message": "Refresh Token 无效",
"data": null
}| HTTP | code | 发生条件 | 调用方动作 |
|---|---|---|---|
| 400 | invalid_token_request | Token 请求字段缺失、格式错误或不支持的 grant_type | 修正请求,不重试原请求 |
| 400 | unauthorized_client | 应用类型不允许使用请求的 grant;例如公开应用请求 client_credentials | 改用该应用类型支持的授权流程 |
| 400 | invalid_grant | 授权码、Refresh Token、PKCE 或 Token Family 无效;也用于检测到重放 | 清除整组本地凭证,重新浏览器认证 |
| 400 | invalid_revoke_request | 撤销请求字段缺失或格式错误 | 修正请求 |
| 401 | invalid_client | App 不存在、已停用、Client Secret 不匹配,或凭证发送到了错误的测试/生产 Auth 环境 | 检查 AUTH_ORIGIN、App 状态和服务端 Secret,修正后重新发起完整授权流程 |
Token 交换和撤销的成功、错误响应均携带 Cache-Control: no-store。请求携带合法 X-SGM-Request-Id 时,错误响应同时返回 request_id 和 X-Request-Id Header。
1.5.4 Token 失效机制
- 自然过期:Token 嵌入创建时间,服务端按当前有效期策略判断是否过期;有效期可能动态调整,客户端不得硬编码固定天数。
- 修改密码:卖家修改 Sugumart 登录密码后,公开应用中由该卖家授权的 Token 失效;私有应用 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/{store_id}/sugumart/authorization/callback |
| 桌面或移动应用 | 由配套 BFF 提供回调与 Token 保管 | https://api.erp.example.com/oauth/callback |
公开应用的完整本地测试服务见 Authorization Code + PKCE 示例;私有应用见 Client Credentials 示例。
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 | 私有应用使用 client_credentials 取得绑定店铺的 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",
"application_type": "private",
"actor_type": "application"
},
"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,允许用户重试 |
401 invalid_client | 这是应用后端的客户端凭证校验失败,不是卖家账号登录失败;确认 App ID、当前 Client Secret 与 AUTH_ORIGIN 属于同一环境,修正后重新发起完整授权流程,不重复兑换原授权码 |
400 unauthorized_client | 凭证有效但应用类型不支持当前 grant;公开应用使用 Authorization Code + PKCE,私有应用使用 client_credentials |
400 redirect_uri_not_allowed | 在 Admin 登记完整回调地址,并确保浏览器授权与 Token 兑换使用完全相同的 redirect_uri |
| Token 未激活 | 引导卖家完成 KYC 审核,完成后使用原 Token 重试;Payoneer 未绑定不影响调用 |
| Access Token 临近到期或已过期 | 使用当前 Refresh Token 刷新,并原子替换返回的整组凭证 |
400 invalid_grant | 清除本地 Access Token 与 Refresh Token;公开应用重新执行浏览器认证,私有应用重新执行 client_credentials |
| 检测到本地并发刷新 | 只允许一个请求刷新,其余请求等待并复用刷新结果 |