⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 夏壹分享 「夏壹分享」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

一、架构设计

核心设计理念

本文将详细讲解一套注解驱动的SpringBoot接口加解密方案,该方案具备高扩展性、可配置性,核心设计包含四大维度:

  1. 注解驱动 - 通过自定义@Crypto注解标记需要加解密的接口,精准控制接口加解密范围。
  2. 配置中心 - 支持动态开关加解密功能,同时可灵活选择加密算法,适配不同业务场景。
  3. 算法扩展 - 采用接口化设计封装加密算法,支持算法模块的可插拔替换,无需改动核心代码。
  4. 消息处理 - 基于Spring的Advice机制拦截请求/响应,无侵入式完成加解密处理。

二、代码实现

2.1 加解密注解定义

首先定义核心注解@Crypto,用于标记需要加解密的接口,可分别控制请求/响应的加解密开关:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 接口加解密注解
* 标记在Controller的方法上,控制该接口的请求/响应加解密行为
*/
@Target(ElementType.METHOD) // 注解仅作用于方法
@Retention(RetentionPolicy.RUNTIME) // 运行时保留,便于AOP拦截获取
public@interface Crypto {
/**
* 是否对响应体加密,默认开启
*/
boolean response() default true;

/**
* 是否对请求体解密,默认开启
*/
boolean request() default true;
}

2.2 加解密配置类

封装加解密相关配置项,通过配置文件注入,支持动态调整:

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
* 加解密配置属性类
* 读取配置文件中crypto前缀的配置项
*/
@Data// Lombok注解,自动生成get/set方法
@ConfigurationProperties(prefix = "crypto") // 绑定配置文件中crypto前缀的配置
publicclass CryptoProperties {
/**
* 加解密功能总开关,默认关闭
*/
privateboolean enabled = false;

/**
* 加密算法类型,默认AES
*/
private String algorithm = "aes";

/**
* AES算法密钥(BASE64格式)
*/
private String aesKey;
}

2.3 加解密处理器接口

为保证算法的可扩展性,定义统一的加解密处理器接口,所有加密算法需实现该接口:

import cn.hutool.crypto.CryptoException;

/**
* 加解密处理器接口
* 定义加解密的标准方法,所有加密算法实现类需遵循此规范
*/
public interface CryptoProcessor {

/**
* 加密方法
* @param content 待加密的明文内容
* @return 加密后的密文
* @throws CryptoException 加密异常
*/
String encrypt(String content) throws CryptoException;

/**
* 解密方法
* @param content 待解密的密文内容
* @return 解密后的明文
* @throws CryptoException 解密异常
*/
String decrypt(String content) throws CryptoException;
}

2.4 AES算法处理器实现类

基于AES/CBC/PKCS5Padding模式实现具体的加解密逻辑,是上述接口的核心实现:

import cn.hutool.crypto.CryptoException;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;

/**
* AES加解密处理器实现类
* 采用CBC模式+PKCS5Padding填充方式,IV向量取自密钥前16字节
*/
publicclass AESCryptoProcessor implements CryptoProcessor {
// AES算法模式:AES/CBC/PKCS5Padding
privatestaticfinal String ALGORITHM = "AES/CBC/PKCS5Padding";
// AES密钥对象
privatefinal SecretKey secretKey;
// 初始化向量(IV)
privatefinal IvParameterSpec iv;

/**
* 构造方法,初始化密钥和IV向量
* @param base64Key BASE64编码的AES密钥(16/24/32字节)
*/
public AESCryptoProcessor(String base64Key) {
// 解码BASE64格式的密钥
byte[] keyBytes = Base64.getDecoder().decode(base64Key);
// 创建AES密钥对象
this.secretKey = new SecretKeySpec(keyBytes, "AES");
// 取密钥前16字节作为IV向量(CBC模式必须指定IV)
this.iv = new IvParameterSpec(Arrays.copyOfRange(keyBytes, 0, 16));
}

/**
* AES加密实现
* @param content 待加密的明文
* @return BASE64编码的密文
* @throws CryptoException 加密失败异常
*/
@Override
public String encrypt(String content) {
try {
// 创建Cipher对象,指定算法模式
Cipher cipher = Cipher.getInstance(ALGORITHM);
// 初始化加密模式
cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
// 执行加密并编码为BASE64字符串
byte[] encrypted = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encrypted);
} catch (Exception e) {
thrownew CryptoException("AES加密失败", e);
}
}

