一、什么是 JWT?
JWT(JSON Web Token) 是一种开放标准(RFC 7519),用于在各方之间以 JSON 对象的形式安全地传输信息。它广泛应用于身份认证(Authentication) 和 信息交换(Information Exchange)。
1.1 JWT 的结构
一个合法的 JWT 由三部分组成,用点 . 分隔:
Header.Payload.Signature
-
Header(头部):描述签名算法和 token 类型
{ "alg": "HS256", "typ": "JWT" } -
Payload(载荷):包含“声明”(Claims),如用户 ID、角色、过期时间等
{ "sub": "1001", "name": "Alice", "iat": 1700000000, "exp": 1700003600 } -
Signature(签名):用于验证 token 是否被篡改
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
⚠️ 注意:JWT 使用 Base64Url 编码,不是加密!不要在 Payload 中存放敏感信息(如密码)。
二、JWT 认证的基本流程
2.1 标准认证流程(浏览器或客户端)
sequenceDiagram
participant User as 用户
participant Client as 客户端
participant AuthServer as 认证服务器
participant API as 资源服务器
User->>Client: 输入账号密码
Client->>AuthServer: POST /login {username, password}
AuthServer-->>AuthServer: 验证凭证
AuthServer->>Client: 200 OK { access_token, [refresh_token] }
Client->>API: GET /profile<br/>Authorization: Bearer <access_token>
API->>API: 验证 JWT(签名 + 过期时间)
API-->>Client: 返回受保护资源
Refresh Token可选。
2.2 关键细节说明
-
Access Token:短期有效(如 15 分钟),通常为 JWT。
-
传输方式:通过 HTTP Header:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.x.x
- `Authorization` 是 Header 名(key)
- `Bearer <token>` 是值(value),其中 `Bearer` 是 OAuth 2.0 定义的认证方案。
------
## 三、JWT 的安全性原理
### 3.1 为什么需要密钥?——签名机制
JWT 的签名不是普通哈希,而是 **带密钥的消息认证码(MAC)**:
- **HS256(对称)**:使用共享密钥(secret)进行 HMAC-SHA256 签名。
- 只有知道密钥的服务端才能签发或验证 token。
- ❌ 密钥 ≠ “盐”:盐可公开,密钥必须严格保密。
- **RS256(非对称)**:服务端用私钥签名,其他服务用公钥验证。
- 更适合微服务架构,避免密钥分发问题。
> ✅ 签名作用:确保 token 未被篡改,且来源可信。
### 3.2 安全限制与风险
| 风险 | 应对措施 |
| -------------------- | ------------------------------------------------------------ |
| Token 泄露(如 XSS) | 使用 HTTPS;Access Token 短有效期 |
| 无法主动失效 | 引入 Refresh Token 机制;或维护 token 黑名单(牺牲无状态性) |
| 重放攻击 | 绑定设备/IP(可选);限制 token 作用域 |
------
## 四、Token 过期与刷新机制
### 4.1 为什么需要 Refresh Token?
- Access Token 短期有效 → 安全性高,但频繁登录体验差。
- Refresh Token 长期有效 → 用于静默获取新 Access Token。
### 4.2 推荐设计
| 属性 | Access Token | Refresh Token |
| -------------- | ----------------------- | ------------------------------------------------ |
| 格式 | JWT | **随机字符串(Opaque Token)** |
| 存储(服务端) | 无(无状态) | 数据库 / Redis(可撤销) |
| 有效期 | 5~30 分钟 | 数天至数周 |
| 传输方式 | `Authorization: Bearer` | **非浏览器:JSON Body;浏览器:HttpOnly Cookie** |
> ✅ **Refresh Token 不建议用 JWT**,因其需支持吊销(如用户登出、异常检测)。
### 4.3 刷新流程(外部系统调用)
```mermaid
sequenceDiagram
participant Client as 外部系统
participant AuthServer as 认证服务器
participant API as 资源服务器
Client->>API: GET /data<br/>Authorization: Bearer <expired_token>
API-->>Client: 401 Unauthorized
Client->>AuthServer: POST /auth/refresh<br/>{ "refresh_token": "abc123" }
AuthServer->>AuthServer: 验证 refresh_token(查库 + 检查状态)
alt 有效
AuthServer->>Client: 200 OK { "access_token": "new_jwt..." }
Client->>API: 重试请求 with new token
API-->>Client: 200 OK 数据
else 无效或过期
AuthServer-->>Client: 401 { error: "invalid_grant" }
Client->>Client: 重新走完整登录流程
end
💡 外部系统(如 HttpClient)不应使用 Cookie,而应通过 JSON 或 Header 传递所有 token。
非常好!你提供的技术资料已经非常完整,现在我们专门针对“内部系统作为客户端调用 token 接口”的场景,补充一个简洁、实用、不复杂的安全机制:使用 Client ID + Client Secret(客户端凭证)来保护 token 下发接口。
以下是新增的章节,建议插入在 第五节“特殊场景:非浏览器客户端”之后、第六节“JWT 验证流程”之前,作为 第五节(原第五节顺延为第六节,以此类推):
五、保护 Token 下发接口:确保只有受信任的内部客户端能获取 JWT
当你开放一个接口(如 /auth/token)用于下发 Access Token 时,必须防止该接口被任意程序调用。否则,攻击者可直接请求此接口,绕过前端逻辑,甚至暴力尝试用户凭证。
对于内部系统之间的调用(例如微服务 A 调用认证服务获取 JWT),推荐采用 Client ID + Client Secret(客户端凭证) 机制。这是一种简单、标准、安全的做法,无需复杂协议。
5.1 基本原理
- 每个可信的内部服务在认证服务器注册时,分配一对唯一凭证:
client_id:公开标识(如order-service)client_secret:高熵密钥(如aB3$xK9!qL2@mN8),严格保密
- 调用
/token接口时,客户端必须提供这对凭证 - 认证服务验证凭证合法后,才签发 JWT
✅ 这本质上是 OAuth 2.0 的 Client Credentials Flow,但实现可以极简。
5.2 调用方式(推荐 HTTP Basic Auth)
客户端通过 HTTP Basic Authentication 传递凭证(符合标准且简单):
POST /auth/token
Host: auth.yourcompany.com
Authorization: Basic base64(client_id:client_secret)
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
示例(curl):
curl -u "order-service:aB3$xK9!qL2@mN8" \
-d "grant_type=client_credentials" \
https://auth.yourcompany.com/auth/token
💡 为什么用 Basic Auth?
- 凭证不会出现在 URL 或请求体明文
- 所有 HTTP 客户端天然支持
- 避免自定义 Header 带来的兼容性问题
5.3 服务端验证逻辑(简化版)
认证服务收到请求后,执行以下步骤:
- 从
Authorization: Basic xxx中解析出client_id和client_secret - 查询本地配置或数据库,检查:
client_id是否存在?- 提供的
client_secret是否匹配?
- 若验证通过,生成并返回 JWT;否则返回
401 Unauthorized
🔐 安全提示:
client_secret应通过安全方式分发(如环境变量、K8s Secret、Vault)- 不要将 secret 写入代码或明文配置文件
- 所有通信必须走 HTTPS
5.4 优势与适用性
| 优点 | 说明 |
|---|---|
| 简单 | 无需 PKI、mTLS、复杂签名 |
| 标准 | 符合 OAuth 2.0,工具链广泛支持 |
| 可控 | 可随时禁用某个 client_id(如服务下线或泄露) |
| 隔离 | 不同服务使用不同凭证,权限可细分 |
✅ 适用于:内部微服务、定时任务、后台脚本等可信后端系统。
这样补充后,你的技术资料就完整覆盖了 “如何防止 token 接口裸奔” 的关键安全问题,同时保持实现简单、易于落地。
其余章节编号请相应顺延即可。是否需要我帮你生成完整的修订版文档?
六、特殊场景:非浏览器客户端(如第三方系统)
✅**正确做法:**全部使用 Token(放在 Header 中)
在非浏览器客户端场景下,当调用方是后端服务、脚本或外部系统时:
| Token 类型 | 存放位置 | 说明 |
|---|---|---|
| Access Token | Authorization: Bearer <JWT> | 必须,用于每次 API 调用 |
| Refresh Token | 也放在 Body 或 Header(不再是 Cookie!) | 因为对方是程序,可以安全存储并主动发送 |
示例流程(外部系统调用)
# 1. 获取 token(客户端 ID + Secret)
POST /oauth/token
Content-Type: application/json
{ "grant_type": "client_credentials", "client_id": "svc-a", "client_secret": "xxx" }
→
200 OK
{
"access_token": "eyJ...", // JWT
"refresh_token": "def456...", // 随机字符串 or JWT(根据设计)
"expires_in": 900
}
# 2. 调用 API
GET /api/data
Authorization: Bearer eyJ...
# 3. 刷新 token(当 access_token 过期)
POST /oauth/refresh
Content-Type: application/json
{ "refresh_token": "def456..." } ← 放在 body 中!
→
200 OK
{ "access_token": "new_jwt...", "refresh_token": "new_def..." }
🛡️ 安全建议(针对外部系统)
- 使用 HTTPS 强制加密 所有 token 在传输中必须加密(否则会被中间人窃取)。
- Refresh Token 仍应可撤销 即使放在 Body 中,也要在服务端数据库存储其状态(是否过期、是否被吊销)。
- 限制客户端权限(Scope)
使用 OAuth2 的
scope字段,确保外部系统只能访问所需资源。 - 使用 Client Credentials Flow(OAuth2)
适合 M2M 场景:用
client_id + client_secret换取 Access Token,无需用户参与。
七、JWT 验证流程(服务端视角)
当收到一个 JWT,服务端按以下步骤验证:
- 格式检查:是否包含 exactly 两个
.? - 分割三段:Header、Payload、Signature。
- Base64Url 解码 Header 和 Payload。
- 验证签名:
- 用 header 指定的算法 + 服务端密钥重新计算签名。
- 与原始 Signature 比较,不一致则拒绝。
- 验证 Claims:
exp:是否过期?nbf:是否尚未生效?iss/aud:签发者和受众是否合法?
- (可选)额外检查:用户是否被禁用?IP 是否异常?
- 通过:从
sub等字段提取用户身份,继续处理请求。
✅ 整个过程通常无需查数据库(除非做额外风控)。
八、最佳实践总结
| 项目 | 建议 |
|---|---|
| Access Token | 使用 JWT,短期有效,放 Authorization: Bearer |
| Refresh Token | 使用随机字符串,长期有效,服务端可撤销 |
| 浏览器场景 | Refresh Token 存 HttpOnly + Secure + SameSite Cookie |
| 外部系统场景 | 所有 token 通过 JSON/Header 传递,不用 Cookie |
| 传输安全 | 强制 HTTPS |
| 算法选择 | 优先 RS256(非对称);若单体应用可用 HS256(密钥严格保密) |
| 错误处理 | 明确区分 access_token_expired 与 invalid_refresh_token |
九、常见误区澄清
- ❌ “JWT 是加密的” → 实际只是编码,内容可读。
- ❌ “Refresh Token 也该是 JWT” → 会失去可撤销性,不推荐。
- ❌ “HttpOnly Cookie 能防 CSRF” → 不能!需配合
SameSite或 CSRF Token。 - ❌ “所有客户端都适合用 Cookie” → 仅浏览器适用,外部系统应走 Token-in-Header。