当前位置:   article > 正文

SpringBoot 实现数据加密脱敏(注解 + 反射 + AOP)_springboot字段加密解密

springboot字段加密解密

场景:响应政府要求,商业软件应保证用户基本信息不被泄露,不能直接展示用户手机号,身份证,地址等敏感信息

根据上面场景描述,我们可以分析出两个点。

  • 不被泄露说明用户信息应被加密储存;
  • 不能直接展示说明用户信息应脱敏展示;

解决方案

  • 傻瓜式编程:将项目中关于用户信息实体类的字段,比如姓名,手机号,身份证,地址等,在新增进数据库之前,对数据进行加密处理;在列表中展示用户信息时,对数据库中的数据进行解密脱敏,然后返回给前端;
  • 切入式编程:将项目中关于用户信息实体类的字段用注解给标记,新增用户信息实体类(这里我们用UserBO来表示,给UserBO里面的name,phone字段添加@EncryptField),返回用户信息实体类(这里我们用UserDO来表示,给UserDO里面的name,phone字段添加@DecryptField);然后利用@EncryptField,@DecryptField做为切入点,以切面的形式实现加密,解密脱敏;

傻瓜式编程不是说傻,而是相当于切入式编程,傻瓜式编程需要对用户信息相关的所有接口进行加密,解密脱敏的逻辑处理,这里改动的地方就比较多,风险高,重复操作相同的逻辑,工作量大,后期不好维护;切入式编程只需要对用户信息字段添加注解,对有注解的字段统一进行加密,解密脱敏逻辑处理,操作方便,高聚合,易维护;

方案实现

傻瓜式编程没什么难度,这里我给大家有切入式编程来实现;在实现之前,跟大家预热一下注解,反射,AOP的知识;

注解实战

创建注解

创建一个只能标记在方法上的注解:

  1. java复制代码package com.weige.javaskillpoint.annotation;
  2. import java.lang.annotation.ElementType;
  3. import java.lang.annotation.Retention;
  4. import java.lang.annotation.RetentionPolicy;
  5. import java.lang.annotation.Target;
  6. @Target(ElementType.METHOD) //METHOD 说明该注解只能用在方法上
  7. @Retention(RetentionPolicy.RUNTIME) //RUNTIME 说明该注解在运行时生效
  8. public @interface Encryption {
  9. }

创建一个只能标记在字段上的注解:

  1. java复制代码package com.weige.javaskillpoint.annotation;
  2. import java.lang.annotation.ElementType;
  3. import java.lang.annotation.Retention;
  4. import java.lang.annotation.RetentionPolicy;
  5. import java.lang.annotation.Target;
  6. @Target(ElementType.FIELD) //FIELD 说明该注解只能用在字段上
  7. @Retention(RetentionPolicy.RUNTIME) //RUNTIME 说明该注解在运行时生效
  8. public @interface EncryptField {
  9. }

创建一个标记在字段上,且有值的注解:

  1. java复制代码package com.weige.javaskillpoint.annotation;
  2. import com.weige.javaskillpoint.enums.DesensitizationEnum;
  3. import java.lang.annotation.ElementType;
  4. import java.lang.annotation.Retention;
  5. import java.lang.annotation.RetentionPolicy;
  6. import java.lang.annotation.Target;
  7. @Target(ElementType.FIELD)
  8. @Retention(RetentionPolicy.RUNTIME)
  9. public @interface DecryptField {
  10. // 注解是可以有值的,这里可以为数组,String,枚举等类型
  11. // DesensitizationEnum desensitizationEnum = field.getAnnotation(DecryptField.class).value(); 这里的field是指当前标记的字段
  12. DesensitizationEnum value();
  13. }

注解使用

创建枚举

  1. java复制代码package com.weige.javaskillpoint.enums;
  2. public enum DesensitizationEnum {
  3. name, // 用户信息姓名脱敏
  4. address, // 用户信息地址脱敏
  5. phone; // 用户信息手机号脱敏
  6. }

