# Envoy Gateway 配置指南(带 JWT 认证) ## 📋 目录 1. [快速开始](#快速开始) 2. [添加新服务](#添加新服务) 3. [JWT 认证配置](#jwt-认证配置) 4. [分级访问控制](#分级访问控制) 5. [故障排查](#故障排查) --- ## 快速开始 ### 前置条件 - 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` 了解完整的项目架构和工作流!