diff --git a/secret-demo/.gitignore b/secret-demo/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/secret-demo/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/secret-demo/pom.xml b/secret-demo/pom.xml new file mode 100644 index 0000000..8d7a462 --- /dev/null +++ b/secret-demo/pom.xml @@ -0,0 +1,66 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.7.2 + + + com.aop + secret + 0.0.1-SNAPSHOT + secret-demo + Demo project for Spring Boot + + 1.8 + + + + org.springframework.boot + spring-boot-starter-web + + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + + + org.springframework.boot + spring-boot-starter-aop + + + + cn.hutool + hutool-all + 5.8.5 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + diff --git a/secret-demo/src/main/java/com/aop/secret/SecretDemoApplication.java b/secret-demo/src/main/java/com/aop/secret/SecretDemoApplication.java new file mode 100644 index 0000000..8ccb757 --- /dev/null +++ b/secret-demo/src/main/java/com/aop/secret/SecretDemoApplication.java @@ -0,0 +1,13 @@ +package com.aop.secret; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SecretDemoApplication { + + public static void main(String[] args) { + SpringApplication.run(SecretDemoApplication.class, args); + } + +} diff --git a/secret-demo/src/main/java/com/aop/secret/annotation/Secret.java b/secret-demo/src/main/java/com/aop/secret/annotation/Secret.java new file mode 100644 index 0000000..c3703ed --- /dev/null +++ b/secret-demo/src/main/java/com/aop/secret/annotation/Secret.java @@ -0,0 +1,25 @@ +package com.aop.secret.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + *

+ * 加解密注解 + *

