JWT 认证原理与流程详解

25 阅读7分钟

一、什么是 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 服务端验证逻辑(简化版)

认证服务收到请求后,执行以下步骤:

  1. Authorization: Basic xxx 中解析出 client_idclient_secret
  2. 查询本地配置或数据库,检查:
    • client_id 是否存在?
    • 提供的 client_secret 是否匹配?
  3. 若验证通过,生成并返回 JWT;否则返回 401 Unauthorized

🔐 安全提示:

  • client_secret 应通过安全方式分发(如环境变量、K8s Secret、Vault)
  • 不要将 secret 写入代码或明文配置文件
  • 所有通信必须走 HTTPS

5.4 优势与适用性

优点说明
简单无需 PKI、mTLS、复杂签名
标准符合 OAuth 2.0,工具链广泛支持
可控可随时禁用某个 client_id(如服务下线或泄露)
隔离不同服务使用不同凭证,权限可细分

✅ 适用于:内部微服务、定时任务、后台脚本等可信后端系统


这样补充后,你的技术资料就完整覆盖了 “如何防止 token 接口裸奔” 的关键安全问题,同时保持实现简单、易于落地。

其余章节编号请相应顺延即可。是否需要我帮你生成完整的修订版文档?

六、特殊场景:非浏览器客户端(如第三方系统)

✅**正确做法:**全部使用 Token(放在 Header 中)

在非浏览器客户端场景下,当调用方是后端服务、脚本或外部系统时:

Token 类型存放位置说明
Access TokenAuthorization: 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..." }

🛡️ 安全建议(针对外部系统)

  1. 使用 HTTPS 强制加密 所有 token 在传输中必须加密(否则会被中间人窃取)。
  2. Refresh Token 仍应可撤销 即使放在 Body 中,也要在服务端数据库存储其状态(是否过期、是否被吊销)。
  3. 限制客户端权限(Scope) 使用 OAuth2 的 scope 字段,确保外部系统只能访问所需资源。
  4. 使用 Client Credentials Flow(OAuth2) 适合 M2M 场景:用 client_id + client_secret 换取 Access Token,无需用户参与。

七、JWT 验证流程(服务端视角)

当收到一个 JWT,服务端按以下步骤验证:

  1. 格式检查:是否包含 exactly 两个 .
  2. 分割三段:Header、Payload、Signature。
  3. Base64Url 解码 Header 和 Payload。
  4. 验证签名:
    • 用 header 指定的算法 + 服务端密钥重新计算签名。
    • 与原始 Signature 比较,不一致则拒绝。
  5. 验证 Claims:
    • exp:是否过期?
    • nbf:是否尚未生效?
    • iss / aud:签发者和受众是否合法?
  6. (可选)额外检查:用户是否被禁用?IP 是否异常?
  7. 通过:从 sub 等字段提取用户身份,继续处理请求。

✅ 整个过程通常无需查数据库(除非做额外风控)。


八、最佳实践总结

项目建议
Access Token使用 JWT,短期有效,放 Authorization: Bearer
Refresh Token使用随机字符串,长期有效,服务端可撤销
浏览器场景Refresh Token 存 HttpOnly + Secure + SameSite Cookie
外部系统场景所有 token 通过 JSON/Header 传递,不用 Cookie
传输安全强制 HTTPS
算法选择优先 RS256(非对称);若单体应用可用 HS256(密钥严格保密)
错误处理明确区分 access_token_expiredinvalid_refresh_token

九、常见误区澄清

  • ❌ “JWT 是加密的” → 实际只是编码,内容可读。
  • ❌ “Refresh Token 也该是 JWT” → 会失去可撤销性,不推荐。
  • ❌ “HttpOnly Cookie 能防 CSRF” → 不能!需配合 SameSite 或 CSRF Token。
  • ❌ “所有客户端都适合用 Cookie” → 仅浏览器适用,外部系统应走 Token-in-Header。