当前位置:   article > 正文

springboot中的后端接口加密解密_springboot @encrypt

springboot @encrypt

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

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

一.结构:

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

  1. /**
  2. * 请求响应处理类
  3. * 对加了@Encrypt的方法的数据进行加密操作
  4. *
  5. * @author gzy
  6. */
  7. @ControllerAdvice
  8. @Slf4j
  9. public class EncryptResponseBodyAdvice implements ResponseBodyAdvice<Object> {
  10. @Override
  11. public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
  12. //在这里调用needEncrypet方法判断是否需要加密
  13. return new NeedCrypto().needEncrypt(returnType);
  14. }
  15. //这个方法截取了接口中返回的对象,在对对象加密后返回
  16. @Override
  17. public Object beforeBodyWrite(Object obj, MethodParameter returnType, MediaType selectedContentType,
  18. Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest serverHttpRequest,
  19. ServerHttpResponse serverHttpResponse) {
  20. // 通过 ServerHttpRequest的实现类ServletServerHttpRequest 获得HttpServletRequest
  21. ServletServerHttpRequest sshr = (ServletServerHttpRequest) serverHttpRequest;
  22. // 此处获取到request 是为了取到在拦截器里面设置的一个对象 是我项目需要,可以忽略
  23. HttpServletRequest request = sshr.getServletRequest();
  24. String returnData;
  25. String realData = "";
  26. JSONObject data = new JSONObject();
  27. try {
  28. data = (JSONObject) JSONObject.toJSON(obj);
  29. if(!data.get("data").toString().isEmpty()){
  30. realData = data.get("data").toString();
  31. }
  32. // 添加encry header,告诉前端数据已加密
  33. serverHttpResponse.getHeaders().add("encrypt", "true");
  34. // 加密
  35. returnData = AESOperator.replace(AESUtil.encrypt(realData));
  36. log.debug("接口={},原始数据={},加密后数据={}", request.getRequestURI(), realData, returnData);
  37. data.put("data", returnData);
  38. } catch (Exception e) {
  39. log.error("异常!", e);
  40. }
  41. return data;
  42. }
  43. }

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

  1. /**
  2. * 请求数据接收处理类
  3. * 对加了@Decrypt的方法的数据进行解密操作
  4. *
  5. * @author gzy
  6. */
  7. @ControllerAdvice
  8. @Slf4j
  9. public class DecryptRequestBodyAdvice implements RequestBodyAdvice {
  10. //needDecrypt判断是否需要解密
  11. @Override
  12. public boolean supports(MethodParameter methodParameter, Type targetType,
  13. Class<? extends HttpMessageConverter<?>> converterType) {
  14. return new NeedCrypto().needDecrypt(methodParameter);
  15. }
  16. @Override
  17. public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter,
  18. Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
  19. return body;
  20. }
  21. @Override
  22. public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
  23. Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
  24. return inputMessage;
  25. }
  26. //拦截接口中的入参,对入参进行解密后返回
  27. @Override
  28. public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
  29. Class<? extends HttpMessageConverter<?>> converterType) {
  30. JSONObject dealData = new JSONObject();
  31. try {
  32. // 解密操作
  33. JSONObject encryptObject = (JSONObject) JSONObject.toJSON(body);
  34. String srcData = String.valueOf(encryptObject.get("encryptData"));
  35. String decryptData = AESUtil.decrypt(srcData);
  36. dealData = JSON.parseObject(decryptData);
  37. } catch (Exception e) {
  38. log.error("请求body参数格式解析异常!", e);
  39. }
  40. return dealData;
  41. }
  42. }

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

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

  1. /**
  2. * 判断是否需要加解密
  3. *
  4. * @author gzy
  5. * @date 2021/09/03
  6. */
  7. @Configuration
  8. @Log4j2
  9. public class NeedCrypto {
  10. //开关变量,在配置文件中配置是否开启加解密功能,不需要可以去掉
  11. @Value("${encrypt.api.flag}")
  12. private boolean encryptApiFlag;
  13. private static boolean flag = false;
  14. @PostConstruct
  15. public void NeedCrypto() {
  16. flag = encryptApiFlag;
  17. }
  18. /**
  19. * 是否需要对结果加密
  20. * 1.类上标注或者方法上标注,并且都为true
  21. * 2.有一个标注为false就不需要加密
  22. */
  23. public boolean needEncrypt(MethodParameter returnType) {
  24. boolean encrypt = false;
  25. if (flag) {
  26. boolean classPresentAnno = returnType.getContainingClass().isAnnotationPresent(EncryptResponse.class);
  27. boolean methodPresentAnno = returnType.getMethod().isAnnotationPresent(EncryptResponse.class);
  28. if (classPresentAnno) {
  29. //类上标注的是否需要加密
  30. encrypt = returnType.getContainingClass().getAnnotation(EncryptResponse.class).value();
  31. if (encrypt) {
  32. return encrypt;
  33. }
  34. //类不加密,所有都不加密
  35. /**if(!encrypt){
  36. return false;
  37. }*/
  38. }
  39. if (methodPresentAnno) {
  40. //方法上标注的是否需要加密
  41. encrypt = returnType.getMethod().getAnnotation(EncryptResponse.class).value();
  42. }
  43. }
  44. return encrypt;
  45. }
  46. /**
  47. * 是否需要参数解密
  48. * 1.类上标注或者方法上标注,并且都为true
  49. * 2.有一个标注为false就不需要解密
  50. */
  51. public boolean needDecrypt(MethodParameter parameter) {
  52. boolean encrypt = false;
  53. if (flag) {
  54. boolean classPresentAnno = parameter.getContainingClass().isAnnotationPresent(DecryptRequest.class);
  55. boolean methodPresentAnno = parameter.getMethod().isAnnotationPresent(DecryptRequest.class);
  56. if (classPresentAnno) {
  57. //类上标注的是否需要解密
  58. encrypt = parameter.getContainingClass().getAnnotation(DecryptRequest.class).value();
  59. if (encrypt) {
  60. return encrypt;
  61. }
  62. //类不加密,所有都不加密
  63. /**if (!encrypt) {
  64. return false;
  65. }*/
  66. }
  67. if (methodPresentAnno) {
  68. //方法上标注的是否需要解密
  69. encrypt = parameter.getMethod().getAnnotation(DecryptRequest.class).value();
  70. }
  71. }
  72. return encrypt;
  73. }
  74. }

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

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