+ * + * @author 蜗牛 + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Secret { + + // 请求解密的属性,默认手机号和身份证号,可以自行扩展。 + String[] reqPropsName() default {"phone","idCard"}; + + // 响应加密的属性,默认手机号和身份证号,可以自行扩展。 + String[] respPropsName() default {"phone","idCard"}; + +} diff --git a/secret-demo/src/main/java/com/aop/secret/aop/SecretAop.java b/secret-demo/src/main/java/com/aop/secret/aop/SecretAop.java new file mode 100644 index 0000000..19b0ca6 --- /dev/null +++ b/secret-demo/src/main/java/com/aop/secret/aop/SecretAop.java @@ -0,0 +1,125 @@ +package com.aop.secret.aop; + +import com.aop.secret.annotation.Secret; +import com.aop.secret.util.AesUtil; +import com.aop.secret.util.Result; +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.aspectj.lang.reflect.MethodSignature; +import org.springframework.stereotype.Component; +import org.springframework.util.ObjectUtils; +import java.lang.reflect.Method; + +/** + *

+ * 加解密注解切面类 + *

+ * + * @author 蜗牛 + */ +@Aspect +@Component +@Slf4j +public class SecretAop { + + /** + * 定义切点,类或方法使用了@Secret注解的地方。 + */ + @Pointcut("@annotation(com.aop.secret.annotation.Secret)") + public void pointcut() { + } + + @Around("pointcut()") + public Object around(ProceedingJoinPoint point) throws Throwable { + // 被代理的对象 + Object target = point.getTarget(); + // 被代理方法的参数 + Object[] args = point.getArgs(); + // 获取通知签名 + MethodSignature signature = (MethodSignature) point.getSignature(); + // 获取被代理的方法 + Method method = target.getClass().getMethod(signature.getName(), signature.getParameterTypes()); + // 获取被代理方法上的注解 + Secret secret = method.getAnnotation(Secret.class); + // 最终返回对象 + Object respObj; + + try { + String[] reqPropsName = secret.reqPropsName(); + String[] respPropsName = secret.respPropsName(); + + // 对请求对象进行解密 + if (!ObjectUtils.isEmpty(reqPropsName)) { + + // 获取请求对象,如果参数只有一个并且是对象,则进行解密处理。 + if (!ObjectUtils.isEmpty(args) && args.length == 1) { + Object arg = args[0]; + // 遍历要解密的属性,和请求对象中的属性进行匹配。 + for (String propName : reqPropsName) { + // 得到匹配上的属性值 + String propVal = (String) arg.getClass().getMethod(getterName(propName)).invoke(arg); + // 解密 + String propDecrypt = AesUtil.decrypt(propVal); + System.out.println("解密--------> " + propName + ": " + propVal + " | " + propDecrypt); + // 设置解密的属性值 + arg.getClass().getMethod(setterName(propName), String.class).invoke(arg, propDecrypt); + } + + } else if (args.length > 1) { + // todo 这里可自行扩展以满足其他请求对象及方式,如多个参数、非对象形式的参数等等。 + } + + } + + // 继续向下执行后得到的响应结果 + respObj = point.proceed(); + + // 对响应对象进行加密 + if (!ObjectUtils.isEmpty(respPropsName)) { + + // 获取响应对象 + Object result = ((Result)respObj).getData(); + log.info("result: {}", result); + + for (String propName : respPropsName) { + // 获取要加密的属性值 + String propVal = (String) result.getClass().getMethod(getterName(propName)).invoke(result); + // 加密 + String propEncrypt = AesUtil.encrypt(propVal); + System.out.println("加密--------> " + propName + ": " + propVal + " | " + propEncrypt); + // 设置加密的属性值 + result.getClass().getMethod(setterName(propName), String.class).invoke(result, propEncrypt); + } + + } + + } catch (Exception ex) { + // 发生异常记录下日志,但正常执行下去,也可以不try..catch直接通过全局异常处理。 + log.error("加解密异常: {}", ex.getMessage(), ex); + respObj = point.proceed(); + } + + return respObj; + } + + /** + * 转化get方法名 + */ + private String getterName(String name) { + String first = name.substring(0, 1); + String last = name.substring(1); + return "get" + first.toUpperCase() + last; + } + + /** + * 转化set方法名 + */ + private String setterName(String name) { + String first = name.substring(0, 1); + String last = name.substring(1); + return "set" + first.toUpperCase() + last; + } +} diff --git a/secret-demo/src/main/java/com/aop/secret/controller/AesTestController.java b/secret-demo/src/main/java/com/aop/secret/controller/AesTestController.java new file mode 100644 index 0000000..9ee7232 --- /dev/null +++ b/secret-demo/src/main/java/com/aop/secret/controller/AesTestController.java @@ -0,0 +1,41 @@ +package com.aop.secret.controller; + +import com.aop.secret.annotation.Secret; +import com.aop.secret.pojo.UserInfo; +import com.aop.secret.pojo.UserInfoReq; +import com.aop.secret.util.Result; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +/** + * 一个注解搞定SpringBoot接口定制属性加解密 + * @author 蜗牛 + */ +@RestController +@RequestMapping("/api/aes") +@Slf4j +public class AesTestController { + + @GetMapping("/getUser") + @Secret(reqPropsName = {}, respPropsName = {"phone", "idCard", "name"}) + public Result getUser() { + + // 模拟返回用户信息 + UserInfo userInfo = new UserInfo(); + userInfo.setId(1001L); + userInfo.setName("蜗牛"); + userInfo.setAge(500); + userInfo.setIdCard("320125199801253349"); + userInfo.setPhone("15912345678"); + userInfo.setAddress("蜗牛洞"); + + return new Result(HttpStatus.OK.value(), "查询成功", userInfo); + } + + @PostMapping("/saveUser") + @Secret(reqPropsName = {"phone", "idCard", "name"}, respPropsName = {}) + public Result saveUser(@RequestBody UserInfoReq userInfoReq) { + return new Result(HttpStatus.OK.value(), "保存成功", userInfoReq); + } +} diff --git a/secret-demo/src/main/java/com/aop/secret/pojo/UserInfo.java b/secret-demo/src/main/java/com/aop/secret/pojo/UserInfo.java new file mode 100644 index 0000000..ca50668 --- /dev/null +++ b/secret-demo/src/main/java/com/aop/secret/pojo/UserInfo.java @@ -0,0 +1,14 @@ +package com.aop.secret.pojo; + +import lombok.Data; + + +@Data +public class UserInfo { + private Long id; + private String name; + private Integer age; + private String idCard; + private String phone; + private String address; +} diff --git a/secret-demo/src/main/java/com/aop/secret/pojo/UserInfoReq.java b/secret-demo/src/main/java/com/aop/secret/pojo/UserInfoReq.java new file mode 100644 index 0000000..57f6ae6 --- /dev/null +++ b/secret-demo/src/main/java/com/aop/secret/pojo/UserInfoReq.java @@ -0,0 +1,13 @@ +package com.aop.secret.pojo; + +import lombok.Data; + + +@Data +public class UserInfoReq { + private String name; + private Integer age; + private String idCard; + private String phone; + private String address; +} diff --git a/secret-demo/src/main/java/com/aop/secret/util/AesUtil.java b/secret-demo/src/main/java/com/aop/secret/util/AesUtil.java new file mode 100644 index 0000000..71669d4 --- /dev/null +++ b/secret-demo/src/main/java/com/aop/secret/util/AesUtil.java @@ -0,0 +1,52 @@ +package com.aop.secret.util; + +import cn.hutool.crypto.symmetric.SymmetricAlgorithm; +import cn.hutool.crypto.symmetric.SymmetricCrypto; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.nio.charset.StandardCharsets; + + +@Component +public class AesUtil { + + // AES工具声明 + private static SymmetricCrypto aes; + + // 密钥字符串 + private static String secret; + + @Value("${aes.secret}") + public void setSecret(String secret) { + AesUtil.secret = secret; + } + + @PostConstruct + public void init() { + // AES工具构建 + byte[] key = secret.getBytes(StandardCharsets.UTF_8); + aes = new SymmetricCrypto(SymmetricAlgorithm.AES, key); + } + + /** + * 加密 + * @param content 内容 + * @return 加密结果 + */ + public static String encrypt(String content) { + return aes.encryptHex(content); + } + + /** + * 解密 + * @param encryptStr 加密内容 + * @return 解密结果 + */ + public static String decrypt(String encryptStr) { + return aes.decryptStr(encryptStr); + } + + +} diff --git a/secret-demo/src/main/java/com/aop/secret/util/Result.java b/secret-demo/src/main/java/com/aop/secret/util/Result.java new file mode 100644 index 0000000..ef99774 --- /dev/null +++ b/secret-demo/src/main/java/com/aop/secret/util/Result.java @@ -0,0 +1,76 @@ +package com.aop.secret.util; + +public class Result { + + private int code; + private String msg; + private T data; + + public Result(int code, String msg, T data) { + this.code = code; + this.msg = msg; + this.data = data; + } + public Result(int code, String msg) { + this.code = code; + this.msg = msg; + } + + public Result() { + + } + + /** + * 构建消息内容 + * @param msg + * @return + */ + public Result buildMessage(String msg){ + this.setMsg(msg); + return this; + } + + /** + * 构建消息data的值,key默认为data + * @param obj data值 + * @return + */ + public Result buildData(T obj){ + this.setData(obj); + return this; + + } + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public T getData() { + return data; + } + + public void setData(T data) { + this.data = data; + } + + @Override + public String toString() { + return "Result{" + + "code=" + code + + ", msg='" + msg + '\'' + + ", data=" + data + + '}'; + } +} diff --git a/secret-demo/src/main/resources/application.yml b/secret-demo/src/main/resources/application.yml new file mode 100644 index 0000000..915f6a4 --- /dev/null +++ b/secret-demo/src/main/resources/application.yml @@ -0,0 +1,6 @@ +server: + port: 8888 + +# 密钥 +aes: + secret: 6hj!xggb@ssg&1ca \ No newline at end of file diff --git a/secret-demo/src/test/java/com/aop/secret/SecretDemoApplicationTests.java b/secret-demo/src/test/java/com/aop/secret/SecretDemoApplicationTests.java new file mode 100644 index 0000000..b1d4272 --- /dev/null +++ b/secret-demo/src/test/java/com/aop/secret/SecretDemoApplicationTests.java @@ -0,0 +1,13 @@ +package com.aop.secret; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class SecretDemoApplicationTests { + + @Test + void contextLoads() { + } + +}