Files
mengyastore/萌芽账户认证中心-第三方应用API接入文档.md

656 lines
23 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 萌芽账户认证中心 API 文档
访问 **`GET /`** 或 **`GET /api`**(无鉴权)可得到 JSON 格式的简要说明(服务名、版本、`links.health``routePrefixes` 等)。**运行时未挂载**返回 Markdown 的 **`GET /api/docs`**;完整 HTTP 清单以 **`sproutgate-backend/main.go`** 与本文件、**`sproutgate-backend/后端文档.md`** 为准。
接入地址:
- 统一登录前端:`https://auth.shumengya.top`
- 后端 API`https://auth.api.shumengya.top`
- 本地开发 API`http://<host>:8080`
对外接入建议:
1. 第三方「登录」按钮跳转到统一登录前端的 **授权入口**(推荐 `.../authorize?...`,见下节)。
2. 用户在统一登录站点完成登录与授权确认后,回跳到业务站点。
3. 业务站点使用回跳 URL **`#`** 哈希中的 `token`,由**自有后端**调用 `POST /api/auth/verify``GET /api/auth/me` 校验并建立会话。
**推荐入口 URL生产** 将下列主机换为你的统一登录部署域名;子路径部署时在前端 `base` 后追加 `authorize`
```html
<a href="https://auth.shumengya.top/authorize?redirect_uri=https%3A%2F%2Fapp.example.com%2Fauth%2Fcallback&amp;state=abc123&amp;client_id=my-app&amp;client_name=%E6%88%91%E7%9A%84%E5%BA%94%E7%94%A8">
使用萌芽统一账户认证登录
</a>
```
说明:
- 若用户打开的是站点根路径且仅携带 `redirect_uri` / `return_url` 等查询参数,前端会自动 **重定向****`/authorize`**(保留同一套 query以使用独立授权确认页。
- `client_id` / `client_name` 会由浏览器写入 session并在用户登录时提交给后端用于累计 **应用接入记录**(详见下文「应用接入记录」)。
回跳说明:
-**`/authorize`** 流程下,已登录用户会看到明确的授权页(应用信息、权限说明、允许/拒绝);拒绝时回跳地址的 hash 中会携带 `error=access_denied` 等。
- 登录并「允许」后,浏览器回跳到 `redirect_uri`(或 `return_url`),并在 URL **`#fragment`**(哈希)中带上令牌与用户信息(见下表)。
- 第三方拿到 `token` 后,建议服务端调用 **`POST /api/auth/verify`**(不更新「最后访问」等侧写,适合网关鉴权)或 **`GET /api/auth/me`**(会更新访问/签到相关侧写,适合拉取个人中心级资料)校验并解析用户身份。
### 统一登录前端:查询参数
| 参数 | 必填 | 说明 |
|------|------|------|
| `redirect_uri` | 与 `return_url` 至少其一 | 登录成功后的回跳地址,须进行 URL 编码;可为绝对 URL 或相对路径(相对路径相对统一登录站点解析)。 |
| `return_url` | 同上 | 与 `redirect_uri` 同义,二者都传时优先 `redirect_uri`。 |
| `state` | 否 | OAuth 风格透传字符串;回跳时原样写入哈希参数,供业务防 CSRF 或关联会话。 |
| `prompt` | 否 | 预留;前端可读,当前可用于将来扩展交互策略。 |
| `client_id` | 否 | 第三方应用稳定标识;格式须符合后端校验(见下文「应用接入记录」)。写入 session 后随登录请求提交,用于 **应用接入记录**。 |
| `client_name` | 否 | 展示用名称(最长 128`client_id` 配对;可选。 |
### 回跳 URL`#` 哈希参数
成功授权后,前端将使用 [`URLSearchParams`](https://developer.mozilla.org/zh-CN/docs/Web/API/URLSearchParams) 写入哈希,例如:`https://app.example.com/auth/callback#token=...&expiresAt=...&account=...&username=...&state=...`
| 参数 | 说明 |
|------|------|
| `token` | JWT调用受保护接口时放在请求头 `Authorization: Bearer <token>`。 |
| `expiresAt` | 过期时间RFC3339与签发侧一致当前默认为登录时起算 **7 天**)。 |
| `account` | 账户名(与 JWT `sub` 一致)。 |
| `username` | 展示用昵称,可能为空。 |
| `state` | 若登录请求携带了 `state`,则原样返回。 |
业务站点回调页应用脚本读取 `location.hash`,解析后**仅在 HTTPS 环境**将 `token` 存于内存或安全存储,并尽快用后端 **`POST /api/auth/verify`** 校验(勿仅信任哈希中的明文字段)。
### 第三方后端接入建议
1. **仅信服务端**:回调页将 `token` 交给自有后端,由后端请求 `POST https://<api-host>/api/auth/verify`JSON body`{"token":"..."}`),根据 `valid``user.account` 建立会话。
2. **CORS**:浏览器直连 API 时须后端已配置 CORS本服务默认允许任意 `Origin`);若从服务端发起请求则不受 CORS 限制。
3. **令牌无效或封禁**`verify` / `me` 返回 **401**`verify` 因封禁返回 **403**`valid: false`)时,应作废本地会话并引导用户重新走统一登录。
## 应用接入记录authClients
用户在统一登录前端的 **个人中心 → 应用接入** 中可看到已与账号发生过认证关联的第三方应用列表。每条记录对应一个稳定的 **`client_id`**,并展示展示名(若有)、**首次接入时间**、**最近接入时间**(均为 RFC3339。持久化在 **MySQL `users` 表**中对应用户的 **`auth_clients` JSON 列**;对外 API 字段名仍为 **`authClients`**(与历史 JSON 文件存储时的形状一致)。
示例展示含义(与界面文案一致):**infogenie**(万象口袋)首次 `2026-03-22T16:33:24+08:00` · 最近 `2026-03-30T19:22:45+08:00` —— 其中括号内为 `client_name` / `X-Auth-Client-Name` / 登录体 `clientName` 带来的展示名,`infogenie``client_id`
### `client_id` 与名称规则
- **`client_id`(必填才能记一条)**:长度 164必须以 **英文字母或数字** 开头;其余字符仅可为 `[A-Za-z0-9_.:-]`(与后端正则一致)。
- **`client_name` / 展示名(可选)**:任意去首尾空白后的字符串,最长 **128** 字符,超出由服务端截断;对应存储字段为 `displayName`
### 在哪些接口上累计记录
记录仅在 **令牌有效、用户未封禁、且 `client_id` 通过校验** 时写入或刷新 **`lastSeenAt`**;已存在同一 `client_id` 时更新 **`lastSeenAt`**,必要时更新 **`displayName`****不改动** **`firstSeenAt`**。
| 方式 | 接口 / 场景 | 如何传递 `client_id` 与展示名 |
|------|----------------|--------------------------------|
| **A. 统一登录 URL** | 用户经前端完成登录 | 授权页 URL 查询参数 **`client_id`**、**`client_name`**(见上表)。前端写入 session登录请求 `POST /api/auth/login` 的 JSON 中会带 **`clientId`** / **`clientName`**。 |
| **B. 资源拥有者密码登录** | `POST /api/auth/login` | JSON **`clientId`**、**`clientName`**(可选,规则同上)。 |
| **C. 服务端持 JWT 调用** | `POST /api/auth/verify` | 请求头 **`X-Auth-Client`**(必须为合法 id、**`X-Auth-Client-Name`**(可选)。**Body 仅含 `token`,不能替代请求头。** |
| **D. 服务端持 JWT 调用** | `GET /api/auth/me` | 同上:**`X-Auth-Client`**、**`X-Auth-Client-Name`**,与 **`Authorization: Bearer`** 同时使用。 |
说明:
- **A + B**:在登录成功、签发 JWT **之后**写库;`POST /api/auth/login` 的响应 `user` 为 `OwnerPublic()`**含** `authClients`(若本次写入了新记录)。
- **C**`POST /api/auth/verify` 在校验通过且未封禁后写库;响应 `user`**`Public()`,不含 `authClients`**,避免向第三方泄露用户在其他应用上的关联列表。
- **D**`GET /api/auth/me` 在校验通过且未封禁后写库;响应 `user`**`OwnerPublic()`,含 `authClients`**,与前端个人中心一致。
**封禁用户**`POST /api/auth/verify``GET /api/auth/me` 在命中封禁或令牌无效时 **不会** 更新应用接入记录(在写库逻辑之前即返回错误)。
**浏览器直连 API**:若前端或网关需带上 `X-Auth-Client`,请确认 CORS 允许该自定义头(本服务 CORS 已包含 `X-Auth-Client``X-Auth-Client-Name`)。
### 存储结构(`authClients` 数组元素)
```json
{
"clientId": "infogenie",
"displayName": "万象口袋",
"firstSeenAt": "2026-03-22T16:33:24+08:00",
"lastSeenAt": "2026-03-30T19:22:45+08:00"
}
```
## 认证与统一登录
### 登录获取统一令牌
`POST /api/auth/login`
请求:
```json
{
"account": "demo",
"password": "demo123",
"clientId": "my-app",
"clientName": "我的应用"
}
```
`clientId` / `clientName` 可选;规则与应用接入请求头一致。传入且格式合法时,会在登录成功后写入 **应用接入记录**(见上文 **「应用接入记录authClients」**)。
响应:
```json
{
"token": "jwt-token",
"expiresAt": "2026-03-14T12:00:00Z",
"user": {
"account": "demo",
"username": "示例用户",
"email": "demo@example.com",
"level": 0,
"sproutCoins": 10,
"secondaryEmails": ["demo2@example.com"],
"phone": "13800000000",
"avatarUrl": "https://example.com/avatar.png",
"websiteUrl": "https://example.com",
"bio": "### 简介",
"createdAt": "2026-03-14T12:00:00Z",
"updatedAt": "2026-03-14T12:00:00Z"
}
}
```
若账户已被管理员封禁,返回 **403**,且**不会签发 JWT**,响应示例:
```json
{
"error": "account is banned",
"banReason": "违规内容"
}
```
`banReason` 可能为空字符串或省略。
**常见 HTTP 状态码(登录)**
| 状态码 | 含义 |
|--------|------|
| 200 | 成功,返回 `token``expiresAt``user`。 |
| 400 | 请求体非法或缺少 `account` / `password`。 |
| 401 | 账户不存在或密码错误(统一文案 `invalid credentials`)。 |
| 403 | 账户已封禁(见上文 JSON。 |
| 500 | 服务器内部错误(读库、签发 JWT 失败等)。 |
**JWT 概要**:算法 **HS256**;载荷含 `account`(与 `sub` 一致)、`iss`(见 `data/config/auth.json`)、`iat` / `exp`。客户端只需透传字符串,**勿在前端解析密钥**。
### 校验令牌
`POST /api/auth/verify`
请求头:**`Content-Type: application/json`**。可选 **`X-Auth-Client`**、**`X-Auth-Client-Name`**(累计应用接入记录,见专章)。
请求体:
```json
{
"token": "jwt-token"
}
```
响应:
```json
{
"valid": true,
"user": { "account": "demo", "...": "..." }
}
```
若账户已封禁,返回 **403**,正文示例(不返回 `user`
```json
{
"valid": false,
"error": "account is banned",
"banReason": "违规内容"
}
```
令牌过期、签名错误、issuer 不匹配、`token` 被撤销(`token_epoch`)等失败时返回 **401**,示例:`{"valid": false, "error": "invalid token"}``{"valid": false, "error": "token revoked"}`(以服务端实际字段为准)。
`verify``me` 的取舍:**仅校验身份、默认不更新「最后访问」等侧写**时用 `verify`;需要最新资料、签到状态或写入「最后访问」时用 `GET /api/auth/me`(需 Bearer
**应用接入记录**:在 **`verify` / `me`** 上使用 **`X-Auth-Client`**、**`X-Auth-Client-Name`** 的约定,以及响应 `user` 是否包含 `authClients`,见上文 **「应用接入记录authClients」**。
### 获取当前用户信息
`GET /api/auth/me`
请求头:
- **`Authorization: Bearer <jwt-token>`**(必填)
可选 — **应用接入记录**(与 `verify` 相同语义,见 **「应用接入记录authClients」**
- **`X-Auth-Client`**、**`X-Auth-Client-Name`**
可选(由前端调用 `https://cf-ip-geo.smyhub.com/api` 等接口解析后传入,用于记录「最后访问 IP」与「最后显示位置」
- `X-Visit-Ip`:客户端公网 IP与地理接口返回的 `ip` 一致即可)
- `X-Visit-Location`:展示用位置文案(例如将 `geo.countryName``regionName``cityName` 拼接为 `中国 四川 成都`
**服务端回退(避免浏览器跨域导致头缺失)**:若未传 `X-Visit-Location`,后端会用 `X-Visit-Ip`;若也未传 `X-Visit-Ip`,则用连接的 `ClientIP()`(请在前置反向代理上正确传递 `X-Forwarded-For` 等,并在生产环境为 Gin 配置可信代理)。随后服务端请求环境变量 **`GEO_LOOKUP_URL`** 指定的基址(默认与 `internal/clientgeo.DefaultLookupURL` 一致:`https://cf-ip-geo.smyhub.com/api`,实际请求会附加 **`?ip=`**)解析展示位置并写入用户记录。
响应:
```json
{
"user": { "account": "demo", "...": "..." },
"checkIn": {
"rewardCoins": 1,
"checkedInToday": false,
"lastCheckInDate": "",
"lastCheckInAt": "",
"today": "2026-03-14"
}
}
```
> `user` 还会包含 `lastVisitAt`、`lastVisitDate`、`checkInDays`、`checkInStreak`、`visitDays`、`visitStreak` 等统计字段。
> 在登录用户本人、管理员列表等场景下,`user` 还可包含 `lastVisitIp`、`lastVisitDisplayLocation`(最近一次通过 `/api/auth/me` 上报的访问 IP 与位置文案)。**`POST /api/auth/verify` 返回的 `user``Public()`)中不包含这两项**(避免第三方校验响应携带访问隐私)。**`GET /api/public/users/:account` 的 `user``PublicProfile()`)会包含这两项**,与公开主页展示策略一致。
> 说明:密码不会返回。
若账户在登录后被封禁,持旧 JWT 调用 `GET /api/auth/me``PUT /api/auth/profile``POST /api/auth/check-in`、辅助邮箱等需登录接口时,返回 **403**,正文同登录封禁响应(`error` + 可选 `banReason`)。客户端应作废本地令牌。
### 每日签到
`POST /api/auth/check-in`
请求头:
`Authorization: Bearer <jwt-token>`
响应:
```json
{
"checkedIn": true,
"alreadyCheckedIn": false,
"rewardCoins": 1,
"awardedCoins": 1,
"message": "签到成功",
"user": { "account": "demo", "...": "..." }
}
```
### 更新当前用户资料
`PUT /api/auth/profile`
请求头:
`Authorization: Bearer <jwt-token>`
请求(字段可选):
```json
{
"password": "newpass",
"username": "新昵称",
"phone": "13800000000",
"avatarUrl": "https://example.com/avatar.png",
"websiteUrl": "https://example.com",
"bio": "### 新简介"
}
```
说明:`websiteUrl` 须为 `http`/`https` 地址;可传空字符串清除;未写协议时服务端会补全为 `https://`
响应:
```json
{
"user": { "account": "demo", "...": "..." }
}
```
## 用户广场
### 获取公开用户目录
`GET /api/public/users`
无需鉴权。返回未封禁用户的简要列表,默认按 **`createdAt`**(注册时间)升序。
响应:
```json
{
"total": 2,
"users": [
{
"account": "demo",
"username": "示例用户",
"level": 0,
"sproutCoins": 10,
"avatarUrl": "https://example.com/avatar.png",
"websiteUrl": "https://example.com",
"bio": "### 简介",
"createdAt": "2026-03-14T12:00:00+08:00"
}
]
}
```
### 获取用户公开主页
`GET /api/public/users/{account}`
说明:
- 仅支持路径参数 **账户名** `account`(可与存储大小写不同,服务端按不区分大小写匹配),不支持昵称查询。
- 适合第三方应用展示用户公开资料。
- 若该账户已被封禁,返回 **404** `{"error":"user not found"}`(与不存在账户相同,避免公开资料泄露)。
- 响应体在 `user` 之外包含累计赞数 **`profileLikeCount`**。
- **`Authorization: Bearer`** 可选:若传入**有效未封禁**用户令牌且非主页主人,响应可额外包含 `viewerHasLikedToday``viewerLikesRemainingToday``profileLikeDailyMax`(当日是否已赞该主页、当日还可给多少人点赞、每自然日上限当前为 **5**,以后端常量为准)。若浏览者即主页主人,可返回 `viewerIsOwner: true`
- `user` 中含该用户**最近一次被服务端记录的**访问 IP`lastVisitIp`)与展示地理位置(`lastVisitDisplayLocation``POST /api/auth/verify` 返回的用户 JSON **不含**上述两项。
响应示例(未带 Bearer
```json
{
"user": {
"account": "demo",
"username": "示例用户",
"level": 3,
"sproutCoins": 10,
"avatarUrl": "https://example.com/avatar.png",
"websiteUrl": "https://example.com",
"lastVisitIp": "203.0.113.1",
"lastVisitDisplayLocation": "中国 广东省 深圳市",
"bio": "### 简介"
},
"profileLikeCount": 12
}
```
### 公开主页点赞
`POST /api/public/users/{account}/like`
请求头:**`Authorization: Bearer <jwt-token>`**(必填)。
为路径中 **`account`** 对应用户的公开主页点赞。规则摘要(与 `internal/storage` 一致):
- 不能给自己点赞;每个点赞者对同一主页 **每个自然日最多一次**
- 每个点赞者每个自然日最多给 **5** 位**不同**用户点赞(超限返回 **400**,正文中可含 `viewerLikesRemainingToday``profileLikeDailyMax`)。
- 目标用户不存在或已封禁:**404**;点赞者封禁或令牌无效:**401** / **403** 等,以服务端 `error` 为准。
成功响应示例:
```json
{
"profileLikeCount": 13,
"viewerHasLikedToday": true,
"viewerLikesRemainingToday": 4,
"profileLikeDailyMax": 5
}
```
### 公开注册策略
`GET /api/public/registration-policy`
无需鉴权。用于前端判断是否展示「邀请码」输入框。
响应:
```json
{
"requireInviteCode": false
}
```
`requireInviteCode`**true** 时,`POST /api/auth/register` 必须携带有效 `inviteCode`(见下节)。
### 注册账号(发送邮箱验证码)
`POST /api/auth/register`
请求:
```json
{
"account": "demo",
"password": "demo123",
"username": "示例用户",
"email": "demo@example.com",
"inviteCode": "ABCD1234"
}
```
- `inviteCode`:可选。若服务端开启「强制邀请码」,则必填且须为管理员发放的未过期、未用尽邀请码。邀请码**不区分大小写**;成功完成 `verify-email` 创建用户后才会扣减使用次数。
响应:
```json
{
"sent": true,
"expiresAt": "2026-03-14T12:10:00Z"
}
```
### 验证邮箱并完成注册
`POST /api/auth/verify-email`
请求:
```json
{
"account": "demo",
"code": "123456"
}
```
响应:
```json
{
"created": true,
"user": { "account": "demo", "...": "..." }
}
```
### 忘记密码(发送重置验证码)
`POST /api/auth/forgot-password`
请求:
```json
{
"account": "demo",
"email": "demo@example.com"
}
```
响应:
```json
{
"sent": true,
"expiresAt": "2026-03-14T12:10:00Z"
}
```
### 重置密码
`POST /api/auth/reset-password`
请求:
```json
{
"account": "demo",
"code": "123456",
"newPassword": "newpass"
}
```
响应:
```json
{ "reset": true }
```
### 申请添加辅助邮箱(发送验证码)
`POST /api/auth/secondary-email/request`
请求头:
`Authorization: Bearer <jwt-token>`
请求:
```json
{
"email": "demo2@example.com"
}
```
响应:
```json
{
"sent": true,
"expiresAt": "2026-03-14T12:10:00Z"
}
```
### 验证辅助邮箱
`POST /api/auth/secondary-email/verify`
请求头:
`Authorization: Bearer <jwt-token>`
请求:
```json
{
"email": "demo2@example.com",
"code": "123456"
}
```
响应:
```json
{
"verified": true,
"user": { "account": "demo", "...": "..." }
}
```
## 管理端接口(需要管理员 Token
管理员 Token 存放在 `data/config/admin.json` 中;如果文件不存在,后端启动时会自动生成并写入该文件。
请求时可使用以下任一方式携带:
- Query`?token=<admin-token>`
- Header`X-Admin-Token: <admin-token>`
### 签到奖励设置
`GET /api/admin/check-in/config`
`PUT /api/admin/check-in/config`
请求:
```json
{
"rewardCoins": 1
}
```
- Header`Authorization: Bearer <admin-token>`
### 注册策略与邀请码
`GET /api/admin/registration`
响应含 `requireInviteCode``invites` 数组(每项含 `code``note``maxUses``uses``expiresAt``createdAt`)。`maxUses` 为 0 表示不限次数。
`PUT /api/admin/registration`
请求:
```json
{ "requireInviteCode": true }
```
`POST /api/admin/registration/invites`
请求:
```json
{
"note": "内测批次",
"maxUses": 10,
"expiresAt": "2026-12-31T15:59:59Z"
}
```
`expiresAt` 可省略;须为 RFC3339。响应 `201``invite` 内含服务端生成的 8 位邀请码。
`DELETE /api/admin/registration/invites/{code}`
删除指定邀请码(`code` 与存储大小写可能不同,按不区分大小写匹配)。
### 获取用户列表
`GET /api/admin/users`
响应:
```json
{
"total": 1,
"users": [{ "account": "demo", "...": "..." }]
}
```
### 新建用户
`POST /api/admin/users`
请求:
```json
{
"account": "demo",
"password": "demo123",
"username": "示例用户",
"email": "demo@example.com",
"level": 0,
"sproutCoins": 10,
"secondaryEmails": ["demo2@example.com"],
"phone": "13800000000",
"avatarUrl": "https://example.com/avatar.png",
"websiteUrl": "https://example.com",
"bio": "### 简介"
}
```
### 更新用户
`PUT /api/admin/users/{account}`
请求(字段可选):
```json
{
"password": "newpass",
"username": "新昵称",
"level": 1,
"secondaryEmails": ["demo2@example.com"],
"sproutCoins": 99,
"websiteUrl": "https://example.com",
"banned": true,
"banReason": "违规说明(最多 500 字)"
}
```
- `banned`:是否封禁;解封时请传 `false`,并可将 `banReason` 置为空字符串。
- `banReason`:仅当用户处于封禁状态时允许设为非空;封禁时若首次写入会记录 `bannedAt`RFC3339存于数据库用户记录
管理员列表 `GET /api/admin/users` 中每条 `user``OwnerPublic()`,可含 `banned``banReason``bannedAt`(及 `authClients` 等)——以后端实际 JSON 为准。
### 删除用户
`DELETE /api/admin/users/{account}`
响应:
```json
{ "deleted": true }
```
## 数据存储说明
当前发行版**以 MySQL 为唯一运行时数据源**GORM `AutoMigrate`),不再使用 `data/users/*.json` 等文件作为线上读写路径。表与用途概要:
| 表名 | 用途 |
|------|------|
| `users` | 用户主数据;`auth_clients` 等为 JSON 列 |
| `pending_users` | 注册邮箱待验证 |
| `password_resets` | 找回密码验证码 |
| `secondary_email_verifications` | 辅助邮箱验证 |
| `app_configs` | `admin` / `auth` / `email` / `checkin` / `registration` 等 JSON 配置 |
| `invite_codes` | 邀请码 |
| `profile_likes` | 公开主页点赞记录 |
| `profile_like_daily_quota` | 点赞每日额度辅助 |
从旧版 **`sproutgate-backend/data/`** 目录迁移时,使用 **`go run ./cmd/migrate --data-dir ./data`**(详见 **`sproutgate-backend/后端文档.md`**)。
## 快速联调用示例
```bash
# 服务根路径 JSON 说明
curl -s http://localhost:8080/ | jq .
# 登录
curl -X POST http://localhost:8080/api/auth/login \
-H 'Content-Type: application/json' \
-d '{"account":"demo","password":"demo123"}'
# 校验令牌(推荐第三方网关先调此接口)
curl -X POST http://localhost:8080/api/auth/verify \
-H 'Content-Type: application/json' \
-H 'X-Auth-Client: my-app' \
-d '{"token":"<jwt-token>"}'
# 同上,并携带应用展示名(累计应用接入记录)
curl -X POST http://localhost:8080/api/auth/verify \
-H 'Content-Type: application/json' \
-H 'X-Auth-Client: infogenie' \
-H 'X-Auth-Client-Name: 万象口袋' \
-d '{"token":"<jwt-token>"}'
# 使用令牌获取用户信息(会更新访问记录);可一并携带 X-Auth-Client 刷新「应用接入」最近时间
curl http://localhost:8080/api/auth/me \
-H 'Authorization: Bearer <jwt-token>' \
-H 'X-Auth-Client: infogenie' \
-H 'X-Auth-Client-Name: 万象口袋'
```