JWT(JSON Web Token)——是一种紧凑的数据传输格式,以受数字签名保护的JSON对象形式在各方之间传输数据。令牌可以使用HMAC(对称密钥)或RSA/ECDSA(非对称对)进行签名,从而保证数据的完整性和真实性。根据IETF RFC 7519,2015,JWT被数百万应用程序用于身份验证、安全claims交换以及作为OpenID Connect中的ID Token格式。
要点
JSON Web Token(JWT)——是一种开放标准(RFC 7519),定义了以JSON对象形式在各方之间传输信息的紧凑且自包含的方式。JWT中的信息称为claims——关于主体(用户)和附加属性的声明。每个claim是一个键值对:用户标识符、角色、过期时间、颁发者。
JWT被称为自包含,因为所有用于验证的信息都包含在令牌本身内部。服务器无需访问数据库或外部存储来确认令牌的有效性——只需验证签名即可。这一特性使JWT成为分布式系统和微服务架构的理想选择,在这种架构中,多个服务必须在没有共享会话存储的情况下对请求进行身份验证。
根据Auth0,2025的数据,超过65%的移动和Web应用程序使用JWT作为API身份验证的主要令牌格式,超过了opaque令牌和会话标识符。
JWT由三个用点分隔的部分组成:header.payload.signature。每个部分都是一个Base64url编码的JSON。让我们详细了解每个部分。
Header包含两个必填字段:alg(algorithm——签名算法)和typ(type——令牌类型,始终为“JWT”)。算法可以是对称的(HS256——带SHA-256的HMAC)或非对称的(RS256——带SHA-256的RSA,ES256——带P-256的ECDSA)。非对称算法更受青睐,因为它们允许客户端在不拥有密钥的情况下验证签名。
解码后的header示例:
{
"alg": "RS256",
"typ": "JWT",
"kid": "key-id-1"
}
Payload包含claims——关于主体的声明。Claims分为三种类型:已注册的(iss、sub、aud、exp、nbf、iat、jti)、公共的(由开发者在IANA Registry中定义)和私有的(各方之间商定的)。sub(subject)——用户的唯一标识符。exp(expiration)——令牌过期的时间戳。iss(issuer)——令牌的颁发者。
{
"sub": "user-abc-123",
"iss": "https://auth.example.com",
"aud": "my-mobile-app",
"exp": 1812345678,
"iat": 1812342078,
"role": "premium_user"
}
Signature是通过使用密钥或私钥将签名算法应用于header和payload的连接来创建的。公式:HMACSHA256(base64UrlEncode(header) + “.” + base64UrlEncode(payload), secret)用于HMAC,或RSASHA256(...)用于非对称算法。接收者以相同方式计算签名并将其与接收到的签名进行比较——如果匹配,则数据未被修改。
工作过程JWT包括两个阶段:身份验证服务器创建(颁发)令牌以及客户端或资源服务器验证令牌。身份验证服务器接收用户的凭据,创建带有claims的payload并对其进行签名。生成的JWT作为登录请求的响应或在OAuth 2.0 / OpenID Connect响应的正文中发送给客户端。
在移动应用程序中,JWT的使用方式如下:成功登录后,用户收到JWT格式的access token。应用程序将其存储在安全的存储中(iOS上的Keychain,Android上的EncryptedSharedPreferences)。每次向API发出请求时,应用程序都会添加Authorization: Bearer <token>标头。API服务器验证JWT签名,提取claims并根据它们做出访问决策——无需访问数据库。
根据Google Codelabs,2025的数据,在Firebase Authentication中使用JWT可以将对身份验证服务器的请求数量减少40–60%,相比于会话令牌,因为数据在每个微服务上本地验证。这对于高负载架构尤其重要,其中每毫秒延迟都会影响用户体验。在每分钟50,000个请求的情况下,切换到JWT可以节省多达10个处理introspection请求的服务器实例。
JWT和Session Token解决相同的任务——请求的身份验证——但在架构上存在根本差异。Session Token是一个随机的标识符字符串,引用存储在服务器上的会话数据(stateful)。JWT——一个自包含令牌,包含所有数据(stateless)。
| 参数 | JWT | Session Token |
|---|---|---|
| 数据存储 | 令牌内部(自包含) | 服务器上(会话存储) |
| 扩展性 | 不需要共享存储 | 多服务器需要Redis/DB |
| 令牌撤销 | 复杂(需要黑名单) | 简单(从DB删除会话) |
| 大小 | 大(500–2000字节) | 小(16–64字节) |
| 签名验证 | 密码学验证 | 无(字符串比较) |
JWT在分布式系统中胜出:微服务可以在没有共享存储的情况下本地验证令牌。例如,在包含五个微服务的架构中,每个服务在1–2毫秒内验证JWT,无需网络调用,而session token每次请求都需要访问集中的Redis,增加10–30毫秒的延迟。然而,JWT难以撤销——如果令牌已经颁发,它在过期之前一直有效。Session Token可以通过从DB或Redis删除记录轻松撤销。
对于移动应用程序,组合方法——短生命周期的JWT(15–30分钟)和Refresh Token——在性能和安全性之间提供了平衡。JWT用于访问API,而refresh token(通常是opaque)用于获取新的JWT。在JWT被攻破的情况下,攻击者有15–30分钟的访问权限;在refresh token被攻破的情况下,会话通过轮换和重用检测被阻止。
安全JWT取决于正确的实现。最常见的漏洞是“alg none”攻击:攻击者将令牌的header改为“alg”: “none”,服务器在未检查算法的情况下接受伪造的令牌。保护措施:始终检查header中的算法是否与预期的匹配(RS256、ES256),并拒绝带有alg: none的令牌。
漏洞JWT还包括:HMAC的弱密钥(几分钟内破解)、私钥泄露(以服务器名义签署任意数据)、在payload中存储敏感数据(JWT不加密,只签名)、通过JWK header注入攻击(插入自己的公钥)。使用经过验证的库——Nimbus JOSE + JWT、jjwt (io.jsonwebtoken)、PyJWT——可降低利用这些漏洞的风险。
额外的保护措施——JWK Thumbprint(RFC 7638):通过header中的指纹将公钥绑定到令牌。如果服务器为每个客户端存储预期的thumbprint,JWK header注入攻击将变得不可能——服务器拒绝任何与已注册密钥不匹配的密钥。OAuth Security Workshop 2025推荐JWK Thumbprint作为在金融和医疗应用程序中使用的所有JWT的强制性保护。
jjwt库(auth0/java-jwt)允许在Android应用程序中几行代码创建和验证JWT。在下面的示例中,服务器生成带有sub和role的令牌,客户端验证签名。为了在服务器上安全存储密钥,请使用环境变量或HSM(硬件安全模块)——将密钥存储在代码或配置文件中是一个严重的安全错误。
val secret = "my-256-bit-secret-key-here"
val token = JWT.create()
.withSubject("user-abc-123")
.withIssuer("auth.example.com")
.withClaim("role", "premium_user")
.withExpiresAt(Date(System.currentTimeMillis() + 3600000))
.sign(Algorithm.HMAC256(secret))
// 向客户端发送令牌
println("JWT: $token")
fun verifyToken(token: String): Boolean {
return try {
val decoded = JWT.require(Algorithm.HMAC256(secret))
.withIssuer("auth.example.com")
.build()
.verify(token)
// 签名有效,claims已提取
println("Subject: ${decoded.subject}")
true
} catch (e: Exception) {
println("Token invalid: ${e.message}")
false
}
}
常见问题
不能。JWT是签名而非加密的——任何人都可以解码Base64 payload并读取数据。敏感信息(密码、卡号、个人数据)只能通过JWE(JSON Web Encryption)以加密形式传输。
推荐ES256(ECDSA搭配P-256)——它在签名大小显著更小的情况下提供等效的RSA 2048位安全级别。为了与遗留系统兼容,RS256适用。HS256(HMAC)需要安全地交换密钥,这在分布式架构中更困难。
JWT无法直接撤销——它在exp之前一直有效。解决方案:使用短生命周期(15–30分钟),在服务器上维护已撤销的jti(JWT ID)黑名单,或将令牌与密钥版本关联。Refresh token通过从存储中删除以标准方式撤销。
Bearer token是一个概念:持有者(bearer)可以用于访问的任何令牌。JWT是一种特定的令牌格式。Bearer token可以是JWT或opaque字符串。JWT为Bearer概念增加了自包含性和密码学验证。
带有RS256签名的典型JWT占用500–2000字节。如果payload包含许多自定义claims或使用带有大密钥的非对称签名,大小可能达到4–5 KB。这比session token(16–64字节)大得多,影响HTTP标头的大小。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。