前言:什么是 API 网关?
类比: API 网关就像一栋办公楼的前台。
- 访客(客户端)只能进前台,不能直接进办公区
- 前台要核验访客身份(鉴权)
- 前台告诉访客去哪个部门(路由)
- 前台控制访客数量(限流)
- 某个部门人满了,前台告诉访客改天再来(熔断)
- 前台还可以限制访客每天能来几次(防爬虫)
没有前台 = 每个部门都要自己处理这些问题,重复又难管理
1. Spring Security OAuth 2.0
1.1 什么是 OAuth 2.0?
类比: OAuth 2.0 就像"代客泊车"。 你去餐厅吃饭,把车钥匙交给泊车员(授权服务器), 泊车员给你一张"取车凭证"(Token),凭这张凭证可以让泊车员把车开出来。 但泊车员不会拿你的钥匙去干别的,他只负责"取车"这一件事。
生活化例子:
你(用户)想用微信登录某个 App
1. App 把你跳转到微信(授权服务器)
2. 你输入微信密码,微信确认你的身份
3. 微信跳回 App 并附带"凭证"(Token)
4. App 拿 Token 找微信要你的昵称头像
5. 微信验 Token 有效,返回你的信息
1.2 架构概览

三者的角色:
| 角色 | 干什么的 | 类比 |
|---|---|---|
| 客户端 | 想要访问资源 | 顾客 |
| 授权服务器 | 验证身份,颁发 Token | 餐厅前台(认证身份) |
| 资源服务器 | 持有资源,验证 Token | 厨房(提供数据) |
1.3 授权模式(4 种取 Token 的方式)
类比: 不同场景下,取餐号的方式不同。
| 模式 | 适用场景 | 流程 | 安全性 |
|---|---|---|---|
| authorization_code | Web 应用 | 重定向 + 回调 | 最高 |
| password | 移动端/自家产品 | 客户端直接拿用户名密码换 Token | 中 |
| client_credentials | 服务间调用 | 只用 clientId/secret | 中 |
| refresh_token | Token 续期 | 用 refresh_token 换新的 | - |
举例说明:
场景 A:网站登录(authorization_code)
1. 浏览器访问 oauth/authorize?client_id=web&redirect=...
2. 跳转登录页,用户输入密码
3. 登录成功跳回网站:https://app.com/callback?code=ABC
4. 网站用 code 换 access_token
5. 拿 access_token 调用 API
场景 B:手机 App(password)
1. App 让用户输入用户名密码
2. App 直接调用:POST /oauth/token
{grant_type=password, username=zhangsan, password=123}
3. 拿到 access_token
4. 调 API
场景 C:内部服务调用(client_credentials)
1. 服务 A 想调服务 B
2. A 用自己的 clientId/secret 换 Token(没有用户参与)
3. 拿 Token 调 B
1.4 JWT 令牌(Token 的格式)
类比: JWT 就像一张"盖了章的工作证",信息写在卡上,不需要回总部查。
结构(3 段,用 . 分隔):
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyMSJ9.signature_here
│ 头部(Header) │ 载荷(Payload) │ 签名(Signature) │
描述算法 描述数据 验证没被篡改
Payload 内容(用户信息):
java
{
"sub": "user123", // 用户 ID
"name": "张三",
"role": "USER",
"exp": 1620000000, // 过期时间
"jti": "abc-123" // Token 唯一编号(用于吊销)
}
1.5 JWT 令牌管理
| 令牌 | 有效期 | 类比 | 为什么这么短 |
|---|---|---|---|
| Access Token | 1~2 小时 | 临时工作证 | 短 = 一旦泄露,影响小 |
| Refresh Token | 7~30 天 | 长期工作证 | 用于换新 Access Token |
为什么 Access Token 不设一年? 假如设一年有效,被黑客偷走你就完全没办法。 1 小时有效,丢了最多损失 1 小时。
令牌吊销机制(用户主动登出):
1. 用户点击"退出登录"
2. 服务端把 Token 的 jti 写入 Redis 黑名单
3. 设置 Redis 过期时间 = Token 剩余有效期
4. 后续所有带这个 Token 的请求都会被网关拦截
1.6 获取 Token 示例
bash
# 密码模式(最简单,适合测试)
curl -X POST http://localhost:8080/oauth/token \
-u "web-app:web-secret-123" \
-d "grant_type=password&username=user&password=user123"
# 响应:
# {
# "access_token": "eyJhbGci...",
# "refresh_token": "eyJhbGci...",
# "expires_in": 3600
# }
# 用 Token 访问受保护接口
curl -H "Authorization: Bearer eyJhbGci..." \
http://localhost:8080/api/user/profile
2. Spring Cloud Gateway
2.1 核心功能
再强调一遍: API 网关 = 办公楼前台

