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/hzh/MS/02-服务治理/网关鉴权策略.md
T

6.9 KiB
Raw Blame History

tags, create time
tags create time
microservice
api-gateway
auth
service-mesh
mTLS
jwt
rbac
2026-04-30 15:30

网关鉴权分层设计

概述

探讨"鉴权是否应该全部放在网关统一处理"这个常见架构争议,给出兼顾统一性和灵活性的分层鉴权方案。

问题的本质

一刀切的陷阱

[!question] 引出思考 有人说:"鉴权放到网关统一做,避免每个服务重复写。"但如果某个内部服务不需要鉴权呢?或者不同团队要求不同的鉴权方式呢?你怎么设计才能兼顾统一性和灵活性?

核心矛盾在于:网关做鉴权的优势是集中化和性能,但代价是 失去对差异化需求的表达能力。现实中至少存在三种网关无法处理的场景:

场景 例子 为什么网关搞不定
内部调用免鉴权 A服务直调B服务的内部查询接口 外部请求到不了这里,网关做无意义开销
不同团队标准不同 财务系统要求 OAuth2+MFA,运营后台只需 API Key 策略硬编码在网关里会变成巨石中间件
精细化权限控制 订单服务需要判断"用户A是否有权看这笔订单" 网关不知道业务数据归属关系

三层鉴权架构

flowchart TB
    subgraph L1["第一层:网关层 - 你是谁?"]
        G1["JWT / Token 校验"]
        G2["IP 白名单"]
        G3["基础限流"]
    end

    subgraph L2["第二层:服务入口层 - 你能做什么?"]
        S1["内部服务间 Token 验证"]
        S2["mTLS 双向认证"]
        S3["角色 / 权限检查"]
    end

    subgraph L3["第三层:业务逻辑层 - 你能操作这个资源吗?"]
        B1["行级权限:userId == order.userId"]
        B2["数据范围:仅本部门可见"]
        B3["审批状态机流转"]
    end

    Client["外部请求"] --> GW["API Gateway<br/>L1 快速过滤非法请求"]
    GW --> svcA["Order Service"]
    GW --> svcB["Finance Service"]
    
    svcA --> L2
    svcB --> L2
    
    L2 --> L3
    
    svcA -.->|"内部调用"| svcC["Inventory Service<br/>仅 L2 鉴权,跳过 L1"]
    svcC --> L2

三层职责划分

[!summary] 鉴权分层原则

层级 关注点 粒度 谁来实现
L1 网关层 身份认证 — "你是合法用户吗?" 粗粒度(用户级) 平台团队统一维护
L2 服务层 接口授权 — "你有权限调这个接口吗?" 中粒度(角色/服务级) 各业务团队自己定义
L3 业务层 数据权限 — "你能操作这条数据吗?" 细粒度(数据级) 业务逻辑自身实现

设计哲学:越往上过滤越早失败,但信息越少;越往下信息越多但成本越高。每一层只做它最擅长的那件事。

内部服务调用的鉴权策略

对于内部服务互不调用网关的问题,答案是:不是不需要鉴权,而是用更轻量级的鉴权。

// 服务间调用:用短期签名 Token 代替完整 JWT
func serviceToServiceAuth(serviceID string, target string) string {
    // 使用共享密钥 HMAC 签发短时效 Token
    payload := map[string]string{
        "sub":    serviceID,
        "target": target,
        "exp":    time.Now().Add(5 * time.Minute).Format(time.RFC3339),
    }
    return signWithSharedKey(payload) // 比解析完整 JWT 便宜得多
}

// 接收方校验
func verifyInterServiceToken(token string) error {
    claims, err := verifyHMAC(token)
    if err != nil {
        return fmt.Errorf("无效的内部服务 Token")
    }
    // 额外检查:该服务是否有权限访问目标服务
    if !allowedAccess(claims.ServiceID, claims.Target) {
        return fmt.Errorf("服务 %s 无权调用 %s", claims.ServiceID, claims.Target)
    }
    return nil
}

[!note] 解释 这段代码展示了两个关键点:一是用 HMAC 短期 Token 替代完整的 JWT 校验链,将延迟控制在 ~1ms 量级;二是在校验身份之外还做了 服务间的访问控制检查,避免被入侵的服务冒充其他服务。

内部鉴权方案对比:

方案 延迟 安全性 适用场景
完全跳过鉴权 ~0ms ❌ 不安全 同 Pod 内 Sidecar 互调
简单 Token 签名 ~1ms ⚠️ 中等 同一安全域内的服务互调
mTLS 双向认证 ~2ms ✅ 高 跨租户、跨团队、混合云环境

多团队差异化鉴权方案

针对第二个问题——不同团队需要不同的鉴权方式——推荐用 策略即代码(Policy-as-Code) + 可插拔鉴权中间件。

// 鉴权处理器接口 — 每个团队实现自己的策略
type AuthMiddleware interface {
    Name() string
    CanHandle(ctx *Context) bool       // 这个服务 / 路由是否适用我的策略
    Authenticate(w http.ResponseWriter, r *http.Request) bool
}

// 注册表模式:运行时动态装配
var registry = make(map[string]AuthMiddleware)

func Register(name string, m AuthMiddleware) {
    registry[name] = m
}

// 路由级别的策略声明
// router.go
router.Use(multiAuth(
    "jwt-gateway",   // L1 网关已经校验过的 JWT
    "rbac-finance",  // 财务团队的额外 RBAC 校验
))

[!note] 解释 通过接口抽象 + 注册表模式,每个团队可以在不修改基础设施代码的前提下,将自己团队的鉴权策略注册到系统中。multiAuth 按顺序执行所有匹配的中间件,任一失败则拒绝请求。

这样做的收益:

[!tip] 核心收益

  1. 统一但不僵化:网关承担公共部分,各服务按需叠加自定义策略
  2. 渐进式迁移:老服务可以逐步从简单鉴权升级到新策略,不用一次性改造
  3. 职责边界清晰:平台团队维护基础设施鉴权,业务团队维护业务鉴权
  4. 审计友好:每层的鉴权结果独立记录,出问题时能精确到是哪一层拦截的

什么时候应该让网关不做鉴权?

[!warning] 反模式警告

以下情况考虑 绕过网关鉴权,由服务自行处理:

  1. 内部工具/API:定时任务脚本、后台管理接口,不走对外网关
  2. IoT 设备直连:设备有自己的凭证体系,不适合套用用户 JWT
  3. Webhook 回调:上游厂商的回调签名验证应在消费侧服务完成
  4. 高性能扫描场景:高频行情推送,鉴权开销占总延迟 >5% 时可考虑批量校验

总结

这套设计的核心理念是 "默认严格、按需放宽":

  • 网关做 防御纵深的第一道——挡住明显非法的请求,保护后方
  • 每个服务保持 独立的鉴权决策能力——因为最了解自身需求的只有服务本身
  • 服务间通信用 轻量级信任机制——不过度设计,但绝不调包

最终达到 统一的安全基线 + 灵活的差异化策略 的平衡。

关联笔记