近期在项目中因为安全红线要求需要进行接口加解密,在此记录一下。

通过@ControllerAdvice扫描所有接口进行接口加密以及接口解密,本文选择的是AES加密,通过密匙及偏移量加密接口数据。

一.结构:

//加密方法,通过@ControllerAdvice扫描所有接口,对含有@EncryptResponse注解的类或者方法进行加密

/*** 请求响应处理类* 对加了@Encrypt的方法的数据进行加密操作** @author gzy*/
@ControllerAdvice
@Slf4j
public class EncryptResponseBodyAdvice implements ResponseBodyAdvice<Object> {@Overridepublic boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {//在这里调用needEncrypet方法判断是否需要加密return new NeedCrypto().needEncrypt(returnType);}//这个方法截取了接口中返回的对象,在对对象加密后返回@Overridepublic Object beforeBodyWrite(Object obj, MethodParameter returnType, MediaType selectedContentType,Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest serverHttpRequest,ServerHttpResponse serverHttpResponse) {// 通过 ServerHttpRequest的实现类ServletServerHttpRequest 获得HttpServletRequestServletServerHttpRequest sshr = (ServletServerHttpRequest) serverHttpRequest;// 此处获取到request 是为了取到在拦截器里面设置的一个对象 是我项目需要,可以忽略HttpServletRequest request = sshr.getServletRequest();String returnData;String realData = "";JSONObject data = new JSONObject();try {data = (JSONObject) JSONObject.toJSON(obj);if(!data.get("data").toString().isEmpty()){realData = data.get("data").toString();}// 添加encry header,告诉前端数据已加密serverHttpResponse.getHeaders().add("encrypt", "true");// 加密returnData = AESOperator.replace(AESUtil.encrypt(realData));log.debug("接口={},原始数据={},加密后数据={}", request.getRequestURI(), realData, returnData);data.put("data", returnData);} catch (Exception e) {log.error("异常!", e);}return data;}
}

//解密方法,通过@ControllerAdvice扫描所有接口,对含有@DecryptRequest注解的类或者方法进行解密

/*** 请求数据接收处理类* 对加了@Decrypt的方法的数据进行解密操作** @author gzy*/
@ControllerAdvice
@Slf4j
public class DecryptRequestBodyAdvice implements RequestBodyAdvice {//needDecrypt判断是否需要解密@Overridepublic boolean supports(MethodParameter methodParameter, Type targetType,Class<? extends HttpMessageConverter<?>> converterType) {return new NeedCrypto().needDecrypt(methodParameter);}@Overridepublic Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter,Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {return body;}@Overridepublic HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,Class<? extends HttpMessageConverter<?>> converterType) throws IOException {return inputMessage;}//拦截接口中的入参,对入参进行解密后返回@Overridepublic Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,Class<? extends HttpMessageConverter<?>> converterType) {JSONObject dealData = new JSONObject();try {// 解密操作JSONObject encryptObject = (JSONObject) JSONObject.toJSON(body);String srcData = String.valueOf(encryptObject.get("encryptData"));String decryptData = AESUtil.decrypt(srcData);dealData = JSON.parseObject(decryptData);} catch (Exception e) {log.error("请求body参数格式解析异常!", e);}return dealData;}
}

//NeedCrypto类,判断是否需要进行加解密,类中是否需要加解密的逻辑可以根据需要进行修改,

目前代码中的逻辑为:注解在类上加密解密整个类,在方法是加密解密单个方法

/*** 判断是否需要加解密** @author gzy* @date 2021/09/03*/
@Configuration
@Log4j2
public class NeedCrypto {//开关变量,在配置文件中配置是否开启加解密功能,不需要可以去掉@Value("${encrypt.api.flag}")private boolean encryptApiFlag;private static boolean flag = false;@PostConstructpublic void NeedCrypto() {flag = encryptApiFlag;}/*** 是否需要对结果加密* 1.类上标注或者方法上标注,并且都为true* 2.有一个标注为false就不需要加密*/public boolean needEncrypt(MethodParameter returnType) {boolean encrypt = false;if (flag) {boolean classPresentAnno = returnType.getContainingClass().isAnnotationPresent(EncryptResponse.class);boolean methodPresentAnno = returnType.getMethod().isAnnotationPresent(EncryptResponse.class);if (classPresentAnno) {//类上标注的是否需要加密encrypt = returnType.getContainingClass().getAnnotation(EncryptResponse.class).value();if (encrypt) {return encrypt;}//类不加密,所有都不加密/**if(!encrypt){return false;}*/}if (methodPresentAnno) {//方法上标注的是否需要加密encrypt = returnType.getMethod().getAnnotation(EncryptResponse.class).value();}}return encrypt;}/*** 是否需要参数解密* 1.类上标注或者方法上标注,并且都为true* 2.有一个标注为false就不需要解密*/public boolean needDecrypt(MethodParameter parameter) {boolean encrypt = false;if (flag) {boolean classPresentAnno = parameter.getContainingClass().isAnnotationPresent(DecryptRequest.class);boolean methodPresentAnno = parameter.getMethod().isAnnotationPresent(DecryptRequest.class);if (classPresentAnno) {//类上标注的是否需要解密encrypt = parameter.getContainingClass().getAnnotation(DecryptRequest.class).value();if (encrypt) {return encrypt;}//类不加密,所有都不加密/**if (!encrypt) {return false;}*/}if (methodPresentAnno) {//方法上标注的是否需要解密encrypt = parameter.getMethod().getAnnotation(DecryptRequest.class).value();}}return encrypt;}
}

ps:encryptApiFlag变量为开关变量,在配置文件中配置加解密是否生效,不需要可以去除这部分

@Value("${encrypt.api.flag}")
private boolean encryptApiFlag;

yml配置文件:

//AESUtil 加解密工具类,这里可以换成自己需要的加密方法

/*** @author gzy*/
public class AESUtil {/*** 加密用的Key 可以用26个字母和数字组成 此处使用AES-128-CBC加密模式,key需要为16位。*/public static final String skey = "smkldospd121daaa";/*** 加密偏移量*/private static final String ivParameter = "1016449182184177";public static String encrypt(String srcData) throws Exception {String enString = AESOperator.getInstance().Encrypt(srcData, skey, ivParameter);return enString;}public static String decrypt(String srcData) throws Exception {String DeString = AESOperator.getInstance().Decrypt(srcData, skey, ivParameter);return DeString;}
}

二.自定义注解:

添加在类或方法中判断接口是否需要加解密

@EncryptResponse (加密)

@DecryptRequest (解密)

/*** 加密注解** <p>加了此注解的接口(true)将进行数据加密操作* 可以放在类上,可以放在方法上 </p>** @author gzy*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EncryptResponse {/*** 是否对结果加密*/boolean value() default true;
}
/*** 解密注解** <p>加了此注解的接口(true)将进行数据解密操作(post的body) 可* 以放在类上,可以放在方法上 </p>** @author gzy*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DecryptRequest {/*** 是否对body进行解密*/boolean value() default true;
}