yml配置文件:

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

  1. /**
  2. * @author gzy
  3. */
  4. public class AESUtil {
  5. /**
  6. * 加密用的Key 可以用26个字母和数字组成 此处使用AES-128-CBC加密模式,key需要为16位。
  7. */
  8. public static final String skey = "smkldospd121daaa";
  9. /**
  10. * 加密偏移量
  11. */
  12. private static final String ivParameter = "1016449182184177";
  13. public static String encrypt(String srcData) throws Exception {
  14. String enString = AESOperator.getInstance().Encrypt(srcData, skey, ivParameter);
  15. return enString;
  16. }
  17. public static String decrypt(String srcData) throws Exception {
  18. String DeString = AESOperator.getInstance().Decrypt(srcData, skey, ivParameter);
  19. return DeString;
  20. }
  21. }

二.自定义注解:

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

@EncryptResponse (加密)

@DecryptRequest (解密)

 

  1. /**
  2. * 加密注解
  3. *
  4. * <p>加了此注解的接口(true)将进行数据加密操作
  5. * 可以放在类上,可以放在方法上 </p>
  6. *
  7. * @author gzy
  8. */
  9. @Target({ElementType.METHOD, ElementType.TYPE})
  10. @Retention(RetentionPolicy.RUNTIME)
  11. @Documented
  12. public @interface EncryptResponse {
  13. /**
  14. * 是否对结果加密
  15. */
  16. boolean value() default true;
  17. }
  1. /**
  2. * 解密注解
  3. *
  4. * <p>加了此注解的接口(true)将进行数据解密操作(post的body) 可
  5. * 以放在类上,可以放在方法上 </p>
  6. *
  7. * @author gzy
  8. */
  9. @Target({ElementType.METHOD, ElementType.TYPE})
  10. @Retention(RetentionPolicy.RUNTIME)
  11. @Documented
  12. public @interface DecryptRequest {
  13. /**
  14. * 是否对body进行解密
  15. */
  16. boolean value() default true;
  17. }