创建UserDO类

  1. java复制代码package com.weige.javaskillpoint.entity;
  2. import com.weige.javaskillpoint.annotation.DecryptField;
  3. import com.weige.javaskillpoint.enums.DesensitizationEnum;
  4. import com.weige.javaskillpoint.utils.AesUtil;
  5. import java.lang.reflect.Field;
  6. // 用户信息返回实体类
  7. public class UserDO {
  8. @DecryptField(DesensitizationEnum.name)
  9. private String name;
  10. @DecryptField(DesensitizationEnum.address)
  11. private String address;
  12. public String getName() {
  13. return name;
  14. }
  15. public void setName(String name) {
  16. this.name = name;
  17. }
  18. public String getAddress() {
  19. return address;
  20. }
  21. public void setAddress(String address) {
  22. this.address = address;
  23. }
  24. public UserDO(String name, String address) {
  25. this.name = name;
  26. this.address = address;
  27. }
  28. public static void main(String[] args) throws IllegalAccessException {
  29. // 生成并初始化对象
  30. UserDO userDO = new UserDO("梦想是什么","湖北省武汉市");
  31. // 反射获取当前对象的所有字段
  32. Field[] fields = userDO.getClass().getDeclaredFields();
  33. // 遍历字段
  34. for (Field field : fields) {
  35. // 判断字段上是否存在@DecryptField注解
  36. boolean hasSecureField = field.isAnnotationPresent(DecryptField.class);
  37. // 存在
  38. if (hasSecureField) {
  39. // 暴力破解 不然操作不了权限为private的字段
  40. field.setAccessible(true);
  41. // 如果当前字段在userDo中不为空 即name,address字段有值
  42. if (field.get(userDO) != null) {
  43. // 获取字段上注解的value
  44. DesensitizationEnum desensitizationEnum = field.getAnnotation(DecryptField.class).value();
  45. // 控制台输出
  46. System.out.println(desensitizationEnum);
  47. // 根据不同的value值 我们可以对字段进行不同逻辑的脱敏 比如姓名脱敏-魏*,手机号脱敏-187****2275
  48. }
  49. }
  50. }
  51. }
  52. }

反射实战

创建UserBO类

  1. java复制代码package com.weige.javaskillpoint.entity;
  2. import com.weige.javaskillpoint.annotation.EncryptField;
  3. import java.lang.reflect.Field;
  4. // 用户信息新增实体类
  5. public class UserBO {
  6. @EncryptField
  7. private String name;
  8. @EncryptField
  9. private String address;
  10. public String getName() {
  11. return name;
  12. }
  13. public void setName(String name) {
  14. this.name = name;
  15. }
  16. public String getAddress() {
  17. return address;
  18. }
  19. public void setAddress(String address) {
  20. this.address = address;
  21. }
  22. public UserBO(String name, String address) {
  23. this.name = name;
  24. this.address = address;
  25. }
  26. @Override
  27. public String toString() {
  28. return "UserBO{" +
  29. "name='" + name + '\'' +
  30. ", address='" + address + '\'' +
  31. '}';
  32. }
  33. public static void main(String[] args) throws IllegalAccessException {
  34. UserBO userBO = new UserBO("周传雄","湖北省武汉市");
  35. Field[] fields = userBO.getClass().getDeclaredFields();
  36. for (Field field : fields) {
  37. boolean annotationPresent = field.isAnnotationPresent(EncryptField.class);
  38. if(annotationPresent){
  39. // 当前字段内容不为空
  40. if(field.get(userBO) != null){
  41. // 这里对字段内容进行加密
  42. Object obj = encrypt(field.get(userBO));
  43. // 字段内容加密过后 通过反射重新赋给该字段
  44. field.set(userBO, obj);
  45. }
  46. }
  47. }
  48. System.out.println(userBO);
  49. }
  50. public static Object encrypt(Object obj){
  51. return "加密: " + obj;
  52. }
  53. }

AOP实战

