feat: add authz-adapter service and Envoy ext_authz integration
- Implemented authz-adapter deployment and service for Envoy gRPC authorization. - Created PowerShell script to generate JWK for JWT authentication. - Documented the integration of ext_authz with user-rpc.ValidateToken in ENVOY_EXT_AUTHZ_ADAPTER.md. - Added comprehensive Envoy Gateway configuration guide with JWT authentication and access control in ENVOY_GATEWAY_GUIDE.md.
This commit is contained in:
@@ -0,0 +1,814 @@
|
||||
# Envoy Gateway 配置指南(带 JWT 认证)
|
||||
|
||||
## 📋 目录
|
||||
|
||||
1. [快速开始](#快速开始)
|
||||
2. [添加新服务](#添加新服务)
|
||||
3. [JWT 认证配置](#jwt-认证配置)
|
||||
4. [分级访问控制](#分级访问控制)
|
||||
5. [故障排查](#故障排查)
|
||||
6. [当前实现说明(与仓库配置对齐)](#当前实现说明与仓库配置对齐)
|
||||
7. [ext_authz 适配方案](#ext_authz-适配方案)
|
||||
8. [前端接入示例(邮箱验证码)](#前端接入示例邮箱验证码)
|
||||
|
||||
---
|
||||
|
||||
## 当前实现说明(与仓库配置对齐)
|
||||
|
||||
> 本节对应当前实际配置文件:`deploy/k8s/envoy/envoy.yaml`。
|
||||
|
||||
### 0) 当前公共路由(不需要登录)
|
||||
|
||||
当前网关对以下路径做了“公共放行”:
|
||||
|
||||
- `/healthz`(直返 200,用于探针)
|
||||
- `POST /api/users/login`
|
||||
- `POST /api/users/register`
|
||||
- `POST /api/email/verification-code/send`(注册/登录前发送验证码)
|
||||
|
||||
实现方式:
|
||||
|
||||
- 在 `jwt_authn.rules` 中将上述路径加入白名单(不要求 JWT)
|
||||
- 在路由层对上述路径关闭 `ext_authz`(避免公共接口和探针被二次鉴权拦截)
|
||||
|
||||
### 1) 用户认证后,`UserId` 放在哪里?
|
||||
|
||||
当前 Envoy 使用 `envoy.filters.http.jwt_authn` 做 JWT 校验,校验通过后通过 `claim_to_headers` 将 claim 注入到转发请求头:
|
||||
|
||||
- `UserId` -> `x-auth-user-id`
|
||||
- `IsAdmin` -> `x-auth-is-admin`
|
||||
|
||||
也就是说,后端 API(如 user-api/email-api)拿到的是 HTTP Header,不是 Envoy 动态元数据。
|
||||
|
||||
### 2) 当前配置是否实现了“换票”(token renew)?
|
||||
|
||||
没有。
|
||||
|
||||
当前 Envoy 配置仅负责:
|
||||
|
||||
- 从 Cookie `JToken` 提取 JWT
|
||||
- 用 HS256 + `issuer: juwan-user-rpc` 验签与过期检查
|
||||
|
||||
当 token 过期时,`jwt_authn` 会直接拒绝请求,不会调用 user-rpc 的 `JwtManager.Renew`。
|
||||
|
||||
### 3) Envoy 能否直接调用 `user-rpc.ValidateToken`?
|
||||
|
||||
结论:不能“直接”用现有 `ValidateToken` protobuf 接口接入 Envoy 认证链。
|
||||
|
||||
原因:
|
||||
|
||||
- Envoy 内置认证过滤器(如 `jwt_authn`、`ext_authz`)要求固定协议。
|
||||
- `ext_authz` 的 gRPC 需要实现 Envoy 标准服务 `envoy.service.auth.v3.Authorization`,不是业务自定义的 `pb.usercenter/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`)
|
||||
- 失败码与错误体规范(401/403)
|
||||
- 性能与可用性策略(超时、失败回退、缓存)
|
||||
|
||||
### 4) 与你现有 `ValidateTokenLogic` 的一致性提醒
|
||||
|
||||
当前 `app/users/rpc/internal/logic/validateTokenLogic.go` 中:
|
||||
|
||||
- 代码使用 `jwt:%v` 格式拼接 `redisKey`
|
||||
- 但 `JwtManager.Valid()` 需要传入的是 **JWT token 字符串本身**
|
||||
|
||||
这意味着若后续接入 `ext_authz` 并调用该逻辑,建议先修正这段逻辑,避免认证结果偏差。
|
||||
|
||||
---
|
||||
|
||||
## ext_authz 适配方案
|
||||
|
||||
如果你希望 Envoy 在鉴权阶段调用 `user-rpc.ValidateToken`,请看完整落地文档:
|
||||
|
||||
- [ENVOY_EXT_AUTHZ_ADAPTER.md](ENVOY_EXT_AUTHZ_ADAPTER.md)
|
||||
|
||||
该文档包含:
|
||||
|
||||
- Envoy 标准 `Authorization.Check` 最小实现骨架
|
||||
- 调用 `user-rpc.ValidateToken` 的适配逻辑示例
|
||||
- 可直接嵌入现有网关的 `ext_authz` filter + cluster 配置片段
|
||||
|
||||
---
|
||||
|
||||
## 前端接入示例(邮箱验证码)
|
||||
|
||||
以下示例基于你当前网关与服务配置:
|
||||
|
||||
- 登录:`POST /api/users/login`(公共放行)
|
||||
- 发送验证码:`POST /api/email/verification-code/send`(公共放行,无需登录)
|
||||
- CSRF 头:`XSRF-TOKEN`(请求头)
|
||||
- CSRF Cookie:`__Host-XSRF-TOKEN`(可读)
|
||||
- JWT Cookie:`JToken`(`HttpOnly`,前端不可读,但会随请求自动携带)
|
||||
|
||||
> 注意:当前 Envoy 给 CSRF Cookie 设置了 `Secure` + `SameSite=Strict`。前端必须走 **HTTPS 且同站点** 才能稳定工作。
|
||||
|
||||
### 接入流程
|
||||
|
||||
1. 先发一个安全方法请求(GET),让 Envoy 下发 XSRF 双 Cookie。
|
||||
2. 注册场景可直接调用发送验证码接口,仅需携带 `XSRF-TOKEN`。
|
||||
3. 登录场景可先调用登录接口拿 `JToken`,后续访问受保护接口时自动携带。
|
||||
|
||||
### 前端示例(TypeScript + fetch)
|
||||
|
||||
```ts
|
||||
const API_BASE = "https://your-gateway-domain";
|
||||
|
||||
function getCookie(name: string): string {
|
||||
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
const match = document.cookie.match(new RegExp(`(?:^|; )${escaped}=([^;]*)`));
|
||||
return match ? decodeURIComponent(match[1]) : "";
|
||||
}
|
||||
|
||||
async function primeXsrfCookies() {
|
||||
await fetch(`${API_BASE}/healthz`, {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
});
|
||||
}
|
||||
|
||||
async function login(username: string, password: string) {
|
||||
const xsrfToken = getCookie("__Host-XSRF-TOKEN");
|
||||
const res = await fetch(`${API_BASE}/api/users/login`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"XSRF-TOKEN": xsrfToken,
|
||||
},
|
||||
body: JSON.stringify({ username, password }),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const text = await res.text();
|
||||
throw new Error(`login failed: ${res.status} ${text}`);
|
||||
}
|
||||
|
||||
return res.json();
|
||||
}
|
||||
|
||||
type SendCodeReq = {
|
||||
email: string;
|
||||
scene: "register" | "login" | "reset_password" | "bind_email";
|
||||
};
|
||||
|
||||
async function sendVerificationCode(req: SendCodeReq) {
|
||||
const xsrfToken = getCookie("__Host-XSRF-TOKEN");
|
||||
const res = await fetch(`${API_BASE}/api/email/verification-code/send`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"XSRF-TOKEN": xsrfToken,
|
||||
},
|
||||
body: JSON.stringify(req),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const text = await res.text();
|
||||
throw new Error(`send code failed: ${res.status} ${text}`);
|
||||
}
|
||||
|
||||
return res.json() as Promise<{
|
||||
requestId: string;
|
||||
expireInSec: number;
|
||||
message: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
// 页面初始化时建议执行一次
|
||||
await primeXsrfCookies();
|
||||
|
||||
// 注册场景:无需登录即可发送验证码
|
||||
const data = await sendVerificationCode({
|
||||
email: "alice@example.com",
|
||||
scene: "register",
|
||||
});
|
||||
|
||||
console.log("code request:", data);
|
||||
|
||||
// 如需调用受保护接口,再执行登录
|
||||
await login("alice", "P@ssw0rd");
|
||||
```
|
||||
|
||||
### 常见前端坑位
|
||||
|
||||
- 必须加 `credentials: "include"`,否则 Cookie 不会带上。
|
||||
- `JToken` 是 `HttpOnly`,前端读不到,这是正常设计。
|
||||
- 如果你前后端跨站点,`SameSite=Strict` 会导致 Cookie 不发送;需要改网关 Cookie 策略。
|
||||
- 本地 `http://localhost` 下,`Secure` Cookie 不会生效;建议本地也走 HTTPS(例如反向代理或证书)。
|
||||
|
||||
---
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 前置条件
|
||||
|
||||
- K8s 集群正在运行(已验证 ✅)
|
||||
- Envoy Gateway Pod 处于 Running 状态
|
||||
- 所有后端服务已部署
|
||||
|
||||
### 当前网关状态
|
||||
|
||||
```bash
|
||||
# 查看 Envoy Pod
|
||||
kubectl get pods -n juwan -l app=envoy-gateway
|
||||
|
||||
# 查看网关 Service
|
||||
kubectl get svc -n juwan envoy-gateway
|
||||
|
||||
# 查看 ConfigMap
|
||||
kubectl get cm -n juwan envoy-config
|
||||
```
|
||||
|
||||
### 访问网关
|
||||
|
||||
```bash
|
||||
# 通过 kubectl 端口转发(本地测试)
|
||||
kubectl port-forward -n juwan svc/envoy-gateway 8080:80 &
|
||||
|
||||
# 测试
|
||||
curl http://localhost:8080/api/users/login
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 添加新服务
|
||||
|
||||
### 场景:添加 Product 服务
|
||||
|
||||
#### 1. 创建服务的 K8s 部署清单
|
||||
|
||||
编辑或创建 `deploy/k8s/service/product/product-api.yaml`:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: juwan
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: product-api-config
|
||||
namespace: juwan
|
||||
data:
|
||||
product-api.yaml: |
|
||||
Name: product-api
|
||||
Host: 0.0.0.0
|
||||
Port: 8890
|
||||
Database:
|
||||
DataSource: postgres://user:pass@pg-dx:5432/juwan
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: product-api
|
||||
namespace: juwan
|
||||
labels:
|
||||
app: product-api
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: product-api
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: product-api
|
||||
spec:
|
||||
containers:
|
||||
- name: api
|
||||
image: your-registry/product-api:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- containerPort: 8890
|
||||
name: http
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /etc/product-api
|
||||
env:
|
||||
- name: TZ
|
||||
value: "Asia/Shanghai"
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8890
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
volumes:
|
||||
- name: config
|
||||
configMap:
|
||||
name: product-api-config
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: product-api-svc
|
||||
namespace: juwan
|
||||
spec:
|
||||
selector:
|
||||
app: product-api
|
||||
ports:
|
||||
- port: 8890
|
||||
targetPort: 8890
|
||||
name: http
|
||||
type: ClusterIP
|
||||
```
|
||||
|
||||
#### 2. 在 Envoy 网关中添加路由
|
||||
|
||||
编辑 `deploy/k8s/envoy-gateway.yaml`,在 `route_config` 的 `routes` 部分添加:
|
||||
|
||||
```yaml
|
||||
# ... 在现有路由下方添加:
|
||||
- match:
|
||||
prefix: /api/products
|
||||
route:
|
||||
cluster: product_api_cluster
|
||||
timeout: 30s
|
||||
```
|
||||
|
||||
#### 3. 在 Envoy 网关中添加上游集群
|
||||
|
||||
编辑 `deploy/k8s/envoy-gateway.yaml`,在 `clusters` 部分添加:
|
||||
|
||||
```yaml
|
||||
- name: product_api_cluster
|
||||
connect_timeout: 5s
|
||||
type: STRICT_DNS
|
||||
dns_lookup_family: V4_ONLY
|
||||
lb_policy: ROUND_ROBIN
|
||||
load_assignment:
|
||||
cluster_name: product_api_cluster
|
||||
endpoints:
|
||||
- lb_endpoints:
|
||||
- endpoint:
|
||||
address:
|
||||
socket_address:
|
||||
address: product-api-svc.juwan.svc.cluster.local
|
||||
port_value: 8890
|
||||
health_checks:
|
||||
- timeout: 3s
|
||||
interval: 10s
|
||||
unhealthy_threshold: 2
|
||||
healthy_threshold: 2
|
||||
http_health_check:
|
||||
path: /health
|
||||
```
|
||||
|
||||
#### 4. 部署到集群
|
||||
|
||||
```bash
|
||||
# 部署 Product API
|
||||
kubectl apply -f deploy/k8s/service/product/product-api.yaml
|
||||
|
||||
# 更新 Envoy 配置
|
||||
kubectl apply -f deploy/k8s/envoy-gateway.yaml
|
||||
|
||||
# 重启 Envoy Pod 以加载新配置
|
||||
kubectl delete pods -n juwan -l app=envoy-gateway
|
||||
|
||||
# 验证
|
||||
kubectl get pods -n juwan
|
||||
|
||||
# 测试新接口
|
||||
curl http://localhost:8080/api/products
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## JWT 认证配置
|
||||
|
||||
### 1. 生成 JWT 密钥并存储
|
||||
|
||||
```bash
|
||||
# 执行设置脚本
|
||||
bash deploy/envoy/setup-jwt-auth.sh
|
||||
|
||||
# 或手动执行
|
||||
JWT_SECRET=$(openssl rand -hex 32)
|
||||
echo "保存这个密钥: $JWT_SECRET"
|
||||
|
||||
# 创建 K8s Secret
|
||||
kubectl create secret generic jwt-secret \
|
||||
--from-literal=key=$JWT_SECRET \
|
||||
-n juwan
|
||||
```
|
||||
|
||||
### 2. 配置 Envoy JWT 认证
|
||||
|
||||
编辑 `deploy/k8s/envoy-gateway.yaml`,更新 `http_filters` 部分:
|
||||
|
||||
```yaml
|
||||
http_filters:
|
||||
# JWT 认证过滤器(必须在 router 之前)
|
||||
- name: envoy.filters.http.jwt_authn
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication
|
||||
|
||||
providers:
|
||||
default:
|
||||
issuer: "juwan"
|
||||
audiences: "api"
|
||||
# 使用 ConfigMap 中的 JWKS(已通过 volumeMount 挂载)
|
||||
local_jwks:
|
||||
filename: /etc/envoy/jwks.json
|
||||
|
||||
rules:
|
||||
# 规则1: 登录端点不需要认证
|
||||
- match:
|
||||
prefix: /api/users/login
|
||||
allow_missing_or_failed: true
|
||||
|
||||
# 规则2: 注册端点不需要认证
|
||||
- match:
|
||||
prefix: /api/users/register
|
||||
allow_missing_or_failed: true
|
||||
|
||||
# 规则3: 获取公开商品列表不需要认证
|
||||
- match:
|
||||
prefix: /api/products
|
||||
case_sensitive: false
|
||||
methods: ["GET"] # 仅 GET 不需要认证
|
||||
allow_missing_or_failed: true
|
||||
|
||||
# 规则4: 其他所有路由需要认证
|
||||
- match:
|
||||
prefix: "/"
|
||||
requires:
|
||||
provider_name: "default"
|
||||
|
||||
# 路由过滤器(在 JWT 认证之后)
|
||||
- name: envoy.filters.http.router
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
|
||||
```
|
||||
|
||||
### 3. 在 Envoy Deployment 中挂载 JWKS
|
||||
|
||||
编辑 `deploy/k8s/envoy-gateway.yaml` 的 Deployment 部分:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
# ... 其他配置 ...
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: envoy
|
||||
# ... 其他配置 ...
|
||||
volumeMounts:
|
||||
- name: envoy-config
|
||||
mountPath: /etc/envoy
|
||||
- name: jwks-config # ← 新增
|
||||
mountPath: /etc/envoy
|
||||
|
||||
volumes:
|
||||
- name: envoy-config
|
||||
configMap:
|
||||
name: envoy-config
|
||||
- name: jwks-config # ← 新增
|
||||
configMap:
|
||||
name: jwks-config
|
||||
items:
|
||||
- key: jwks.json
|
||||
path: jwks.json
|
||||
```
|
||||
|
||||
### 4. 在 API 服务中生成 JWT Token
|
||||
|
||||
在 User API 的 login 端点(`app/users/api/internal/logic/user/loginlogic.go`):
|
||||
|
||||
```go
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"app/users/api/internal/svc"
|
||||
"app/users/api/internal/types"
|
||||
)
|
||||
|
||||
type LoginLogic struct {
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func (l *LoginLogic) Login(req *types.LoginReq) (*types.LoginResp, error) {
|
||||
// TODO: 验证用户名和密码
|
||||
|
||||
// 从配置中获取 JWT 密钥
|
||||
jwtSecret := l.svcCtx.Config.JwtSecret
|
||||
if jwtSecret == "" {
|
||||
jwtSecret = "default-secret" // 开发环境默认值
|
||||
}
|
||||
|
||||
// 生成 JWT Token
|
||||
claims := jwt.MapClaims{
|
||||
"userId": 1, // 实际应从数据库获取
|
||||
"username": req.Username,
|
||||
"exp": time.Now().Add(24 * time.Hour).Unix(),
|
||||
"iat": time.Now().Unix(),
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
tokenString, err := token.SignedString([]byte(jwtSecret))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.LoginResp{
|
||||
Token: tokenString,
|
||||
Expires: time.Now().Add(24 * time.Hour).Unix(),
|
||||
UserId: 1,
|
||||
Username: req.Username,
|
||||
Email: "user@example.com",
|
||||
}, nil
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 在 API 配置中设置 JWT 密钥
|
||||
|
||||
编辑 `app/users/api/etc/user-api.yaml`:
|
||||
|
||||
```yaml
|
||||
Name: user-api
|
||||
Host: 0.0.0.0
|
||||
Port: 8888
|
||||
|
||||
JwtSecret: "${JWT_SECRET}" # 环境变量
|
||||
|
||||
Database:
|
||||
DataSource: postgres://...
|
||||
|
||||
UserRpc:
|
||||
Endpoints:
|
||||
- user-rpc-svc.juwan.svc.cluster.local:50051
|
||||
```
|
||||
|
||||
编辑 `app/users/api/internal/config/config.go`:
|
||||
|
||||
```go
|
||||
package config
|
||||
|
||||
import "github.com/zeromicro/go-zero/rest"
|
||||
|
||||
type Config struct {
|
||||
rest.RestConf
|
||||
JwtSecret string `json:"jwtSecret"`
|
||||
}
|
||||
```
|
||||
|
||||
在 K8s Deployment 中设置环境变量:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: api
|
||||
env:
|
||||
- name: JWT_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: jwt-secret
|
||||
key: key
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 分级访问控制
|
||||
|
||||
### 场景1: 获取用户信息(有权限区分)
|
||||
|
||||
如果用户查看自己的信息 → 返回完整数据
|
||||
如果用户查看他人信息 → 返回部分数据
|
||||
|
||||
#### 在 RPC 服务中实现
|
||||
|
||||
编辑 `app/users/rpc/internal/logic/getUsersByIdLogic.go`:
|
||||
|
||||
```go
|
||||
func (l *GetUsersByIdLogic) GetUsersById(ctx context.Context, in *pb.GetUsersByIdReq) (*pb.GetUsersByIdResp, error) {
|
||||
// 获取请求者的 userId(由 API 层通过 context 传递)
|
||||
requesterID, ok := ctx.Value("userId").(int64)
|
||||
if !ok {
|
||||
requesterID = 0 // 未认证用户
|
||||
}
|
||||
|
||||
targetID := in.Id
|
||||
|
||||
// 查询数据库
|
||||
user := l.svcCtx.UserModel.FindOne(ctx, targetID)
|
||||
if user == nil {
|
||||
return nil, status.Error(codes.NotFound, "user not found")
|
||||
}
|
||||
|
||||
resp := &pb.GetUsersByIdResp{
|
||||
Users: &pb.Users{
|
||||
UserId: user.UserId,
|
||||
Username: user.Username,
|
||||
CreatedAt: user.CreatedAt,
|
||||
},
|
||||
}
|
||||
|
||||
// 权限检查:自己可以看全部,别人只能看部分
|
||||
if requesterID == targetID {
|
||||
resp.Users.Email = user.Email // ✅ 自己可见
|
||||
resp.Users.Phone = user.Phone // ✅ 自己可见
|
||||
resp.Users.Passwd = "" // ❌ 密码永远不返回
|
||||
}
|
||||
// else: 只返回基本信息(username, userId)
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
```
|
||||
|
||||
#### 在 API 层调用时传递 userId
|
||||
|
||||
编辑 `app/users/api/internal/logic/user/getUserInfoLogic.go`:
|
||||
|
||||
```go
|
||||
func (l *GetUserInfoLogic) GetUserInfo(req *types.GetUserInfoReq) (*types.UserInfo, error) {
|
||||
// 从 context 获取当前认证用户
|
||||
currentUserID, ok := l.ctx.Value("userId").(int64)
|
||||
if !ok {
|
||||
// 未认证 → 只能查看公开信息
|
||||
currentUserID = 0
|
||||
}
|
||||
|
||||
// 调用 RPC,传递 userId
|
||||
ctx := context.WithValue(l.ctx, "userId", currentUserID)
|
||||
rpcResp, err := l.svcCtx.UserRpc.GetUsersById(ctx, &pb.GetUsersByIdReq{
|
||||
Id: req.UserId,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.UserInfo{
|
||||
UserId: rpcResp.Users.UserId,
|
||||
Username: rpcResp.Users.Username,
|
||||
Email: rpcResp.Users.Email,
|
||||
Phone: rpcResp.Users.Phone,
|
||||
CreateAt: rpcResp.Users.CreatedAt,
|
||||
}, nil
|
||||
}
|
||||
```
|
||||
|
||||
### 场景2: 修改用户信息(只能修改自己)
|
||||
|
||||
```go
|
||||
func (l *UpdateUserInfoLogic) UpdateUserInfo(req *types.UpdateUserInfoReq) (*types.UpdateUserInfoResp, error) {
|
||||
// 获取当前认证用户
|
||||
currentUserID, ok := l.ctx.Value("userId").(int64)
|
||||
if !ok {
|
||||
return nil, errors.New("unauthorized")
|
||||
}
|
||||
|
||||
// 权限检查:只能修改自己的信息
|
||||
if currentUserID != req.UserId {
|
||||
return nil, errors.New("forbidden: can only update your own info")
|
||||
}
|
||||
|
||||
// 更新用户信息
|
||||
// ...
|
||||
|
||||
return &types.UpdateUserInfoResp{
|
||||
Message: "更新成功",
|
||||
}, nil
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 故障排查
|
||||
|
||||
### 问题1: Envoy Pod 启动失败
|
||||
|
||||
```bash
|
||||
# 查看日志
|
||||
kubectl logs -n juwan -l app=envoy-gateway --tail=100
|
||||
|
||||
# 常见错误及解决
|
||||
# Error: "no such field"
|
||||
# → YAML 字段名拼写错误或与 Envoy 版本不兼容
|
||||
# → 检查 Envoy 版本并查看官方文档
|
||||
|
||||
# Error: "unknown cluster"
|
||||
# → envoy-gateway.yaml 中缺少 cluster 定义
|
||||
# → 确保添加了所有需要的 cluster 部分
|
||||
|
||||
# Error: "unknown extension type"
|
||||
# → 使用了 Envoy 不支持的扩展类型
|
||||
# → 检查 "@type" 字段是否正确
|
||||
```
|
||||
|
||||
### 问题2: JWT 认证失败
|
||||
|
||||
```bash
|
||||
# 验证 JWKS ConfigMap 是否存在
|
||||
kubectl get cm -n juwan jwks-config
|
||||
|
||||
# 查看 JWKS 内容
|
||||
kubectl get cm jwks-config -n juwan -o jsonpath='{.data.jwks\.json}'
|
||||
|
||||
# 验证 Envoy 能否读取 JWKS
|
||||
kubectl exec -it {envoy-pod-name} -n juwan -- ls -la /etc/envoy/
|
||||
|
||||
# 测试没有 Token 的请求(应返回 401)
|
||||
curl -v http://localhost/api/users/1
|
||||
|
||||
# 测试有效 Token 的请求
|
||||
TOKEN="your-jwt-token"
|
||||
curl -H "Authorization: Bearer $TOKEN" http://localhost/api/users/1
|
||||
```
|
||||
|
||||
### 问题3: 后端服务无法访问
|
||||
|
||||
```bash
|
||||
# 查看 Service 是否存在
|
||||
kubectl get svc -n juwan
|
||||
|
||||
# 测试 DNS 解析
|
||||
kubectl exec -it {pod-name} -n juwan -- \
|
||||
nslookup product-api-svc.juwan.svc.cluster.local
|
||||
|
||||
# 查看 Pod 是否正确运行
|
||||
kubectl get pods -n juwan -l app=product-api
|
||||
|
||||
# 查看后端服务日志
|
||||
kubectl logs -n juwan -l app=product-api --tail=50
|
||||
|
||||
# Envoy 检查上游集群状态
|
||||
kubectl exec -it {envoy-pod-name} -n juwan -- \
|
||||
curl localhost:9901/clusters | grep -A5 product_api_cluster
|
||||
```
|
||||
|
||||
### 问题4: 跨域请求失败
|
||||
|
||||
如果前端遇到 CORS 问题:
|
||||
|
||||
```yaml
|
||||
# 在 Envoy 配置中添加 CORS 过滤器
|
||||
http_filters:
|
||||
- name: envoy.filters.http.cors
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/envoy.extensions.filters.http.cors.v3.Cors
|
||||
|
||||
# JWT 认证过滤器(在 CORS 之后)
|
||||
- name: envoy.filters.http.jwt_authn
|
||||
# ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 配置更新流程
|
||||
|
||||
每次修改 `envoy-gateway.yaml` 后的完整更新步骤:
|
||||
|
||||
```bash
|
||||
# 1. 验证 YAML 语法
|
||||
kubectl apply -f deploy/k8s/envoy-gateway.yaml --dry-run=client
|
||||
|
||||
# 2. 应用配置
|
||||
kubectl apply -f deploy/k8s/envoy-gateway.yaml
|
||||
|
||||
# 3. 监控 Pod 重启(应该自动重新加载)
|
||||
kubectl get pods -n juwan -l app=envoy-gateway -w
|
||||
|
||||
# 4. 查看最新日志确认无错误
|
||||
kubectl logs -n juwan -l app=envoy-gateway --tail=50
|
||||
|
||||
# 5. 测试新配置
|
||||
curl http://localhost/api/your-new-endpoint
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
| 任务 | 文件 | 说明 |
|
||||
|-----|------|------|
|
||||
| 添加新 API | `desc/api/`, `app/*/api/` | 定义接口并实现业务逻辑 |
|
||||
| 添加新 RPC | `desc/rpc/`, `app/*/rpc/` | 内部服务通信(不通过网关) |
|
||||
| 更新网关路由 | `deploy/k8s/envoy-gateway.yaml` | 添加路由、集群、认证规则 |
|
||||
| 配置认证 | `deploy/envoy/setup-jwt-auth.sh` | 生成和管理 JWT 密钥 |
|
||||
| 部署到 K8s | `deploy/k8s/service/` | 创建服务的 Deployment 和 Service |
|
||||
|
||||
需要更多帮助?查看 `PROJECT_GUIDE.md` 了解完整的项目架构和工作流!
|
||||
Reference in New Issue
Block a user