SpringBoot3 登录注册基础篇(二) - RSA 非对称加密实战
SpringBoot3 登录注册基础篇(二) - RSA 非对称加密实战
ProriseSpringBoot3 登录注册基础篇(二) - RSA 非对称加密实战
第一章. 为什么选择 RS256
在开始生成密钥之前,我们需要先回答一个核心问题:JWT 的签名算法应该选择 HS256 还是 RS256?
1.1. HS256 的架构困境
HS256 是对称加密签名和验证使用同一个密钥算法。它的工作流程是:
1 | 签名:Token + 密钥 → 签名 |
假设我们的系统有 20 个微服务,每个服务都需要验证 Token。那么这 20 个服务都必须持有同一个密钥。这带来两个风险:
风险一:攻击面扩大
密钥存在于 20 个不同的配置文件、环境变量、容器镜像中。任何一个环节泄漏,整个系统就沦陷了。
风险二:密钥轮换困难
当我们需要更换密钥时(如定期安全审计、员工离职),必须同时更新 20 个服务的配置并重启。在生产环境中,这几乎不可能做到原子性切换。
1.2. RS256 的架构优势
RS256 是非对称加密使用私钥签名,公钥验证算法。它的工作流程是:
1 | 签名:Token + 私钥 → 签名 |
在这种架构下:
- 认证服务 持有私钥,负责签发 Token。私钥是整个系统中唯一的敏感数据,只需要保护一个点。
- 网关和业务服务 持有公钥,负责验证 Token。公钥可以公开分发,即使泄漏也不会影响安全性(攻击者无法用公钥伪造签名)。
这种设计的核心价值在于 职责分离:只有认证服务才有 “印钞权”,其他服务只能 “验钞” 而不能 “造钞”。
1.3. 架构对比
| 对比维度 | HS256(对称加密) | RS256(非对称加密) |
|---|---|---|
| 密钥数量 | 1 个(签名和验证共用) | 2 个(私钥签名,公钥验证) |
| 密钥分发 | 所有服务都需要持有密钥 | 只有认证服务持有私钥 |
| 安全风险 | 任何一个服务泄漏,全局沦陷 | 只有认证服务泄漏才会沦陷 |
| 密钥轮换 | 需要同时更新所有服务 | 只需要更新认证服务 |
| 性能 | 快(对称加密) | 稍慢(非对称加密) |
在微服务架构下,我们选择 RS256,用稍微的性能损失换取更高的安全性和更灵活的架构。
第二章. 使用 OpenSSL 生成 RSA 密钥对
2.1. 密钥格式要求
Java 的 KeyFactory 对密钥格式有严格要求:
- 私钥必须是 PKCS#8 格式
- 公钥必须是 X.509 格式
如果格式不对,会抛出 InvalidKeySpecException 异常。
2.2. 生成 PKCS#8 格式的私钥
打开终端(Windows 用户可以使用 Git Bash),输入以下命令:
注意,openssl 通常在 git 窗口上才可以执行,请使用 git bash 执行此命令
1 | openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048 |
命令解释:
openssl genpkey:使用 OpenSSL 生成密钥(新版命令,自动生成 PKCS#8 格式)-algorithm RSA:指定算法为 RSA-out private_key.pem:输出文件名-pkeyopt rsa_keygen_bits:2048:密钥长度为 2048 位
执行后,你会看到类似这样的输出:
1 | .....+++ |
并且在当前目录下会生成一个 private_key.pem 文件。
用文本编辑器打开 private_key.pem,你应该看到:
1 | -----BEGIN PRIVATE KEY----- |
关键标识:-----BEGIN PRIVATE KEY-----(没有 “RSA” 字样),这就是 PKCS#8 格式。
2.3. 从私钥中提取公钥
继续在终端中输入:
1 | openssl rsa -pubout -in private_key.pem -out public_key.pem |
命令解释:
openssl rsa:使用 OpenSSL 处理 RSA 密钥-pubout:输出公钥-in private_key.pem:指定输入文件(刚才生成的私钥)-out public_key.pem:输出文件名
执行后,你会看到:
1 | writing RSA key |
用文本编辑器打开 public_key.pem,你应该看到:
1 | -----BEGIN PUBLIC KEY----- |
2.4. 将密钥放入项目
在 auth-web 模块中,找到 src/main/resources 目录,创建 certs 文件夹:
1 | auth-web/src/main/resources/ |
将刚才生成的两个文件复制到这个目录下。
安全提示:必须在 .gitignore 中添加 **/certs/private_key.pem,防止私钥被提交到 Git 仓库。
第三章. 封装 RsaKeyManager 工具类
3.1. 为什么需要转换
Java 的 PrivateKey 和 PublicKey 对象需要的是二进制格式。我们需要一个工具类来完成以下工作:
- 读取
resources目录下的密钥文件 - 去除 PEM 格式的头尾标记(
-----BEGIN...-----) - 将 Base64 编码的字符串解码为字节数组
- 使用 Java 的
KeyFactory将字节数组转换为密钥对象
3.2. RsaKeyManager 核心代码
📄 文件路径:auth-core/src/main/java/com/example/auth/core/util/crypto/RsaKeyManager.java
1 | package com.example.auth.core.util.crypto; |
3.3. 关键知识点
为什么要去除 PEM 格式的头尾标记?
PEM 格式是一种文本格式,用于存储密钥和证书。它的结构是:
1 | -----BEGIN [类型]----- |
Java 的 KeyFactory 只能识别纯粹的 Base64 编码数据,不能识别这些标记,所以必须先去除。
PKCS8 和 X509 是什么?
- PKCS8:私钥的存储格式标准(Public Key Cryptography Standards #8)
- X509:公钥证书的标准格式
这两个标准定义了密钥在二进制层面的存储结构。
为什么用 ClassPathResource 而不是 File?
当项目打包成 jar 包后,resources 目录下的文件会被打包到 jar 包内部。此时:
- ❌
new File("certs/public_key.pem")会失败 - ✅
new ClassPathResource("certs/public_key.pem")可以正确读取
第四章. 本章小结
我们完成了 RSA 密钥对的生成与加载。
核心成果:
| 步骤 | 操作 | 产出 |
|---|---|---|
| 1 | 使用 OpenSSL 生成私钥 | private_key.pem(PKCS#8 格式) |
| 2 | 从私钥中提取公钥 | public_key.pem(X.509 格式) |
| 3 | 将密钥放入项目 | auth-web/src/main/resources/certs/ |
| 4 | 封装工具类 | RsaKeyManager |
方法速查:
| 类名 | 方法名 | 作用 |
|---|---|---|
| RsaKeyManager | readResourceFile(String path) | 读取 resources 目录下的文件 |
| RsaKeyManager | loadPrivateKey(String pem) | 加载私钥 |
| RsaKeyManager | loadPublicKey(String pem) | 加载公钥 |
架构对比:
| 对比维度 | HS256 | RS256 |
|---|---|---|
| 密钥数量 | 1 个 | 2 个(私钥+公钥) |
| 密钥分发 | 所有服务都需要 | 只有认证服务持有私钥 |
| 安全风险 | 任何服务泄漏,全局沦陷 | 只有认证服务泄漏才沦陷 |




