Skip to content

JSON Web Token Cheat Sheet / JSON Web 令牌速查表

Introduction / 介绍

This cheat sheet provides tips to prevent common security issues when using JSON Web Tokens (JWT).

本速查表提供了在使用 JSON Web 令牌(JWT)时防止常见安全问题的技巧。

JSON Web Tokens) (JSON Web 令牌) (JWT) are security tokens for carrying information (claims), often about a user, an application, etc. (subject). JWTs can provide authenticity of the claims (signed JWT) and/or confidentiality of the claims (encrypted JWT). In addition, JWT defines standard claims.

JWTs are used in a wide range of applications such as:

JWT 在广泛的应用程序中使用,例如:

  • In OpenID Connect, the ID token is a JWT used to represent the identity and attributes of the connected user.
    中文:在 OpenID Connect 中,ID令牌 是一个JWT,用于表示连接用户的身份和属性。
  • In OAuth 2, the access token used to obtain access to a protected resource can be a JWT.
    中文:在 OAuth 2 中,用于获取受保护资源访问权限的访问令牌可以是JWT。
  • A JWT is often suggested for “stateless” user sessions. However, this usage is frowned upon.
    中文:JWT常被建议用于“无状态”用户会话。然而,这种用法并不被推荐。
  • A DPoP Proof JWT can be used to prove possession of a private key.
    中文:可以使用DPoP证明JWT来证明私钥的拥有。
  • In SPIFFE, a JWT-SVID can be used to authenticate a workload.
    中文:在 SPIFFE 中,可以使用 JWT-SVID 来验证工作负载。

In its most common form (signed JWT), this information is protected by the generating application (issuer) using a signature to ensure it has not been tampered with. This signature prevents attackers, such as a malicious client or user, from forging a token or modifying the claims in an existing token, for example changing the user role from a simple user to an admin or altering the client's login. The JWT can be seen as a protected identity card or certificate about a user, an application, etc. An application (presenter) presents the token to a consuming application (audience) which can verify the token's authenticity and validity and take decisions or actions based on these claims.

在其最常见的形式(已签名的 JWT)中,这些信息由生成应用程序(发行者)使用签名进行保护,以确保其未被篡改。此签名阻止攻击者,如恶意客户端或用户,伪造令牌或修改现有令牌中的声明,例如将用户角色从普通用户更改为管理员或更改客户端的登录。JWT 可以被视为关于用户、应用程序等的受保护的身份卡或证书。一个应用程序(展示者)将令牌展示给消费应用程序(受众),该应用程序可以验证令牌的真实性和有效性,并根据这些声明做出决策或采取行动。

JWT can also provide confidentiality of the claims (encrypted JWT). Encryption itself is only introduced briefly in Token Confidentiality and JWE, but many aspects of this cheat sheet also apply to encrypted JWTs.

JWT 还可以提供声明的机密性(加密 JWT)。加密本身只在 令牌机密性和 JWE 中简要介绍,但本速查表的许多方面也适用于加密 JWT。

Token Structure / 令牌结构

Signed JWTs have the following structure:

已签名的 JWT 具有以下结构:

{base64url(json(header))}.{base64url(json(claims))}.{base64url(signature)}

The following elements are present in signed JWTs:

以下元素存在于已签名的 JWT 中:

  • Protected Header: the JWT header contains some information about the token such as the type of token (IANA media type) and the cryptographic algorithms used to protect the token.
    中文:受保护的头信息:JWT 头部包含有关令牌的一些信息,例如令牌类型(IANA 媒体类型)以及用于保护令牌的加密算法。
  • Claims: the content JWT is a list of claims (usually about the subject). See the JWT IANA Registry for a list of standard claims.
    中文:声明: JWT 中的内容是一个声明列表(通常关于主题)。请参阅 JWT IANA 注册表 以获取标准声明的列表。
  • Signature: a signature in JWT is either a public-key digital signature (using a public/private key pair) or a MAC (using a shared secret). The signature protects both the protected headers and the claims.
    中文:签名: JWT 中的签名可以是公钥数字签名(使用公钥/私钥对)或 MAC(使用共享密钥)。签名保护受保护的头和声明。

Example / 示例

For example, the following example (taken from JWT.IO):

例如,以下示例(来自 JWT.IO):

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30

The first part (protected header) can be decoded into:

第一部分(受保护的头信息)可以解码为:

{
  "alg": "HS256",
  "typ": "JWT"
}

The second part (claims) can be decoded into:

第二部分(声明)可以解码为:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true,
  "iat": 1516239022
}

The last part (signature) guarantees the authenticity of both the header and the claims, either using a public/private key pair (digital signature) or a shared secret (MAC), depending on the alg header value. For our example, it is computed as:

最后一部分(签名)保证了头部和声明的真实性,根据 alg 头信息值,可以使用公私钥对(数字签名)或共享密钥(MAC),对于我们的示例,它是这样计算的:

base64url(
    HMACSHA256(
        "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
        + "."
        + "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0",
        key
    )
)

Considerations about using JWTs / 关于使用 JWT 的注意事项

Not using JWTs / 不使用JWTs

Before using JWTs to solve your problems, you should consider if they are really necessary for your use case.

在用JWTs解决问题之前,您应该考虑它们是否真的适用于您的用例。

JWTs are often suggested for “stateless” user sessions. However, if you use JWTs for user sessions, you will need a solution for managing session invalidation. This can be achieved using a deny list of revoked sessions/tokens. If your application implements such a deny list, user sessions won't be completely stateless anymore which might defeat the benefits of stateless sessions. You might want to consider using a plain session system and follow the advices from the dedicated session management cheat sheet.