三.AES加密

//加解密操作类,本文使用的是AES,可以换成自己需要的加解密方式

// 提供了两种加解密方式(加入或者不加入偏移量,通过加入偏移量来增加加密算法复杂度)。这里需要注意一点,加密后的字符串需要去除换行符及制表符,不然前端解密时会出现无法解密的问题(下文类中replace方法)。

/*** AES加解密操作类*/
public class AESOperator {/*** 加密用的Key 可以用26个字母和数字组成 此处使用AES-128-CBC加密模式,key需要为16位。*/public static final String skey = "smkldospd121daaa";/*** 偏移量,可自行修改*/private static String ivParameter = "1016449182184177";private static AESOperator instance = null;private AESOperator() {}public static AESOperator getInstance() {if (instance == null) {instance = new AESOperator();}return instance;}public String Encrypt(String encData, String secretKey, String vector) throws Exception {if (secretKey == null) {return null;}if (secretKey.length() != 16) {return null;}Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");byte[] raw = secretKey.getBytes();SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");// 使用CBC模式,需要一个向量iv,可增加加密算法的强度IvParameterSpec iv = new IvParameterSpec(vector.getBytes());cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);byte[] encrypted = cipher.doFinal(encData.getBytes("utf-8"));// 此处使用BASE64做转码。return new BASE64Encoder().encode(encrypted);}/*** 加密** @param sSrc* @param sKey* @return* @throws Exception*/public String encrypt(String sSrc, String sKey) throws Exception {Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");byte[] raw = sKey.getBytes();SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");// 使用CBC模式,需要一个向量iv,可增加加密算法的强度IvParameterSpec iv = new IvParameterSpec(ivParameter.getBytes());cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);byte[] encrypted = cipher.doFinal(sSrc.getBytes("utf-8"));// 此处使用BASE64做转码。return replace(new BASE64Encoder().encode(encrypted));}/*** 解密** @param sSrc* @param sKey* @return*/public String decrypt(String sSrc, String sKey) {try {byte[] raw = sKey.getBytes("ASCII");SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");IvParameterSpec iv = new IvParameterSpec(ivParameter.getBytes());cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);// 先用base64解密byte[] encrypted1 = new BASE64Decoder().decodeBuffer(sSrc);byte[] original = cipher.doFinal(encrypted1);String originalString = new String(original, "utf-8");return originalString;} catch (Exception ex) {return null;}}public String Decrypt(String sSrc, String key, String ivs) {try {byte[] raw = key.getBytes("ASCII");SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");IvParameterSpec iv = new IvParameterSpec(ivs.getBytes());cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);// 先用base64解密byte[] encrypted1 = new BASE64Decoder().decodeBuffer(sSrc);byte[] original = cipher.doFinal(encrypted1);String originalString = new String(original, "utf-8");return originalString;} catch (Exception ex) {return null;}}public static String encodeBytes(byte[] bytes) {StringBuffer strBuf = new StringBuffer();for (int i = 0; i < bytes.length; i++) {strBuf.append((char) (((bytes[i] >> 4) & 0xF) + ((int) 'a')));strBuf.append((char) (((bytes[i]) & 0xF) + ((int) 'a')));}return strBuf.toString();}/*** 去除 换行符、制表符** @param str* @return*/public static String replace(String str) {if (!StringUtil.isEmpty(str)) {return str.replaceAll("\r|\n", "");}return str;}//测试public static void main(String[] args) throws Exception {// 需要加密的字串String cSrc = "{\"loginName\":\"master\",\"secret\":\"123456\"}";System.out.println(cSrc);// 加密String enString = AESOperator.getInstance().Encrypt(cSrc, skey, ivParameter);System.out.println("加密后的字串是:" + replace(enString));String test = replace(enString);// 解密String DeString = AESOperator.getInstance().Decrypt(test, skey, ivParameter);System.out.println("解密后的字串是:" + DeString);}
}