/**
* AES解密实现
* @param content BASE64编码的密文
* @return 解密后的明文
* @throws CryptoException 解密失败异常
*/
@Override
public String decrypt(String content) {
try {
// 创建Cipher对象,指定算法模式
Cipher cipher = Cipher.getInstance(ALGORITHM);
// 初始化解密模式
cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
// 解码BASE64密文并执行解密
byte[] decoded = Base64.getDecoder().decode(content);
byte[] decrypted = cipher.doFinal(decoded);
// 转换为UTF-8编码的明文
returnnew String(decrypted, StandardCharsets.UTF_8);
} catch (Exception e) {
thrownew CryptoException("AES解密失败", e);
}
}
}

2.5 请求体解密Advice

基于Spring的RequestBodyAdvice拦截请求,对标记@Crypto的接口请求体进行解密处理:

import cn.hutool.crypto.CryptoException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.Objects;

/**
* 请求体解密Advice
* 拦截POST/PUT等带请求体的请求,对标记@Crypto的接口解密请求体
*/
@ControllerAdvice// 全局控制器增强
publicclass CryptoRequestBodyAdvice implements RequestBodyAdvice {

// 加解密处理器
privatefinal CryptoProcessor cryptoProcessor;
// 加解密配置
privatefinal CryptoProperties properties;
// JSON解析器
privatefinal ObjectMapper objectMapper = new ObjectMapper();

/**
* 构造注入依赖
* @param cryptoProcessor 加解密处理器
* @param properties 加解密配置
*/
public CryptoRequestBodyAdvice(CryptoProcessor cryptoProcessor, CryptoProperties properties) {
this.cryptoProcessor = cryptoProcessor;
this.properties = properties;
}

/**
* 判断是否需要处理当前请求
* @param parameter 方法参数
* @param targetType 目标类型
* @param converterType 消息转换器类型
* @return true=需要处理,false=跳过
*/
@Override
public boolean supports(MethodParameter parameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
// 条件:配置开启 + 方法有@Crypto注解 + 注解开启request解密
return properties.isEnabled()
&& parameter.hasMethodAnnotation(Crypto.class)
&& Objects.requireNonNull(parameter.getMethodAnnotation(Crypto.class)).request();
}

/**
* 读取请求体前解密处理
* @param inputMessage 原始请求消息
* @param parameter 方法参数
* @param targetType 目标类型
* @param converterType 消息转换器类型
* @return 解密后的请求消息
* @throws IOException IO异常
*/
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage,
MethodParameter parameter,
Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) throws IOException {

if (properties.isEnabled()) {
// 读取原始加密请求体
String encrypted = StreamUtils.copyToString(inputMessage.getBody(), StandardCharsets.UTF_8);
// 移除可能的首尾引号(前端传参可能带引号)
encrypted = encrypted.replaceAll("^\"|\"$", "");
// 解密请求体
String decrypted = cryptoProcessor.decrypt(encrypted);
// 验证解密后的数据格式是否合法(JSON)
validateDecryptedData(decrypted, targetType);
// 返回解密后的请求体
returnnew ByteArrayHttpInputMessage(
decrypted.getBytes(StandardCharsets.UTF_8),
inputMessage.getHeaders()
);
}
return inputMessage;
}

/**
* 验证解密后的数据是否符合目标类型的JSON格式
* @param decrypted 解密后的字符串
* @param targetType 目标类型
* @throws CryptoException 数据格式异常
*/
private void validateDecryptedData(String decrypted, Type targetType) {
try {
// 尝试解析JSON,验证格式合法性
new ObjectMapper().readValue(decrypted, constructJavaType(targetType));
} catch (IOException e) {
thrownew CryptoException("解密数据格式无效: " + e.getMessage(), e);
}
}

/**
* 构建JavaType对象(适配泛型)
* @param targetType 目标类型
* @return JavaType
*/
private JavaType constructJavaType(Type targetType) {
return TypeFactory.defaultInstance().constructType(targetType);
}

/**
* 自定义HttpInputMessage实现类
* 用于封装解密后的请求体字节数组
*/
publicstaticclass ByteArrayHttpInputMessage implements HttpInputMessage {
// 解密后的请求体字节数组
privatefinalbyte[] body;
// 请求头
privatefinal HttpHeaders headers;

public ByteArrayHttpInputMessage(byte[] body, HttpHeaders headers) {
this.body = body;
this.headers = new HttpHeaders();
this.headers.putAll(headers);
// 更新Content-Length为解密后的长度
this.headers.setContentLength(body.length);
}

@Override
public InputStream getBody() throws IOException {
// 返回字节数组输入流
returnnew ByteArrayInputStream(body);
}

@Override
public HttpHeaders getHeaders() {
return headers;
}
}

