赞
踩
场景:响应政府要求,商业软件应保证用户基本信息不被泄露,不能直接展示用户手机号,身份证,地址等敏感信息。
根据上面场景描述,我们可以分析出两个点。
傻瓜式编程不是说傻,而是相当于切入式编程,傻瓜式编程需要对用户信息相关的所有接口进行加密,解密脱敏的逻辑处理,这里改动的地方就比较多,风险高,重复操作相同的逻辑,工作量大,后期不好维护;切入式编程只需要对用户信息字段添加注解,对有注解的字段统一进行加密,解密脱敏逻辑处理,操作方便,高聚合,易维护;
傻瓜式编程没什么难度,这里我给大家有切入式编程来实现;在实现之前,跟大家预热一下注解,反射,AOP的知识;
创建一个只能标记在方法上的注解:
- java复制代码package com.weige.javaskillpoint.annotation;
-
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
-
- @Target(ElementType.METHOD) //METHOD 说明该注解只能用在方法上
- @Retention(RetentionPolicy.RUNTIME) //RUNTIME 说明该注解在运行时生效
- public @interface Encryption {
-
- }
创建一个只能标记在字段上的注解:
- java复制代码package com.weige.javaskillpoint.annotation;
-
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
-
- @Target(ElementType.FIELD) //FIELD 说明该注解只能用在字段上
- @Retention(RetentionPolicy.RUNTIME) //RUNTIME 说明该注解在运行时生效
- public @interface EncryptField {
-
- }
创建一个标记在字段上,且有值的注解:
- java复制代码package com.weige.javaskillpoint.annotation;
-
- import com.weige.javaskillpoint.enums.DesensitizationEnum;
-
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
-
- @Target(ElementType.FIELD)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface DecryptField {
- // 注解是可以有值的,这里可以为数组,String,枚举等类型
- // DesensitizationEnum desensitizationEnum = field.getAnnotation(DecryptField.class).value(); 这里的field是指当前标记的字段
- DesensitizationEnum value();
- }
创建枚举
- java复制代码package com.weige.javaskillpoint.enums;
-
- public enum DesensitizationEnum {
- name, // 用户信息姓名脱敏
- address, // 用户信息地址脱敏
- phone; // 用户信息手机号脱敏
- }
-
创建UserDO类
- java复制代码package com.weige.javaskillpoint.entity;
-
- import com.weige.javaskillpoint.annotation.DecryptField;
- import com.weige.javaskillpoint.enums.DesensitizationEnum;
- import com.weige.javaskillpoint.utils.AesUtil;
-
- import java.lang.reflect.Field;
-
- // 用户信息返回实体类
- public class UserDO {
-
- @DecryptField(DesensitizationEnum.name)
- private String name;
-
- @DecryptField(DesensitizationEnum.address)
- private String address;
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- public String getAddress() {
- return address;
- }
-
- public void setAddress(String address) {
- this.address = address;
- }
-
- public UserDO(String name, String address) {
- this.name = name;
- this.address = address;
- }
-
- public static void main(String[] args) throws IllegalAccessException {
- // 生成并初始化对象
- UserDO userDO = new UserDO("梦想是什么","湖北省武汉市");
- // 反射获取当前对象的所有字段
- Field[] fields = userDO.getClass().getDeclaredFields();
- // 遍历字段
- for (Field field : fields) {
- // 判断字段上是否存在@DecryptField注解
- boolean hasSecureField = field.isAnnotationPresent(DecryptField.class);
- // 存在
- if (hasSecureField) {
- // 暴力破解 不然操作不了权限为private的字段
- field.setAccessible(true);
- // 如果当前字段在userDo中不为空 即name,address字段有值
- if (field.get(userDO) != null) {
- // 获取字段上注解的value值
- DesensitizationEnum desensitizationEnum = field.getAnnotation(DecryptField.class).value();
- // 控制台输出
- System.out.println(desensitizationEnum);
- // 根据不同的value值 我们可以对字段进行不同逻辑的脱敏 比如姓名脱敏-魏*,手机号脱敏-187****2275
- }
- }
- }
- }
- }
创建UserBO类
- java复制代码package com.weige.javaskillpoint.entity;
-
- import com.weige.javaskillpoint.annotation.EncryptField;
-
- import java.lang.reflect.Field;
-
- // 用户信息新增实体类
- public class UserBO {
- @EncryptField
- private String name;
-
- @EncryptField
- private String address;
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- public String getAddress() {
- return address;
- }
-
- public void setAddress(String address) {
- this.address = address;
- }
-
- public UserBO(String name, String address) {
- this.name = name;
- this.address = address;
- }
-
- @Override
- public String toString() {
- return "UserBO{" +
- "name='" + name + '\'' +
- ", address='" + address + '\'' +
- '}';
- }
-
- public static void main(String[] args) throws IllegalAccessException {
- UserBO userBO = new UserBO("周传雄","湖北省武汉市");
- Field[] fields = userBO.getClass().getDeclaredFields();
- for (Field field : fields) {
- boolean annotationPresent = field.isAnnotationPresent(EncryptField.class);
- if(annotationPresent){
- // 当前字段内容不为空
- if(field.get(userBO) != null){
- // 这里对字段内容进行加密
- Object obj = encrypt(field.get(userBO));
- // 字段内容加密过后 通过反射重新赋给该字段
- field.set(userBO, obj);
- }
- }
- }
- System.out.println(userBO);
- }
-
- public static Object encrypt(Object obj){
- return "加密: " + obj;
- }
- }
切入点:
- java复制代码package com.weige.javaskillpoint.controller;
-
- import com.weige.javaskillpoint.annotation.Encryption;
- import com.weige.javaskillpoint.entity.UserBO;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.web.bind.annotation.PostMapping;
- import org.springframework.web.bind.annotation.RequestBody;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RestController;
-
- @RestController
- @RequestMapping("/encrypt")
- @Slf4j
- public class EncryptController {
-
- @PostMapping("/v1")
- @Encryption // 切入点
- public UserBO insert(@RequestBody UserBO user) {
- log.info("加密后对象:{}", user);
- return user;
- }
- }
-
切面:
- java复制代码package com.weige.javaskillpoint.aop;
-
- import lombok.extern.slf4j.Slf4j;
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.annotation.Around;
- import org.aspectj.lang.annotation.Aspect;
- import org.aspectj.lang.annotation.Pointcut;
- import org.springframework.stereotype.Component;
-
- @Slf4j
- @Aspect
- @Component
- public class EncryptAspect {
-
- //拦截需加密注解 切入点
- @Pointcut("@annotation(com.weige.javaskillpoint.annotation.Encryption)")
- public void point() {
-
- }
-
- @Around("point()") //环绕通知
- public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
- //加密逻辑处理
- encrypt(joinPoint);
- return joinPoint.proceed();
- }
-
- }
为什么这里要使用AOP:无论是注解,反射,都需要一个启动方法,我上面演示的是通过main函数来启动。使用AOP,项目启动后,只要调用切入点对应的方法,就会根据切入点来形成一个切面,进行统一的逻辑增强;如果大家熟悉SpringMVC,SpringMVC提供了 ResponseBodyAdvice 和 RequestBodyAdvice两个接口,这两个接口可以对请求和响应进行预处理,就可以不需要使用AOP;
项目目录:
pom.xml文件:
- java复制代码<dependencies>
- <!--Springboot项目自带 -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-test</artifactId>
- <scope>test</scope>
- </dependency>
- <!--Springboot Web项目 -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
-
- <!--lombok -->
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- <version>1.18.22</version>
- </dependency>
-
- <!-- hutool -->
- <dependency>
- <groupId>cn.hutool</groupId>
- <artifactId>hutool-all</artifactId>
- <version>5.7.20</version>
- </dependency>
-
- <!-- 切面 aop -->
- <dependency>
- <groupId>org.aspectj</groupId>
- <artifactId>aspectjweaver</artifactId>
- <version>1.9.7</version>
- </dependency>
- </dependencies>
用户信息新增实体类 :UserBO
- java复制代码package com.weige.javaskillpoint.entity;
-
- import com.weige.javaskillpoint.annotation.EncryptField;
-
- // 实体类
- public class UserBO {
- @EncryptField
- private String name;
-
- @EncryptField
- private String address;
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- public String getAddress() {
- return address;
- }
-
- public void setAddress(String address) {
- this.address = address;
- }
-
- public UserBO(String name, String address) {
- this.name = name;
- this.address = address;
- }
-
- @Override
- public String toString() {
- return "UserBO{" +
- "name='" + name + '\'' +
- ", address='" + address + '\'' +
- '}';
- }
- }
用户信息返回实体类 :UserDO
- java复制代码package com.weige.javaskillpoint.entity;
-
- import com.weige.javaskillpoint.annotation.DecryptField;
- import com.weige.javaskillpoint.enums.DesensitizationEnum;
-
- // 实体类
- public class UserDO {
-
- @DecryptField(DesensitizationEnum.name)
- private String name;
-
- @DecryptField(DesensitizationEnum.address)
- private String address;
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- public String getAddress() {
- return address;
- }
-
- public void setAddress(String address) {
- this.address = address;
- }
-
- public UserDO(String name, String address) {
- this.name = name;
- this.address = address;
- }
- }
- java复制代码package com.weige.javaskillpoint.enums;
-
- public enum DesensitizationEnum {
- name,
- address,
- phone;
- }
-
解密字段注解(字段):
- java复制代码package com.weige.javaskillpoint.annotation;
-
- import com.weige.javaskillpoint.enums.DesensitizationEnum;
-
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
-
- @Target(ElementType.FIELD)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface DecryptField {
- DesensitizationEnum value();
- }
解密方法注解(方法 作切入点):
- java复制代码package com.weige.javaskillpoint.annotation;
-
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
-
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface Decryption {
-
- }
-
加密字段注解(字段):
- java复制代码package com.weige.javaskillpoint.annotation;
-
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
-
- @Target(ElementType.FIELD)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface EncryptField {
-
- }
加密方法注解(方法 作切入点):
- java复制代码package com.weige.javaskillpoint.annotation;
-
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
-
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface Encryption {
-
- }
解密 Controller:
- java复制代码package com.weige.javaskillpoint.controller;
-
- import com.weige.javaskillpoint.annotation.Decryption;
- import com.weige.javaskillpoint.entity.UserDO;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RestController;
-
- @RestController
- @RequestMapping("/decrypt")
- public class DecryptController {
-
- @GetMapping("/v1")
- @Decryption
- public UserDO decrypt() {
- return new UserDO("7c29e296e92893476db5f9477480ba7f", "b5c7ff86ac36c01dda45d9ffb0bf73194b083937349c3901f571d42acdaa7bae");
- }
-
- }
-
加密 Controller:
- java复制代码package com.weige.javaskillpoint.controller;
-
- import com.weige.javaskillpoint.annotation.Encryption;
- import com.weige.javaskillpoint.entity.UserBO;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.web.bind.annotation.PostMapping;
- import org.springframework.web.bind.annotation.RequestBody;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RestController;
-
- @RestController
- @RequestMapping("/encrypt")
- @Slf4j
- public class EncryptController {
-
- @PostMapping("/v1")
- @Encryption
- public UserBO insert(@RequestBody UserBO user) {
- log.info("加密后对象:{}", user);
- return user;
- }
- }
解密脱敏切面:
- java复制代码package com.weige.javaskillpoint.aop;
-
- import com.weige.javaskillpoint.annotation.DecryptField;
- import com.weige.javaskillpoint.enums.DesensitizationEnum;
- import com.weige.javaskillpoint.utils.AesUtil;
- import lombok.extern.slf4j.Slf4j;
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.annotation.Around;
- import org.aspectj.lang.annotation.Aspect;
- import org.aspectj.lang.annotation.Pointcut;
- import org.springframework.stereotype.Component;
-
- import java.lang.reflect.Field;
- import java.util.ArrayList;
- import java.util.Collection;
- import java.util.List;
- import java.util.Objects;
-
- @Slf4j
- @Aspect
- @Component
- public class DecryptAspect {
- //拦截需解密注解
- @Pointcut("@annotation(com.weige.javaskillpoint.annotation.Decryption)")
- public void point() {
-
- }
-
- @Around("point()")
- public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
- //解密
- return decrypt(joinPoint);
- }
-
- public Object decrypt(ProceedingJoinPoint joinPoint) {
- Object result = null;
- try {
- Object obj = joinPoint.proceed();
- if (obj != null) {
- //抛砖引玉 ,可自行扩展其他类型字段的判断
- if (obj instanceof String) {
- decryptValue();
- } else {
- result = decryptData(obj);
- }
- }
- } catch (Throwable e) {
- e.printStackTrace();
- }
- return result;
- }
-
- private Object decryptData(Object obj) throws IllegalAccessException {
-
- if (Objects.isNull(obj)) {
- return null;
- }
- if (obj instanceof ArrayList) {
- decryptList(obj);
- } else {
- decryptObj(obj);
- }
- return obj;
- }
-
- private void decryptObj(Object obj) throws IllegalAccessException {
- Field[] fields = obj.getClass().getDeclaredFields();
- for (Field field : fields) {
- boolean hasSecureField = field.isAnnotationPresent(DecryptField.class);
- if (hasSecureField) {
- field.setAccessible(true);
- if (field.get(obj) != null) {
- String realValue = (String) field.get(obj);
- DesensitizationEnum desensitizationEnum = field.getAnnotation(DecryptField.class).value();
- String value = (String) AesUtil.decrypt(realValue,desensitizationEnum);
- field.set(obj, value);
- }
- }
- }
- }
-
- private void decryptList(Object obj) throws IllegalAccessException {
- List<Object> result = new ArrayList<>();
- if (obj instanceof ArrayList) {
- result.addAll((Collection<?>) obj);
- }
- for (Object object : result) {
- decryptObj(object);
- }
- }
-
- private void decryptValue() {
- log.info("根据对象进行解密脱敏,单个字段不做处理!");
- }
-
-
-
- }
-
加密切面:
- java复制代码package com.weige.javaskillpoint.aop;
-
- import com.weige.javaskillpoint.annotation.EncryptField;
- import com.weige.javaskillpoint.entity.UserBO;
- import com.weige.javaskillpoint.utils.AesUtil;
- import lombok.extern.slf4j.Slf4j;
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.annotation.Around;
- import org.aspectj.lang.annotation.Aspect;
- import org.aspectj.lang.annotation.Pointcut;
- import org.springframework.stereotype.Component;
-
- import java.lang.reflect.Field;
-
- @Slf4j
- @Aspect
- @Component
- public class EncryptAspect {
-
- //拦截需加密注解
- @Pointcut("@annotation(com.weige.javaskillpoint.annotation.Encryption)")
- public void point() {
-
- }
-
- @Around("point()")
- public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
- //加密
- encrypt(joinPoint);
- return joinPoint.proceed();
- }
-
- public void encrypt(ProceedingJoinPoint joinPoint) {
- Object[] objects;
- try {
- objects = joinPoint.getArgs();
- if (objects.length != 0) {
- for (Object object : objects) {
- if (object instanceof UserBO) {
- Field[] fields = object.getClass().getDeclaredFields();
- for (Field field : fields) {
- if (field.isAnnotationPresent(EncryptField.class)) {
- field.setAccessible(true);
- if (field.get(object) != null) {
- // 进行加密
- Object o = field.get(object);
- Object encrypt = AesUtil.encrypt(field.get(object));
- field.set(object, encrypt);
- }
- }
- }
- }
- }
- }
- } catch (Exception e) {
- log.error(e.getMessage());
- }
- }
- }
加密工具类:AesUtil
- java复制代码package com.weige.javaskillpoint.utils;
-
- import cn.hutool.core.util.CharsetUtil;
- import cn.hutool.crypto.SecureUtil;
- import cn.hutool.crypto.symmetric.AES;
- import com.weige.javaskillpoint.enums.DesensitizationEnum;
-
- public class AesUtil {
-
- // 默认16位 或 128 256位
- public static String AES_KEY = "Wk#qerdfdshbd910";
-
- public static AES aes = SecureUtil.aes(AES_KEY.getBytes());
-
- public static Object encrypt(Object obj) {
- return aes.encryptHex((String) obj);
- }
-
- public static Object decrypt(Object obj, DesensitizationEnum desensitizationEnum) {
- // 解密
- Object decrypt = decrypt(obj);
- // 脱敏
- return DesensitizationUtil.desensitization(decrypt, desensitizationEnum);
- }
-
- public static Object decrypt(Object obj) {
- return aes.decryptStr((String) obj, CharsetUtil.CHARSET_UTF_8);
- }
-
- }
-
脱敏工具类:DesensitizationUtil
- java复制代码package com.weige.javaskillpoint.utils;
-
- import cn.hutool.core.util.StrUtil;
- import com.weige.javaskillpoint.enums.DesensitizationEnum;
-
- public class DesensitizationUtil {
-
-
- public static Object desensitization(Object obj, DesensitizationEnum desensitizationEnum) {
- Object result;
- switch (desensitizationEnum) {
- case name:
- result = strUtilHide(obj, 1);
- break;
- case address:
- result = strUtilHide(obj, 3);
- break;
- default:
- result = "";
- }
- return result;
- }
-
- /**
- * start从0开始
- */
- public static Object strUtilHide(String obj, int start, int end) {
- return StrUtil.hide(obj, start, end);
- }
-
- public static Object strUtilHide(Object obj, int start) {
- return strUtilHide(((String) obj), start, ((String) obj).length());
- }
-
- }
-
以上代码不难,大伙复制到本地跑一遍,基本就能理解;愿每一位程序员少走弯路!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。