--- tags: [microservice, security, mTLS, JWT, OAuth2, zero-trust] create time: 2026-05-05 10:00 --- # 安全机制 ## 概述 微服务架构下,服务间通信的安全保障比单体应用复杂得多。每个服务的 API 都是潜在的攻击面,需要多层防御。 ```mermaid graph TB subgraph "纵深防御体系" L1["网络隔离
VPC / 安全组"] L2["传输加密
mTLS / TLS"] L3["身份认证
JWT / SPIFFE"] L4["授权控制
RBAC / ABAC"] L5["审计追踪
操作日志"] end L1 -.-> L2 -.-> L3 -.-> L4 -.-> L5 ``` > [!tip] Zero Trust 原则 > **永不信任,始终验证**。无论请求来自内网还是外网,每个服务调用都要经过认证和授权。传统"内网即安全"的假设是微服务安全的最大盲区——一旦某个服务被攻破,攻击者可以横向移动到其他服务。 ## 服务间认证 ### mTLS (双向 TLS) mTLS 是零信任架构的基石——建立加密通道前,双方必须互相验证证书身份。 ```mermaid sequenceDiagram participant C as 调用方 Service participant CA as CA / SPIFFE participant S as 被调方 Service C->>CA: 申请证书 (SPIFFE ID) S->>CA: 申请证书 (SPIFFE ID) CA-->>C: 签发客户端证书 CA-->>S: 签发服务端证书 C->>S: ClientHello + ClientCert S->>S: 验证书链 + SPIFFE ID alt 验证通过 S-->>C: ServerHello + ServerCert note over C,S: 建立加密通道 else 验证失败 S->>C: TLS Alert (handshake failure) end ``` > [!question] 思考 > 为什么内网服务间通信也需要加密?如果攻击者已经进入了内网网络,明文 gRPC 调用会发生什么? > [!answer] 答案 > 传统安全模型假设"内网即安全",但这个前提是整个内网都不可入侵——这在实际中几乎不成立。以下场景说明为什么内网也必须加密: > > **1. 攻击者进入内网的途径很多** > - 某个前端服务存在远程代码执行漏洞 → 获得容器权限 → 嗅探同 VPC 其他 Pod 流量 > - 开发者电脑中毒 / CI/CD 供应链被投毒 → 从合法节点发起内网横向访问 > - 第三方插件或依赖库被植恶意代码 → 在构建产物中留下后门 > > **2. 明文 gRPC 被窃听后的具体后果** > > | 攻击方式 | 可获取的内容 | 影响 | > |---------|------------|------| > | **流量嗅探** | 全部请求/响应体、gRPC metadata | 用户隐私数据、业务逻辑泄露 | > | **Metadata 劫持** | JWT Token、追踪 ID、认证头 | 直接伪造身份调用下游服务 | > | **中间人篡改** | 修改请求参数、响应 payload | 注入脏数据、绕过业务校验 | > > ```mermaid > graph LR > A["A: order-service
(发送请求)"] -->|"明文 gRPC"| D["D: attacker
(嗅探 + 注入)"] > A --> B["交换机 / router"] > B --> C["C: payment-service
(接收请求)"] > D -.->|"伪造成 order-service"| C > > style D fill:#f99,stroke:#c00 > ``` > > **关键理解**:即使没有"主动攻击"能力,仅靠二层/三层抓包(ARP spoofing、镜像端口、VLAN hop),攻击者就能完整回放 orca 一个 gRPC stream——包括其中传递的 JWT token、数据库查询条件等敏感信息。 > > **结论**:mTLS 不是"防外部黑客"的,而是让内网中的**任何一个被攻破的点**都无法读取其他服务的流量。这就是 Zero Trust 的核心思想。 **mTLS 的核心优势**: - 双向证书验证,防止中间人攻击和非法服务接入 - 证书自动轮换(配合 Istio / Cert-Manager 等服务网格方案) - 零代码侵入——Sidecar 代理处理握手,业务代码无需关心 **生产落地要点**: | 要素 | 推荐方案 | 说明 | |------|---------|------| | **CA 体系** | SPIFFE / Istio CA | 基于 SPIFFE ID 自动签发短生命周期证书 | | **证书管理** | cert-manager + Vault | K8s 场景下自动续期,避免人工干预 | | **降级策略** | Peer Authentication MESH_STRICT | Istio 中设置为 STRICT 可强制所有流量走 mTLS | ```yaml # Istio PeerAuthentication 示例 apiVersion: security.istio.io/v1beta1 kind: PeerAuthentication metadata: name: default namespace: prod spec: mtls: mode: STRICT # 拒绝明文连接 ``` ### JWT / Service Token 适用于不具备 mTLS 基础设施的场景,或作为跨边界调用的身份传递载体: ```go // 生成 Service Token(短生命周期,≤ 1h) token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "sub": "order-service", // 调用方服务标识 "iss": "auth-server", // 签发者 "aud": "payment-service", // 目标服务 "exp": time.Now().Add(1 * time.Hour).Unix(), "iat": time.Now().Unix(), "jti": uuid.New().String(), // 唯一 ID,防重放 }) tokenString, _ := token.SignedString(secretKey) ``` | 方案 | 安全性 | 复杂度 | 适用场景 | |------|--------|--------|---------| | **mTLS** | ⭐⭐⭐⭐⭐ | 中(需 PKI 基础设施) | 服务网格环境、K8s 内部通信 | | **JWT + Shared Secret** | ⭐⭐⭐⭐ | 低 | 中小规模、快速上手 | | **mTLS + JWT** | ⭐⭐⭐⭐⭐ | 中高 | 金融级安全要求 | > [!tip] 最佳实践 > 在生产环境中,推荐 **mTLS + JWT 双层防护**:mTLS 保证通道安全,JWT 传递调用方的身份信息(谁在调用)。仅用 JWT 而不加密通道的做法,一旦 TLS 被绕开(如配置错误),所有凭据将暴露于明文。 ## API 鉴权模型 ### RBAC (基于角色的访问控制) ```mermaid flowchart LR User["用户"] -->|"拥有"| Role["角色"] Role -->|"拥有"| Permission["权限"] Permission -->|"访问"| Resource["资源"] Admin["Admin"] --> ReadWrite Editor["Editor"] --> ReadWrite Viewer["Viewer"] --> ReadOnly ReadWrite --> OrderCRUD ReadOnly --> OrderRead OrderCRUD --> CRUD["Create/Read/Update/Delete"] OrderRead --> Read["Read Only"] ``` ### ABAC (基于属性的访问控制) 当权限决策需要依赖多维属性(部门、地域、时间、资源类型)时,ABAC 是更自然的选择: ```go type User struct { ID string Role string Department string Level int // 职级 } type Resource struct { ID string OwnerID string Department string Sensitivity int // 1-4, 敏感度等级 } // 策略即代码:灵活定义访问规则 func canAccess(user User, resource Resource, action string) bool { rules := []Rule{ // 同部门员工可读本部门文档 {Condition: user.Department == resource.Department && user.Level >= resource.Sensitivity, Action: "read"}, // 管理员或资源所有者可写 {Condition: user.Role == "admin" || user.ID == resource.OwnerID, Action: "write"}, } for _, r := range rules { if r.Condition && r.Action == action { return true } } return false } ``` > [!tip] RBAC vs ABAC 选型指南 > | 维度 | RBAC | ABAC | > |------|------|------| > | **适用规模** | 中小团队,角色边界清晰 | 大型组织,权限维度复杂 | > | **管理方式** | 预定义角色 + 分配 | 编写动态策略规则 | > | **扩展性** | 新增场景 → 新增角色 → 组合爆炸 | 新增场景 → 新增规则,不影响现有 | > | **落地成本** | 低(JWT claims 中直接携带 role) | 中(需策略引擎,如 OPA / Casbin) | > > 实际项目中常采用 **RBAC 为主,ABAC 补充关键资源**的混合模式。 ## 输入校验与防攻击 微服务架构中,输入校验是最后一道防线。无论上游做了什么,每个服务都应独立校验进入边界的输入数据。 > [!question] 如果网关已经做了鉴权和限流,下游服务还需要校验输入吗? > **必须校验**。网关只负责基础设施级别的防护(认证、限流),业务层面的合法性(如字段格式、长度、枚举值)应由各服务自行把关。 ### SQL 注入防护 ```go // ❌ 危险:字符串拼接 query := fmt.Sprintf("SELECT * FROM users WHERE name = '%s'", userInput) // ✅ 安全:参数化查询 rows, err := db.Query("SELECT * FROM users WHERE name = ?", userInput) ``` ### XSS 与 CSP 头 ```go // Go HTML template 自动转义 tpl.ExecuteTemplate(w, "page.html", data) // {{.Name}} 自动 HTML 转义 // JSON API 天然免疫(Content-Type: application/json) // 强制浏览器遵守 CSP 策略 w.Header().Set("Content-Security-Policy", "default-src 'self'") ``` ### 常见攻击防护清单 | 威胁 | 防护手段 | 实现位置 | |------|---------|---------| | **SQL 注入** | 参数化查询 / ORM | 数据访问层 | | **XSS** | 输出转义 / CSP Header | Web 框架 / Gateway | | **CSRF** | SameSite Cookie / CSRF Token | 网关 / Session | | **DDoS** | 限流 + WAF | API Gateway | | **重放攻击** | Nonce + Timestamp | API 签名中间件 | | **凭证泄露** | HTTPS + Secure Cookie | 传输层 | ## API 签名(防重放攻击) 对高安全要求的 API(如支付、转账),客户端需要对请求做 HMAC 签名,服务端验证签名的完整性: ```mermaid sequenceDiagram participant C as 客户端 participant S as 服务端 C->>C: 构造请求体 + timestamp + nonce C->>C: Signature = HMAC(Secret, Method+URL+TS+Body) C->>S: GET /api/pay?ts=...&nonce=...&sig=... S->>S: 1. 检查 timestamp 是否过期 alt 已过期 S-->>C: 401 Request Expired else 未过期 S->>S: 2. 检查 nonce 是否已使用 alt nonce 已存在 S-->>C: 409 Replay Detected else 新 nonce S->>S: 3. 重新计算签名并比对 alt 一致 S-->>C: 200 OK else 不一致 S-->>C: 403 Invalid Signature end end end ``` ### Go 实现示例 ```go // 客户端:生成签名 func signRequest(secret string, method, url, body string, ts int64, nonce string) string { payload := fmt.Sprintf("%s|%s|%d|%s|%s", method, url, ts, nonce, body) mac := hmac.New(sha256.New, []byte(secret)) mac.Write([]byte(payload)) return base64.StdEncoding.EncodeToString(mac.Sum(nil)) } // 服务端:签名验证中间件 func verifySignature(secret string) middleware.Handler { return func(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ts := getParam(r, "timestamp") nonce := getParam(r, "nonce") sig := getParam(r, "signature") // 1. 时间窗口校验 (±5 min) if time.Since(time.Unix(ts, 0)).Abs() > 5*time.Minute { http.Error(w, "request expired", http.StatusUnauthorized) return } // 2. Nonce 去重 (Redis SETNX, TTL=300s) key := fmt.Sprintf("nonce:%s", nonce) if !redis.SetNX(key, "1", 300*time.Second) { http.Error(w, "replay detected", http.StatusConflict) return } // 3. 签名比对 expected := signRequest(secret, r.Method, r.URL.String(), r.Body, ts, nonce) if !hmac.Equal([]byte(sig), []byte(expected)) { http.Error(w, "invalid signature", http.StatusForbidden) } next(w, r) } } } ``` > [!note] Nonce 设计要点 > - 每次请求必须携带唯一的 `nonce`(随机字符串或递增序号) > - 服务端用 Redis `SETNX` 记录已使用的 nonce,TTL 略大于时间窗口(如 300s) > - 对于高频调用场景,可考虑基于布隆过滤器优化存储空间 ## 关联笔记 - [[02-服务治理/01-API网关]] — 网关层的统一鉴权和限流 - [[02-服务治理/04-服务发现]] — 服务注册时也需要安全认证 - [[hzh/MS/API 设计原则]] — API 设计中的安全考量