springboot 对 RequestBody入参统一加解密处理
一、前言
在实质第三方系统对接或者安全系数比较搞得数据处理过程中,往往需要对通讯进行加密处理,而我们实际开发前期可能不会进行对对应得数据通讯加解密操作,只有在项目上线,方加上对应得加密方法,而如果对代码进行大批量修改加密,不但不利于代码得扩展,而且代码审美,维护上都存在瑕疵,那么本小结主要介绍springboot 基于spring的面向切面编程对数据链路实现自动无侵入式的加解密操作。
二、实现步骤
1、实现RequestBodyAdvice接口,实现supports方法并做是否需要做解密操作
2、实现ResponseBodyAdvice接口,实现supports方法并做是否需要做加密处理
3、编写加密配置类,并做加密yaml配置
4、编写方法注解类,以便后续做是否检查判断
5、将配置及实现类配置为bean
三、代码案例
1、实现RequestBodyAdvice接口,实现supports方法并处理是否做解密操作
package com.jiuzhou.common.advice;
import com.jiuzhou.common.annotation.Decrypt;
import com.jiuzhou.common.configure.SecretKeyConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
import java.lang.reflect.Type;
/**
* 【问题】群组表
* github地址 http://www.github.com/wanyushu
* gitee地址 http://www.gitee.com/wanyushu
* @author yushu
* @email 921784721@qq.com
* 对请求的参数进行解密
**/
@ControllerAdvice
public class DecryptRequestBodyAdvice implements RequestBodyAdvice {
private Logger log = LoggerFactory.getLogger(this.getClass());
private boolean encrypt;
@Autowired
private SecretKeyConfig secretKeyConfig;
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
if (methodParameter.getMethod().isAnnotationPresent(Decrypt.class) && secretKeyConfig.isOpen()) {
encrypt = true;
}
return encrypt;
}
@Override
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType){
if (encrypt) {
try {
return new DecryptHttpInputMessage(inputMessage, secretKeyConfig.getPublicKey(), secretKeyConfig.getCharset(),secretKeyConfig.isShowLog());
} catch (Exception e) {
log.error("Decryption failed", e);
}
}
return inputMessage;
}
@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
}
2、实现ResponseBodyAdvice接口,实现supports方法并做加密判断
package com.jiuzhou.common.advice;
import com.alibaba.fastjson.JSON;
import com.jiuzhou.common.annotation.Encrypt;
import com.jiuzhou.common.configure.SecretKeyConfig;
import com.jiuzhou.utils.AESEncryption;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
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.util.StringUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
/**
* github地址 http://www.github.com/wanyushu
* gitee地址 http://www.gitee.com/wanyushu
* @author yushu
* @email 921784721@qq.com
* 对请求的参数进行加密
**/
@ControllerAdvice
public class EncryptResponseBodyAdvice implements ResponseBodyAdvice<Object> {
private Logger log = LoggerFactory.getLogger(this.getClass());
private boolean encrypt;
@Autowired
private SecretKeyConfig secretKeyConfig;
private static ThreadLocal<Boolean> encryptLocal = new ThreadLocal<>();
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
encrypt = false;
if (returnType.getMethod().isAnnotationPresent(Encrypt.class) && secretKeyConfig.isOpen()) {
encrypt = true;
}
return encrypt;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
// EncryptResponseBodyAdvice.setEncryptStatus(false);
// Dynamic Settings Not Encrypted
Boolean status = encryptLocal.get();
if (null != status && !status) {
encryptLocal.remove();
return body;
}
if (encrypt) {
String publicKey = secretKeyConfig.getPublicKey();
try {
String content = JSON.toJSONString(body);
if (!StringUtils.hasText(publicKey)) {
throw new NullPointerException("Please configure rsa.encrypt.privatekeyc parameter!");
}
//加密过程
final String result = AESEncryption.encrypt(content, publicKey);
// String result = SMCipherCaculater.instance.SM4_encrypt(content.getBytes(Charset.defaultCharset()), publicKey.getBytes(Charset.defaultCharset())).toString();
if(secretKeyConfig.isShowLog()) {
log.info("Pre-encrypted data:{},After encryption:{}", content, result);
}
return result;
} catch (Exception e) {
log.error("Encrypted data exception", e);
}
}
return body;
}
}
3、编写加密配置类,并做加密yaml配置
package com.jiuzhou.common.configure;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* Author:Bobby
* DateTime:2019/4/9
**/
@Data
@ConfigurationProperties(prefix = "aes.encrypt")
@Configuration
public class SecretKeyConfig {
private String privateKey;
private String publicKey;
private String charset = "UTF-8";
private boolean open = false;
private boolean showLog = false;
private Integer time;
}
aes:
encrypt:
open: true # 是否开启加密 true or false
showLog: true # 是否打印加解密log true or false
publicKey: mN4Yn8Or8r7SH1w3 # AES密钥
privateKey: # RSA私钥
time: 600000
4、编写方法注解类,以便后续做是否检查判断
package com.jiuzhou.common.annotation;
import java.lang.annotation.*;
/**
* github地址 http://www.github.com/wanyushu
* gitee地址 http://www.gitee.com/wanyushu
* @author yushu
* @email 921784721@qq.com
**/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Encrypt {
}
package com.jiuzhou.common.annotation;
import java.lang.annotation.*;
/**
* github地址 http://www.github.com/wanyushu
* gitee地址 http://www.gitee.com/wanyushu
* @author yushu
* @email 921784721@qq.com
**/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Encrypt {
}
5、配置spring bean
package com.jiuzhou.common.annotation;
import com.jiuzhou.common.advice.DecryptRequestBodyAdvice;
import com.jiuzhou.common.advice.EncryptResponseBodyAdvice;
import com.jiuzhou.common.configure.SecretKeyConfig;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
/**
* github地址 http://www.github.com/wanyushu
* gitee地址 http://www.gitee.com/wanyushu
* @author yushu
* @email 921784721@qq.com
**/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Import({SecretKeyConfig.class,
EncryptResponseBodyAdvice.class,
DecryptRequestBodyAdvice.class})
public @interface EnableSecurity {
}
package com.jiuzhou;
import com.jiuzhou.common.annotation.EnableSecurity;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@EnableSecurity
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class,args);
}
}
四、demo演示
1、在controller添加加解密注解 @Encrypt @Decrypt
package com.jiuzhou.controller;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jiuzhou.common.annotation.Decrypt;
import com.jiuzhou.common.annotation.Encrypt;
import com.jiuzhou.common.annotation.Repeat;
import com.jiuzhou.entity.QuestionGroup;
import com.jiuzhou.service.QuestionGroupService;
import com.jiuzhou.utils.RestResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Date;
import java.util.List;
/**
* 【问题】群组表
* github地址 http://www.github.com/wanyushu
* gitee地址 http://www.gitee.com/wanyushu
* @author yushu
* @email
* @date 2023-04-27 18:00:53
*/
@Api(tags = "【问题】群组表controller",value= "【问题】群组表相关接口")
@RestController
@RequestMapping("questionGroup")
public class QuestionGroupController {
@Autowired
private QuestionGroupService questionGroupService;
/**
* 根据id删除对应的问题组
*/
@Encrypt
@Decrypt
@Repeat
@ApiOperation(value = "新增聊天组")
@PostMapping("save")
public RestResult save(@RequestBody QuestionGroup questionGroup){
if(StringUtils.isEmpty(questionGroup.getGroupName())){
return RestResult.failed("新增聊天组名不能为空");
}
questionGroup.setCreateTime(new Date());
questionGroup.setIsDel(0);
System.out.println("入参情况打印:"+JSON.toJSONString(questionGroup));
return RestResult.ok();
}
}
2 、创建加密密钥

3、postman请求
post请求
http://127.0.0.1:8081/questionGroup/save
入参 application/json
VViXp/JPYRbUWSCII5NbFU8avzSotbpgXEuyMZP9csE=
postman输出

控制台打印信息

五、总结
在使用的过程中,大家注意
1、开启 加密的同时,需要在springboot启动类上面加@EnableSecurity 注解便spring初始化对应的bean信息
2、在生成环境使用,请注意关闭openlog
3、开发的过程中,可以将open设置为false
4、如果使用其它加密算法可以自行修改EncryptResponseBodyAdvice 类
5、最后加密比较耗性能,注意只有在安全级别较高时使用,对具体的方法加密需要添加@Decrypt
6、对输入的参数解密请在controller层方法上加@Encrypt
7、需要修改加解密密钥,请长度设置为16的倍数的字符串长度
七、源码分享
以上本编为大家演示了springboot的加解密参数统一处理,如有问题或建议,可在评论区留言或私信,记得帮忙点赞并收藏,分享给大家让程序猿的生活不在为编码而愁!
最后源码已上传至 springboot框架使用技巧
3742

被折叠的 条评论
为什么被折叠?



