SpringBoot中,接口加解密
1、背景
在项目做等保,要求需要对接口数据进行加密,所做了调用方发送请求前,先对明文加密,然后发送密文,被调用方收到数据后,先进行解密,然后再进行处理。返回的结果同样也可以加密,被调用方将需要返回的数据进行加密,然后将密文返回给调用方,调用方收到后,再进行解密便得到明文。
2、解决方案
使用AES的方式对数据加密
AES介绍
- 对称加密
加密与解密使用同一密钥,效率高但需安全分发密钥。 - 分组密码
将明文分割为固定128位(16字节)的块独立处理。 - 密钥长度灵活
支持128位、192位、256位三种长度,安全性依次递增(AES-128/192/256)
利用RequestBodyAdviceAdapter,继承这个类重写其beforeBodyRead方法,完成请求在进入方法前对参数解密
RequestBodyAdviceAdapter介绍
RequestBodyAdviceAdapter 是 Spring Framework 中用于拦截和处理 **@RequestBody
注解参数**的核心组件,属于 Spring MVC 的全局增强机制。它通过 AOP 思想实现对请求体的统一预处理,避免在 Controller 中重复编写与业务无关的逻辑(如解密、校验、日志记录等)
方法 | 调用时机 | 用途 | 返回值 |
| 请求进入时优先判断 | 决定当前 Advice 是否生效(如按包路径、注解过滤) |
|
| 请求体被 读取前 | 修改原始请求体(如解密、字符编码转换) | 自定义的 |
| 请求体转换为 Java 对象后 | 修改对象(如参数校验、字段注入) | 处理后的 Java 对象 |
| 请求体为空时 | 处理空请求体场景(如设置默认值) | 替代空体的对象 |
在数据返回时候需要加密,用到ResponseBodyAdvice
ResponseBodyAdvice 介绍
ResponseBodyAdvice 是 Spring MVC(4.1+)及 Spring Boot 中用于全局拦截并定制化处理响应体的核心接口,通常与 @ControllerAdvice
或 @RestControllerAdvice
配合使用。它允许开发者在控制器方法执行后、响应数据写入 HTTP 响应体之前,对返回的数据进行统一处理,适用于多种通用场景(如数据包装、脱敏、日志记录等)。
- 定位与触发时机
- 作用阶段:在控制器方法执行完毕且返回值被
HttpMessageConverter
序列化之前介入。 - 触发条件:仅对标注
@ResponseBody
或@RestController
的控制器方法生效。
- 作用阶段:在控制器方法执行完毕且返回值被
- 核心方法
方法 | 作用 | 参数说明 |
| 判断当前响应是否需被处理(返回 则触发 ) |
|
| 实际处理响应体,可修改或替换原始返回值 |
|
作用方法指定方案
1.用到了@RestControllerAdvice这个注解指定扫描包路径来实现,这样就需要supports()
方法直接返回true
@ControllerAdvice(basePackages = "cn.shenzhihe.collection.controller")
2.如果想指定具体方法,可以自定义注解方式,在supports()方法下指定注解
@Overridepublic boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {return methodParameter.hasMethodAnnotation(Decrypt.class);}
3.代码实现
指定配置类讲AES注册到ioc
@Configuration
@EnableConfigurationProperties(SecureProperties.class)
public class SecureConfiguration {@Autowiredprivate SecureProperties secureProperties;@Beanpublic AES aes() {return SecureUtil.aes(this.secureProperties.getKey().getBytes(StandardCharsets.UTF_8));}
}
配置
@Getter
@Setter
@ConfigurationProperties("secure")
public class SecureProperties {/*** 秘钥(长度只能是 128、192或256位,一个普通字符是8位)*/private String key;
}
yml
secure:key: 1234567890123456
入参加密
/*** 请求到达controller中的方法之前,会拦截标注有 @Decrypt 注解或者指定包下的方法,负责将原始请求中的密文转换为明文*/
@ControllerAdvice(basePackages = "cn.shenzhihe.collection.controller")
public class DecryptRequestBodyAdvice extends RequestBodyAdviceAdapter {@Autowiredprivate AES aes;@Overridepublic boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {// 注解方式//return methodParameter.hasMethodAnnotation(Decrypt.class);//路径方式直接返回truereturn true;}@Overridepublic HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {String encoding = "UTF-8";try {//①:获取http请求中原始的bodyString body = IOUtils.toString(inputMessage.getBody(), encoding);//②:解密body,使用AES算法解密,得到明文String decryptBody = aes.decryptStr(body);//将解密之后的body数据重新封装为HttpInputMessage作为当前方法的返回值InputStream inputStream = new ByteArrayInputStream(decryptBody.getBytes(encoding));return new HttpInputMessage() {@Overridepublic InputStream getBody() throws IOException {return inputStream;}@Overridepublic HttpHeaders getHeaders() {return inputMessage.getHeaders();}};} catch (Exception e) {// 如果解密失败,返回原始消息return inputMessage;}}
}
/*** 在结果返回给调用者之前,会拦截标注有@Encrypt 注解或者包下方法的方法,对接口的返回值进行处理,将其转换为密文*/
@ControllerAdvice(basePackages = "cn.shenzhihe.collection.controller")
public class EncryptResponseBodyAdvice implements ResponseBodyAdvice<Object> {@Autowiredprivate AES aes;private ObjectMapper objectMapper = new ObjectMapper();@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {// 注解方式//return methodParameter.hasMethodAnnotation(Decrypt.class);//路径方式直接返回truereturn true;}@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {if (body == null) {return body;}String result;if (body instanceof String) {result = (String) body;} else {//如果是对象,则转换为json字符串try {result = objectMapper.writeValueAsString(body);} catch (JsonProcessingException e) {throw new RuntimeException(e);}}//加密返回return this.aes.encryptHex(result);}
}
两个注解一起使用,入参解密,返回加密
注解
/*** 接口方法上添加该注解,这表示这个接口的参数是被加密的,进入方法之前,参数会自动被解密*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Decrypt {
}
/*** 接口方法上添加该注解,则返回的结果,会自动加密*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Encrypt {
}
4.测试
@RestController
@RequestMapping("/secure")
@Slf4j
public class SecureController {/*** 参数加密测试,需要在方法上标注 @Decrypt 注解** @param body* @return*/// @Decrypt@PostMapping("/decryptTest")public List<String> decryptTest(@RequestBody List<String> body) {log.info("参数加密测试,请求参数:{}", body);return body;}// @Encrypt@GetMapping("/encryptTest")public List<String> encryptTest() {List<String> encyptList = new ArrayList<>();encyptList.add("1");encyptList.add("2");return encyptList;}
}
返回密文
解析密文