切入点:

  1. java复制代码package com.weige.javaskillpoint.controller;
  2. import com.weige.javaskillpoint.annotation.Encryption;
  3. import com.weige.javaskillpoint.entity.UserBO;
  4. import lombok.extern.slf4j.Slf4j;
  5. import org.springframework.web.bind.annotation.PostMapping;
  6. import org.springframework.web.bind.annotation.RequestBody;
  7. import org.springframework.web.bind.annotation.RequestMapping;
  8. import org.springframework.web.bind.annotation.RestController;
  9. @RestController
  10. @RequestMapping("/encrypt")
  11. @Slf4j
  12. public class EncryptController {
  13. @PostMapping("/v1")
  14. @Encryption // 切入点
  15. public UserBO insert(@RequestBody UserBO user) {
  16. log.info("加密后对象:{}", user);
  17. return user;
  18. }
  19. }

切面:

  1. java复制代码package com.weige.javaskillpoint.aop;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.aspectj.lang.ProceedingJoinPoint;
  4. import org.aspectj.lang.annotation.Around;
  5. import org.aspectj.lang.annotation.Aspect;
  6. import org.aspectj.lang.annotation.Pointcut;
  7. import org.springframework.stereotype.Component;
  8. @Slf4j
  9. @Aspect
  10. @Component
  11. public class EncryptAspect {
  12. //拦截需加密注解 切入点
  13. @Pointcut("@annotation(com.weige.javaskillpoint.annotation.Encryption)")
  14. public void point() {
  15. }
  16. @Around("point()") //环绕通知
  17. public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
  18. //加密逻辑处理
  19. encrypt(joinPoint);
  20. return joinPoint.proceed();
  21. }
  22. }

为什么这里要使用AOP:无论是注解,反射,都需要一个启动方法,我上面演示的是通过main函数来启动。使用AOP,项目启动后,只要调用切入点对应的方法,就会根据切入点来形成一个切面,进行统一的逻辑增强;如果大家熟悉SpringMVC,SpringMVC提供了 ResponseBodyAdvice 和 RequestBodyAdvice两个接口,这两个接口可以对请求和响应进行预处理,就可以不需要使用AOP;

加密解密脱敏实战

项目目录:

在这里插入图片描述

pom.xml文件:

  1. java复制代码<dependencies>
  2. <!--Springboot项目自带 -->
  3. <dependency>
  4. <groupId>org.springframework.boot</groupId>
  5. <artifactId>spring-boot-starter</artifactId>
  6. </dependency>
  7. <dependency>
  8. <groupId>org.springframework.boot</groupId>
  9. <artifactId>spring-boot-starter-test</artifactId>
  10. <scope>test</scope>
  11. </dependency>
  12. <!--Springboot Web项目 -->
  13. <dependency>
  14. <groupId>org.springframework.boot</groupId>
  15. <artifactId>spring-boot-starter-web</artifactId>
  16. </dependency>
  17. <!--lombok -->
  18. <dependency>
  19. <groupId>org.projectlombok</groupId>
  20. <artifactId>lombok</artifactId>
  21. <version>1.18.22</version>
  22. </dependency>
  23. <!-- hutool -->
  24. <dependency>
  25. <groupId>cn.hutool</groupId>
  26. <artifactId>hutool-all</artifactId>
  27. <version>5.7.20</version>
  28. </dependency>
  29. <!-- 切面 aop -->
  30. <dependency>
  31. <groupId>org.aspectj</groupId>
  32. <artifactId>aspectjweaver</artifactId>
  33. <version>1.9.7</version>
  34. </dependency>
  35. </dependencies>

实体类

用户信息新增实体类 :UserBO

  1. java复制代码package com.weige.javaskillpoint.entity;
  2. import com.weige.javaskillpoint.annotation.EncryptField;
  3. // 实体类
  4. public class UserBO {
  5. @EncryptField
  6. private String name;
  7. @EncryptField
  8. private String address;
  9. public String getName() {
  10. return name;
  11. }
  12. public void setName(String name) {
  13. this.name = name;
  14. }
  15. public String getAddress() {
  16. return address;
  17. }
  18. public void setAddress(String address) {
  19. this.address = address;
  20. }
  21. public UserBO(String name, String address) {
  22. this.name = name;
  23. this.address = address;
  24. }
  25. @Override
  26. public String toString() {
  27. return "UserBO{" +
  28. "name='" + name + '\'' +
  29. ", address='" + address + '\'' +
  30. '}';
  31. }
  32. }

