一、什么是 JWT?

JWT,全称是 JSON Web Token,是 https://datatracker.ietf.org/doc/html/rfc7519 制定的一种开放标准,用于在各方之间安全地传递声明(claims)。

一个标准的 JWT 通常由三部分组成,使用 . 分隔:

text
Header.Payload.Signature

举个例子:

text
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEyMywiZXhwIjoxNjkzOTk5MjAwfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

各部分含义:

  • Header:通常是算法和类型信息,比如使用 HS256 签名。
  • Payload:包含实际的数据声明,比如用户 ID、过期时间等 —— 但它是 Base64Url 编码,不是加密!
  • Signature:用来校验 Token 是否被篡改。

JWT 的最大问题:Payload 是明文的!

JWT 的 Payload 虽然看起来像是“加密”的,但实际上只是经过了 Base64Url 编码。也就是说,任何人拿到这个 Token,都可以通过在线工具或代码轻松解码,看到里面的内容!

这就是为什么 JWT 不适合直接用来传递敏感信息,比如用户的密码、邮箱、权限角色、身份证号等。


二、如何让 Token 中的信息真正安全?

JWE(JSON Web Encryption)

JWE,全称 JSON Web Encryption,也是 JWT 标准家族的一部分(详见 https://datatracker.ietf.org/doc/html/rfc7516),它为 JWT 引入了真正的加密能力,确保 Token 中的数据即使被截获,也无法被轻易查看或篡改。


三、JWE 是什么?和 JWT 有什么不同?

简单来说:

特性JWTJWE
是否加密否(Payload 是 Base64 编码)是(Payload 是加密的,不可直接查看)
是否可读是(可直接解码)否(必须解密后才能查看内容)
安全性低(不适合敏感信息)高(适合加密传输敏感数据)
典型用途身份令牌、信息交换加密的 Token、敏感数据传输、隐私保护场景

所以,如果需要在 Token 中存放敏感信息,或者不希望别人轻易窥探 Token 内容,可以选择 JWE。


四、JWE 的组成结构

一个标准的 JWE Token,由以下 五部分组成,同样使用 . 分隔:

text
{Protected Header}.{Encrypted Key}.{Initialization Vector}.{Ciphertext}.{Authentication Tag}

1. Protected Header(保护头部)

这是 JWE 的元数据部分,指定了加密相关的参数,例如:

  • 使用了哪种加密算法(如 RSA、AES)
  • 使用了哪种内容加密方式(如 AES-GCM)
  • 可选的额外参数,比如 kid(密钥 ID)

示例(JSON 格式,但实际在 Token 中是经过 Base64Url 编码的):

json
{
  "alg": "RSA-OAEP",
  "enc": "A256GCM",
  "kid": "12345"
}
  • alg:表示用于加密 Content Encryption Key(CEK)的算法,比如 RSA-OAEP。
  • enc:表示用于加密实际数据内容的对称加密算法,比如 A256GCM(即 AES-256-GCM)。
  • kid:密钥标识符,用于密钥管理和轮换。

⚠️ 注意:这个 Header 本身也是被加密保护的(不像 JWT 中是明文的),所以外部无法直接查看。


2. Encrypted Key(加密的密钥)

这是用接收方的 公钥 加密后的对称密钥(即 CEK,Content Encryption Key)。

因为对称加密效率高,但密钥分发是个问题,所以 JWE 采用了一种混合加密策略:

  • 发送方生成一个随机的对称密钥(CEK),用它来加密实际的 Payload。
  • 然后,用接收方的公钥加密这个 CEK,只有接收方能用自己的私钥解开,拿到 CEK。

这样就既保证了加密性能,又解决了密钥安全分发的问题。


3. Initialization Vector(IV,初始化向量)

在使用某些加密算法(比如 AES-GCM)时,为了保证同样的明文每次加密结果不同,需要引入一个随机的 IV。

IV 是加密过程中的重要参数,通常与密文一起传输。


4. Ciphertext(密文)

这是使用 CEK 和 IV 对原始数据进行加密后得到的内容,也就是你真正想保护的敏感信息。

比如,你可以把用户的身份信息、权限声明等放在这里,然后进行加密。


5. Authentication Tag(认证标签)

这是加密算法(如 GCM 模式)生成的用于校验数据完整性的标签。

它的作用是:确保密文在传输过程中没有被篡改。

如果有人修改了密文,验证时就会失败,从而防止重放攻击或数据伪造。


五、JWE 的实际意义:让 Token “不再裸奔”

在项目中,经常需要使用 Token 来传递用户身份或敏感上下文,比如:

  • 用户登录后返回的 Access Token,里面可能包含用户角色、部门信息等;
  • 微服务之间通过 Token 传递调用方身份;
  • 在单点登录(SSO)场景中,ID Token 可能携带邮箱、姓名等 PII 信息。

如果这些信息直接放在 JWT 中,就等于“明文快递”,极其不安全。

而使用 JWE,你可以:

✅ 保证 Token 中的数据是加密的,只有拥有私钥的接收方才能解密查看;
✅ 避免敏感信息泄露,增强系统的隐私保护能力;
✅ 依然保持 Token 的自包含和可传递特性,和 JWT 一样易于集成;


六、时序图

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#00aaff', 'secondaryColor': '#ffaa00', 'tertiaryColor': '#00ffaa' }}}%%
sequenceDiagram
    participant C as 客户端
    participant ES as 加密服务
    participant KMS as 密钥管理系统
    participant R as 接收方

    %% JWE 加密流程
    Note over C, R: JWE 加密流程
    C->>ES: 发送明文数据和加密请求
    ES->>KMS: 请求内容加密密钥(CEK)
    KMS-->>ES: 返回随机生成的CEK
    ES->>ES: 使用CEK加密明文,生成密文
    ES->>KMS: 请求接收方公钥
    KMS-->>ES: 返回接收方公钥
    ES->>ES: 使用公钥加密CEK,生成Encrypted Key
    ES->>ES: 生成JWE Header、IV、AAD
    ES->>ES: 计算认证标签(Authentication Tag)
    ES->>ES: 组装JWE Token(Header, Encrypted Key, IV, Ciphertext, Tag)
    ES-->>C: 返回JWE Token
    C->>R: 发送JWE Token

    %% JWE 解密流程
    Note over C, R: JWE 解密流程
    R->>DS: 发送JWE Token和解密请求
    participant DS as 解密服务
    DS->>DS: 解析JWE Token(Header, Encrypted Key, IV, Ciphertext, Tag)
    DS->>KMS: 根据Header.kid请求私钥
    KMS-->>DS: 返回私钥
    DS->>DS: 使用私钥解密Encrypted Key,获取CEK
    DS->>DS: 使用CEK、IV解密Ciphertext,获取明文
    DS->>DS: 使用Tag和AAD验证完整性
    DS-->>R: 返回明文数据(或错误)

七、基础代码实现

生成公私密钥

java
/**
 * 生成 RSA 密钥对
 */
public class KeyGenerator {
    public static KeyPair generatorRsaKey() {
        KeyPair keyPair;

        try {
            KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
            generator.initialize(2048);
            keyPair = generator.generateKeyPair();
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
        return keyPair;
    }
}

加解密工具类

java

public class JWEUtil {

    private final KeyPair keyPair;

    public JWEUtil(KeyPair keyPair) {
        this.keyPair = keyPair;
    }

    public String encrypt(String subject, String issuer) throws JOSEException {
        // 构建加密内容
        JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
                .subject(subject)
                .issuer(issuer)
                .issueTime(new Date())
                .build();

        Payload payload = new Payload(claimsSet.toJSONObject());

        // header
        JWEHeader header = new JWEHeader.Builder(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A256GCM)
                .build();

        // object
        JWEObject object = new JWEObject(header, payload);

        RSAEncrypter encrypter = new RSAEncrypter((RSAPublicKey) keyPair.getPublic());
        object.encrypt(encrypter);

        return object.serialize();
    }

    public JWTClaimsSet decrypt(String jweString) throws JOSEException, ParseException {
        JWEObject jweObject = JWEObject.parse(jweString);

        // 使用私钥解密
        RSADecrypter decrypter = new RSADecrypter(keyPair.getPrivate());
        jweObject.decrypt(decrypter);

        // 获取明文内容
        Payload payload = jweObject.getPayload();
        return JWTClaimsSet.parse(payload.toJSONObject());
    }
}

测试

java
public class Main {
    public static void main(String[] args) {
        try {
            KeyPair keyPair = KeyGenerator.generatorRsaKey();
            JWEUtil jweUtil = new JWEUtil(keyPair);
            String encrypt = jweUtil.encrypt("user123", "AuthService");
            System.out.println(encrypt);
            System.out.println("==================");
            JWTClaimsSet decrypt = jweUtil.decrypt(encrypt);
            System.out.printf("%s, %s, %s", decrypt.getSubject(), decrypt.getIssuer(), decrypt.getIssueTime());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

八、写在最后:JWE 适合你吗?

场景推荐方案
Token 中不包含敏感信息,只需要防篡改✅ 使用普通 JWT(配合签名即可)
Token 中包含用户隐私、权限等敏感信息✅ 使用 JWE,对内容进行加密保护
需要兼顾性能与安全✅ 使用 JWE,但注意加密运算有开销,避免高频使用
希望 Token 可被任意服务读取(如公开信息)❌ 无需使用 JWE,用 JWT 即可

💡 小贴士:在实际项目中,推荐使用成熟的 JWT/JWE 库,比如 Java 生态中的 https://connect2id.com/products/nimbus-jose-jwt ,避免自己手动实现加密逻辑,降低安全风险。


🔧 延伸阅读(如果你感兴趣)


你的 Token,值得被加密。