SpringBoot3集成Oauth2.1——10重启程序Token失效(RSA持久化)
每次颁发的token,在重启以后,有小伙伴可能说想,是不是token使用内存存储,重启以后token就没了,其实不是这样,这个和之前的老版本的Oauth2不一样。因此并不是因为存储失效了,导致JWT失效。
特性 | JWT | 传统随机字符串 Token |
---|---|---|
信息载体 | 自包含(Payload 带用户/有效期信息) | 仅为随机字符串(无实际含义) |
服务端存储需求 | 无需存储 Token,仅存密钥 | 必须存储 Token(如 Redis/数据库),并关联用户信息 |
验证逻辑 | 本地验证(密钥+签名对比) | 需查存储系统(验证 Token 是否存在、关联用户是否合法) |
扩展性(分布式场景) | 天然支持(无状态,服务端可水平扩展) | 需共享存储(如 Redis 集群),否则多服务节点无法验证 |
具体来说,如果系统采用 “本地生成 RSA 密钥对” 的模式(例如授权服务器自己生成 RSA 密钥对用于签名 JWT,或资源服务器持有本地 RSA 公钥用于验证签名),而这些密钥对仅存在内存中、未持久化到磁盘或安全存储(如密钥管理服务 KMS),那么程序重启后:
1.授权服务器会重新生成新的 RSA 密钥对,导致之前用旧密钥签名的 Token 全部失效(验证时签名不匹配);
2.资源服务器的JWKSource会丢失之前的 RSA 公钥,无法验证重启前签发的 Token(找不到对应的验证密钥)
如下所示,每次我们都是重新创建keyPair,并且每次的KeyID都是从新生成的UUID,因此,也就是我们上面所说的问题。
/** @Description: 创建用于JWT签名和验证的JWK源,生成RSA密钥对并构建JWK集合,用于OAuth2授权服务器* @author: 胡涛* @mail: hutao_2017@aliyun.com* @date: 2025年5月27日 上午11:46:45*/@Beanpublic JWKSource<SecurityContext> jwkSource() {KeyPair keyPair = generateRsaKey();RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();RSAKey rsaKey = new RSAKey.Builder(publicKey).privateKey(privateKey).keyID(UUID.randomUUID().toString()).build();JWKSet jwkSet = new JWKSet(rsaKey);return new ImmutableJWKSet<>(jwkSet);}/** @Description: 生成RSA密钥对,密钥长度为2048位* @author: 胡涛* @mail: hutao_2017@aliyun.com* @date: 2025年5月20日 上午10:47:00*/private static KeyPair generateRsaKey() {KeyPair keyPair;try {KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");keyPairGenerator.initialize(2048);keyPair = keyPairGenerator.generateKeyPair();}catch (Exception ex) {throw new IllegalStateException(ex);}return keyPair;}
}
现在我们微调代码如下
- 调整1:固定 RSAKey 的 keyID
- 调整2:持久化 KeyPair
private static final String KEY_ID = "oauth2-rsa-key-pair.ser";@Beanpublic JWKSource<SecurityContext> jwkSource() {KeyPair keyPair = generateRsaKey();RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();RSAKey rsaKey = new RSAKey.Builder(publicKey).privateKey(privateKey).keyID(KEY_ID)//调整1 KEY_ID 固定.build();JWKSet jwkSet = new JWKSet(rsaKey);return new ImmutableJWKSet<>(jwkSet);}private static KeyPair generateRsaKey() {//调整2 从文件获取File file = new File(KEY_ID);if (file.exists() && file.length() > 0) {try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) {return (KeyPair) ois.readObject();} catch (Exception e) {e.printStackTrace();}}//如果文件不存在,则新建KeyPairGenerator keyPairGenerator;KeyPair keyPair = null;try {keyPairGenerator = KeyPairGenerator.getInstance("RSA");keyPairGenerator.initialize(2048);keyPair = keyPairGenerator.generateKeyPair();ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(KEY_ID));oos.writeObject(keyPair);oos.close();} catch (Exception e) {e.printStackTrace();}return keyPair;}
最后,在程序运行的相对根目录就会产生密钥对文件