四.实战

注解加在类或者方法上就可以使用啦(类的优先级高于方法)

ps:解密使用时入参不可以直接写实体类,需要先用json接收,使用实体类无法存放加密后的数据

@DecryptRequest
@EncryptResponse
@RestController
@RequestMapping("/menu")
public class MenuController {
.......
}或/*** 新增菜单** @param params menu* @return addMenu*/@DecryptRequest@EncryptResponse@PostMapping("/add")public ApiResult addMenu(@RequestBody JSONObject params) {Menu menu = params.toJavaObject(Menu.class);return menuService.addMenu(menu);}

欢迎大家技术交流,有问题可以与我联系,微信13022509053

springboot中的后端接口加密解密相关推荐

  1. 【SpringBoot】22、SpringBoot中整合knife4j接口文档

    在项目开发中,web项目的前后端分离开发,APP开发,需要由前后端工程师共同定义接口,编写接口文档,之后大家都根据这个接口文档进行开发,到项目结束前都要一直维护 接口文档使得项目开发过程中前后端工程师 ...

  2. php base64怎么加密,怎么在php项目中利用base64进行加密解密

    怎么在php项目中利用base64进行加密解密 发布时间:2020-12-19 14:55:49 来源:亿速云 阅读:76 作者:Leah 怎么在php项目中利用base64进行加密解密?很多新手对此 ...

  3. php中的base64,php中base64_decode与base64_encode加密解密函数

    php中base64_decode与base64_encode加密解密函数,实例分析了base64加密解密函数的具体用法,具有一定的实用价值,需要的朋友可以参考下 本文实例讲述了php中base64_ ...

  4. SpringBoot 接口加密解密,新姿势!

    我是 ABin-阿斌:写一生代码,创一世佳话,筑一览芳华.如果小伙伴们觉得不错就一键三连吧~ 声明:原文作者:微信公众号:方志朋 文章目录 1 . 介绍 2 . 前置知识 2.1 hutool-cry ...

  5. SpringBoot 接口加密解密,新姿势

    1. 介绍 在我们日常的Java开发中,免不了和其他系统的业务交互,或者微服务之间的接口调用 如果我们想保证数据传输的安全,对接口出参加密,入参解密. 但是不想写重复代码,我们可以提供一个通用star ...

  6. vue中根据后端接口数据控制按钮的显示与隐藏(手动添加响应式this.$set())

    项目涉及到的问题,按钮需要根据后端返回的参数情况,实现按钮的显示与隐藏. 效果如图: 1.查看后端接口 get请求,需要传businessId和compoCode两个参数,返回的data有数据就显示按 ...

  7. CTF中(Crypto)常见的加密解密网站

    xssee:http://web2hack.org/xssee xssee:http://evilcos.me/lab/xssee 程默的博客(DES,3DES,AES,RC,Blowfish,Two ...

  8. JS前端接口加密/解密

    CryptoJS (crypto.js) 为 JavaScript 提供加密和解密算法.目前已支持的算法包括 crypto, Hash, MD5, SHA1, SHA-1, SHA256, SHA-2 ...

  9. SpringBoot中实现CommandLineRunner接口在项目启动后立即执行某方法

    场景 在启动SpringBoot项目的启动类之后需要其立即执行某方法. 注: 博客: https://blog.csdn.net/badao_liumang_qizhi 关注公众号 霸道的程序猿 获取 ...

