SpringBoot API接口RSA加密实战:基于Hutool的5分钟集成方案

1. 项目概述:为什么我们需要为SpringBoot接口穿上“加密铠甲”?

在微服务架构和前后端分离成为主流的今天,API接口作为数据交换的桥梁,其安全性直接关系到整个系统的命脉。想象一下,你的用户登录请求、支付信息、个人隐私数据,如果都以明文的形式在网络上“裸奔”,那将是一场灾难。我见过太多因为接口传输未加密而导致数据泄露、用户信息被篡改甚至资金损失的案例。因此,为关键接口,尤其是涉及认证和敏感数据的接口,实施传输层加密,不是一道“加分题”,而是必须完成的“必答题”。

RSA非对称加密算法,凭借其公钥加密、私钥解密的特性,天然适合解决客户端与服务器端在不安全信道下安全交换密钥或加密少量核心数据的问题。它不像对称加密(如AES)那样需要预先共享一个密钥,避免了密钥分发过程中的安全风险。而Hutool,作为一个功能强大且“不重复造轮子”的Java工具库,它封装了包括RSA在内的多种加密工具,其API设计极其友好,能让我们在SpringBoot项目中,以极低的成本、极高的效率实现接口的加密传输。

这个实战项目的目标非常明确: 在5分钟内,为你的SpringBoot API接口集成RSA加密传输能力。 我们将从零开始,生成RSA密钥对,到后端编写加解密工具类,再到设计一个完整的加密请求/响应流程,最后通过Postman进行测试验证。整个过程我会附上每一步的完整代码,并重点讲解那些官方文档不会告诉你的“坑”和最佳实践,确保你拿到的是一份能直接运行、安全可靠的解决方案。

2. 核心工具与原理:Hutool RSA的“三板斧”

在动手编码之前,我们必须先理解手中的工具和背后的原理。知其然,更要知其所以然,这样在遇到问题时你才能从容应对,而不是盲目地复制粘贴代码。

2.1 Hutool Crypto模块简介

Hutool的 cn.hutool.crypto 包是其加密解密功能的集大成者。它支持对称加密(AES、DES)、非对称加密(RSA、DSA)、摘要算法(MD5、SHA256)等多种加密方式。其最大优点在于 极简的API 对JDK原生复杂操作的深度封装 。例如,原生Java进行RSA操作需要处理 KeyPairGenerator Cipher Base64 编解码等一系列繁琐步骤,而Hutool可能只需要一行代码。

