一、什么是 JWT?
JWT,全称是 JSON Web Token,是 https://datatracker.ietf.org/doc/html/rfc7519 制定的一种开放标准,用于在各方之间安全地传递声明(claims)。
一个标准的 JWT 通常由三部分组成,使用 . 分隔:
Header.Payload.Signature举个例子:
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 有什么不同?
简单来说:
| 特性 | JWT | JWE |
|---|---|---|
| 是否加密 | 否(Payload 是 Base64 编码) | 是(Payload 是加密的,不可直接查看) |
| 是否可读 | 是(可直接解码) | 否(必须解密后才能查看内容) |
| 安全性 | 低(不适合敏感信息) | 高(适合加密传输敏感数据) |
| 典型用途 | 身份令牌、信息交换 | 加密的 Token、敏感数据传输、隐私保护场景 |
所以,如果需要在 Token 中存放敏感信息,或者不希望别人轻易窥探 Token 内容,可以选择 JWE。
四、JWE 的组成结构
一个标准的 JWE Token,由以下 五部分组成,同样使用 . 分隔:
{Protected Header}.{Encrypted Key}.{Initialization Vector}.{Ciphertext}.{Authentication Tag}1. Protected Header(保护头部)
这是 JWE 的元数据部分,指定了加密相关的参数,例如:
- 使用了哪种加密算法(如 RSA、AES)
- 使用了哪种内容加密方式(如 AES-GCM)
- 可选的额外参数,比如
kid(密钥 ID)
示例(JSON 格式,但实际在 Token 中是经过 Base64Url 编码的):
{
"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: 返回明文数据(或错误)七、基础代码实现
生成公私密钥
/**
* 生成 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;
}
}加解密工具类
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());
}
}测试
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 ,避免自己手动实现加密逻辑,降低安全风险。
🔧 延伸阅读(如果你感兴趣)
- https://datatracker.ietf.org/doc/html/rfc7519
- https://datatracker.ietf.org/doc/html/rfc7516
- https://jwt.io/introduction/
- 推荐库: https://bitbucket.org/connect2id/nimbus-jose-jwt/src/master/
你的 Token,值得被加密。