# 萌芽账户认证中心 API 文档 访问 **`GET /`** 或 **`GET /api`**(无鉴权)可得到 JSON 格式的简要说明(服务名、版本、`/api/docs` 与 `/api/health` 入口、路由前缀摘要)。 接入地址: - 统一登录前端:`https://auth.shumengya.top` - 后端 API:`https://auth.api.shumengya.top` - 本地开发 API:`http://:8080` 对外接入建议: 1. 第三方应用按钮跳转到统一登录前端。 2. 登录成功后回跳到业务站点。 3. 业务站点使用回跳带回的 `token` 调用后端 API。 示例按钮: ```html 使用萌芽统一账户认证登录 ``` 回跳说明: - 用户已登录时,统一登录前端会提示“继续授权”或“切换账号”。 - 登录成功后会回跳到 `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` | 否 | 第三方应用稳定标识(字母数字开头,可含 `_.:-`,最长 64)。写入用户「应用接入记录」,并随登录请求提交给后端。 | | `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 `。 | | `expiresAt` | 过期时间,RFC3339(与签发侧一致,当前默认为登录时起算 **7 天**)。 | | `account` | 账户名(与 JWT `sub` 一致)。 | | `username` | 展示用昵称,可能为空。 | | `state` | 若登录请求携带了 `state`,则原样返回。 | 业务站点回调页应用脚本读取 `location.hash`,解析后**仅在 HTTPS 环境**将 `token` 存于内存或安全存储,并尽快用后端 **`POST /api/auth/verify`** 校验(勿仅信任哈希中的明文字段)。 ### 第三方后端接入建议 1. **仅信服务端**:回调页将 `token` 交给自有后端,由后端请求 `POST https:///api/auth/verify`(JSON body:`{"token":"..."}`),根据 `valid` 与 `user.account` 建立会话。 2. **CORS**:浏览器直连 API 时须后端已配置 CORS(本服务默认允许任意 `Origin`);若从服务端发起请求则不受 CORS 限制。 3. **令牌过期**:`verify` / `me` 返回 401 或 `verify` 中 `valid:false` 时,应引导用户重新走统一登录。 ## 认证与统一登录 ### 登录获取统一令牌 `POST /api/auth/login` 请求: ```json { "account": "demo", "password": "demo123", "clientId": "my-app", "clientName": "我的应用" } ``` `clientId` / `clientName` 可选;规则与请求头 `X-Auth-Client` / `X-Auth-Client-Name` 一致。传入且格式合法时,会在登录成功后写入该用户的 **应用接入记录**(见下文 `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` 请求: ```json { "token": "jwt-token" } ``` 响应: ```json { "valid": true, "user": { "account": "demo", "...": "..." } } ``` 若账户已封禁,返回 **200** 且 `valid` 为 **false**(不返回 `user` 对象),示例: ```json { "valid": false, "error": "account is banned", "banReason": "违规内容" } ``` 令牌过期、签名错误、issuer 不匹配等解析失败时返回 **401**,示例:`{"valid": false, "error": "invalid token"}`。 `verify` 与 `me` 的取舍:**仅校验身份、不改变用户数据**时用 `verify`;需要最新资料、签到状态或写入「最后访问」时用 `GET /api/auth/me`(需 Bearer)。 **应用接入记录(可选)**:第三方在 **`POST /api/auth/verify`** 或 **`GET /api/auth/me`** 上携带请求头: - `X-Auth-Client`:应用 ID(格式同登录 JSON 的 `clientId`) - `X-Auth-Client-Name`:可选展示名 校验成功且用户未封禁时,服务端会更新该用户 JSON 中的 `authClients` 数组(`clientId`、`displayName`、`firstSeenAt`、`lastSeenAt`)。**`POST /api/auth/verify` 的响应体 `user` 仍为 `Public()`,不含 `authClients`**,避免向调用方泄露用户在其他应用的接入情况;**`GET /api/auth/me`** 与管理员列表中的 `user`(`OwnerPublic`)**包含** `authClients`,用户可在统一登录前端的个人中心查看。 ### 获取当前用户信息 `GET /api/auth/me` 请求头: `Authorization: Bearer ` 可选(由前端调用 `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`(默认 `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 与位置文案)。**公开用户资料接口** `GET /api/public/users/:account` 与 **`POST /api/auth/verify` 的 `user` 中不包含这两项**(避免公开展示或第三方校验时令牌响应携带访问隐私)。 > 说明:密码不会返回。 若账户在登录后被封禁,持旧 JWT 调用 `GET /api/auth/me`、`PUT /api/auth/profile`、`POST /api/auth/check-in`、辅助邮箱等需登录接口时,返回 **403**,正文同登录封禁响应(`error` + 可选 `banReason`)。客户端应作废本地令牌。 ### 每日签到 `POST /api/auth/check-in` 请求头: `Authorization: Bearer ` 响应: ```json { "checkedIn": true, "alreadyCheckedIn": false, "rewardCoins": 1, "awardedCoins": 1, "message": "签到成功", "user": { "account": "demo", "...": "..." } } ``` ### 更新当前用户资料 `PUT /api/auth/profile` 请求头: `Authorization: Bearer ` 请求(字段可选): ```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/{account}` 说明: - 仅支持账户名 `account`,不支持昵称查询。 - 适合第三方应用展示用户公开资料。 - 若该账户已被封禁,返回 **404** `{"error":"user not found"}`(与不存在账户相同,避免公开资料泄露)。 - 响应中含该用户**最近一次被服务端记录的**访问 IP(`lastVisitIp`)与展示用地理位置(`lastVisitDisplayLocation`,与本人中心一致);`POST /api/auth/verify` 返回的用户 JSON **不含**上述两项。 响应: ```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": "### 简介" } } ``` ### 公开注册策略 `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 ` 请求: ```json { "email": "demo2@example.com" } ``` 响应: ```json { "sent": true, "expiresAt": "2026-03-14T12:10:00Z" } ``` ### 验证辅助邮箱 `POST /api/auth/secondary-email/verify` 请求头: `Authorization: Bearer ` 请求: ```json { "email": "demo2@example.com", "code": "123456" } ``` 响应: ```json { "verified": true, "user": { "account": "demo", "...": "..." } } ``` ## 管理端接口(需要管理员 Token) 管理员 Token 存放在 `data/config/admin.json` 中;如果文件不存在,后端启动时会自动生成并写入该文件。 请求时可使用以下任一方式携带: - Query:`?token=` - Header:`X-Admin-Token: ` ### 签到奖励设置 `GET /api/admin/check-in/config` `PUT /api/admin/check-in/config` 请求: ```json { "rewardCoins": 1 } ``` - Header:`Authorization: Bearer ` ### 注册策略与邀请码 `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,存于用户 JSON)。 管理员列表 `GET /api/admin/users` 中每条 `user` 可含 `banned`、`banReason`(不含 `bannedAt` 亦可从存储文件中查看)。 ### 删除用户 `DELETE /api/admin/users/{account}` 响应: ```json { "deleted": true } ``` ## 数据存储说明 - 用户数据:`data/users/*.json` - 注册待验证:`data/pending/*.json` - 密码重置记录:`data/reset/*.json` - 辅助邮箱验证:`data/secondary/*.json` - 管理员 Token:`data/config/admin.json` - JWT 配置:`data/config/auth.json` - 邮件配置:`data/config/email.json` - 注册策略与邀请码:`data/config/registration.json` ## 快速联调用示例 ```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' \ -d '{"token":""}' # 使用令牌获取用户信息(会更新访问记录) curl http://localhost:8080/api/auth/me \ -H 'Authorization: Bearer ' ```