对于RSA,Hutool提供了 RSA 类,它内部已经处理了密钥格式(PKCS#8)、填充方式(默认使用 RSA/ECB/PKCS1Padding )等细节。我们只需要关心:公钥是什么?私钥是什么?要加密/解密的文本是什么?

2.2 RSA非对称加密原理解读

简单来说,RSA算法基于一个简单的数论事实: 将两个大质数相乘是容易的,但想要对其乘积进行因式分解却是极其困难的

  1. 密钥生成 :随机选择两个大质数p和q,计算它们的乘积n(n就是模数)。再计算欧拉函数φ(n) = (p-1)*(q-1)。然后选择一个整数e(通常为65537),作为公钥指数,要求e与φ(n)互质。接着,计算私钥指数d,使得 (d * e) % φ(n) = 1 。最终,公钥就是 (n, e) ,私钥就是 (n, d)
  2. 加密过程 :假设明文为m(在计算机中是一段数字),加密运算为 c = m^e mod n 。c即为密文。
  3. 解密过程 :解密运算为 m = c^d mod n

在Hutool中,我们无需关心这些数学细节。我们只需要知道:

  • 公钥加密,私钥解密 :常用于客户端用公钥加密数据发送给服务器,服务器用私钥解密。确保只有持有私钥的服务器能读取数据。
  • 私钥签名,公钥验签 :服务器用私钥对数据生成签名,客户端用公钥验证签名。用于验证数据是否由服务器发出且未被篡改。

注意 :RSA算法本身有长度限制。对于 RSA/ECB/PKCS1Padding 这种填充方式,加密的明文长度不能超过 密钥长度/8 - 11 字节。例如,2048位的密钥(256字节),最大加密明文长度为 256 - 11 = 245 字节。因此,RSA通常不用于加密长文本,而是用于加密对称加密的密钥(如AES密钥),或者加密关键的小数据(如登录密码、令牌)。

2.3 密钥对生成与安全存储策略

密钥对是RSA安全体系的基石,其生成和存储必须谨慎。

生成密钥对: Hutool提供了非常简便的方法。你可以使用 RSA.generateKeyPair(密钥长度) 来生成。目前推荐使用 2048位 的密钥长度,它提供了足够的安全强度,且性能开销在可接受范围内。1024位已被认为不够安全,4096位则加解密性能下降明显,通常用于CA证书等更高安全要求的场景。

// 生成一对 2048 位的 RSA 密钥对
KeyPair keyPair = SecureUtil.generateKeyPair(“RSA”, 2048);
// 获取Base64编码的公钥和私钥字符串
String privateKey = Base64.encode(keyPair.getPrivate().getEncoded());
String publicKey = Base64.encode(keyPair.getPublic().getEncoded());

安全存储策略(实操心得): 这是最容易出错和忽视的环节。 绝对不要将私钥硬编码在客户端(如网页、移动端App)或提交到代码仓库(如Git)中!

  1. 后端私钥 :应存储在服务器的安全位置。在生产环境中,最佳实践是使用 硬件安全模块(HSM) 密钥管理服务(KMS,如阿里云KMS、AWS KMS) 。在开发或中小型项目中,可以将其放在 配置文件(如application.yml)中,并通过环境变量注入 ,或者存储在 独立的、访问权限严格控制的外部配置服务器 上。
  2. 前端公钥 :可以由后端接口动态下发(例如,在登录前先请求一个获取公钥的接口),或者打包在客户端中。虽然公钥可以公开,但动态下发可以方便后期轮换密钥。
  3. 配置文件示例(application.yml) :
    rsa:
      private-key: “你的Base64私钥字符串,很长...”
      public-key: “你的Base64公钥字符串,很长...”
    
    然后通过 @Value @ConfigurationProperties 注入到Spring Bean中。

3. SpringBoot后端实现:构建加密通信核心

理论准备就绪,现在开始实战。我们将创建一个SpringBoot项目,并构建完整的加密、解密、签名、验签能力。

3.1 项目初始化与依赖引入

首先,使用Spring Initializr或IDE创建一个标准的SpringBoot Web项目。在 pom.xml 中,除了SpringBoot的基础Web依赖,最关键的就是引入Hutool。

<dependencies>
    <!— SpringBoot Web —>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!— Hutool 全能工具包 —>
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.8.22</version> <!— 请使用最新稳定版本 —>
    </dependency>
    <!— 其他可能需要的依赖,如Lombok —>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

3.2 核心工具类 RsaUtils 封装

这是整个加密体系的心脏。我们将创建一个 RsaUtils 工具类,它从配置文件中读取密钥,并提供静态方法供全局调用。

package com.yourpackage.util;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;

/**
 * RSA 加解密工具类
 * 采用单例模式,初始化时加载密钥
 */
@Component
@Slf4j
public class RsaUtils {
    /**
     * 应用配置中的私钥
     */
    @Value(“${rsa.private-key}”)
    private String privateKeyStr;

    /**
     * 应用配置中的公钥
     */
    @Value(“${rsa.public-key}”)
    private String publicKeyStr;

    private RSA rsa;

    /**
     * 在Bean初始化后,构建RSA实例
     */
    @PostConstruct
    public void init() {
        if (StrUtil.hasBlank(privateKeyStr, publicKeyStr)) {
            log.error(“RSA密钥未配置!”);
            throw new RuntimeException(“RSA密钥配置错误”);
        }
        // 初始化RSA对象,传入私钥和公钥。
        // Hutool的RSA构造器:new RSA(私钥, 公钥)
        this.rsa = new RSA(privateKeyStr, publicKeyStr);
        log.info(“RSA工具初始化成功。”);
    }

    /**
     * 公钥加密
     * @param plainText 明文
     * @return Base64编码的密文
     */
    public String encryptWithPublicKey(String plainText) {
        try {
            // KeyType.PublicKey 表示使用公钥加密
            byte[] encryptBytes = rsa.encrypt(plainText.getBytes(“UTF-8”), KeyType.PublicKey);
            return Base64.encode(encryptBytes);
        } catch (Exception e) {
            log.error(“公钥加密失败”, e);
            throw new RuntimeException(“加密失败”, e);
        }
    }

    /**
     * 私钥解密
     * @param encryptedBase64Text Base64编码的密文
     * @return 解密后的明文
     */
    public String decryptWithPrivateKey(String encryptedBase64Text) {
        try {
            byte[] decryptBytes = rsa.decrypt(Base64.decode(encryptedBase64Text), KeyType.PrivateKey);
            return new String(decryptBytes, “UTF-8”);
        } catch (Exception e) {
            log.error(“私钥解密失败”, e);
            throw new RuntimeException(“解密失败”, e);
        }
    }

    /**
     * 私钥签名
     * @param content 待签名的内容
     * @return Base64编码的签名
     */
    public String sign(String content) {
        try {
            byte[] signBytes = rsa.sign(content.getBytes(“UTF-8”));
            return Base64.encode(signBytes);
        } catch (Exception e) {
            log.error(“私钥签名失败”, e);
            throw new RuntimeException(“签名失败”, e);
        }
    }

    /**
     * 公钥验签
     * @param content 原始内容
     * @param signBase64 Base64编码的签名
     * @return 验签是否通过
     */
    public boolean verify(String content, String signBase64) {
        try {
            return rsa.verify(content.getBytes(“UTF-8”), Base64.decode(signBase64));
        } catch (Exception e) {
            log.error(“公钥验签失败”, e);
            return false;
        }
    }

    // 提供获取当前公钥的方法,可供接口动态下发
    public String getPublicKey() {
        return publicKeyStr;
    }
}

关键点解析:

  1. @PostConstruct 注解确保Spring容器在注入配置值后,立即初始化 RSA 对象,避免空指针。
  2. 所有方法都进行了 try-catch 包装,并将 Exception 转换为 RuntimeException 或记录日志后抛出业务异常,避免密码学操作异常污染上层业务逻辑。
  3. 编解码统一使用 UTF-8 字符集,防止中文乱码。
  4. 输入输出都采用 Base64编码的字符串 ,因为加密后的字节数组是二进制数据,不适合在JSON或URL中直接传输,Base64是网络传输二进制数据的标准方式。

3.3 统一加密请求/响应体设计

为了规范前后端通信协议,我们需要定义一套标准的加密数据格式。

请求体(客户端 -> 服务器) :通常,客户端用公钥加密一个 data 字段,里面包含真正的业务参数。 响应体(服务器 -> 客户端) :服务器用私钥加密响应数据。更常见的做法是,对关键响应数据进行签名,确保其完整性和来源可信。

我们创建两个DTO类:

// EncryptedRequest.java
package com.yourpackage.dto;

import lombok.Data;
import javax.validation.constraints.NotBlank;

/**
 * 加密请求通用体
 */
@Data
public class EncryptedRequest {
    /**
     * 经过RSA公钥加密的业务数据(JSON字符串)
     */
    @NotBlank(message = “加密数据不能为空”)
    private String data;

    /**
     * 时间戳,用于防止重放攻击(可选,但推荐)
     */
    private Long timestamp;

    /**
     * 签名,用于验证请求完整性(可选,若需更高安全等级)
     */
    private String sign;
}
// EncryptedResponse.java
package com.yourpackage.dto;

import lombok.Data;

/**
 * 加密响应通用体
 */
@Data
public class EncryptedResponse<T> {
    /**
     * 响应状态码
     */
    private Integer code;
    /**
     * 响应消息
     */
    private String msg;
    /**
     * 经过RSA私钥加密的业务数据(JSON字符串),非加密接口可为null
     */
    private String encryptedData;
    /**
     * 对响应内容的签名(防止数据在传输中被篡改)
     */
    private String sign;
    /**
     * 未加密的简单数据(用于返回成功/失败状态等非敏感信息)
     */
    private T plainData;
}

3.4 全局控制器与AOP切面处理

我们不希望在每个Controller方法里都写重复的解密、加密代码。利用Spring的AOP(面向切面编程)或拦截器(Interceptor)是实现统一处理的最佳方式。这里我展示一个更直观的 自定义注解+Controller方法 的方式,AOP方式类似但更解耦。

首先,创建一个自定义注解,标记需要加密传输的接口:

// EncryptedApi.java
package com.yourpackage.annotation;

import java.lang.annotation.*;

/**
 * 标记该接口需要进行请求解密/响应加密
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EncryptedApi {
}

然后,创建一个BaseController,提供加解密方法,供其他Controller继承或调用。这里为了清晰,我们直接在Controller里写逻辑。

// TestEncryptController.java
package com.yourpackage.controller;

import com.yourpackage.annotation.EncryptedApi;
import com.yourpackage.dto.EncryptedRequest;
import com.yourpackage.dto.EncryptedResponse;
import com.yourpackage.util.RsaUtils;
import com.yourpackage.vo.LoginVO;
import com.yourpackage.dto.LoginDTO;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping(“/api/encrypt”)
@Slf4j
public class TestEncryptController {

    @Autowired
    private RsaUtils rsaUtils;

    /**
     * 获取RSA公钥接口(用于前端动态获取)
     */
    @GetMapping(“/publicKey”)
    public String getPublicKey() {
        return rsaUtils.getPublicKey();
    }

    /**
     * 处理加密请求的登录接口
     * @param encryptedRequest 加密的请求体
     * @return 加密的响应体
     */
    @PostMapping(“/login”)
    @EncryptedApi // 使用自定义注解标记
    public EncryptedResponse<LoginVO> login(@RequestBody EncryptedRequest encryptedRequest) {
        // 1. 解密请求数据
        String decryptedData = rsaUtils.decryptWithPrivateKey(encryptedRequest.getData());
        log.info(“解密后的数据: {}”, decryptedData);

        // 2. 将解密后的JSON字符串转换为业务对象
        LoginDTO loginDTO = JSONUtil.toBean(decryptedData, LoginDTO.class);
        // 这里可以进行业务验证,如用户名密码校验...
        if (!“admin”.equals(loginDTO.getUsername()) || !“123456”.equals(loginDTO.getPassword())) {
            return EncryptedResponse.<LoginVO>builder()
                    .code(401)
                    .msg(“用户名或密码错误”)
                    .plainData(null) // 错误信息可以不加密
                    .build();
        }

        // 3. 模拟业务处理,生成响应数据
        LoginVO loginVO = new LoginVO();
        loginVO.setUserId(1L);
        loginVO.setUsername(loginDTO.getUsername());
        loginVO.setToken(“模拟生成的JWT令牌”);

        // 4. 将响应对象转换为JSON字符串,并用私钥加密
        String responseJsonStr = JSONUtil.toJsonStr(loginVO);
        String encryptedResponseData = rsaUtils.encryptWithPrivateKey(responseJsonStr);

        // 5. 生成响应签名(对原始响应JSON签名)
        String sign = rsaUtils.sign(responseJsonStr);

        // 6. 构建加密响应
        return EncryptedResponse.<LoginVO>builder()
                .code(200)
                .msg(“登录成功”)
                .encryptedData(encryptedResponseData) // 敏感数据加密
                .sign(sign) // 附上签名
                .plainData(null) // 敏感数据已放在encryptedData,这里置空
                .build();
    }
}