用户信息返回实体类 :UserDO

  1. java复制代码package com.weige.javaskillpoint.entity;
  2. import com.weige.javaskillpoint.annotation.DecryptField;
  3. import com.weige.javaskillpoint.enums.DesensitizationEnum;
  4. // 实体类
  5. public class UserDO {
  6. @DecryptField(DesensitizationEnum.name)
  7. private String name;
  8. @DecryptField(DesensitizationEnum.address)
  9. private String address;
  10. public String getName() {
  11. return name;
  12. }
  13. public void setName(String name) {
  14. this.name = name;
  15. }
  16. public String getAddress() {
  17. return address;
  18. }
  19. public void setAddress(String address) {
  20. this.address = address;
  21. }
  22. public UserDO(String name, String address) {
  23. this.name = name;
  24. this.address = address;
  25. }
  26. }

脱敏枚举

  1. java复制代码package com.weige.javaskillpoint.enums;
  2. public enum DesensitizationEnum {
  3. name,
  4. address,
  5. phone;
  6. }

注解

解密字段注解(字段):

  1. java复制代码package com.weige.javaskillpoint.annotation;
  2. import com.weige.javaskillpoint.enums.DesensitizationEnum;
  3. import java.lang.annotation.ElementType;
  4. import java.lang.annotation.Retention;
  5. import java.lang.annotation.RetentionPolicy;
  6. import java.lang.annotation.Target;
  7. @Target(ElementType.FIELD)
  8. @Retention(RetentionPolicy.RUNTIME)
  9. public @interface DecryptField {
  10. DesensitizationEnum value();
  11. }

解密方法注解(方法 作切入点):

  1. java复制代码package com.weige.javaskillpoint.annotation;
  2. import java.lang.annotation.ElementType;
  3. import java.lang.annotation.Retention;
  4. import java.lang.annotation.RetentionPolicy;
  5. import java.lang.annotation.Target;
  6. @Target(ElementType.METHOD)
  7. @Retention(RetentionPolicy.RUNTIME)
  8. public @interface Decryption {
  9. }

加密字段注解(字段):

  1. java复制代码package com.weige.javaskillpoint.annotation;
  2. import java.lang.annotation.ElementType;
  3. import java.lang.annotation.Retention;
  4. import java.lang.annotation.RetentionPolicy;
  5. import java.lang.annotation.Target;
  6. @Target(ElementType.FIELD)
  7. @Retention(RetentionPolicy.RUNTIME)
  8. public @interface EncryptField {
  9. }

加密方法注解(方法 作切入点):

  1. java复制代码package com.weige.javaskillpoint.annotation;
  2. import java.lang.annotation.ElementType;
  3. import java.lang.annotation.Retention;
  4. import java.lang.annotation.RetentionPolicy;
  5. import java.lang.annotation.Target;
  6. @Target(ElementType.METHOD)
  7. @Retention(RetentionPolicy.RUNTIME)
  8. public @interface Encryption {
  9. }

控制层

解密 Controller:

  1. java复制代码package com.weige.javaskillpoint.controller;
  2. import com.weige.javaskillpoint.annotation.Decryption;
  3. import com.weige.javaskillpoint.entity.UserDO;
  4. import org.springframework.web.bind.annotation.GetMapping;
  5. import org.springframework.web.bind.annotation.RequestMapping;
  6. import org.springframework.web.bind.annotation.RestController;
  7. @RestController
  8. @RequestMapping("/decrypt")
  9. public class DecryptController {
  10. @GetMapping("/v1")
  11. @Decryption
  12. public UserDO decrypt() {
  13. return new UserDO("7c29e296e92893476db5f9477480ba7f", "b5c7ff86ac36c01dda45d9ffb0bf73194b083937349c3901f571d42acdaa7bae");
  14. }
  15. }