最新文章

  1. ubuntu下安装2个mysql_Linux 同一系统安装两个MySQL
  2. 自适应分辨率可扩展二层JS下拉菜单
  3. CoffeeScript 存在操作符“?” 详解
  4. 【HEVC】1、HM-16.7编码器的基本结构
  5. 产品运营必须知道的几个概念,什么是定向营销?
  6. ASP.NET Core 1.0开发Web API程序
  7. java 实现 常见排序算法(一) 冒泡排序
  8. 三菱fx5u编程手册_FX5U系列PLC控制伺服3种方式
  9. linux系统之间无密传输,scp采用无密码在两台linux服务器之间传输数据(示例代码)...
  10. 数组长度属性以及JavaScript中的示例
  11. hive2 mysql_架构hive2mysql流程
  12. Machine Learning Algorithms Study Notes--Supervised Learning
  13. ⚡【图像描述】pytorch_image_caption
  14. python多线程抢红包代码_python实现的抢红包程序代码
  15. 第38件事 心理设计的6种方法
  16. 【测试工具】五、jmeter在GUI与non-GUI模式下结果集自定义封装展示
  17. Vue框架基本页面的使用模板
  18. 【详解】SPI中的极性CPOL和相位CPHA是什么以及如何设置
  19. 安装Linux平台Orion Health Rhapsody
  20. Anaconda卸载

热门文章

  1. 100+w条深圳一卡通刷卡数据,含地铁卡和公交卡数据
  2. 美图秀秀让你的照片散发出回忆般小清新的韵味
  3. pandas模块之SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame
  4. 帆软iframe 嵌套页面,dom操作
  5. unity3d 实现LOL英雄的寒冰的(万箭齐发)
  6. ICPC训练联盟2021寒假冬令营(7)部分题解
  7. web的demo制造_demoshow - webdemo展示助手
  8. 最少次数找出重量最轻的苹果(面试智力题)
  9. JS滚动图片代码(含横向和纵向)
  10. Cisco(31)——期末模拟试题