作为现代软件架构的骨干,API 实现了不同服务之间的无缝通信。虽然这种连接带来了巨大的价值,但同时也开辟了新的安全漏洞途径。保护这些关键接口不仅是一种建议,更是一种必然的要求。
在保护 API 的各种方法中,JSON Web Token (JWT) 已成为创建包含声明(claims)的访问令牌的一种流行且有效的标准。本指南为开发者提供了有关实现 JWT 以构建强大的 API 安全性的全面演练,特别是结合 API 网关的使用。
API 安全简介与 JWT 的需求
API 安全包含防止和缓解针对 API 攻击的策略和解决方案。由于 API 暴露了应用程序逻辑和敏感数据,因此它们是攻击者的主要目标。常见的威胁包括未经授权的访问、数据泄露、注入攻击以及 拒绝服务 (DoS) 攻击。
传统的基于会话的身份验证可能是有状态的,并且难以扩展,尤其是在微服务架构中。这正是 JWT 等无状态身份验证机制大放异彩的地方。JWT 提供了一种紧凑、自包含的方式,以 JSON 对象的形式在各方之间安全地传输信息。由于这些信息经过了数字签名,因此可以被验证和信任。
为什么选择 JWT?
- 无状态: 服务器不需要存储会话状态。令牌本身包含了验证所需的所有信息。
- 可扩展性: 能够轻松扩展以适应分布式系统和微服务。
- 解耦: 身份验证逻辑可以与应用服务解耦。
- 移动端友好: 非常适合保护移动端和单页应用访问的 API。
了解 JSON Web Token
在深入了解实现之前,了解 JWT 的基础知识至关重要。
什么是 JWT?
JWT 是一项开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间以 JSON 对象安全地传输信息。该信息经过数字签名(也可以加密),以确保其真实性和完整性。JWT 通常用于身份验证和信息交换。
JWT 的结构
JWT 由三部分组成,各部分之间用点号 (.) 分隔:
Header(头部): 通常由两部分组成:令牌类型(JWT)和所使用的签名算法,例如 HMAC SHA256 (HS256) 或 RSA。
1{ 2 "alg": "HS256", 3 "typ": "JWT" 4}Payload(负载): 包含声明。声明是关于实体(通常是用户)以及其他数据的陈述。
共有三种类型的声明:
* 注册声明: 这些是一组预定义的声明,它们不是强制性的,但建议使用,例如 iss(发行者)、exp(过期时间)、sub(主题)、aud(受众)。
* 公共声明: 这些可以由使用 JWT 的人员随意定义。但为了避免冲突,它们应该在 IANA JSON Web Token 注册表中定义,或者是包含防冲突命名空间的 URI。
* 私有声明: 这些是为在同意使用它们的各方之间共享信息而创建的自定义声明,既不是注册声明也不是公共声明。
1```json
2{
3 "sub": "1234567890",
4 "name": "John Doe",
5 "admin": true,
6 "iat": 1516239022,
7 "exp": 1516242622
8}
9```
Signature(签名): 要创建签名部分,你必须获取编码后的 Header、编码后的 Payload、一个密钥(secret)以及 Header 中指定的算法,并对其进行签名。例如,如果想要使用 HMAC SHA256 算法,签名的创建方式如下:
1HMACSHA256( 2 base64UrlEncode(header) + "." + 3 base64UrlEncode(payload), 4 secret)
签名用于验证 JWT 的发送者身份,并确保消息在传输过程中未被篡改。
JWT 的工作原理
使用 JWT 进行身份验证的过程通常遵循以下步骤:
- 用户登录(User Login): 用户或客户端应用向身份验证服务器发送凭据(例如,用户名/密码)。
- 令牌生成(Token Generation): 如果凭据有效,身份验证服务器将生成一个 JWT,使用密钥(对于 HS256 等对称算法)或私钥(对于 RS256 等非对称算法)对其进行签名,并将其发送回客户端。
- 令牌存储(Token Storage): 客户端安全地存储 JWT(例如,在 HTTPOnly Cookie、本地存储或内存中)。
- API 请求(API Request): 对于后续向受保护 API 发出的请求,客户端会在
Authorization请求头中使用 Bearer 模式包含 JWT:Authorization: Bearer <token>。 - 令牌验证(Token Validation): API 服务器(或 API 网关)接收请求、提取 JWT 并进行验证。验证通常包括:
- 使用已知密钥或公钥验证签名。
- 检查标准声明,如过期时间 (
exp)、发行者 (iss) 和受众 (aud)。
- 授予/拒绝访问(Access Granted/Denied): 如果令牌有效,服务器将处理该请求。否则,它会返回身份验证错误(例如,401 Unauthorized)。
1sequenceDiagram
2 participant Client
3 participant AuthServer as Authentication Server
4 participant APIGateway as API Gateway / Resource Server
5
6 Client->>AuthServer: Login Request (username, password)
7 AuthServer->>AuthServer: Validate Credentials
8 alt Credentials Valid
9 AuthServer->>AuthServer: Generate JWT (Header, Payload, Sign)
10 AuthServer-->>Client: Return JWT
11 else Credentials Invalid
12 AuthServer-->>Client: Return Error (e.g., 401 Unauthorized)
13 end
14
15 Client->>Client: Store JWT Securely
16
17 Client->>APIGateway: API Request + Authorization Header (Bearer JWT)
18 APIGateway->>APIGateway: Extract JWT
19 APIGateway->>APIGateway: Validate JWT Signature
20 APIGateway->>APIGateway: Validate JWT Claims (exp, iss, aud)
21 alt JWT Valid
22 APIGateway->>APIGateway: Process Request / Forward to Backend
23 APIGateway-->>Client: API Response
24 else JWT Invalid
25 APIGateway-->>Client: Return Error (e.g., 401 Unauthorized / 403 Forbidden)
26 end使用 JWT 的优势
- 无状态和可扩展: 由于服务器不需要维护会话信息,JWT 非常适合分布式系统和微服务架构。每个令牌都是自包含的。
- 性能: 减少了在每次请求时进行数据库查询以验证会话的需求,因为令牌本身就包含了经过验证的信息。
- 安全性: 如果实现得当(例如,使用强算法、HTTPS 和适当的密钥管理),JWT 可提供强大的安全性。签名确保数据完整性,如果需要机密性,还可以对令牌进行加密。
- 解耦: 身份验证可以由专属服务处理,从而将其与资源服务器解耦。
- 多功能性: 可以在不同域中用于各种客户端类型(Web、移动端、物联网)。
API 网关在基于 JWT 的安全中的作用
API 网关充当所有客户端请求访问后端服务的单一入口点。在基于 JWT 的安全环境中,API 网关扮演着关键角色:
- 集中身份验证与授权: 你可以无需在每个微服务中实现 JWT 验证逻辑,而是将其集中在 API 网关上。网关拦截传入请求、验证 JWT,并仅将有效请求转发到适当的后端服务。
- 卸载安全顾虑: 后端服务可以专注于其核心业务逻辑,因为网关处理了身份验证、限流和 SSL 终止等横切关注点。
- 增强的安全态势: 网关可以在所有 API 中一致地实施安全策略,例如检查令牌作用域以进行细粒度的访问控制。
- 简化的客户端交互: 客户端与单一、定义明确的网关端点交互,抽象了底层微服务架构的复杂性。
- 请求丰富(Request Enrichment): 验证 JWT 后,网关可以从令牌的 Payload 中提取用户信息(例如用户 ID、角色),并将其作为请求头传递给上游服务,从而消除这些服务再次解析 JWT 的需要。
使用 API 网关可以显著简化基于 JWT 的安全性的实现和管理,为令牌验证、策略执行和流量管理提供强大的功能。
实现 API 安全的 JWT 的分步指南
实现 JWT 涉及几个关键步骤,从设置身份验证服务器到在 API 端点验证令牌。
步骤 1:设置身份验证服务器
身份验证服务器负责验证用户凭据并颁发 JWT。
- 选择 JWT 库: 为你的后端编程语言/框架选择一个经过充分审查的 JWT 库(例如 Node.js 的
jsonwebtoken,Python 的PyJWT,Java 中 Spring Security 的 JWT 支持)。 - 登录逻辑: 实现一个接受用户凭据的端点(例如
/login或/token)。 - 凭据验证: 针对你的用户存储(例如数据库)验证收到的凭据。
- 密钥(Secret/Key)管理:
- 对于对称算法(例如 HS256),生成一个强随机密钥(Secret)。安全地存储此密钥(例如,在环境变量或机密管理系统中)。切勿在应用代码中硬编码密钥。
- 对于非对称算法(例如 RS256),生成一个公钥/私钥对。身份验证服务器使用私钥对令牌进行签名,资源服务器使用公钥对其进行验证。
步骤 2:创建和签名 JWT
成功身份验证后,服务器将创建并签名一个 JWT。
定义 Payload(负载): 构建包含必要声明的 JSON 负载。
- 标准声明(Standard Claims):
iss(Issuer,发行者):颁发 JWT 的主体。sub(Subject,主题):作为 JWT 主体的主体(例如,用户 ID)。aud(Audience,受众):JWT 的预期接收者(例如,你的 API 的基础 URL)。exp(Expiration Time,过期时间):JWT 过期的时间(时间戳)。对安全性至关重要。iat(Issued At,签发时间):颁发 JWT 的时间(时间戳)。jti(JWT ID):JWT 的唯一标识符,可用于防止重放攻击。
- 自定义声明(Custom Claims): 包含任何特定于应用程序的数据,例如用户角色、权限或其他非敏感用户信息。
1// 在 Node.js 中使用 jsonwebtoken 库的 payload 示例 2const payload = { 3 iss: '[https://auth.yourapi.com](https://auth.yourapi.com)', 4 sub: userId, 5 aud: '[https://api.yourapi.com](https://api.yourapi.com)', 6 exp: Math.floor(Date.now() / 1000) + (60 * 60), // 1 小时后过期 7 roles: ['user', 'editor'] 8};- 标准声明(Standard Claims):
签名令牌: 使用你选择的库和密钥/私钥对令牌进行签名。
1// Node.js 中的签名示例 2const token = jwt.sign(payload, process.env.JWT_SECRET, { algorithm: 'HS256' });返回令牌: 将生成的 JWT 发送回客户端,通常是在登录请求的响应体中。
步骤 3:安全传输 JWT
客户端必须在每次请求受保护的 API 端点时发送 JWT。
- Authorization Header: 标准做法是使用带有
Bearer模式的Authorization请求头:Authorization: Bearer <your_jwt_token> - HTTPS: 始终通过 HTTPS 传输 JWT,以防止可能拦截令牌的中间人攻击。
步骤 4:在 API 网关/后端验证 JWT
你的 API 网关或各个后端服务必须验证传入的 JWT。
提取令牌: 从
Authorization请求头中检索令牌。验证签名: 使用相同的算法和密钥(对于 HS256)或公钥(对于 RS256)来验证令牌的签名。如果验证失败,则表明令牌无效或已被篡改。
验证声明:
- 检查
exp声明以确保令牌未过期。 - 验证
iss和aud声明,以确保令牌由受信任的颁发机构颁发,并适用于你的 API。 - 执行任何其他必要的声明验证。
1// Node.js 中的验证示例 2try { 3 const decoded = jwt.verify(token, process.env.JWT_SECRET, { algorithms: ['HS256'], audience: '[https://api.yourapi.com](https://api.yourapi.com)', issuer: '[https://auth.yourapi.com](https://auth.yourapi.com)' }); 4 // 令牌有效,解码后的 payload 在 'decoded' 中可用 5 req.user = decoded; // 将用户信息附加到请求对象 6} catch (err) { 7 // 令牌无效(例如,已过期,签名不匹配) 8 return res.status(401).send('Invalid Token'); 9}- 检查
步骤 5:实现授权逻辑
验证令牌(身份验证)后,你可以使用其 Payload 中的信息(例如,用户 ID、角色)来做出授权决策。
基于角色的访问控制 (RBAC): 检查令牌中的
roles声明是否允许用户访问请求的资源或执行请求的操作。1// Express.js 中间件中的 RBAC 检查示例 2function authorizeAdmin(req, res, next) { 3 if (req.user && req.user.roles && req.user.roles.includes('admin')) { 4 next(); // 用户是管理员,继续 5 } else { 6 res.status(403).send('Forbidden: Admin access required'); 7 } 8}
安全实现 JWT 的最佳实践
虽然 JWT 功能强大,但必须正确实现才能保证安全。
- 使用强算法和密钥:
- 避免使用
alg: none。这本质上意味着没有签名,你的库应将其禁用或显式检查它。 - 使用强签名算法,如 HS256(使用强密钥),或最好使用 RS256/ES256(具有适当的密钥长度)。
- 密钥管理至关重要: 安全地存储你的密钥和私钥。使用环境变量、专用的机密管理工具(例如 HashiCorp Vault、AWS Secrets Manager)或 HSM。定期轮换密钥。
- 避免使用
- 始终使用 HTTPS: 仅通过 HTTPS 传输 JWT 以防止拦截。
- 保持 Payload 精简: 不要将敏感信息直接存储在 JWT Payload 中,除非它经过加密(JWE)。JWT 通常只进行签名而不加密,这意味着 Payload 采用 Base64Url 编码,易于读取。
- 设置令牌过期时间 (
exp):- 访问令牌(Access tokens)应具有较短的生命周期(例如 15 分钟到 1 小时)。如果令牌遭到泄露,这会限制被利用的窗口期。
- 实现刷新令牌(Refresh token)机制以获取新的访问令牌,而无需用户频繁重新进行身份验证。刷新令牌应具有较长的生命周期、安全存储(例如 HTTPOnly Cookie)并具有严格的轮换策略。
- 验证所有相关的声明: 始终在服务器端验证
exp、iss和aud声明。 - 令牌吊销: 无状态的 JWT 很难立即吊销。如果立即吊销至关重要:
- 短暂过期: 依靠较短的过期时间。
- 黑名单(Blocklisting): 维护已吊销的
jti(JWT ID)或sub(主题)声明的列表。这会引入一些状态,但在关键安全事件中可能是必要的。API 网关可以在验证令牌之前检查此黑名单。
- 防止跨站脚本攻击 (XSS): 如果在浏览器本地存储中存储 JWT,请注意 XSS 风险。如果攻击者可以注入脚本,他们就可以窃取令牌。考虑使用 HTTPOnly Cookie 来存储令牌以减轻这种情况,但要注意 CSRF(跨站请求伪造)。
- 防止跨站请求伪造 (CSRF): 如果使用 Cookie 存储 JWT,请实现 CSRF 保护机制(例如,CSRF 令牌、SameSite Cookie 属性)。
- 彻底审查库: 使用维护良好且信誉可靠的 JWT 库。保持更新以修补漏洞。
常见的 JWT 陷阱及如何避免
- 弱密钥或
alg: none:- 陷阱: 为 HS256 使用较弱的、容易猜测的密钥,或者意外允许
alg: none头。 - 避免: 生成强随机密钥。在验证期间显式指定允许的算法并拒绝
none。
- 陷阱: 为 HS256 使用较弱的、容易猜测的密钥,或者意外允许
- Payload 中的敏感数据:
- 陷阱: 将密码、信用卡号或其他高度敏感的数据直接存储在 JWT Payload 中。
- 避免: 默认情况下,JWT Payload 仅进行 Base64Url 编码,未加密。仅存储必要的、非敏感的标识符或角色。如果敏感数据必须在令牌中,请使用 JWE(JSON Web Encryption)。
- 无令牌过期时间或过期时间过长:
- 陷阱: 创建永不过期或具有过长生命周期的令牌。
- 避免: 始终设置合理的
exp声明。使用刷新令牌以获得更好的用户体验,同时保持访问令牌寿命较短。
- 忽略签名验证或声明验证:
- 陷阱: 信任 Payload 而不验证签名或检查
exp、iss或aud等声明。 - 避免: 对于每个请求,在服务器端严格验证签名和所有关键声明。
- 陷阱: 信任 Payload 而不验证签名或检查
- 客户端上不安全的令牌存储:
- 陷阱: 如果 XSS 风险很高,将 JWT 存储在容易访问的位置,如本地存储。
- 避免: 了解权衡。HTTPOnly Cookie 通常能更安全地防范 XSS,但需要 CSRF 保护。安全存储取决于客户端类型和安全要求。
- 令牌泄漏:
- 陷阱: 令牌通过 URL 参数、浏览器历史记录或不安全的日志记录泄露。
- 避免: 仅在
Authorization请求头或安全 Cookie 中传输令牌。对日志记录保持谨慎。
结语
JSON Web Token 提供了一种强大、灵活且可扩展的 API 安全保护方法。其无状态特性使其特别适合现代分布式架构,包括微服务和无服务器应用。然而,JWT 的安全性在很大程度上依赖于正确的实现。
将 API 网关整合到你的架构中,通过集中验证逻辑、从后端服务卸载安全顾虑以及实现一致的策略执行,进一步增强了基于 JWT 的安全性。随着 API 继续成为应用程序开发的基石,掌握 JWT 实现是构建安全可靠系统的基本技能。