对应的DTO和VO:

// LoginDTO.java
@Data
public class LoginDTO {
    private String username;
    private String password;
}
// LoginVO.java
@Data
public class LoginVO {
    private Long userId;
    private String username;
    private String token;
}

4. 前端模拟与接口测试实战

后端已经准备完毕,现在我们需要模拟前端行为,并使用Postman进行完整的接口测试。这是验证我们加密体系是否正常工作的关键一步。

4.1 模拟前端加密流程

前端(或测试客户端)的职责是:

  1. 调用 /api/encrypt/publicKey 接口获取公钥(或使用预先配置的公钥)。
  2. 将业务参数(如 {“username”: “admin”, “password”: “123456”} )序列化为JSON字符串。
  3. 使用获取到的公钥,对该JSON字符串进行RSA加密,得到Base64格式的密文。
  4. 将密文放入 EncryptedRequest 对象的 data 字段,并发送POST请求到加密接口。

这里我用一段Node.js的伪代码来示意前端逻辑:

// 伪代码,使用 node-rsa 库
const NodeRSA = require(‘node-rsa’);
const axios = require(‘axios’);

async function encryptedLogin() {
  // 1. 获取公钥 (假设接口返回的是 Base64 编码的 PKCS#8 公钥)
  const publicKeyBase64 = await axios.get(‘http://localhost:8080/api/encrypt/publicKey’);
  const publicKey = `—–BEGIN PUBLIC KEY—–\n${publicKeyBase64}\n—–END PUBLIC KEY—–`;

  // 2. 创建RSA实例并导入公钥
  const rsa = new NodeRSA();
  rsa.importKey(publicKey, ‘pkcs8-public-pem’);

  // 3. 准备业务数据并加密
  const businessData = {
    username: “admin”,
    password: “123456”
  };
  const jsonStr = JSON.stringify(businessData);
  const encryptedData = rsa.encrypt(jsonStr, ‘base64’); // 输出Base64密文

  // 4. 构建请求体
  const requestBody = {
    data: encryptedData,
    timestamp: Date.now()
  };

  // 5. 发送加密请求
  const response = await axios.post(‘http://localhost:8080/api/encrypt/login’, requestBody);
  console.log(‘加密响应:’, response.data);

  // 6. 处理加密响应(需要前端用公钥解密encryptedData字段)
  if (response.data.encryptedData) {
    const decryptedRespStr = rsa.decryptPublic(response.data.encryptedData, ‘utf8’);
    const realData = JSON.parse(decryptedRespStr);
    console.log(‘解密后的真实数据:’, realData);
  }
}

4.2 使用Postman进行完整测试

对于后端开发者,使用Postman测试更为直接。我们需要先在Postman中编写一段 Pre-request Script ,用JavaScript模拟加密过程。

步骤一:设置环境变量或全局变量 在Postman中,将你的 Base64公钥 保存为一个环境变量,例如 rsa_public_key

步骤二:编写Pre-request Script 在请求的 Pre-request Script 标签页中,粘贴以下代码。这段代码使用了Postman内置的 CryptoJS 库和 forge 库(Postman原生支持)来进行RSA加密。

// Postman Pre-request Script: RSA 加密
// 注意:此脚本依赖于 forge 库,Postman 内置支持

// 1. 获取公钥(Base64格式,不含头尾)
const publicKeyBase64 = pm.environment.get(“rsa_public_key”);
// 假设你的公钥是 PKCS#8 格式的 Base64 字符串
// 如果是从Hutool生成的,通常是这种格式

// 2. 准备要加密的业务数据
const requestData = {
    username: “admin”,
    password: “123456”
};
const jsonStr = JSON.stringify(requestData);
console.log(“待加密的原始JSON: “, jsonStr);

// 3. 使用 forge 进行 RSA 加密
// 将Base64公钥转换为字节数组
const publicKeyDer = forge.util.decode64(publicKeyBase64);
// 导入公钥
const publicKey = forge.pki.publicKeyFromAsn1(forge.asn1.fromDer(publicKeyDer));
// 加密数据,使用 RSAES-PKCS1-V1_5 填充方案(与Hutool默认匹配)
const encrypted = publicKey.encrypt(jsonStr, ‘RSAES-PKCS1-V1_5’);
// 将加密结果转换为Base64字符串
const encryptedBase64 = forge.util.encode64(encrypted);
console.log(“加密后的Base64: “, encryptedBase64);

// 4. 将加密后的数据设置到请求Body中
pm.request.body.raw = JSON.stringify({
    data: encryptedBase64,
    timestamp: new Date().getTime()
});
// 记得将请求Body格式设置为 JSON (application/json)

步骤三:配置请求

  • URL : POST http://localhost:8080/api/encrypt/login
  • Headers : Content-Type: application/json
  • Body : 选择 raw JSON ,但内容会被Pre-request Script覆盖。

步骤四:发送请求并查看结果 发送请求后,你会在响应中看到一个 encryptedData 字段,里面就是服务器用私钥加密后的响应数据。同时,还会有一个 sign 字段,是服务器对原始响应数据的签名。

步骤五(验证签名,可选) : 你可以在Postman的 Tests 标签页中编写脚本来验证签名,确保数据在传输过程中未被篡改。这需要用到公钥和 forge 库进行验签操作。

4.3 测试结果分析与验证

一个成功的测试响应应该类似这样:

{
    “code”: 200,
    “msg”: “登录成功”,
    “encryptedData”: “很长的一串Base64密文...”,
    “sign”: “另一串Base64签名...”,
    “plainData”: null
}

此时,你可以复制 encryptedData 的值,在服务器端写一个简单的测试方法,用 RsaUtils.decryptWithPrivateKey 解密,看看是否能得到正确的 LoginVO 的JSON字符串。同时,也可以用 RsaUtils.verify 方法,用公钥对 encryptedData 解密后的原文和 sign 进行验签,确保一致性。

5. 深度优化、安全加固与生产级考量

一个能在生产环境跑起来的加密方案,绝不仅仅是“能加密解密”这么简单。下面这些点,是我在多个项目中趟过坑后总结出来的经验,它们决定了你方案的安全性和健壮性。

5.1 性能优化与数据分段加密

如前所述,RSA不适合加密大块数据。一个常见的优化模式是 “RSA + AES” 混合加密

  1. 流程
    • 客户端每次会话随机生成一个AES密钥(session key)。
    • 客户端用服务器的RSA公钥加密这个AES密钥,发送给服务器。
    • 服务器用RSA私钥解密,得到AES密钥。
    • 后续所有通信数据,都用这个AES密钥进行对称加密和解密。
  2. 优势 :结合了RSA的安全密钥交换和AES的高效大数据加密。
  3. 在SpringBoot中的实现思路 :可以设计一个握手接口( /handshake ),客户端发送加密的AES密钥,服务器解密后将其存入当前会话(Session)或与一个临时Token绑定,后续请求头中携带此Token来标识使用哪个AES密钥。

5.2 防御重放攻击与数据篡改

我们的基础方案已经通过签名(Sign)来防御数据篡改。但要防御重放攻击(攻击者截获一个有效的加密请求并重复发送),还需要更多策略。

  1. 时间戳+有效期 :在 EncryptedRequest 中强制要求 timestamp 字段。服务器收到请求后,检查当前服务器时间与 timestamp 的差值。如果超过一个合理的窗口(如5分钟),则拒绝请求。
    // 在Controller解密后,业务逻辑前
    Long timestamp = encryptedRequest.getTimestamp();
    if (timestamp == null || Math.abs(System.currentTimeMillis() - timestamp) > 5 * 60 * 1000) {
        throw new BusinessException(“请求已过期”);
    }
    
  2. Nonce(一次性随机数) :客户端每次请求生成一个唯一的随机字符串 nonce ,并和 timestamp 一起放入待加密的 data 中,或者作为明文字段。服务器维护一个短时间内(如5分钟)已使用过的 nonce 缓存(如使用Redis)。如果接收到重复的 nonce ,则判定为重放攻击,拒绝请求。这比单纯的时间戳更安全。
  3. 签名涵盖所有参数 :在生成签名时,不仅要对业务数据签名,还要将 timestamp nonce 甚至请求路径(URI)一起纳入签名计算,这样任何一部分被篡改都会导致验签失败。

5.3 密钥轮换与动态管理

一套密钥永远不换是不安全的。你需要一个密钥轮换策略。

  1. 双密钥对机制 :系统同时维护两对密钥: (公钥A, 私钥A) (公钥B, 私钥B) 。当前使用A对,B对作为备用。
  2. 客户端兼容 :客户端在请求公钥接口时,服务器可以返回一个公钥列表及其ID。客户端使用最新的公钥进行加密。当密钥需要轮换时,服务器将B对设为主密钥,并在一段时间内同时支持用A私钥解密旧数据,用B私钥解密新数据。
  3. 轮换时机 :可以定期(如每季度)轮换,也可以在怀疑密钥可能泄露时立即强制轮换。轮换过程需要平滑,确保线上业务不受影响。

5.4 常见陷阱与排查清单

即使按照教程一步步来,你也可能会遇到以下问题。这里是一个快速排查清单:

| 问题现象 | 可能原因 | 解决方案 | | :— | :— | :— | | 解密失败: DecryptionException InvalidKeyException | 1. 公钥私钥不匹配。
2. 密钥格式错误(如PEM格式带了 —–BEGIN… 头,但Hutool需要纯Base64)。
3. 加密的填充方式与解密时不一致。 | 1. 检查配置的 private-key public-key 是否是一对。
2. 确保配置的是 Base64编码后的纯字符串 ,不含头尾标记和换行符。
3. Hutool默认使用 RSA/ECB/PKCS1Padding ,确保前端加密库使用相同填充。 | | 前端加密后,后端解密得到乱码 | 1. 前后端字符集不一致(如前端 UTF-8 ,后端 GBK )。
2. 在加密前或解密后,没有正确处理字节到字符串的转换。 | 1. 统一使用 UTF-8
2. 在 RsaUtils 中,显式指定 getBytes(“UTF-8”) new String(bytes, “UTF-8”) 。 | | 签名验证总是失败 | 1. 签名内容和验签内容不完全一致(如空格、换行符差异)。
2. 签名算法不一致。 | 1. 确保用于签名和验签的字符串 完全一致 。建议在签名前对JSON字符串进行一次标准化(如按字母序排序键)。
2. Hutool RSA默认使用 SHA256withRSA ,检查前端库是否使用相同算法。 | | 加密数据长度超限 | 明文长度超过了RSA密钥长度限制。 | 改用“RSA+AES”混合加密方案。RSA只用于加密短的AES密钥。 | | rsa public key not find 错误 | 通常出现在使用某些数据库客户端(如Navicat)或特定库时,表示无法加载或解析公钥。 | 检查公钥字符串格式是否正确、完整,是否符合该工具要求的格式(如PEM格式需要正确的头尾行)。在Hutool场景下,确保你传递给 new RSA(privateKey, publicKey) 的字符串是纯Base64。 |

一个关键的实操心得 :在开发联调阶段, 务必先关闭加密 ,让接口以明文方式调通。然后再分别测试:1) 前端加密、后端解密;2) 后端加密、前端解密。分步排查,能极大降低调试复杂度。可以将 RsaUtils 中的加解密方法加上详细的日志,打印出输入输出的Base64字符串,便于对比。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值