接口文档
基于标准 OAuth2 授权码 + PKCE 流程。所有请求使用 HTTPS,API 响应均为 JSON 格式。
HTTP 状态码总览
| 状态码 | 含义 | 典型场景 |
|---|---|---|
| 200 | OK | 请求成功,返回数据 |
| 302 | Found | OAuth 授权重定向,携带 code 或 error |
| 400 | Bad Request | 参数缺失/格式错误/授权码无效 |
| 401 | Unauthorized | 未认证(凭据无效、令牌过期、无权限) |
| 404 | Not Found | 用户不存在 |
| 409 | Conflict | 资源冲突(手机号/邮箱已被注册) |
| 429 | Too Many Requests | 触发限流,请稍后重试 |
| 500 | Internal Server Error | 服务端异常 |
错误响应格式
所有 API 错误均返回 JSON,结构如下:
错误返回字段
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| error | number / string | 是 | 错误码(用户 API 为整数,OAuth2 端点 /oauth/token 为标准字符串如 invalid_grant) |
| message | string | 否 | 人类可读的错误描述(部分端点) |
| error_description | string | 否 | 详细错误说明(OAuth 端点必返回此字段) |
// 用户 API 错误示例(整数错误码)
{"error": 40001, "message": "需提供密码及手机号或邮箱"}
{"error": 40105}
// OAuth2 /oauth/token 错误示例(标准字符串错误码,RFC 6749 §5.2)
{"error": "invalid_grant", "error_description": "授权码不存在或已被消费"}
{"error": "unsupported_grant_type", "error_description": "仅支持 grant_type=authorization_code"}
{"error": "invalid_client", "error_description": "客户端认证失败"}
限流策略
注册/登录/刷新端点按 IP + 端点粒度限流,默认 20 次 / 60 秒。超限返回 429 状态码。
第三方应用登录 — 完整调用流程
第三方应用使用自己的登录界面,收集用户的手机号/邮箱和密码,按照以下步骤完成 OAuth2 授权码登录流程。
· 运营者在平台管理后台注册应用 → 获得 client_id + client_secret
· 配置 redirect_uri 白名单(如 https://yourapp.com/callback)
· client_secret 只能保存在应用后端,绝不暴露给浏览器/App
· 第三方应用展示自己的登录界面,用户输入手机号/邮箱 + 密码
· 第三方应用客户端在提交前生成 PKCE 参数:
— 随机生成 code_verifier(43~128 字符)
— 计算 code_challenge = base64url(SHA256_raw(code_verifier))(对 SHA-256 原始 32 字节做 base64url 编码,不可先转 hex)
— 指定 code_challenge_method = "S256"(推荐)或 "plain"
— 对用户密码做 SHA256(64 位 hex 字符串),不要传明文
— 生成随机 state(防 CSRF,建议存 localStorage/session)
记住 code_verifier,第 3 步要用。
将用户凭据 + PKCE 参数发送到平台,换取授权码(code):
POST /api/login
Content-Type: application/json
{
"identifier": "13800000000",
"password": "a1b2c3d4...(SHA256 后的 64 位 hex)",
"client_id": "cli_abc123def456",
"redirect_uri": "https://yourapp.com/callback",
"code_challenge": "vB8rF2kQ...(第 1 步算出的 base64url 值)",
"code_challenge_method": "S256",
"state": "rand0mSt4teStr1ng"
}
平台验证凭据 → 返回授权码:
{
"code": "a1b2c3d4e5f6...",
"redirect_uri": "https://yourapp.com/callback",
"transfer_token": "abc123...(跨设备 SSO 中转令牌,EXE → 浏览器场景使用)",
"state": "rand0mSt4teStr1ng"
}
!code 有效期 10 分钟,一次性使用。此时尚未获得任何 Token,code 不包含敏感信息,可在客户端传输。
最关键的一步——从这一步开始必须由后端完成,因为需要用到 client_secret:
POST /oauth/token Content-Type: application/x-www-form-urlencoded grant_type=authorization_code &code=a1b2c3d4e5f6...(第 2 步拿到的 code) &redirect_uri=https://yourapp.com/callback(必须与第 2 步一致) &client_id=cli_abc123def456 &client_secret=cs_xyz789secret &code_verifier=dBjftJeZ4CVP...(第 1 步生成的原始值)
平台验证 client_secret + code_verifier + code 三要素 → 返回:
{
"access_token": "eyJhbGciOi...(JWT, 含 sub + cid + aud)",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "rF8abc123...",
"scope": "profile"
}
code 已被消费(不可复用),access_token 有效期 1 小时,refresh_token 有效期 30 天。
用第 3 步拿到的 access_token 获取用户资料:
GET /oauth/userinfo Authorization: Bearer eyJhbGciOi...
{
"sub": "uuid-of-user",
"username": "user123",
"phone": "138****0000",
"email": "user@example.com"
}
此时登录完成,第三方应用可根据 sub 建立本地用户关联。
access_token 过期(1 小时)后,用 refresh_token 换取新的。支持 JSON 和 form-urlencoded 两种格式:
POST /api/refresh Content-Type: application/x-www-form-urlencoded refresh_token=rF8abc123...
{
"access_token": "eyJhbGciOi...(新 JWT)",
"token_type": "Bearer",
"expires_in": 3600
}
新 access_token 仍然携带 cid,可直接用于第 4 步。刷新后旧的 refresh_token 仍然有效。
接口调用顺序总览(推荐路径):
第 1-2 步在第三方应用客户端完成(收集凭据 + PKCE → 获取 code),第 3-5 步在第三方应用后端完成(code 换 Token → 获取用户信息 → 刷新)。
备选路径(浏览器 SSO):如果应用没有自定义登录 UI,可让用户通过平台授权页完成登录与授权。支持跨应用 SSO:
首次授权:GET 渲染确认页 → 用户同意 → POST 302 重定向带回 code。
SSO 特性:同一设备登录过任一应用后,访问其他应用的 /oauth/authorize — 已授权应用302 重定向直接发码,未授权应用弹出确认页。
OAuth2 端点
/oauth/token
授权码换取 access_token(第三方后端调用)
调用方:第三方应用的后端服务(不可在前端暴露 client_secret)。
前置条件:用户已完成登录并授权(/oauth/authorize 或 /api/login),获得 code。
注意:code 为一次性使用(DELETE RETURNING 原子删除),有效期 10 分钟。终端用户所有登录都会产生 code(不管从 /oauth/authorize 还是 /api/login),统一通过本接口换取 Token。
请求体(application/x-www-form-urlencoded)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| grant_type | string | 是 | 固定值 authorization_code(RFC 6749 §4.1.3 强制要求) |
| code | string | 是 | 授权码(来自 /api/login 或 /oauth/authorize 回调) |
| redirect_uri | string | 是 | 必须与 /oauth/authorize 或 /api/login 请求中的 redirect_uri 完全一致 |
| client_id | string | 是 | 审批通过后获得的 client_id |
| client_secret | string | 是 | 审批通过后获得的 client_secret(仅在后端使用) |
| code_verifier | string | 是 | PKCE 原始随机串(43-128 字符),与授权请求中的 code_challenge 配对验证 |
以上 全部 6 项均为必填。
code_verifier 经 SHA-256 后做 base64url 编码(RFC 7636 §4.2,不可先转 hex)应与授权请求中的 code_challenge 匹配。
若授权请求中 code_challenge_method 为 plain,则 verifier 与 challenge 直接字符串比对。
返回的 JWT 包含 cid(第三方 clientId)+ aud(受众),代表第三方代表用户访问。
成功响应 (200)
{
"access_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "abc123...",
"scope": "profile"
}
请求示例
POST http://oauth.deitydo.com/oauth/token Content-Type: application/x-www-form-urlencoded grant_type=authorization_code &code=SplxlOBeZQQYbYS6WxSbIA &redirect_uri=https://yourapp.com/callback &client_id=cli_abc123 &client_secret=cs_xyz789 &code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
错误响应
| 错误码 | 说明 |
|---|---|
| invalid_client | client_id 不存在、client_secret 不匹配或应用未审批通过 |
| invalid_grant | 授权码无效、已过期(>10 分钟)、已被消费、redirect_uri 不匹配或 PKCE 校验失败 |
| invalid_request | code 缺失或请求参数不合法 |
| unsupported_grant_type | grant_type 不为 authorization_code |
redirect_uri 参数使用规范
同一个字段名,不同接口起不同作用——禁止混用:
所有登录/注册/授权场景中,redirect_uri 必须以该应用注册的某个回调地址为前缀。
不匹配 → 40003。
必须严格匹配 /partner/apply 注册的白名单回调地址。
不匹配 → invalid_grant。这是 OAuth2 安全边界的核心。
/oauth/userinfo
获取用户信息(OAuth2 通道)
调用方:第三方应用后端。
限制:仅接受通过 /oauth/token(授权码流程)签发的 Token(JWT 中 cid 不为空)。
请求头
| Header | 说明 |
|---|---|
| Authorization | Bearer <access_token> |
成功响应 (200)
{
"sub": "uuid",
"username": "user123",
"phone": "138****0000",
"email": "user@example.com"
}
请求示例
GET http://oauth.deitydo.com/oauth/userinfo
Authorization: Bearer {access_token}
错误响应
| 错误码 | 说明 |
|---|---|
| 40103 | invalid_token — access_token 无效、过期或未通过 OAuth2 流程签发(无 cid) |
/api/userinfo
获取用户信息(通用通道)
调用方:终端用户的前端 / App。
区别:接受所有有效 Token(含 OAuth2 授权码 Token),不校验 cid。
注意:返回用户自身信息,无 scope 字段,仅供前端渲染用户资料使用。
请求头
| Header | 说明 |
|---|---|
| Authorization | Bearer <access_token> |
成功响应 (200)
{
"sub": "uuid",
"username": "user123",
"phone": "138****0000",
"email": "user@example.com"
}
请求示例
GET http://oauth.deitydo.com/api/userinfo
Authorization: Bearer {access_token}
错误响应
| 错误码 | 说明 |
|---|---|
| 40103 | invalid_token — access_token 无效、过期或未提供 |
| 404 | user_not_found — 用户不存在 |
用户 API
/api/send-code
发送邮箱验证码
适用场景:邮箱注册 / 邮箱登录前,获取 6 位数字验证码。
限制:同一邮箱 60 秒内只能发送一次,验证码 5 分钟有效。
无需 Token 认证即可调用。
请求体(JSON)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| string | 是 | 接收验证码的邮箱地址 |
成功响应 (200)
{
"success": true,
"message": "验证码已发送"
}
请求示例
POST http://oauth.deitydo.com/api/send-code
Content-Type: application/json
{"email":"user@example.com"}
错误响应
| 错误码 | 说明 |
|---|---|
| 429 | 发送过于频繁 — 60 秒内同邮箱重复请求 |
| 500 | 邮件发送失败 — 邮箱服务异常 |
/api/register
注册终端用户
请求体(JSON)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| type | string | 是 | 注册类型:email / phone / wechat(为未来扩展预留,当前仅 email/phone 可用) |
| username | string | 是 | 用户名(必填,全局唯一,注册后不可通过此接口修改) |
| phone | string | 条件必填 | 手机号(type=phone 时必填) |
| string | 条件必填 | 邮箱(type=email 时必填) | |
| code | string | 条件必填 | 邮箱验证码(type=email 时必填,需先调用 /api/send-code 获取) |
| password | string | 是 | 客户端 SHA256 后的 64 位 hex 字符串 |
| confirm_password | string | 是 | 重复密码,需与 password 完全一致(同为 SHA256 hex) |
| client_id | string | 是 | 应用 client_id(必填,注册成功后自动绑定该应用) |
| redirect_uri | string | 否 | 注册成功后前端返回地址(需通过 client_id 对应应用的白名单前缀校验) |
type 为必填项,决定采用哪种注册方式:
· type=email:必须提供 email 与 code(先调用 POST /api/send-code 获取验证码);
· type=phone:必须提供 phone,无需验证码;
· type=wechat:暂未开放,将返回 40005。
username 为必填项,全局唯一(重复将返回 40901)。
password 与 confirm_password 均为客户端 SHA256 后的 64 位 hex,服务端校验两者一致(不一致返回 40006)。
注册接口无需 Token 认证。client_id 为必填项,注册成功后用户将自动与该第三方应用建立授权绑定。
redirect_uri 为可选参数。必须以该应用在 /partner/apply 注册的某个回调地址为前缀(如注册了 https://yourapp.com/reg,则可填写 https://yourapp.com/reg/dashboard),不匹配返回 40003。
成功响应 (200)
{
"id": "uuid",
"username": "alice",
"phone": "13800000000",
"email": "user@example.com",
"redirect_uri": "https://yourapp.com/page-a" // 仅当入参传入时返回
}
请求示例
POST http://oauth.deitydo.com/api/register
Content-Type: application/json
{"type":"email","username":"alice","email":"user@example.com","code":"123456","password":"a1b2c3...","confirm_password":"a1b2c3...","client_id":"cli_abc123","redirect_uri":"https://yourapp.com/page-a"}
错误响应
| 错误码 | 说明 |
|---|---|
| 40001 | invalid_params — 必填参数缺失(client_id/password/confirm_password)或 password 格式错误 |
| 40003 | invalid_redirect_uri — redirect_uri 不在该应用注册的回调地址白名单内 |
| 40004 | invalid_verify_code — 验证码错误或已过期 |
| 40005 | invalid_type — type 缺失或取值非法(仅支持 email/phone/wechat) |
| 40006 | password_mismatch — confirm_password 与 password 不一致 |
| 40105 | invalid_client — 提供的 client_id 无效或未审批 |
| 40901 | 资源冲突 — 用户名/手机号/邮箱已被注册 |
/api/login
用户登录(返回授权码)
适用场景:原生 App 或自定义 UI,程序化调用登录接口获取授权码,再由后端调用 /oauth/token 换取 Token。
注意:浏览器端登录请使用 POST /api/login(登录成功同样返回授权码)。
请求体(JSON)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| type | string | 是 | 登录类型:email / phone / wechat(为未来扩展预留;wechat 需携带 access_token 走联合登录) |
| response_type | string | 否 | 固定值 code(缺省视为 code) |
| identifier | string | 条件必填 | 手机号 / 邮箱(type=email/phone 时必填) |
| password | string | 条件必填 | 客户端 SHA256 后的 64 位 hex(type=email/phone 时必填;type=wechat 由 access_token 替代) |
| access_token | string | 条件必填 | 跨应用 SSO 令牌(type=wechat 或跨应用登录时提供) |
| client_id | string | 是 | 第三方应用的 client_id |
| redirect_uri | string | 是 | 回调地址,必须以该应用注册的回调地址为前缀(发码目标,必填) |
| code_challenge | string | 是 | PKCE code_challenge |
| code_challenge_method | string | 否 | PKCE 方式,S256(推荐)或 plain(默认 plain) |
| state | string | 否 | 透传防 CSRF,原样返回 |
type 为必填项:email / phone 走密码认证;wechat 需携带 access_token 走联合登录。
无需 Token 认证。
此接口完成认证后按 RFC 6749 §4.1.2 以 302 重定向发码(不再返回 JSON 授权码):重定向到 redirect_uri?code=xxx&state=xxx,授权码通过 Location 查询参数投递,绝不出现在响应体。程序化客户端需以 redirect:"manual" 读取 Location 头。后续步骤:
1) 从 302 的 Location 取出 code
2) 调用 POST /oauth/token(需 client_secret + code_verifier)
3) 获得 access_token + refresh_token
成功响应 (302 Found)
HTTP/1.1 302 Found Location: https://yourapp.com/callback?code=AUTH_CODE_xxx&state=xyz123 Set-Cookie: user_token=...; HttpOnly; SameSite=Lax ← SSO 设备会话 Set-Cookie: transfer_token=...; HttpOnly; SameSite=Lax ← 跨设备 SSO 中转(原 JSON 字段已废弃)
请求示例
POST http://oauth.deitydo.com/api/login
Content-Type: application/json
{"type":"phone","response_type":"code","identifier":"13800000000","password":"a1b2c3...","client_id":"cli_abc123","redirect_uri":"https://yourapp.com/callback","code_challenge":"...","code_challenge_method":"S256","state":"xyz123"}
错误响应
| 错误码 | 说明 |
|---|---|
| 400 | invalid_request — 缺参/格式错误:type、client_id、password、redirect_uri、code_challenge 缺失或格式错,或 response_type 非法 |
| 400 | unsupported_response_type — response_type 不为 code |
| 400 | invalid_scope — scope 非法(仅支持 profile / userinfo / openid) |
| 401 | invalid_client — client_id 无效或未审批 |
| 401 | invalid_grant — 账户不存在或密码错误 / access_token 无效或已过期 |
/oauth/authorize
OAuth2 授权端点(RFC 6749 Authorization Code)
适用场景:浏览器端 OAuth2 授权码流程 — 第三方应用重定向到本端点,平台根据设备会话自动决策。
职责分离:凭证录入通过 POST /api/login;授权决策通过本端点的 POST 提交 consent。
SSO 特性:同一设备登录过任一应用后,访问其他应用的 /oauth/authorize 即可跳过登录,已授权应用静默重定向发码,未授权应用弹确认页。
GET — 授权入口
查询参数
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| response_type | string | 是 | 固定值 code |
| client_id | string | 是 | 第三方应用的 client_id |
| redirect_uri | string | 是 | 回调地址,必须以注册的回调地址为前缀 |
| code_challenge | string | 是 | PKCE code_challenge |
| code_challenge_method | string | 否 | PKCE 方式,S256(推荐)或 plain(默认) |
| scope | string | 否 | 权限范围,默认 profile;仅允许 profile / userinfo / openid,其余返回 invalid_scope |
| state | string | 推荐 | 防 CSRF,原样返回 |
三种分支行为:
① 无会话 → 401 JSON 错误(前端需跳转到 /api/login)
② 已登录 + 未授权 → 200 HTML 授权确认页(表单 POST 到本端点)
③ 已登录 + 已授权(SSO 静默) → 302 重定向到 redirect_uri?code=xxx&state=xxx
请求示例
GET http://oauth.deitydo.com/oauth/authorize?response_type=code&client_id=app_xxx&redirect_uri=https%3A%2F%2Fyourapp.com%2Fcallback&code_challenge=abc123...&state=xyz789
成功响应 — ③ SSO 静默 (302)
HTTP/1.1 302 Found Location: https://yourapp.com/callback?code=AUTH_CODE_xxx&state=xyz789
POST — 处理授权确认
表单参数
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| consent | string | 是 | approve(同意)/ deny(拒绝) |
| client_id | string | 是 | 第三方应用的 client_id |
| redirect_uri | string | 是 | 回调地址 |
| code_challenge | string | 是 | PKCE code_challenge |
| code_challenge_method | string | 否 | PKCE 方式,S256(推荐)或 plain(默认) |
| scope | string | 否 | 权限范围,默认 profile |
| state | string | 否 | 防 CSRF,原样返回 |
请求示例
POST http://oauth.deitydo.com/oauth/authorize Content-Type: application/x-www-form-urlencoded consent=approve&client_id=app_xxx&redirect_uri=https%3A%2F%2Fyourapp.com%2Fcallback&code_challenge=abc123...&state=xyz789
成功响应 — 同意授权 (302)
HTTP/1.1 302 Found Location: https://yourapp.com/callback?code=AUTH_CODE_xxx&state=xyz789
拒绝授权 (302)
HTTP/1.1 302 Found Location: https://yourapp.com/callback?error=access_denied&error_description=用户拒绝授权&state=xyz789
错误响应
| 错误码 | 说明 |
|---|---|
| 40001 | invalid_params — client_id / redirect_uri / code_challenge 缺失 |
| 40001 | unsupported_response_type — response_type 不为 code |
| 40003 | invalid_redirect_uri — redirect_uri 不在应用注册的回调地址白名单内 |
| 40103 | unauthorized — 未登录,请先通过 /api/login 建立会话 |
| 40105 | invalid_client — client_id 未注册或未审批 |
/api/refresh
刷新 access_token
支持 application/json 和 application/x-www-form-urlencoded 两种 Content-Type。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| refresh_token | string | 是 | 登录或上轮刷新返回的 refresh_token |
刷新接口无需 Token 认证,仅凭 refresh_token 即可换取新的 access_token(JWT 含 aud 声明)。
成功响应 (200)
{
"access_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 3600
}
请求示例
POST http://oauth.deitydo.com/api/refresh
Content-Type: application/x-www-form-urlencoded
refresh_token={refresh_token}
错误响应
| 错误码 | 说明 |
|---|---|
| invalid_request | 未提供 refresh_token 参数 |
| invalid_grant | refresh_token 无效、已被撤销或已过期 |
/api/logout
清除用户 SSO 会话
适用场景:前端 / App 调用,清除 user_token Cookie 以退出 SSO 登录状态。
无需认证:直接调用即可清除 Cookie,后续访问需重新登录。
成功响应 (200)
Set-Cookie: user_token=; Max-Age=0
{
"message": "已登出"
}
请求示例
POST http://oauth.deitydo.com/api/logout