2.2 路由配置(访客分流)
类比: 路由就是告诉访客"你要找的部门在几楼"。 路径匹配就像门牌号:
/api/user/*走用户部门,/api/order/*走订单部门。
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service # lb:// 表示"按服务名找,做负载均衡"
predicates: # 条件(满足才走这个路由)
- Path=/api/user/** # 路径以 /api/user/ 开头
- Method=GET,POST # 只能是 GET 或 POST
filters: # 转发前/后做的事
- StripPrefix=1 # 去掉路径最前面的 1 段(去掉 /api)
- name: RequestRateLimiter # 限流(每秒最多 100 个请求)
args:
redis-rate-limiter.replenishRate: 100
redis-rate-limiter.burstCapacity: 200
Predicates 常用匹配(可选"办公位"):
| Predicate | 作用 | 类比 |
|---|---|---|
Path=/user/** |
路径匹配 | 楼栋+楼层 |
Method=GET |
HTTP 方法 | 进门方式(步行/开车) |
Header=X-Token, \d+ |
Header 匹配 | 凭证类型 |
Query=name,zh |
参数匹配 | 携带的物品 |
After=2024-01-01 |
时间匹配 | 营业时间 |
2.3 全局过滤器(统一安检)
类比: 办公楼大门的统一安检流程,每个访客都要走一遍。
过滤器链执行顺序:
1. 请求日志记录("访客张三来访 14:23:45")
2. 限流检查("访客太多,暂不接待")
3. 公开路径放行("来访客走快速通道")
4. Token 校验("请出示工作证")
5. 黑名单检查("对不起,您的证已吊销")
6. 传递用户信息("登记后去 5 楼技术部")
7. 响应日志("访客离开,耗时 15 分钟")
2.4 限流策略(4 种控制"访客量"的方式)
类比: 餐厅限制客人进店的 4 种方法。
| 算法 | 特点 | 类比 | 适用 |
|---|---|---|---|
| 固定窗口 | 简单,1 秒只放 N 个 | 每 1 分钟发 100 个号,发完等下分钟 | 低精度要求 |
| 滑动窗口 | 精确,按比例 | 把窗口分 10 段,每段 10 个号 | 中等精度 |
| 令牌桶 | 允许突发 | 水龙头流令牌,桶里积压的能用 | API 限流(推荐) |
| 漏桶 | 平滑 | 不管来多少,流出速度固定 | 严格限速 |
令牌桶通俗理解:
想象一个每秒滴出 10 个令牌的水龙头,访客要进店就拿走 1 个令牌。
- 平时:每秒进 10 个访客(稳定)
- 突发:桶里攒了 100 个令牌(长期没人来),可以一次性放进 100 人
- 桶空了:拒绝进入(限流)
2.5 熔断降级("今日已满,请明天再来")
类比: 餐厅的某个厨师生病了,餐厅立刻贴出告示"今日某菜不可点"。 既不接新单(熔断),又给客户一个备选方案(降级)。
filters:
- name: CircuitBreaker # 启用熔断
args:
name: orderCB # 熔断器名字
fallbackUri: forward:/fallback/order # 熔断后去哪
熔断器三种状态:

2.6 灰度路由(金丝雀发布)
类比: 新菜品上线,先给 5% 的客人尝尝,没问题再全量铺开。
# 20% 的流量走新版本,80% 走老版本
predicates:
- Path=/api/product/**
- Weight=gray, 20 # 权重 gray 的 20%
好处:
- 出问题只影响 20% 的用户
- 可以观察新版本的实际表现
- 不需要发布时"停机切换"
3. 实际工作流(端到端)
给你一个完整的请求生命周期,让你理解所有组件怎么配合:

整个流程清晰了: 认证在网关做,授权在业务层做,二者职责分明。