/**
* 读取请求体后处理(此处无需额外处理)
*/
@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}

/**
* 空请求体处理(此处无需额外处理)
*/
@Override
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}

/**
* 解析解密后的数据(备用方法)
* @param decrypted 解密后的字符串
* @param targetType 目标类型
* @return 解析后的对象
* @throws JsonProcessingException JSON解析异常
*/
private Object parseDecryptedData(String decrypted, Type targetType) throws JsonProcessingException {
return objectMapper.readValue(decrypted, objectMapper.constructType(targetType));
}
}

2.6 响应体加密Advice

基于Spring的ResponseBodyAdvice拦截响应,对标记@Crypto的接口响应体进行加密处理:

import cn.hutool.crypto.CryptoException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.util.Objects;

/**
* 响应体加密Advice
* 拦截控制器响应,对标记@Crypto的接口加密响应体
*/
@ControllerAdvice// 全局控制器增强
publicclass CryptoResponseBodyAdvice implements ResponseBodyAdvice<Object> {

// 加解密处理器
privatefinal CryptoProcessor cryptoProcessor;
// 加解密配置
privatefinal CryptoProperties properties;
// JSON解析器
privatefinal ObjectMapper objectMapper = new ObjectMapper();

/**
* 构造注入依赖
* @param cryptoProcessor 加解密处理器
* @param properties 加解密配置
*/
public CryptoResponseBodyAdvice(CryptoProcessor cryptoProcessor, CryptoProperties properties) {
this.cryptoProcessor = cryptoProcessor;
this.properties = properties;
}

/**
* 判断是否需要处理当前响应
* @param returnType 返回值类型
* @param converterType 消息转换器类型
* @return true=需要处理,false=跳过
*/
@Override
public boolean supports(MethodParameter returnType,
Class<? extends HttpMessageConverter<?>> converterType) {
// 条件:配置开启 + (方法有@Crypto注解且开启response加密 或 异常响应)
return (properties.isEnabled() && ((returnType.hasMethodAnnotation(Crypto.class)
&& Objects.requireNonNull(returnType.getMethodAnnotation(Crypto.class)).response()))
|| isExceptionResponse(returnType)
);
}

/**
* 判断是否为异常响应
* @param returnType 返回值类型
* @return true=异常响应,false=正常响应
*/
private boolean isExceptionResponse(MethodParameter returnType) {
// 异常处理类通常标记@ResponseBody
return Objects.requireNonNull(returnType.getMethod()).getDeclaringClass()
.isAnnotationPresent(ResponseBody.class);
}

/**
* 返回响应体前加密处理
* @param body 原始响应体
* @param returnType 返回值类型
* @param selectedContentType 响应媒体类型
* @param selectedConverterType 消息转换器类型
* @param request 服务端请求
* @param response 服务端响应
* @return 加密后的响应体
*/
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {

try {
// 仅对自定义Result类型的响应加密(适配业务统一返回格式)
if (body instanceof Result result && properties.isEnabled()) {
// 添加响应头,标记响应已加密
response.getHeaders().add("x-encrypt-response", "AES");
response.getHeaders().add("x-encrypt-error", "AES");
// 将响应体转为JSON字符串
String rawData = objectMapper.writeValueAsString(result);
// 加密并返回密文
return cryptoProcessor.encrypt(rawData);
}
} catch (Exception e) {
thrownew CryptoException("异常响应加密失败", e);
}
// 非Result类型直接返回原始数据
return body;
}
}

2.7 自动配置类

