fix: 对齐 authz 认证链路

This commit is contained in:
zetaloop
2026-04-05 12:06:39 +08:00
parent dc87df28a4
commit 384471edca
9 changed files with 864 additions and 58 deletions
+27 -24
View File
@@ -22,9 +22,14 @@
当前网关对以下路径做了“公共放行”:
- `/healthz`(直返 200,用于探针)
- `POST /api/users/login`
- `POST /api/users/register`
- `POST /api/email/verification-code/send`(注册/登录前发送验证码)
- `POST /api/v1/auth/login`
- `POST /api/v1/auth/register`
- `POST /api/v1/auth/forgot-password`
- `POST /api/v1/auth/reset-password`
- `POST /api/v1/auth/forgot-password/send`
- `POST /api/v1/email/verification-code/send`
此外,当前配置还对一批只读接口做了 JWT 白名单豁免,例如 `GET /api/v1/games*``GET /api/v1/players*``GET /api/v1/services*``GET /api/v1/posts*``GET /api/v1/shops*` 以及部分 `GET /api/v1/users/*` 子路径。精确范围以 `deploy/k8s/envoy/envoy.yaml` 中的 `jwt_authn.rules` 为准。
实现方式:
@@ -60,27 +65,24 @@
- Envoy 内置认证过滤器(如 `jwt_authn``ext_authz`)要求固定协议。
- `ext_authz` 的 gRPC 需要实现 Envoy 标准服务 `envoy.service.auth.v3.Authorization`,不是业务自定义的 `pb.usercenter/ValidateToken`
可行方案(推荐顺序):
当前仓库已经采用 `authz-adapter` 方案:Envoy 先做 `jwt_authn`,再通过 `ext_authz` 调用 `authz-adapter`,由它内部调用 `user-rpc.ValidateToken` 做会话态二次校验。
1. **推荐**:新增一个 `authz-adapter` 服务,实现 Envoy `ext_authz` 协议,内部再调用 `user-rpc.ValidateToken`
2. 备选:提供一个内部 HTTP 鉴权端点(例如 user-api internal route),Envoy 通过 `ext_authz` HTTP 模式或 Lua `httpCall()` 调用。
如果要走方案 1(推荐),你需要补齐:
当前链路包含:
- `authz-adapter` 服务(实现 Envoy `CheckRequest/CheckResponse`
- Envoy 新增 `ext_authz` filter 与对应 cluster
- 鉴权透传头约定(至少 `x-auth-user-id``x-auth-is-admin`
- Envoy `ext_authz` filter 与 `authz_adapter_cluster`
- `jwt_authn` 注入 `x-auth-user-id``x-auth-is-admin`
- `authz-adapter` 透传 `x-auth-user-id``x-auth-role-type`
- 失败码与错误体规范(401/403)
- 性能与可用性策略(超时、失败回退、缓存)
### 4) 与你现有 `ValidateTokenLogic` 的一致性提醒
当前 `app/users/rpc/internal/logic/validateTokenLogic.go` 中:
- 代码使用 `jwt:%v` 格式拼接 `redisKey`
- `JwtManager.Valid()` 需要传入的是 **JWT token 字符串本身**
- `JwtManager.Valid()` 负责验证 JWT 字符串本身
- 当前逻辑还会校验 JWT payload 中的 `UserId` 与请求传入的 `userId` 一致,再查询数据库回填 `RoleType`
这意味着若后续接入 `ext_authz` 并调用该逻辑,建议先修正这段逻辑,避免认证结果偏差
这意味着当前 `ext_authz -> user-rpc.ValidateToken` 链已经具备 token 有效性和 userId 一致性校验
---
@@ -100,12 +102,13 @@
## 前端接入示例(邮箱验证码)
以下示例基于当前网关与服务配置:
以下示例基于当前 K8s 网关配置:
- 登录:`POST /api/users/login`(公共放行)
- 发送验证码:`POST /api/email/verification-code/send`(公共放行,无需登录)
- CSRF 头:`XSRF-TOKEN`(请求头)
- 登录:`POST /api/v1/auth/login`(公共放行)
- 发送验证码:`POST /api/v1/email/verification-code/send`(公共放行,无需登录)
- CSRF 头:`xsrf-token`(请求头)
- CSRF Cookie`__Host-XSRF-TOKEN`(可读)
- CSRF Guard Cookie`__Host-XSRF-GUARD``HttpOnly`
- JWT Cookie`JToken``HttpOnly`,前端不可读,但会随请求自动携带)
> 注意:当前 Envoy 给 CSRF Cookie 设置了 `Secure` + `SameSite=Strict`。前端必须走 **HTTPS 且同站点** 才能稳定工作。
@@ -113,7 +116,7 @@
### 接入流程
1. 先发一个安全方法请求(GET),让 Envoy 下发 XSRF 双 Cookie。
2. 注册场景可直接调用发送验证码接口,仅需携带 `XSRF-TOKEN`
2. 注册场景可直接调用发送验证码接口,仅需携带 `xsrf-token`
3. 登录场景可先调用登录接口拿 `JToken`,后续访问受保护接口时自动携带。
### 前端示例(TypeScript + fetch
@@ -136,12 +139,12 @@ async function primeXsrfCookies() {
async function login(username: string, password: string) {
const xsrfToken = getCookie("__Host-XSRF-TOKEN");
const res = await fetch(`${API_BASE}/api/users/login`, {
const res = await fetch(`${API_BASE}/api/v1/auth/login`, {
method: "POST",
credentials: "include",
headers: {
"Content-Type": "application/json",
"XSRF-TOKEN": xsrfToken,
"xsrf-token": xsrfToken,
},
body: JSON.stringify({ username, password }),
});
@@ -161,12 +164,12 @@ type SendCodeReq = {
async function sendVerificationCode(req: SendCodeReq) {
const xsrfToken = getCookie("__Host-XSRF-TOKEN");
const res = await fetch(`${API_BASE}/api/email/verification-code/send`, {
const res = await fetch(`${API_BASE}/api/v1/email/verification-code/send`, {
method: "POST",
credentials: "include",
headers: {
"Content-Type": "application/json",
"XSRF-TOKEN": xsrfToken,
"xsrf-token": xsrfToken,
},
body: JSON.stringify(req),
});
@@ -235,7 +238,7 @@ kubectl get cm -n juwan envoy-config
kubectl port-forward -n juwan svc/envoy-gateway 8080:80 &
# 测试
curl http://localhost:8080/api/users/login
curl http://localhost:8080/healthz
```
---