一、概述
运用AOP技术实现对api接口的加密及日志功能。
- 加密:
- 需要加密的api接口上加注解:
@Encrypt
(自定义注解) - 接口返回类型为
String
时才加密 - 采用对称加密:加密和解密使用相同的密钥
- 需要加密的api接口上加注解:
- 日志:
对所有的api接口添加日志功能:记录接口的执行时间
- 服务请求、处理的基本流程
二、制作starter
制作过程参考:
1、总体结构
2、外部引用模块
名称:
tuwer-encrypt-log-spring-boot-starter
引用模块用于外部引用。只有
pom.xml
文件
- pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.tuwer</groupId>
<artifactId>tuwer-encrypt-log-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
<description>api结果加密/接口日志starter</description>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<!-- 编译编码 -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- 自动配置模块 -->
<dependency>
<groupId>com.tuwer</groupId>
<artifactId>tuwer-encrypt-log-spring-boot-starter-autoconfigure</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
3、自动配置模块
名称:
tuwer-encrypt-log-spring-boot-starter-autoconfigure
1> pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.7</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.tuwer</groupId>
<artifactId>tuwer-encrypt-log-spring-boot-starter-autoconfigure</artifactId>
<version>1.0-SNAPSHOT</version>
<description>api结果加密/接口日志starter自动配置模块</description>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<!-- 编译编码 -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- 基础启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
<!-- 加密工具包 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-crypto</artifactId>
<version>5.8.16</version>
</dependency>
</dependencies>
</project>
2> 自定义注解 Encrypt
package com.tuwer.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* <p>加密注解</p>
*
* @author 土味儿
* Date 2023/4/18
* @version 1.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Encrypt {
String desc() default "";
}
3> 加密工具类
package com.tuwer.util;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import cn.hutool.crypto.symmetric.SymmetricCrypto;
/**
* 加解密工具类
*
* @author 土味儿
* Date 2023/4/8
* @version 1.0
*/
public class TuwerEncryptAesUtil {
private static String key;
public static String getKey() {
// 超过16位时,截取
if (TuwerEncryptAesUtil.key.length() > 16) {
TuwerEncryptAesUtil.key = TuwerEncryptAesUtil.key.substring(0, 16);
}
// 少于16位时,补全
if (TuwerEncryptAesUtil.key.length() < 16) {
int n = 16 - TuwerEncryptAesUtil.key.length();
StringBuilder _s = new StringBuilder();
for (int i = 0; i < n; i++) {
_s.append("-");
}
TuwerEncryptAesUtil.key = TuwerEncryptAesUtil.key + _s;
}
return key;
}
public static void setKey(String key) {
TuwerEncryptAesUtil.key = key;
}
/**
* 获取AES对象
*
* @return
*/
private static SymmetricCrypto getAes() {
// 生成密钥
byte[] byteKey = SecureUtil.generateKey(SymmetricAlgorithm.AES.getValue(), getKey().getBytes()).getEncoded();
SymmetricCrypto aes = SecureUtil.aes(byteKey);
return aes;
}
/**
* 加密
*
* @param content
* @return 返回null时,加密失败
*/
public static String encrypt(String content) {
try {
return getAes().encryptBase64(content);
} catch (Exception e) {
// 加密失败
//e.printStackTrace();
return null;
}
}
/**
* 解密
*
* @param encryptData
* @return 返回null时,解密失败
*/
public static String decrypt(String encryptData) {
try {
return getAes().decryptStr(encryptData);
} catch (Exception e) {
// 解密失败
//e.printStackTrace();
return null;
}
}
}
4> 密钥属性类
用于注入外部配置的密钥;
对称加密:加密和解密使用同一个密钥;即:请求方和服务方共用密钥。统一在外部配置。
package com.tuwer.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* <p>对称加密属性</p>
*
* @author 土味儿
* Date 2023/4/18
* @version 1.0
*/
@Data
@ConfigurationProperties(prefix = "encrypt")
public class AesProperty {
/**
* 对称加密的密钥
*/
private String aesSecretKey;
}
5> 加密切面类
拦截api方法,对结果进行加密。
只对有
@Encrypt
注解的方法进行拦截。拦截后判断方法的返回类型,只有String
类型时才加密。
package com.tuwer.aop;
import com.tuwer.util.TuwerEncryptAesUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import java.util.Objects;
/**
* <p>加密切面类</p>
*
* @author 土味儿
* Date 2023/4/18
* @version 1.0
*/
@Aspect
@Slf4j
public class EncryptAspect {
/**
* 切入点
*/
@Pointcut(value = "@annotation(com.tuwer.annotation.Encrypt)")
private void pointCut(){ }
/**
* 加密增强方法
* 只拦载有 @Encrypt 注解的方法
* 方法返回类型是字符串时加密
*
* @param pj
* @return
* @throws Throwable
*/
@Around("pointCut()")
public Object around(ProceedingJoinPoint pj) throws Throwable {
log.info("\n");
log.info("--------------- 加密拦截器 -----------------");
// 方法签名:String com.tuwer.api...xxx(Integer)
Signature signature = pj.getSignature();
log.info("拦截到方法【{}】,准备对结果加密...", signature.toShortString());
// 执行目标方法proceed
Object result = pj.proceed();
// 方法返回类型
String signatureStr = signature.toString();
String resultType = signatureStr.substring(0, signatureStr.indexOf(" ")).trim();
String strModel = "string";
if (strModel.equalsIgnoreCase(resultType)) {
// 返回类型是字符串;加密
log.info("加密中...");
String encryptResult = TuwerEncryptAesUtil.encrypt(result.toString());
if (Objects.isNull(encryptResult)) {
log.info("加密失败!明文返回!");
log.info("\n");
return result;
}
log.info("已加密!");
log.info("\n");
return encryptResult;
}
log.info("方法的返回类型不是字符串!不用加密!");
log.info("\n");
return result;
}
}
6> 日志切面类
对所有api接口方法增加日志功能
package com.tuwer.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;
/**
* <p>日志切面类</p>
*
* @author 土味儿
* Date 2023/4/18
* @version 1.0
*/
@Aspect
@Slf4j
public class LogAspect {
/**
* 切入点
*/
@Pointcut(value = "execution(* com.tuwer.controller..*(..)) || execution(* com.tuwer.api..*(..))")
//@Pointcut("${配置文件中key}")
private void pointCut(){ }
/**
* 日志增强方法
* @param pj
* @return
* @throws Throwable
*/
//@Around("execution(* com.tuwer.controller..*(..))")
//@Around(value = "execution(* com.tuwer.controller..*(..)) || execution(* com.tuwer.api..*(..))")
@Around("pointCut()")
public Object around(ProceedingJoinPoint pj) throws Throwable {
// 当前时间
long start = System.currentTimeMillis();
// 执行目标方法proceed
Object result = pj.proceed();
// 执行时间
long end = System.currentTimeMillis();
long dur = end - start;
// 输出日志
log.info("\n");
log.info("【日志拦载器】:执行方法【{}】,耗时: {}", pj.getSignature().toShortString(), dur);
log.info("\n");
return result;
}
}
7> 自动配置类
package com.tuwer.config;
import com.tuwer.aop.EncryptAspect;
import com.tuwer.aop.LogAspect;
import com.tuwer.util.TuwerEncryptAesUtil;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
/**
* <p>自动配置类</p>
*
* @author 土味儿
* Date 2023/4/18
* @version 1.0
*/
@Configuration
@EnableConfigurationProperties(AesProperty.class)
public class TuwerEncryptLogAutoConfiguration {
/**
* 注入 AesProperty 属性配置类
*/
@Resource
private AesProperty aesProperty;
/**
* 初始化
*/
@PostConstruct
private void init(){
// 给加密工具类注入密钥
TuwerEncryptAesUtil.setKey(aesProperty.getAesSecretKey());
}
/**
* 加密切面类
* @return
*/
@Bean
public EncryptAspect encryptAspect(){
return new EncryptAspect();
}
/**
* 日志切面类
* @return
*/
@Bean
public LogAspect logAspect(){
return new LogAspect();
}
}
8> spring.factories
指明自动配置类的地址,在
resources
目录下编写一个自己的META-INF\spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.tuwer.config.TuwerEncryptLogAutoConfiguration
9> install
把starter安装install到本地maven仓库中
三、使用starter
1、引入依赖
<!-- 加密与日志starter -->
<dependency>
<groupId>com.tuwer</groupId>
<artifactId>tuwer-encrypt-log-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
2、配置密钥
在
application.yml
中添加密钥;16位:超出将截取,不足将补齐
# 密钥
encrypt:
aes-secret-key: 0123456789123456
3、添加加密注解
- 在需要加密的api接口上添加
@Encrypt
即可。 - 关于解密:请求方收到服务方返回的密文后需要先解密。如何自动判断是明文还是密文?明文和密文有明显的区别,通过二者的区别,可以判断是否加密了:
- 明文:就是Result对象的json字符串。可以直接看到Result对象的code等特征信息。
- 密文:表面上看就是一串随机的字符,看不到Result对象的特征信息。
4、日志功能
关于日志功能,只要引入了依赖后,自动生效,不需要额外的配置。
默认的切入点:com.tuwer.controller
或 com.tuwer.api
包下的所有方法。