基于SpringBoot自动配置机制,初始化加解密相关Bean,降低接入成本:

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* 加解密自动配置类
* 自动初始化加解密相关Bean,无需手动配置
*/
@Configuration// 配置类
@ConditionalOnWebApplication// 仅在Web环境下生效
@EnableConfigurationProperties(CryptoProperties.class) // 启用配置属性绑定
public class CryptoAutoConfiguration {

/**
* 初始化加解密处理器Bean
* 根据配置的算法类型创建对应实现类
* @param properties 加解密配置
* @return 加解密处理器
*/
@Bean
@ConditionalOnMissingBean// 不存在时才创建,支持自定义覆盖
public CryptoProcessor cryptoProcessor(CryptoProperties properties) {
if ("aes".equalsIgnoreCase(properties.getAlgorithm())) {
// 校验AES密钥是否配置
if (properties.getAesKey() == null || properties.getAesKey().isEmpty()) {
thrownew IllegalArgumentException("AES密钥未配置,请设置crypto.aes-key");
}
returnnew AESCryptoProcessor(properties.getAesKey());
}
// 暂不支持其他算法,可扩展
thrownew UnsupportedOperationException("不支持的加密算法: " + properties.getAlgorithm());
}

/**
* 初始化请求体解密Advice Bean
* @param processor 加解密处理器
* @param properties 加解密配置
* @return 请求体解密Advice
*/
@Bean
public CryptoRequestBodyAdvice cryptoRequestBodyAdvice(CryptoProcessor processor,
CryptoProperties properties) {
returnnew CryptoRequestBodyAdvice(processor, properties);
}

/**
* 初始化响应体加密Advice Bean
* @param processor 加解密处理器
* @param properties 加解密配置
* @return 响应体加密Advice
*/
@Bean
public CryptoResponseBodyAdvice cryptoResponseBodyAdvice(CryptoProcessor processor,
CryptoProperties properties) {
returnnew CryptoResponseBodyAdvice(processor, properties);
}
}

2.8 配置文件示例

在application.yml中配置加解密相关参数,支持动态调整:

# 加解密配置
crypto:
enabled: true # 是否开启加解密功能,true=开启,false=关闭
algorithm: aes # 加密算法类型,当前仅支持aes
aes-key: "MTIzNDU2Nzg5MDEyMzQ1Ng==" # BASE64格式的AES密钥(16字节=128位,示例密钥仅用于测试,生产需替换为安全密钥)

三、验证测试

3.1 测试接口定义

编写用户登录接口,标记@Crypto注解启用加解密:

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* 用户控制器
* 测试加解密功能的示例接口
*/
@RestController
@RequestMapping("/api/auth/admin")
publicclass UserController {

privatefinal IUserService userService;

// 构造注入UserService
public UserController(IUserService userService) {
this.userService = userService;
}

/**
* 登录接口
* @Crypto注解:开启请求解密和响应加密
* @param userInfo 登录参数(自动解密)
* @return 登录结果(自动加密)
*/
@PostMapping("/login")
@Crypto// 标记接口需要加解密
public Result<String> login(@RequestBody UserDto userInfo) {
String token = userService.login(userInfo);
return Result.ok("登录成功", token);
}
}

3.2 测试Service实现

编写登录业务逻辑,模拟用户名密码校验:

import cn.dev33.satoken.stp.StpUtil;
import org.springframework.stereotype.Service;

/**
* 用户服务实现类
* 模拟登录业务逻辑
*/
@Service
publicclass UserServiceImpl implements IUserService {

/**
* 登录业务逻辑
* @param userInfo 登录参数
* @return 登录令牌
* @throws RuntimeException 用户名或密码错误时抛出异常
*/
@Override
public String login(UserDto userInfo) {
// 模拟用户名密码校验
if ("admin".equals(userInfo.getUsername()) && "123456".equals(userInfo.getPassword())) {
// 模拟登录,生成令牌
StpUtil.login(546);
return StpUtil.getTokenValue();
}
thrownew RuntimeException("用户名或密码错误");
}
}

3.3 单元测试

编写AES算法单元测试,验证加解密逻辑正确性:

import org.junit.jupiter.api.Test;
importstatic org.junit.jupiter.api.Assertions.assertEquals;

/**
* AES加解密处理器单元测试
* 验证加解密逻辑是否正确
*/
class AESCryptoProcessorTest {

// 测试用BASE64密钥(对应原始密钥:1234567890123456)
privatefinal String testKey = "MTIzNDU2Nzg5MDEyMzQ1Ng==";
// 初始化AES处理器
privatefinal AESCryptoProcessor aesCryptoProcessor = new AESCryptoProcessor(testKey);
// 测试明文
privatefinal String testContent = "{\n" +
" \"username\": \"admin\",\n" +
" \"password\": \"123456\",\n" +
" \"role\": \"\"\n" +
"}";

/**
* 测试加密方法
*/
@Test
void encrypt() {
String encrypt = aesCryptoProcessor.encrypt(testContent);
System.out.println("加密后的密文:" + encrypt);
// 密文非空校验
assert encrypt != null && !encrypt.isEmpty();
}

/**
* 测试解密方法
* 验证解密后的数据与原明文一致
*/
@Test
void decrypt() {
// 先加密生成密文
String encrypt = aesCryptoProcessor.encrypt(testContent);
// 解密密文
String decrypt = aesCryptoProcessor.decrypt(encrypt);
System.out.println("解密后的明文:" + decrypt);
// 校验解密结果与原明文一致
assertEquals(testContent.replaceAll("\\s", ""), decrypt.replaceAll("\\s", ""));
}
}

