> 文章列表 > 用starter实现api接口的加密与日志功能

用starter实现api接口的加密与日志功能

用starter实现api接口的加密与日志功能

一、概述

运作AOP技术实现对api接口的加密及日志功能。

  • 加密
    • 需要加密的api接口上加注解:@Encrypt(自定义注解)
    • 接口返回类型为String时才加密
    • 采用对称加密:加密和解密使用相同的密钥
  • 日志

对所有的api接口添加日志功能:记录接口的执行时间

  • 服务请求、处理的基本流程

在这里插入图片描述

二、制作starter

制作过程参考:

  • 【SpringBoot】自定义启动器 Starter【保姆级教程】
  • 用starter实现Oauth2中资源服务的统一配置
  • 用spring-boot-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());// 执行目标方法proceedObject 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();// 执行目标方法proceedObject 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 属性配置类*/@Resourceprivate AesProperty aesProperty;/* 初始化*/@PostConstructprivate void init(){// 给加密工具类注入密钥TuwerEncryptAesUtil.setKey(aesProperty.getAesSecretKey());}/* 加密切面类* @return*/@Beanpublic EncryptAspect encryptAspect(){return new EncryptAspect();}/* 日志切面类* @return*/@Beanpublic 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.controllercom.tuwer.api 包下的所有方法。