加密 Controller:

  1. java复制代码package com.weige.javaskillpoint.controller;
  2. import com.weige.javaskillpoint.annotation.Encryption;
  3. import com.weige.javaskillpoint.entity.UserBO;
  4. import lombok.extern.slf4j.Slf4j;
  5. import org.springframework.web.bind.annotation.PostMapping;
  6. import org.springframework.web.bind.annotation.RequestBody;
  7. import org.springframework.web.bind.annotation.RequestMapping;
  8. import org.springframework.web.bind.annotation.RestController;
  9. @RestController
  10. @RequestMapping("/encrypt")
  11. @Slf4j
  12. public class EncryptController {
  13. @PostMapping("/v1")
  14. @Encryption
  15. public UserBO insert(@RequestBody UserBO user) {
  16. log.info("加密后对象:{}", user);
  17. return user;
  18. }
  19. }

切面

解密脱敏切面:

  1. java复制代码package com.weige.javaskillpoint.aop;
  2. import com.weige.javaskillpoint.annotation.DecryptField;
  3. import com.weige.javaskillpoint.enums.DesensitizationEnum;
  4. import com.weige.javaskillpoint.utils.AesUtil;
  5. import lombok.extern.slf4j.Slf4j;
  6. import org.aspectj.lang.ProceedingJoinPoint;
  7. import org.aspectj.lang.annotation.Around;
  8. import org.aspectj.lang.annotation.Aspect;
  9. import org.aspectj.lang.annotation.Pointcut;
  10. import org.springframework.stereotype.Component;
  11. import java.lang.reflect.Field;
  12. import java.util.ArrayList;
  13. import java.util.Collection;
  14. import java.util.List;
  15. import java.util.Objects;
  16. @Slf4j
  17. @Aspect
  18. @Component
  19. public class DecryptAspect {
  20. //拦截需解密注解
  21. @Pointcut("@annotation(com.weige.javaskillpoint.annotation.Decryption)")
  22. public void point() {
  23. }
  24. @Around("point()")
  25. public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
  26. //解密
  27. return decrypt(joinPoint);
  28. }
  29. public Object decrypt(ProceedingJoinPoint joinPoint) {
  30. Object result = null;
  31. try {
  32. Object obj = joinPoint.proceed();
  33. if (obj != null) {
  34. //抛砖引玉 ,可自行扩展其他类型字段的判断
  35. if (obj instanceof String) {
  36. decryptValue();
  37. } else {
  38. result = decryptData(obj);
  39. }
  40. }
  41. } catch (Throwable e) {
  42. e.printStackTrace();
  43. }
  44. return result;
  45. }
  46. private Object decryptData(Object obj) throws IllegalAccessException {
  47. if (Objects.isNull(obj)) {
  48. return null;
  49. }
  50. if (obj instanceof ArrayList) {
  51. decryptList(obj);
  52. } else {
  53. decryptObj(obj);
  54. }
  55. return obj;
  56. }
  57. private void decryptObj(Object obj) throws IllegalAccessException {
  58. Field[] fields = obj.getClass().getDeclaredFields();
  59. for (Field field : fields) {
  60. boolean hasSecureField = field.isAnnotationPresent(DecryptField.class);
  61. if (hasSecureField) {
  62. field.setAccessible(true);
  63. if (field.get(obj) != null) {
  64. String realValue = (String) field.get(obj);
  65. DesensitizationEnum desensitizationEnum = field.getAnnotation(DecryptField.class).value();
  66. String value = (String) AesUtil.decrypt(realValue,desensitizationEnum);
  67. field.set(obj, value);
  68. }
  69. }
  70. }
  71. }
  72. private void decryptList(Object obj) throws IllegalAccessException {
  73. List<Object> result = new ArrayList<>();
  74. if (obj instanceof ArrayList) {
  75. result.addAll((Collection<?>) obj);
  76. }
  77. for (Object object : result) {
  78. decryptObj(object);
  79. }
  80. }
  81. private void decryptValue() {
  82. log.info("根据对象进行解密脱敏,单个字段不做处理!");
  83. }
  84. }

