This repository has been archived on 2026-05-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
all-in-kingsoft/hhs/MS/02-服务治理/02-安全机制.md
T
2026-05-17 22:00:24 +08:00

12 KiB
Raw Blame History

tags, create time
tags create time
microservice
security
mTLS
JWT
OAuth2
zero-trust
2026-05-05 10:00

安全机制

概述

微服务架构下,服务间通信的安全保障比单体应用复杂得多。每个服务的 API 都是潜在的攻击面,需要多层防御。

graph TB
    subgraph "纵深防御体系"
        L1["网络隔离<br/>VPC / 安全组"]
        L2["传输加密<br/>mTLS / TLS"]
        L3["身份认证<br/>JWT / SPIFFE"]
        L4["授权控制<br/>RBAC / ABAC"]
        L5["审计追踪<br/>操作日志"]
    end

    L1 -.-> L2 -.-> L3 -.-> L4 -.-> L5

[!tip] Zero Trust 原则 永不信任,始终验证。无论请求来自内网还是外网,每个服务调用都要经过认证和授权。传统"内网即安全"的假设是微服务安全的最大盲区——一旦某个服务被攻破,攻击者可以横向移动到其他服务。

服务间认证

mTLS (双向 TLS)

mTLS 是零信任架构的基石——建立加密通道前,双方必须互相验证证书身份。

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 注入脏数据、绕过业务校验
graph LR
    A["A: order-service<br/>(发送请求)"] -->|"明文 gRPC"| D["D: attacker<br/>(嗅探 + 注入)"]
    A --> B["交换机 / router"]
    B --> C["C: payment-service<br/>(接收请求)"]
    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
# Istio PeerAuthentication 示例
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
  namespace: prod
spec:
  mtls:
    mode: STRICT  # 拒绝明文连接

JWT / Service Token

适用于不具备 mTLS 基础设施的场景,或作为跨边界调用的身份传递载体:

// 生成 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 (基于角色的访问控制)

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 是更自然的选择:

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 注入防护

// ❌ 危险:字符串拼接
query := fmt.Sprintf("SELECT * FROM users WHERE name = '%s'", userInput)

// ✅ 安全:参数化查询
rows, err := db.Query("SELECT * FROM users WHERE name = ?", userInput)

XSS 与 CSP 头

// 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 签名,服务端验证签名的完整性:

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 实现示例

// 客户端:生成签名
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)
  • 对于高频调用场景,可考虑基于布隆过滤器优化存储空间

关联笔记