feat(auth): refresh token 续期(B2) — 不透明哈希+单次旋转+/auth/refresh+/auth/logout
- auth/refresh.go: RefreshStore(Issue/Rotate/Revoke/RevokeAll/RevokeByUser), SHA-256哈希256-bit随机串, 单次旋转事务(并发重放fail-closed) - 迁移0008_refresh_tokens(id/user_id/role/token_hash UNIQUE/expires_at/revoked_at + 索引) - gateway: POST /v1/auth/refresh(旋转/续期) + /v1/auth/logout(单/全设备); login响应增refreshToken - config: AUTH_REFRESH_TTL(720h)/AUTH_REFRESH_ROTATE_DISABLED; decode.go 支持time.Duration - rotate-jwt成功后RevokeAll(响应refreshTokensRevoked), revoke-keys不动refresh - 14测试+真实二进制e2e, build/vet/test绿+交叉编译 - Docs/api-client v0.25 + api-reference §2.3/2.4 同步
This commit is contained in:
1 parent
7ecb43bd5d
commit
026b27b310
17 files changed
+1237
-13
No files matched your search
+15
-2
@@ -157,13 +157,14 @@ curl -s http://<host>:8080/api/v1/health
|
||||
{"username":"admin","password":"s3cret"}
|
||||
```
|
||||
|
||||
- **响应**:`200` `{"data":{"token":"<JWT>","expiresAt":<unix秒>,"role":"owner"}}`(`loginResponse`)。
|
||||
- **响应**:`200` `{"data":{"token":"<JWT>","expiresAt":<unix秒>,"role":"owner","refreshToken":"<opaque>"}}`(`loginResponse`)。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `token` | string | 已签名 JWT(HS256),下文请求头 `Authorization: Bearer <token>` |
|
||||
| `expiresAt` | int64 | 过期时间,Unix 秒 = `now + 24h`(`tokenTTL=24h`) |
|
||||
| `role` | string | **恒为 `"owner"`**(代码硬编码 `auth.RoleOwner`) |
|
||||
| `refreshToken` | string | **不透明 256-bit 随机串**(B2)——服务端**仅存其 SHA-256 哈希**,明文只在响应出现一次;用于 `POST /api/v1/auth/refresh` 静默续期,`AUTH_REFRESH_TTL` 默认 `720h` |
|
||||
|
||||
- **错误**:`400` `{"error":{"code":"bad_request","message":"invalid request body"}}`(JSON 解析失败);`401` `{"error":{"code":"unauthorized","message":"invalid username or password"}}`;`500` `{"error":{"code":"login_failed",...}}`(签名失败)。
|
||||
- **备注**:登录响应**以代码为准** = `{token, expiresAt, role}`(**不是** `{token,jwt}`)。旧 `api-client.md` §1 表内一行写 `{token,jwt}` 为**历史笔误**,以本档为准。curl:
|
||||
@@ -172,9 +173,21 @@ curl -s http://<host>:8080/api/v1/health
|
||||
curl -s -X POST http://<host>:8080/api/v1/auth/login \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"username":"admin","password":"s3cret"}'
|
||||
# => {"data":{"token":"eyJ...","expiresAt":1799...,"role":"owner"}}
|
||||
# => {"data":{"token":"eyJ...","expiresAt":1799...,"role":"owner","refreshToken":"...base64url..."}}
|
||||
```
|
||||
|
||||
### 2.3 POST /api/v1/auth/refresh(B2)
|
||||
- **鉴权**:公开,body 带 `refreshToken`。
|
||||
- **请求 Body**:`{refreshToken:"<opaque>"}`。
|
||||
- **响应**:`200` `{"data":{"token":"<新JWT>","expiresAt":<unix秒>,"refreshToken":"<新opaque?>"}}`——默认**单次使用旋转**:旧 refresh 立即作废并签发新的;`AUTH_REFRESH_ROTATE_DISABLED=true` 时旧 token 保持有效、不返回新 refresh(`refreshToken` 省略)。
|
||||
- **错误**:`400 bad_request`(JSON 解析失败 / 空 token);`401` `{"error":{"code":"invalid_grant",...}}`(未知/已撤销/已过期,统一口径,防枚举);仅成功才审计。
|
||||
- **curl**:`curl -s -X POST http://<host>:8080/api/v1/auth/refresh -H 'Content-Type: application/json' -d '{"refreshToken":"<opaque>"}'`
|
||||
|
||||
### 2.4 POST /api/v1/auth/logout(B2)
|
||||
- **鉴权**:公开,两种方式二选一——body `{refreshToken}` 撤**单个** grant(幂等);或无 body 但带 `Authorization: Bearer <jwt>` 撤**该账号全部** grant(全设备登出)。
|
||||
- **响应**:`200` `{"data":{"ok":true,"revokedCount":<n>}}`(单撤 `revokedCount` 为 0/1;全撤为该账号 refresh 行数)。
|
||||
- **错误**:不存在的 token 为幂等 no-op(仍 `200`);非法 body `400`。
|
||||
|
||||
---
|
||||
|
||||
## 3. 用户侧端点(已认证)
|
||||
|
||||
Reference in new issue
Block a user