3.4 测试结果验证

1.执行单元测试,加密测试明文得到密文:

w4JeMik62JKILEZAulgav0rFDr+J2HegTk+2XxQgMXF4UrmiYPtLg5sd5pwPYqnXrskGhjC0VrFaVR1xFQz3dA==

2.调用登录接口,传入上述密文,接口返回加密后的响应:

6tmr623112r3OFkPes2XetsKcVO/FgDE+UD3cT8dxBWC8c8gKMNcqAhxPORuk3W581r0KKbDY/O4iOWX79HQN2hqv/LDMdlTEhJml4zZUd5qtomeU51eaHauujsg5FoGsn7/Hgq2vHze2q5JuQbQUw==

3.对返回的密文解密,得到最终响应结果:

{"code":200,"success":true,"msg":"登录成功","data":"xxxx-xxxx-xxxx-xxxx"}

四、前后端联调

4.1 前端加解密工具类

封装AES加解密工具类,与后端算法保持一致:

// /src/utils/crypto.js
import CryptoJS from'crypto-js'

// 加解密配置(与后端一致:CBC模式+PKCS7填充)
const CRYPTO_CONFIG = {
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}

/**
* AES加解密工具
* 与后端AESCryptoProcessor算法完全对齐
*/
exportconst aes = {
/**
* AES加密
* @param {Object} data 待加密的原始数据
* @param {String} key BASE64格式的AES密钥
* @returns {String} 加密后的密文
*/
encrypt(data, key) {
// 解码BASE64密钥
const keyBytes = CryptoJS.enc.Base64.parse(key)
// 取密钥前16字节作为IV向量(与后端一致)
const iv = CryptoJS.lib.WordArray.create(keyBytes.words.slice(0, 4))
// 执行加密
const encrypted = CryptoJS.AES.encrypt(
JSON.stringify(data),
keyBytes,
{ ...CRYPTO_CONFIG, iv }
)
// 返回BASE64格式密文
return encrypted.toString()
},

/**
* AES解密
* @param {String} ciphertext 加密后的密文
* @param {String} key BASE64格式的AES密钥
* @returns {Object} 解密后的原始数据
*/
decrypt(ciphertext, key) {
// 解码BASE64密钥
const keyBytes = CryptoJS.enc.Base64.parse(key)
// 取密钥前16字节作为IV向量(与后端一致)
const iv = CryptoJS.lib.WordArray.create(keyBytes.words.slice(0, 4))
// 执行解密
const decrypted = CryptoJS.AES.decrypt(
ciphertext,
keyBytes,
{ ...CRYPTO_CONFIG, iv }
)
// 转换为JSON对象返回
returnJSON.parse(decrypted.toString(CryptoJS.enc.Utf8))
}
}

4.2 axios请求封装

封装axios拦截器,自动加密请求体、解密响应体:

// /src/api/request.js
import axios from'axios'
import { aes } from'../utils/crypto'
import qs from'qs'

// 创建axios实例
const service = axios.create({
baseURL: import.meta.env.VUE_APP_API_BASE_URL, // 接口基础地址
timeout: 10000, // 请求超时时间
paramsSerializer:params => qs.stringify(params, { arrayFormat: 'repeat' }) // 参数序列化
})

/**
* 请求拦截器
* 对标记crypto:true的请求加密请求体
*/
service.interceptors.request.use(config => {
if (config.crypto) {
// 从环境变量获取AES密钥
const key = import.meta.env.VITE_VUE_APP_AES_KEY
// 加密请求体
config.data = aes.encrypt(config.data, key)
// 添加请求头,标记请求已加密
config.headers['x-encrypt-request'] = 'AES'
// 设置Content-Type为JSON
config.headers['Content-Type'] = 'application/json'
}
return config
},error => {
// 请求错误处理
returnPromise.reject(error)
})

