vault backup: 2026-04-30 08:58:36
This commit is contained in:
@@ -133,6 +133,7 @@ graph LR
|
||||
|
||||
> [!question] 权衡题
|
||||
> 有人说:"鉴权放到网关统一做,避免每个服务重复写。"但如果某个内部服务不需要鉴权呢?或者不同团队要求不同的鉴权方式呢?你怎么设计才能兼顾统一性和灵活性?
|
||||
> 👉 [[02-服务治理/网关鉴权策略]] — 分层鉴权模型详解
|
||||
|
||||
## 服务间通信
|
||||
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
---
|
||||
tags: [microservice, api-gateway, auth, service-mesh, mTLS, jwt, rbac]
|
||||
create time: 2026-04-30 15:30
|
||||
---
|
||||
|
||||
# 网关鉴权分层设计
|
||||
|
||||
## 概述
|
||||
|
||||
探讨"鉴权是否应该全部放在网关统一处理"这个常见架构争议,给出兼顾统一性和灵活性的分层鉴权方案。
|
||||
|
||||
## 问题的本质
|
||||
|
||||
### 一刀切的陷阱
|
||||
|
||||
> [!question] 引出思考
|
||||
> 有人说:"鉴权放到网关统一做,避免每个服务重复写。"但如果某个内部服务不需要鉴权呢?或者不同团队要求不同的鉴权方式呢?你怎么设计才能兼顾统一性和灵活性?
|
||||
|
||||
核心矛盾在于:**网关做鉴权的优势是集中化和性能**,但代价是 **失去对差异化需求的表达能力**。现实中至少存在三种网关无法处理的场景:
|
||||
|
||||
| 场景 | 例子 | 为什么网关搞不定 |
|
||||
|------|------|----------------|
|
||||
| **内部调用免鉴权** | A服务直调B服务的内部查询接口 | 外部请求到不了这里,网关做无意义开销 |
|
||||
| **不同团队标准不同** | 财务系统要求 OAuth2+MFA,运营后台只需 API Key | 策略硬编码在网关里会变成巨石中间件 |
|
||||
| **精细化权限控制** | 订单服务需要判断"用户A是否有权看这笔订单" | 网关不知道业务数据归属关系 |
|
||||
|
||||
## 三层鉴权架构
|
||||
|
||||
```mermaid
|
||||
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 业务层** | 数据权限 — "你能操作这条数据吗?" | 细粒度(数据级) | 业务逻辑自身实现 |
|
||||
|
||||
**设计哲学**:越往上过滤越早失败,但信息越少;越往下信息越多但成本越高。每一层只做它最擅长的那件事。
|
||||
|
||||
## 内部服务调用的鉴权策略
|
||||
|
||||
对于内部服务互不调用网关的问题,答案是:**不是不需要鉴权,而是用更轻量级的鉴权**。
|
||||
|
||||
```go
|
||||
// 服务间调用:用短期签名 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)** + **可插拔鉴权中间件**。
|
||||
|
||||
```go
|
||||
// 鉴权处理器接口 — 每个团队实现自己的策略
|
||||
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% 时可考虑批量校验
|
||||
|
||||
## 总结
|
||||
|
||||
这套设计的核心理念是 **"默认严格、按需放宽"**:
|
||||
|
||||
- 网关做 **防御纵深的第一道**——挡住明显非法的请求,保护后方
|
||||
- 每个服务保持 **独立的鉴权决策能力**——因为最了解自身需求的只有服务本身
|
||||
- 服务间通信用 **轻量级信任机制**——不过度设计,但绝不调包
|
||||
|
||||
最终达到 **统一的安全基线 + 灵活的差异化策略** 的平衡。
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[hzh/MS/02-服务治理]] — 服务治理总览
|
||||
Reference in New Issue
Block a user