加密切面:

  1. java复制代码package com.weige.javaskillpoint.aop;
  2. import com.weige.javaskillpoint.annotation.EncryptField;
  3. import com.weige.javaskillpoint.entity.UserBO;
  4. import com.weige.javaskillpoint.utils.AesUtil;
  5. import lombok.extern.slf4j.Slf4j;
  6. import org.aspectj.lang.ProceedingJoinPoint;
  7. import org.aspectj.lang.annotation.Around;
  8. import org.aspectj.lang.annotation.Aspect;
  9. import org.aspectj.lang.annotation.Pointcut;
  10. import org.springframework.stereotype.Component;
  11. import java.lang.reflect.Field;
  12. @Slf4j
  13. @Aspect
  14. @Component
  15. public class EncryptAspect {
  16. //拦截需加密注解
  17. @Pointcut("@annotation(com.weige.javaskillpoint.annotation.Encryption)")
  18. public void point() {
  19. }
  20. @Around("point()")
  21. public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
  22. //加密
  23. encrypt(joinPoint);
  24. return joinPoint.proceed();
  25. }
  26. public void encrypt(ProceedingJoinPoint joinPoint) {
  27. Object[] objects;
  28. try {
  29. objects = joinPoint.getArgs();
  30. if (objects.length != 0) {
  31. for (Object object : objects) {
  32. if (object instanceof UserBO) {
  33. Field[] fields = object.getClass().getDeclaredFields();
  34. for (Field field : fields) {
  35. if (field.isAnnotationPresent(EncryptField.class)) {
  36. field.setAccessible(true);
  37. if (field.get(object) != null) {
  38. // 进行加密
  39. Object o = field.get(object);
  40. Object encrypt = AesUtil.encrypt(field.get(object));
  41. field.set(object, encrypt);
  42. }
  43. }
  44. }
  45. }
  46. }
  47. }
  48. } catch (Exception e) {
  49. log.error(e.getMessage());
  50. }
  51. }
  52. }

工具类

加密工具类:AesUtil

  1. java复制代码package com.weige.javaskillpoint.utils;
  2. import cn.hutool.core.util.CharsetUtil;
  3. import cn.hutool.crypto.SecureUtil;
  4. import cn.hutool.crypto.symmetric.AES;
  5. import com.weige.javaskillpoint.enums.DesensitizationEnum;
  6. public class AesUtil {
  7. // 默认16位 或 128 256位
  8. public static String AES_KEY = "Wk#qerdfdshbd910";
  9. public static AES aes = SecureUtil.aes(AES_KEY.getBytes());
  10. public static Object encrypt(Object obj) {
  11. return aes.encryptHex((String) obj);
  12. }
  13. public static Object decrypt(Object obj, DesensitizationEnum desensitizationEnum) {
  14. // 解密
  15. Object decrypt = decrypt(obj);
  16. // 脱敏
  17. return DesensitizationUtil.desensitization(decrypt, desensitizationEnum);
  18. }
  19. public static Object decrypt(Object obj) {
  20. return aes.decryptStr((String) obj, CharsetUtil.CHARSET_UTF_8);
  21. }
  22. }

脱敏工具类:DesensitizationUtil

  1. java复制代码package com.weige.javaskillpoint.utils;
  2. import cn.hutool.core.util.StrUtil;
  3. import com.weige.javaskillpoint.enums.DesensitizationEnum;
  4. public class DesensitizationUtil {
  5. public static Object desensitization(Object obj, DesensitizationEnum desensitizationEnum) {
  6. Object result;
  7. switch (desensitizationEnum) {
  8. case name:
  9. result = strUtilHide(obj, 1);
  10. break;
  11. case address:
  12. result = strUtilHide(obj, 3);
  13. break;
  14. default:
  15. result = "";
  16. }
  17. return result;
  18. }
  19. /**
  20. * start从0开始
  21. */
  22. public static Object strUtilHide(String obj, int start, int end) {
  23. return StrUtil.hide(obj, start, end);
  24. }
  25. public static Object strUtilHide(Object obj, int start) {
  26. return strUtilHide(((String) obj), start, ((String) obj).length());
  27. }
  28. }

完结

以上代码不难,大伙复制到本地跑一遍,基本就能理解;愿每一位程序员少走弯路!

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

闽ICP备14008679号