三.AES加密

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

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

  1. /**
  2. * AES加解密操作类
  3. */
  4. public class AESOperator {
  5. /**
  6. * 加密用的Key 可以用26个字母和数字组成 此处使用AES-128-CBC加密模式,key需要为16位。
  7. */
  8. public static final String skey = "smkldospd121daaa";
  9. /**
  10. * 偏移量,可自行修改
  11. */
  12. private static String ivParameter = "1016449182184177";
  13. private static AESOperator instance = null;
  14. private AESOperator() {
  15. }
  16. public static AESOperator getInstance() {
  17. if (instance == null) {
  18. instance = new AESOperator();
  19. }
  20. return instance;
  21. }
  22. public String Encrypt(String encData, String secretKey, String vector) throws Exception {
  23. if (secretKey == null) {
  24. return null;
  25. }
  26. if (secretKey.length() != 16) {
  27. return null;
  28. }
  29. Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
  30. byte[] raw = secretKey.getBytes();
  31. SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
  32. // 使用CBC模式,需要一个向量iv,可增加加密算法的强度
  33. IvParameterSpec iv = new IvParameterSpec(vector.getBytes());
  34. cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
  35. byte[] encrypted = cipher.doFinal(encData.getBytes("utf-8"));
  36. // 此处使用BASE64做转码。
  37. return new BASE64Encoder().encode(encrypted);
  38. }
  39. /**
  40. * 加密
  41. *
  42. * @param sSrc
  43. * @param sKey
  44. * @return
  45. * @throws Exception
  46. */
  47. public String encrypt(String sSrc, String sKey) throws Exception {
  48. Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
  49. byte[] raw = sKey.getBytes();
  50. SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
  51. // 使用CBC模式,需要一个向量iv,可增加加密算法的强度
  52. IvParameterSpec iv = new IvParameterSpec(ivParameter.getBytes());
  53. cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
  54. byte[] encrypted = cipher.doFinal(sSrc.getBytes("utf-8"));
  55. // 此处使用BASE64做转码。
  56. return replace(new BASE64Encoder().encode(encrypted));
  57. }
  58. /**
  59. * 解密
  60. *
  61. * @param sSrc
  62. * @param sKey
  63. * @return
  64. */
  65. public String decrypt(String sSrc, String sKey) {
  66. try {
  67. byte[] raw = sKey.getBytes("ASCII");
  68. SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
  69. Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
  70. IvParameterSpec iv = new IvParameterSpec(ivParameter.getBytes());
  71. cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
  72. // 先用base64解密
  73. byte[] encrypted1 = new BASE64Decoder().decodeBuffer(sSrc);
  74. byte[] original = cipher.doFinal(encrypted1);
  75. String originalString = new String(original, "utf-8");
  76. return originalString;
  77. } catch (Exception ex) {
  78. return null;
  79. }
  80. }
  81. public String Decrypt(String sSrc, String key, String ivs) {
  82. try {
  83. byte[] raw = key.getBytes("ASCII");
  84. SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
  85. Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
  86. IvParameterSpec iv = new IvParameterSpec(ivs.getBytes());
  87. cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
  88. // 先用base64解密
  89. byte[] encrypted1 = new BASE64Decoder().decodeBuffer(sSrc);
  90. byte[] original = cipher.doFinal(encrypted1);
  91. String originalString = new String(original, "utf-8");
  92. return originalString;
  93. } catch (Exception ex) {
  94. return null;
  95. }
  96. }
  97. public static String encodeBytes(byte[] bytes) {
  98. StringBuffer strBuf = new StringBuffer();
  99. for (int i = 0; i < bytes.length; i++) {
  100. strBuf.append((char) (((bytes[i] >> 4) & 0xF) + ((int) 'a')));
  101. strBuf.append((char) (((bytes[i]) & 0xF) + ((int) 'a')));
  102. }
  103. return strBuf.toString();
  104. }
  105. /**
  106. * 去除 换行符、制表符
  107. *
  108. * @param str
  109. * @return
  110. */
  111. public static String replace(String str) {
  112. if (!StringUtil.isEmpty(str)) {
  113. return str.replaceAll("\r|\n", "");
  114. }
  115. return str;
  116. }
  117. //测试
  118. public static void main(String[] args) throws Exception {
  119. // 需要加密的字串
  120. String cSrc = "{\"loginName\":\"master\",\"secret\":\"123456\"}";
  121. System.out.println(cSrc);
  122. // 加密
  123. String enString = AESOperator.getInstance().Encrypt(cSrc, skey, ivParameter);
  124. System.out.println("加密后的字串是:" + replace(enString));
  125. String test = replace(enString);
  126. // 解密
  127. String DeString = AESOperator.getInstance().Decrypt(test, skey, ivParameter);
  128. System.out.println("解密后的字串是:" + DeString);
  129. }
  130. }

四.实战

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

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

  1. @DecryptRequest
  2. @EncryptResponse
  3. @RestController
  4. @RequestMapping("/menu")
  5. public class MenuController {
  6. .......
  7. }
  8. /**
  9. * 新增菜单
  10. *
  11. * @param params menu
  12. * @return addMenu
  13. */
  14. @DecryptRequest
  15. @EncryptResponse
  16. @PostMapping("/add")
  17. public ApiResult addMenu(@RequestBody JSONObject params) {
  18. Menu menu = params.toJavaObject(Menu.class);
  19. return menuService.addMenu(menu);
  20. }

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/618949
推荐阅读
相关标签
  

闽ICP备14008679号