JWTs通常建议用于“无状态”用户会话。然而,如果您使用JWTs进行用户会话,您将需要一个解决方案来管理会话失效。这可以通过使用已撤销会话/令牌的拒绝列表来实现。如果您的应用程序实现了这样的拒绝列表,用户会话将不再是完全无状态的,这可能会抵消无状态会话的优势。您可能需要考虑使用纯会话系统,并遵循专门的会话管理作弊表中的建议。

Public-key Signatures vs. MAC / 公钥签名与MAC比较

A signed JWT can be authenticated using either a digital signature or a MAC:

已签名的JWT可以使用数字签名或MAC进行认证:

  • When using a digital signature, the issuer of the token uses its private key to generate a signature. The audience of the token can use the associated public key to verify the authenticity of the token. Whereas the private key must only be known by the issuer, the public key can be public.
    中文:当使用数字签名时,令牌的发行者使用其私钥生成签名。令牌的受众可以使用相关的公钥来验证令牌的真实性。而私钥必须只由发行者知道,公钥可以公开。
  • When using a MAC, a shared secret is shared between the issuer and the audience. The same shared secret is used by the issuer to generate the token and by the audience to verify the authenticity of the token.
    中文:当使用MAC时,发行者和受众之间共享一个共享密钥。发行者使用相同的共享密钥生成令牌,受众使用相同的共享密钥来验证令牌的真实性。

The two approaches differ on how credentials are managed.

这两种方法在凭证管理方式上有所不同。

When using a digital signature:

在使用数字签名时:

  • The issuer can reuse the same public key for many different audiences.
    中文:签发者可以为许多不同的受众重复使用相同的公钥。
  • The audience of the token only need public information to validate the token authenticity which removes the risk of secret leakage by the audience.
    中文:令牌的受众只需要公开信息来验证令牌的真实性,这消除了受众泄露秘密的风险。
  • Because the public key does not need to be secret, it can easily be distributed (eg. by publishing it at a public HTTPS URI).
    中文:由于公钥不需要保密,它可以很容易地分发(例如,通过在公开的HTTPS URI上发布)。
  • This makes key rotation simpler as well.
    中文:这使得密钥轮换更加简单。
  • Traditional digital signature schemes might be broken by post quantum computers in the future. They would need to be replaced with post-quantum digital signature schemes which heavier are traditional signature schemes.
    中文:传统的数字签名方案可能会在未来被后量子计算机 破坏。它们需要被后量子数字签名方案所取代,这些方案 更重于传统的签名方案。

When using a MAC:

当使用MAC时:

  • A different secret must be used for each (issuer, audience) pair. If, for example, the same secret is reused for difference audiences, one audience can forge a token (impersonating the issuer).
    中文:每个(签发者,受众)对必须使用不同的秘密。例如,如果为不同的受众重复使用相同的秘密,一个受众可以伪造令牌(冒充签发者)。
  • Secret keys must obviously not be published at a public HTTPS URI. Some solution for secret distribution and rotation must be found.
    中文:显然,秘密密钥不能发布在公开的HTTPS URI上。必须找到一些秘密分发和轮换的解决方案。
  • MAC are much faster than digital signatures (but this is usually negligible in practice).
    中文:MAC比数字签名快得多(但在实际应用中通常可以忽略不计)

Using a MAC may be interesting in the following cases:

以下情况下使用MAC可能很有趣:

  • The issuer of the token is the sole audience of the token. Even in this case, it might be easier to use digital signature for secret rotation/distribution.
    中文:令牌的发行者是令牌的唯一受众。即使在这种情况下,使用数字签名进行密钥轮换/分发可能更容易。
  • The issuer of the token is the audience. In this case, there is no problem of secret rotation/distribution.
    中文:令牌的发行者是受众。在这种情况下,不存在密钥轮换/分发的问题。

Public-key Signatures / 公钥签名

Signature scheme Identifier Type Status
EdDSA EdDSA, Ed448, Ed25519 Traditional Recommended, limited support
ECDSA ES256, ES384, ES512 Traditional Recommended
RSASSA-PSS PS256, PS384, PS512 Traditional Recommended
RSASSA-PKCS1-v1_5 RS256, RS384, RS512 Traditional Not recommended
Hybrid ML-DSA / EdDSA ML-DSA-44-Ed25519, etc. PQ/T hybrid Draft
Hybrid ML-DSA / ECDSA ML-DSA-44-ES256, etc. PQ/T hybrid Draft
ML-DSA ML-DSA-44, ML-DSA-65, ML-DSA-87 Post-quantum Very limited support at best

| 签名方案 | 标识符 | 类型 | 状态 |-----------------------|---------------------------------|--------------|--------- | EdDSA | EdDSA, Ed448, Ed25519 | 传统 | 推荐,支持有限 | ECDSA | ES256, ES384, ES512 | 传统 | 推荐 | RSASSA-PSS | PS256, PS384, PS512 | 传统 | 推荐 | RSASSA-PKCS1-v1_5 | RS256, RS384, RS512 | 传统 | 不推荐 | 混合ML-DSA / EdDSA | ML-DSA-44-Ed25519, 等. | PQ/T混合 | 草案 | 混合ML-DSA / ECDSA | ML-DSA-44-ES256, 等. | PQ/T混合 | 草案 | ML-DSA | ML-DSA-44, ML-DSA-65, ML-DSA-87 | 后量子 | 至多非常有限的支持