/**
* 响应拦截器
* 对标记加密的响应自动解密
*/
service.interceptors.response.use(response => {
// 响应头包含x-encrypt-response,说明响应已加密
if (response.headers['x-encrypt-response']) {
const key = import.meta.env.VITE_VUE_APP_AES_KEY
// 解密响应体
response.data = aes.decrypt(response.data, key)
}
return response.data
},error => {
// 异常响应解密(如后端返回的加密错误信息)
if (error.response?.headers['x-encrypt-error']) {
const key = import.meta.env.VITE_VUE_APP_AES_KEY
const decryptedError = aes.decrypt(error.response.data, key)
// 替换错误信息为解密后的内容
error.message = decryptedError.msg || '请求失败'
}
console.log('请求失败:', error)
returnPromise.reject(error)
})

exportdefault service

4.3 测试接口调用

封装登录接口调用方法,启用加解密:

// /src/api/userInfoRequest.js
import request from "./request.js";

/**
* 登录接口请求
* @param {Object} data 登录参数(用户名+密码)
* @returns {Promise} 请求Promise
*/
export function postSecureData(data) {
return request({
url: '/api/auth/admin/login',
method: 'post',
data: data,
crypto: true // 启用加解密
})
}

4.4 Vue3页面测试

编写Vue3页面,调用登录接口测试加解密:

<template>
<div class="login-container">
<a-input v-model:value="user.username" placeholder="请输入用户名" style="margin-bottom: 10px"/>
<a-input v-model:value="user.password" type="password" placeholder="请输入密码" style="margin-bottom: 10px"/>
<a-button type="primary" @click="login">请求接口</a-button>
</div>
</template>

<script setup>
import { postSecureData } from'./api/userInfoRequest.js'
import { reactive } from"vue";
import { aes } from'./utils/crypto.js'
import { message } from"ant-design-vue";

// 响应式用户数据
const user = reactive({
username: 'admin',
password: '123456'
})

// 登录方法
const login = async () => {
try {
console.log("点击成功,原始数据:", user)
// 调用登录接口
const res = await postSecureData(user)
if (res.code === 200 && res.success) {
message.success('登录成功')
console.log('登录成功,令牌:', res.data)
} else {
message.error(res.msg)
}
} catch (error) {
message.error(error.message)
}
}

// 测试加解密工具类
const testKey = 'MTIzNDU2Nzg5MDEyMzQ1Ng=='
console.log('前端加密测试:', aes.encrypt(user, testKey))
console.log('前端解密测试:', aes.decrypt('6tmr623112r3OFkPes2XetsKcVO/FgDE+UD3cT8dxBWC8c8gKMNcqAhxPORuk3W52YPDDP0XKLacMp1Jk8h5MUDOsYKzVo6BaH6Y2aUdggJCu5+j/q65nMDMXTY5qPWwOFSYGIr4Hz9tm6VEwAXEAQ==', testKey))
</script>

4.5 联调结果验证

  1. 请求参数加密: 前端传入的{username: "admin", password: "123456"}会被自动加密为密文传输;
  2. 响应结果加密: 后端返回的登录结果会被自动加密,前端接收后解密展示;
  3. 异常场景处理: 用户名/密码错误时,后端抛出的异常信息会加密返回,前端解密后提示错误。

总结

本文实现的SpringBoot注解式接口加解密方案,具备以下优势:

  1. 无侵入性:基于注解和AOP实现,无需修改业务代码;
  2. 高扩展性:算法接口化设计,可快速扩展RSA、SM4等其他算法;
  3. 灵活性:支持动态开关、算法配置,适配不同环境需求;
  4. 易用性:前后端封装工具类,联调成本低。

该方案可直接应用于需要接口加解密的场景(如支付、用户敏感信息传输),提升接口安全性。

文章目录
  1. 1. 一、架构设计
    1. 1.1. 核心设计理念
  2. 2. 二、代码实现
    1. 2.1. 2.1 加解密注解定义
    2. 2.2. 2.2 加解密配置类
    3. 2.3. 2.3 加解密处理器接口
    4. 2.4. 2.4 AES算法处理器实现类
    5. 2.5. 2.5 请求体解密Advice
    6. 2.6. 2.6 响应体加密Advice
    7. 2.7. 2.7 自动配置类
    8. 2.8. 2.8 配置文件示例
  3. 3. 三、验证测试
    1. 3.1. 3.1 测试接口定义
    2. 3.2. 3.2 测试Service实现
    3. 3.3. 3.3 单元测试
    4. 3.4. 3.4 测试结果验证
  4. 4. 四、前后端联调
    1. 4.1. 4.1 前端加解密工具类
    2. 4.2. 4.2 axios请求封装
    3. 4.3. 4.3 测试接口调用
    4. 4.4. 4.4 Vue3页面测试
    5. 4.5. 4.5 联调结果验证
  5. 5. 总结