Explanations:

说明:

  • Support for EdDSA in JWT implementations is currently limited.
    中文:JWT实现中EdDSA的支持目前有限。
  • Generating ECDSA signatures may be dangerous on embedded systems where the quality of the randomness may be problematic. In this case, you the implementation must use deterministic ECDA as defined in RFC 6979.
    中文:在嵌入式系统中生成 ECDSA 签名可能很危险,因为随机性的质量可能有问题。在这种情况下,实现必须使用 RFC 6979 中定义的确定性 ECDA。
  • Post-quantum signatures (ML-DSA) or hybrid post quantum signatures are designed to be resistant against quantum computers. However, they produce very large signatures, resulting in very large JWTs. Their usage is probably not justified at the moment unless you need signatures with a long validity.
    中文:后量子签名 (ML-DSA) 或 混合后量子签名 被设计成对量子计算机具有抵抗力。然而,它们产生非常大的签名,导致 JWT 非常大。除非您需要具有长期有效性的签名,否则目前它们的用途可能是不合理的。

Key management:

密钥管理:

  • Do not reuse the key pair for another purpose (eg. for encryption).
    中文:不要将密钥对用于其他目的(例如加密)。
  • Using the same key for authenticating different types of JWTs is fine as long as this does not introduce a risk of token type confusion.
    中文:使用相同的密钥来验证不同类型的JWT是可行的,只要这不会引入令牌类型混淆的风险。
  • Do not publish your private key!
    中文:不要发布你的私钥!

MAC

Signature scheme Identifier Status
HMAC with SHA-2 HS256, HS384, HS512 Recommended

| 签名方案 | 标识符 | 状态 |-----------------------|---------------------------------|--------------- | HMAC with SHA-2 | HS256, HS384, HS512 | 推荐

Secret management:

秘密管理:

  • Do not reuse the same secret for another purpose (eg. for encryption).
    中文:不要将相同的秘密用于其他目的(例如加密)。
  • Using the same key for authenticating different types of JWTs is fine as long as this does not introduce a risk of token type confusion.
    中文:使用相同的密钥来验证不同类型的JWT是可行的,只要这不会引入令牌类型混淆的风险。
  • Do not reuse the same secret with another audience.
    中文:不要将相同的秘密用于其他受众。
  • Do not reuse the same secret with another issuer.
    中文:不要将相同的秘密用于其他发行者。
  • Do not use a password as MAC secret.
    中文:不要使用密码作为MAC密钥。
  • The secret must be generated using a local, cryptographically secure secret generator.
    中文:密钥必须使用本地加密安全的密钥生成器生成。
  • The secret must have at least the same size as the output (eg. 256, 384 and 512 bits respectively for HS256, HS384 and HS512).
    中文:密钥的大小必须至少与输出相同(例如,HS256、HS384和HS512分别对应256位、384位和512位)。
  • Do not publish your secret key!
    中文:不要发布您的密钥!
  • The secret must have at least 160 bits of entropy.
    中文:密钥必须至少有160位的熵。
  • For HMAC, the secret should be at least as long as the output size.
    中文:对于HMAC,密钥至少应与输出大小相同。

Valid HMAC secret generation example:

有效的HMAC密钥生成示例:

import secrets
secret_for_hs256 = secrets.token_bytes(256//8)
secret_for_hs512 = secrets.token_bytes(512//8)

Invalid HMAC secret generation:

无效的HMAC密钥生成示例:

import random
import secrets

# Using a password/passphrase is not OK:
bad_secret = b"MyProject2026"

# Using a hardcoded secret is not OK:
bad_secret = urlsafe_b64decode(b'KYkbbclxtjJMiHzoPvuahOfarej0VV-nQZPFxK0hyro=')

# Not a secure randomness source:
bad_secret = random.randbytes(256//8)

# Not enough entropy:
bad_secret = secrets.token_bytes(128//8)

# Not enough entropy for HS512
meh_secret_for_hs512 = secrets.token_bytes(256//8)

Header fields / 头部字段

The following table lists some important JWT (JOSE) header parameters for security purpose.

Parameter Semantic Security impact
alg Signature algorithm Signature algorithm used, risk of key type confusion
typ Media type Protection against token type confusion
jku Verification key (URL to the JWK) Risk of untrusted key usage, risk or SSRF
x5u Verification key (URL to certificate) Risk of untrusted key usage, risk or SSRF
jwk Verification key (JWK) Risk of untrusted key usage
kid Verification key (key ID) Risk of untrusted key usage
x5c Verification key (certificate chain) Risk of untrusted key usage
x5t Verification key (certificate hash) Risk of untrusted key usage
x5t#S256 Verification key (certificate hash) Risk of untrusted key usage

See the header parameters subregistry for a list of standard JWT (JOSE) header parameters.

Claims

The following table lists some important JWT claims for security purpose.

Parameter Semantic Security impact
exp Expiration Token validity
nbf Not valid before Token validity
status Reference to token status list Token revocation, risk of SSRF
iss Issuer Scoping of claims (eg. iss), risk of untrusted issuer, risk of SSRF
aud Audience Protection against audience confusion
sub, sub_id Subject identifier Subject/user identification, risk of cross-issuer user impersonation
jti Token identifier Audit (logs)
iat Issuance timestamp Audit (logs)
azp Authorized Party (OIDC) Audit (logs), authorization
client_id Client (OAuth 2) Audit (logs), authorization
auth_time Authentication timestamp (OIDC) Enforcing authentication freshness
acr Authentication class Enforcing authentication strength (eg. MFA)
amr Authentication method reference Enforcing authentication strength (eg. MFA)
cnf Token holder (public) key Sender constrained token
may_act Authorized Actor (impersonation/delegation) Risk of cross-issuer user impersonation
act Actor (delegation, “on behalf of”) Audit (logs), risk of invalid actor imputation
scope Token restriction (OAuth 2) Authorization
roles User roles Authorization, risk of spoofed cross-issuer authorization
groups User groups Authorization, risk of spoofed cross-issuer authorization
entitlements User entitlements Authorization, risk of spoofed cross-issuer authorization
authorization_details Fine grained authorizations Authorization, risk of spoofed cross-issuer authorization

See the JSON Web Token Claims subregistry for a list of standard JWT claims.

Many implementation have built-in support for validating core JWT claims such as nbf, exp, iss and aud.

Threats on JWTs / JWT 的威胁

See RFC 8725 for a discussion on threats and vulnerabilities related to JWT.

参见 RFC 8725 了解与 JWT 相关的威胁和漏洞的讨论。

Unsecured JWTs / 不安全的 JWT

Some JWT libraries, used to accept unsecured JWTs by default ("alg":"none"). In this case, an attacker would be able to forge their own JWTs: depending on the application, they might be able to impersonate arbitrary users, obtains arbitrary authorizations, etc.

一些 JWT 库默认 接受不安全的 JWT ("alg":"none")。在这种情况下,攻击者可以伪造自己的 JWT:根据应用的不同,他们可能能够冒充任意用户、获取任意授权等。

This issue should now be fixed in JWT libraries.

此问题现在应该已在 JWT 库中得到修复。

Mitigation:

缓解措施:

  • Make sure that "alg":"none" is not accepted by your JWT parser. It should be disabled by default by recent implementations.
    中文:确保您的 JWT 解析器不接受 "alg":"none"。最近实现应默认禁用。

Key type confusion / 密钥类型混淆

Some JWT implementations would accept to use a public key intended for public-key digital signature as if it was a secret key used for MAC verification. In this context, an attacker could forge a MAC-based JWT by using the public key of the real issuer as if it was a secret key.

一些JWT实现会接受使用为公钥数字签名设计的公钥,仿佛它是用于MAC验证的密钥。在这种情况下,攻击者可以使用真实发行者的公钥伪造基于MAC的JWT,仿佛它是密钥一样。

This threat is also called “key confusion” or “algorithm confusion”.

这种威胁也被称为“密钥混淆”或“算法混淆”。

Example of legitimate token issuance:

合法令牌发行示例:

token = jwt.encode(claims, private_key_bytes, algorithm="ES256")

Example of attacker forging a token based on key type confusion:

基于密钥类型混淆伪造令牌的攻击者示例:

token = jwt.encode(claims, public_key_bytes, algorithm="HS256")

Example of validation potentially vulnerable to key type confusion:

可能受到密钥类型混淆影响的验证示例:

# If the token is using a MAC, the library might interpret the public key bytes as a MAC secret:
decoded = jwt.decode(token, public_key_bytes, algorithms=jwt.algorithms.get_default_algorithms())

Note: this issue is mitigated in recent versions of the PyJWT library by detecting whether a MAC key appears to be a public key (in PEM of SSH format).

注意:此问题已在PyJWT库的最近版本中通过检测MAC密钥是否看起来像公钥(SSH格式的PEM)得到缓解。

Mitigations (at validation):

缓解措施(在验证时):

  • use a library which is not vulnerable to the issue (eg. strong-typing of the type of key);
    中文:使用不受该问题影响的库(例如,对密钥类型进行强类型检查);
  • chose the key depending on the requested signature algorithm or validate that the key used for validation is consistent with the signature algorithm;
    中文:根据请求的签名算法选择密钥或验证用于验证的密钥是否与签名算法一致;
  • if possible, hardcode the accepted algorithms and do not mix public-key digital signatures algorithms and MAC algorithms.
    中文:如果可能,将接受的算法硬编码,不要混合公钥数字签名算法和MAC算法。

Example of validation not vulnerable because MAC algorithms are not accepted:

验证示例不受影响,因为不接受MAC算法:

decoded = jwt.decode(token, public_key_bytes, algorithms=["ES256"])

Illustrative signature verification using joserfc's typed key import and a trusted EC public key. Claim validation is still required before accepting the token. Replace the example key with your issuer's trusted public key; encoded is the received token string.

使用joserfc的键类型导入和受信任的EC公钥进行说明性签名验证。在接受令牌之前,声明验证仍然是必需的。将示例密钥替换为您的发行者的受信任公钥;encoded是接收到的令牌字符串。

from joserfc import jwt, jwk

public_jwk = {
    "kty": "EC",
    "crv": "P-256",
    "x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",
    "y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0",
}
public_key = jwk.import_key(public_jwk)
decoded = jwt.decode(encoded, public_key, algorithms=["ES256"])

References:

参考资料:

Trusting key material named in the token header / 信任令牌头部中命名的密钥材料

A JWS header can carry the verification key itself or a pointer to it: jwk (an embedded key), jku (a URL to a JWK Set), x5u (a URL to an X.509 certificate) and x5c (an embedded certificate chain), alongside the key selection hints kid, x5t and x5t#S256. An application that resolves or selects its verification key from these header parameters, without tying the result back to something it already trusts, can be steered into trusting a key the attacker controls, because the header is unauthenticated attacker input.

JWS头部可以携带验证密钥本身或指向它的指针:jwk(嵌入式密钥)、jku(指向JWK集的URL)、x5u(指向X.509证书的URL)和x5c(嵌入式证书链),以及密钥选择提示kid、x5t和x5t#S256。如果一个应用程序从这些头部参数中解析或选择其验证密钥,而不将其结果与它已经信任的内容关联,那么它可能会被引导去信任攻击者控制的密钥,因为头部是不经过验证的攻击者输入。

An attacker can forge their own token, include their own public key in jwk, or point jku or x5u at a JWK Set or certificate they host, and sign the token with the matching private key. A verifier that trusts the key it has just read from the token accepts the forgery. An attacker can also try to smuggle a symmetric key through the same parameters, in the hope that the implementation will use it for MAC verification.

攻击者可以伪造自己的令牌,将自己的公钥包含在jwk中,或者将jku或x5u指向他们自己托管的一个JWK集或证书,并使用匹配的私钥签名令牌。如果一个验证者信任了它刚刚从令牌中读取的密钥,那么它就会接受伪造。攻击者还可以尝试通过相同的参数走私对称密钥,希望实现会使用它进行MAC验证。

These parameters have legitimate uses, so the distinction is anchoring rather than avoidance. x5c and x5u are usable where the certificate chain validates up to an anchor already trusted for that issuer, and kid, x5t and x5t#S256 are the normal way to choose which key from an already configured JWKS should verify a given token. What must not happen is treating any of them as the source of trust rather than as a pointer within it.

这些参数有合法用途,所以区分是锚定而不是避免。x5c和x5u可以在证书链验证到已为该发行者信任的锚点时使用,而kid、x5t和x5t#S256是选择从已配置的JWKS中选择哪个密钥来验证给定令牌的正常方式。必须避免的是将它们中的任何一个视为信任的来源,而不是作为其中的指针。

Mitigations:

缓解措施:

  • Do not take the verification key from the token unless that key can be tied, through a chain of trust, to a root trust anchor associated with the issuer.
    中文:除非该密钥可以通过信任链与发行者关联的根信任锚点关联,否则不要从令牌中获取验证密钥。
  • Prefer trust material established out of band, such as a pinned key or the jwks_uri published in the issuer's metadata.
    中文:优先考虑在带外建立的信任材料,例如已固定的密钥或发行者元数据中发布的jwks_uri。
  • Validate or sanitize kid before using it in a lookup, since it also reaches databases and directories as an injection vector.
    中文:在使用之前验证或清理kid,因为它也作为注入向量到达数据库和目录。
  • Where keys are fetched by URL, see the Server Side Request Forgery Prevention Cheat Sheet.
    中文:在通过URL获取密钥的情况下,请参阅服务器端请求伪造预防备忘单。

References:

参考资料:

Issuer and audience confusion / 发行者和受众混淆

A JWT typically carries the iss (issuer) claim to say who created it and the aud (audience) claim to say which party is expected to consume and validate it. Neither claim is mandatory, and some designs omit one deliberately: a token an application issues and consumes itself, under a key used for nothing else, or an SD-JWT whose audience is established by the accompanying key binding JWT rather than by an aud claim. Where a key does speak for more than one issuer or more than one recipient, however, a verifier that checks only the signature and the expiration accepts any token that key has signed, whatever iss and aud it carries. An attacker holding such a token, whether legitimately issued to them or obtained from a service they control, may then be able to replay it against a different recipient. RFC 8725 calls this a substitution attack.

JWT 通常携带 iss(发行者)声明来指明谁创建了它,以及 aud(受众)声明来指明哪个实体预期将消费和验证它。这两个声明都不是强制的,有些设计故意省略一个:一个应用程序发行并消费自己的令牌,使用一个用于其他目的的密钥,或者一个 SD-JWT,其受众由伴随的密钥绑定 JWT 而不是由 aud 声明确定。然而,如果一个密钥代表多个发行者或多个接收者,那么仅检查签名和过期的验证者将接受该密钥签名的任何令牌,无论其 iss 和 aud 如何。持有此类令牌的攻击者,无论是合法发行给他们还是从他们控制的某个服务中获取的,然后可能能够将其重新播放给不同的接收者。RFC 8725 将此称为替换攻击。

Two variants are worth separating, because different checks defeat them.

有两个变体值得区分,因为不同的检查可以击败它们。

Audience confusion. An attacker presents a token issued for one service to a second service that trusts the same issuer. If the second service does not require its own identifier in aud, the token is accepted. For example, a token minted for a low-privilege service is replayed against an internal API, and the attacker gains access that was never granted. On a different axis, a third-party application that legitimately receives tokens for its own use can replay one against another application, first-party or third-party, reaching data or operations it was never granted.

受众混淆。攻击者将一个为某个服务发行的令牌展示给信任相同发行者的第二个服务。如果第二个服务不需要在 aud 中包含自己的标识符,则接受该令牌。例如,为低权限服务铸造的令牌被重新播放到内部 API 上,攻击者获得了从未被授予的访问权限。在另一个维度上,一个合法接收用于自身使用的令牌的第三方应用程序可以将其中一个重新播放到另一个应用程序,无论是第一方还是第三方,从而访问或执行它从未被授予的数据或操作。

Issuer confusion. An attacker presents a token from a different issuer that the verifier also trusts, such as a partner tenant or a self-service account at a public identity provider. Comparing the iss string alone does not stop this if the verifier resolves its verification key independently of iss. A verifier that looks up the key by kid across the union of several trusted issuers' JWK Sets will accept a token whose iss names one issuer and whose kid names a key belonging to another: the signature verifies against the key that kid selected, and the iss and aud comparisons pass because the attacker set both to what the verifier expects. No key needs to be stolen, and the deployment need not be multi-tenant.

发行者混淆。攻击者展示了一个来自不同发行者的令牌,验证者也信任它,例如合作伙伴租户或公共身份提供者的自助账户。仅比较 iss 字符串本身并不能阻止这种情况,如果验证者独立于 iss 解析其验证密钥。一个通过 kid 在多个受信任发行者的 JWK 集合中查找密钥的验证者将接受一个 iss 命名一个发行者而 kid 命名属于另一个密钥的令牌:签名与 kid 选定的密钥验证,而 iss 和 aud 的比较通过,因为攻击者将两者都设置为验证者预期的值。无需窃取密钥,部署也不必是多租户的。

Mitigations:

缓解措施:

  • Validate iss against what the deployment trusts, as a case-sensitive comparison of the whole string including scheme and path. Where a single issuer is expected this is an equality check. Where issuers are provisioned dynamically, through a discovery protocol for example, it becomes membership of an explicit allowlist of issuer identifiers, rather than acceptance of whatever iss the token presents.
    中文:验证 iss 与部署信任的内容,作为一个区分大小写的整个字符串的比较,包括方案和路径。如果预期只有一个发行者,这是一个相等检查。如果发行者是动态配置的,例如通过发现协议,它成为显式允许列表中发行者标识符的成员,而不是接受令牌中呈现的任何 iss。
  • Select the verification key from the set bound to the validated iss, for example that issuer's jwks_uri; never verify against a union of keys from several issuers. In multi-tenant deployments this means resolving the key set from an allowlist keyed by issuer rather than searching every tenant's keys.
    中文:从与已验证的 iss 绑定的集合中选择验证密钥,例如该发行者的 jwks_uri;切勿针对多个发行者的密钥联合进行验证。在多租户部署中,这意味着从按发行者键索引的允许列表中解析密钥集,而不是搜索每个租户的密钥。
  • Validate that the recipient's own identifier is present in aud, whether aud is a single string or an array of strings.
    中文:验证接收者的自身标识符是否存在于 aud 中,无论 aud 是单个字符串还是字符串数组。
  • Where the deployment relies on these claims, reject tokens in which iss or aud is missing. RFC 7519 makes both optional, so this is a deployment decision rather than a specification requirement: a verifier with no iss has nothing to bind the key to, and one with no aud cannot tell whether the token was meant for it, but a profile that establishes either by other means does not need the claim itself.
    中文:当部署依赖于这些声明时,拒绝缺少 iss 或 aud 的令牌。RFC 7519 将这两者都视为可选,因此这是一个部署决策而不是规范要求:没有 iss 的验证者无法将密钥绑定到任何东西,而没有任何 aud 的验证者无法判断令牌是否针对它,但通过其他方式建立配置文件的不需要这个声明本身。

The related case, where the key material itself is taken from the token rather than merely selected by it, is covered in Trusting key material named in the token header. Where verification uses a MAC, see also the secret reuse points under MAC.

相关情况,即密钥材料本身是从令牌中提取的而不是仅由它选择的,在信任令牌头部命名的密钥材料中进行了说明。当验证使用 MAC 时,请参阅 MAC 下的秘密重用点。

Example of strict validation in Python with PyJWT, resolving the key from the expected issuer's JWK Set rather than accepting one supplied independently:

使用 PyJWT 在 Python 中进行严格验证的示例,从预期的发行者的 JWK 集中解析密钥,而不是接受独立提供的密钥:

import jwt

ISSUER = "https://auth.example.com/"
AUDIENCE = "https://api.example.com/v1/payments"

# Keys come from this issuer's JWK Set only (its published jwks_uri),
# so a `kid` naming a key of some other trusted issuer cannot verify
# this token.
jwks_client = jwt.PyJWKClient("https://auth.example.com/.well-known/jwks.json")
signing_key = jwks_client.get_signing_key_from_jwt(token)

decoded_payload = jwt.decode(
    token,
    signing_key,
    algorithms=["ES256"],
    issuer=ISSUER,
    audience=AUDIENCE,
    options={"require": ["exp", "iss", "aud"]},
)

Note: in PyJWT the issuer and audience arguments perform the validation. The verify_iss and verify_aud options are enabled by default and gate checks that do nothing on their own, so a token is only checked against an expected issuer and audience when those arguments are passed.

注意:在 PyJWT 中,issuer 和 audience 参数执行验证。verify_iss 和 verify_aud 选项默认启用,并门控检查本身不执行任何操作,因此只有当传递这些参数时,令牌才会被检查是否与预期的发行者和受众匹配。

References:

参考资料:

Cross-JWT and token type confusion / 跨JWT和令牌类型混淆

Cross-JWT (or token type) confusion occurs when validation rules fail to distinguish token kinds, allowing a token issued for one purpose (such as an ID or password-reset token) to be accepted as another (such as an access token). Overlapping claims or shared signing keys are common enabling conditions (see RFC 8725 §2.8).

当验证规则无法区分令牌类型时,会发生跨JWT(或令牌类型)混淆,允许为一种目的(如ID或密码重置令牌)签发的令牌被接受为另一种(如访问令牌)。重叠声明或共享签名密钥是常见的启用条件(参见RFC 8725 §2.8)。

Mitigations:

缓解措施:

  • Use explicit typing (typ): Set the typ header parameter to a specific media type distinguishing the token's purpose, such as "at+jwt" for OAuth 2.0 access tokens (RFC 9068), "logout+jwt" for logout tokens (OpenID Connect Back-Channel Logout 1.0 §2.4), or custom types (e.g., example-reset+jwt) for internal tokens.
    中文:使用显式类型(typ):将typ头参数设置为特定媒体类型,以区分令牌的目的,例如OAuth 2.0访问令牌使用"at+jwt"(RFC 9068),注销令牌使用"logout+jwt"(OpenID Connect Back-Channel Logout 1.0 §2.4),或自定义类型(例如,example-reset+jwt)用于内部令牌。
  • Validate typ at the verifier: For token profiles that require or reliably provide explicit typing, reject tokens with missing or unexpected typ values at that endpoint. Note that typ is case-insensitive and the application/ prefix may be omitted (RFC 7515 §4.1.9).
    中文:在验证器中验证typ:对于需要或可靠提供显式类型的令牌配置文件,在该端点拒绝缺少或意外的typ值。请注意,typ不区分大小写,并且可以省略application/前缀(RFC 7515 §4.1.9)。
  • Use mutually exclusive validation rules: Where explicit typing cannot be enforced interoperably (e.g., standard OIDC ID tokens omitting typ), distinguish token kinds using separate signing keys, required claims, or strict iss and aud isolation (RFC 8725 §3.12).
    中文:使用互斥的验证规则:在无法互操作地强制执行显式类型(例如,省略typ的标准OIDC ID令牌)的情况下,使用单独的签名密钥、必需的声明或严格的iss和aud隔离(RFC 8725 §3.12)来区分令牌类型。

Example of validation enforcing explicit token type:

显式令牌类型验证的示例

import jwt

# Verify signature and standard claims first
decoded = jwt.decode_complete(
    token,
    public_key,
    algorithms=["ES256"],
    audience="https://api.example.com",
    issuer="https://auth.example.com",
    options={"require": ["exp", "iss", "aud"]},
)

# Enforce explicit token type from the verified header
typ = str(decoded["header"].get("typ", "")).lower()
if typ not in ["at+jwt", "application/at+jwt"]:
    raise jwt.InvalidTokenError("Invalid token type: expected at+jwt")

References:

参考资料:

JWT revocation / JWT撤销

Token Status List / 令牌状态列表

If revocation of the JWTs by the issuer is needed, the Token Status Lists (TSL) can be used:

如果需要发行者撤销JWT,可以使用令牌状态列表(TSL):

  • the JWT contains the URI of a TSL;
    中文:JWT包含TSL的URI;
  • the TSL aggregates the revocation status of several tokens in compressed form;
    中文:TSL以压缩形式聚合多个令牌的撤销状态;
  • the consumer of the token can fetch the TSL to obtain the revocation status of the JWT.
    中文:令牌的消费者可以获取TSL以获取JWT的撤销状态。

The issuer includes a status claim in the JWT. This claims contains the URI of the associated TSL and the index of the status of the JWT within this list:

发行者在JWT中包含一个status声明。这个声明包含相关TSL的URI以及JWT在此列表中的状态索引:

{
    "iss": "https://issuer.example/",
    "sub": "NsxuACbpJ9N7Ix96aWrYxHX-EZ4",
    "iat": 1783635268,
    "nbf": 1783635268,
    "exp": 1783653268,
    "status": {
        "status_list": {
            "idx": 6,
            "uri": "https://issuer.example/tsl/JAffke55FR5gtJQ_rtktWkSaTlI"
        }
    }
}

Replay protection / 重放保护

JWT denylist / JWT 拒绝列表

In some cases, the consumer of the token might want to maintain a JWT denylist. This might be for example used a simple form of JWT replay protection or as a workaround for the “stateless session” invalidation problem.

在某些情况下,令牌的消费者可能希望维护一个 JWT 拒绝列表。这可能被用作 JWT 重放保护的一种简单形式,或者作为解决“无状态会话”失效问题的解决方案。

A JWT deny list can typically be implemented based on the jti and iss claims:

JWT 拒绝列表通常可以根据 jti 和 iss 声明来实现:

def revoke_token(claims):
    jti = claims.get("jti")
    iss = claims.get("iss")
    exp = claims.get("exp")
    deny_list.insert((jti, iss), exp)

def is_token_revoked(claims) -> bool:
    jti = claims.get("jti")
    iss = claims.get("iss")
    return deny_list.contains((jti, iss))

Depending on the application and the type of JWT, other claims might be more suitable.

根据应用程序和 JWT 类型,其他声明可能更适合。

Warning: Using the raw JWT or a secure hash of the JWT (SHA-256(token)) as the denylist key is not safe and might expose the application to denylist bypass through JWT malleability. An attacker in possession of a revoked JWT might be able to modify an alternative representation of the JWT that still passes signature verification:

警告: 使用原始 JWT 或 JWT 的安全哈希(SHA-256(token))作为拒绝列表的键是不安全的,可能会使应用程序面临通过 JWT 可塑性绕过拒绝列表的风险。拥有已撤销 JWT 的攻击者可能能够修改 JWT 的另一种表示形式,该表示形式仍然可以通过签名验证:

  • because of non-strict JWT parsing of the JWT implementation;
    中文:因为 JWT 实现对 JWT 的非严格解析;
  • for ECDSA JWTs, because of the malleability of ECDSA signatures.
    中文:对于 ECDSA JWT,因为 ECDSA 签名的可塑性。
# Not secure. Might be vulnerable to JWT malleability:
def unsafe_revoke_token(claims):
    exp = claims.get("exp")
    token_hash = hashlib.sha256(token.encode("utf-8")).digest()
    deny_list.insert(token_hash, exp)

Before implementing such a JWT denylist, you should consider whether there is a better solution for your problem:

在实施此类 JWT 黑名单之前,您应该考虑是否有更好的解决方案来解决问题:

  • Token Status List is a scalable solution for revocation of the JWT by the issuer.
    中文:令牌状态列表是发行者吊销 JWT 的可扩展解决方案。
  • Freshness and replay protection can often by implementing by using a nonce bound to the session in the JWT claims. This approach is used in OpenID Connect.
    中文:新鲜度和重放保护通常可以通过在JWT声明中使用绑定到会话的nonce来实现。这种方法在OpenID Connect中被使用。
  • Token reuse can be mitigated by using short expiration time in the JWT.
    中文:通过在 JWT 中使用较短的过期时间可以减轻令牌重用的风险。
  • Sender-constrained access tokens, such as DPoP-bound tokens or mutual-TLS certificate-bound tokens, limit an attacker's ability to use a stolen token without its associated private key. They do not prevent token disclosure. Protect tokens and keys, use HTTPS, and prevent untrusted code from using the client's signing key.
    中文:发送者约束的访问令牌,如DPoP-bound令牌或相互TLS证书绑定令牌,限制了攻击者使用未关联私钥的被盗令牌的能力。它们不能防止令牌泄露。保护令牌和密钥,使用HTTPS,并防止不受信任的代码使用客户端的签名密钥。

Token Confidentiality and JWE / 令牌机密性和 JWE

Signed JWTs are not confidential / 签名 JWT 不可保密

A signed JWT (JSON Web Signature, JWS) provides integrity and authenticity, but not confidentiality. The payload is only base64url encoded, not encrypted, so anyone who obtains the token can read every claim. With a MAC (HS*), a valid signature also only proves that the token was produced by some holder of the shared secret, see Public-key Signatures vs. MAC.

签名 JWT(JSON Web 签名,JWS)提供完整性和真实性,但不提供保密性。有效载荷仅进行 base64url 编码,未加密,因此任何获得令牌的人都可以读取每个声明。使用 MAC (HS*),有效的签名也仅证明令牌是由共享秘密的某个持有者生成的,参见 公钥签名与 MAC。

TLS prevents the token from being read in transit, but the claims remain exposed elsewhere: in application logs, in browser storage, in referrer headers, and to any intermediary that terminates TLS.

TLS 防止令牌在传输中被读取,但声明仍然在其他地方暴露:在应用程序日志中、在浏览器存储中、在引用头中,以及任何终止 TLS 的中间件中。

Omitting privacy-sensitive information from a JWT is the simplest way of minimizing privacy issues) (从 JWT 中省略敏感信息是最大限度地减少隐私问题的最简单方法。). Prefer keeping sensitive data server-side behind an opaque reference token. Use JWE only when the claims must travel with the token to a party that cannot resolve them with the issuer.

Using JWE / 使用JWE

When claims must be kept confidential, use JSON Web Encryption (JWE). JWE uses two algorithms:

当声明必须保密时,请使用JSON Web加密 (JWE)。JWE使用两种算法:

  • alg: the key management algorithm, which encrypts or agrees upon the Content Encryption Key (CEK) for the intended recipient (for example RSA-OAEP-256 or ECDH-ES+A256KW).
    中文:alg:密钥管理算法,它加密或协商内容加密密钥(CEK)以供预期接收者使用(例如RSA-OAEP-256或ECDH-ES+A256KW)。
  • enc: the content encryption algorithm, which encrypts the payload using authenticated encryption (for example A256GCM).
    中文:enc:内容加密算法,使用认证加密(例如 A256GCM)对有效载荷进行加密。

JWE provides confidentiality and ciphertext integrity, not issuer authentication. With a public-key alg, anyone holding the recipient's public key can produce a token that decrypts successfully, so never make authorization decisions on claims from an unsigned JWE.

JWE提供机密性和密文完整性,不提供发行者身份验证。使用公钥 alg,任何持有接收者公钥的人都可以生成一个可以成功解密的令牌,因此绝不要基于未签名的JWE中的声明做出授权决策。

When both authenticity and confidentiality are needed, use a nested JWT: sign the claims first (JWS), then encrypt the result (JWE). This prevents attacks in which the signature is stripped, leaving just an encrypted message, as well as providing privacy for the signer. In the outer JWE, the cty header MUST be set to JWT to signal the nesting.

当需要真实性和机密性时,请使用嵌套JWT:首先对声明进行签名(JWS),然后加密结果(JWE)。这防止了签名被剥离,只留下加密消息的攻击,并为签名者提供隐私。在外部JWE中,cty头必须设置为JWT以表示嵌套。

When consuming a nested JWT, decrypt the outer JWE and verify the inner JWS signature, rejecting the token if either step fails. Both the outer and the inner operations MUST be validated: successful decryption on its own proves nothing about who issued the claims.

在消费嵌套JWT时,必须解密外部JWE并验证内部JWS签名,如果任一步骤失败则拒绝令牌。外部和内部操作必须进行验证:仅成功解密本身并不能证明谁发布了声明。

Two further requirements apply to JWE:

JWE还适用以下两个进一步的要求:

Note:

注意:

Full JWE implementation guidance is out of scope for this cheat sheet and will be addressed in a dedicated JWE cheat sheet.

本速查表不包含完整的JWE实现指南,相关内容将在专门的JWE速查表中说明。

References / 参考