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

一、概述

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

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

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

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

用starter实现api接口的加密与日志功能_第1张图片

二、制作starter

制作过程参考:

  • 【SpringBoot】自定义启动器 Starter【保姆级教程】
  • 用starter实现Oauth2中资源服务的统一配置
  • 用spring-boot-starter实现事务的统一配置

1、总体结构

用starter实现api接口的加密与日志功能_第2张图片

2、外部引用模块

名称:tuwer-encrypt-log-spring-boot-starter

引用模块用于外部引用。只有pom.xml文件

  • pom.xml

<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.0modelVersion>

    <groupId>com.tuwergroupId>
    <artifactId>tuwer-encrypt-log-spring-boot-starterartifactId>
    <version>1.0-SNAPSHOTversion>
    <description>api结果加密/接口日志starterdescription>

    <properties>
        <maven.compiler.source>8maven.compiler.source>
        <maven.compiler.target>8maven.compiler.target>
        
        <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
    properties>
    <dependencies>
        
        <dependency>
            <groupId>com.tuwergroupId>
            <artifactId>tuwer-encrypt-log-spring-boot-starter-autoconfigureartifactId>
            <version>1.0-SNAPSHOTversion>
        dependency>
    dependencies>

project>

3、自动配置模块

名称:tuwer-encrypt-log-spring-boot-starter-autoconfigure

1> pom.xml


<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.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.6.7version>
    parent>
    <modelVersion>4.0.0modelVersion>

    <groupId>com.tuwergroupId>
    <artifactId>tuwer-encrypt-log-spring-boot-starter-autoconfigureartifactId>
    <version>1.0-SNAPSHOTversion>
    <description>api结果加密/接口日志starter自动配置模块description>

    <properties>
        <maven.compiler.source>8maven.compiler.source>
        <maven.compiler.target>8maven.compiler.target>
        
        <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
    properties>
    <dependencies>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starterartifactId>
        dependency>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-aopartifactId>
        dependency>
        
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <version>1.18.22version>
        dependency>
        
        <dependency>
            <groupId>cn.hutoolgroupId>
            <artifactId>hutool-cryptoartifactId>
            <version>5.8.16version>
        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;

/**
 * 

加密注解

* * @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;

/**
 * 

对称加密属性

* * @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;

/**
 * 

加密切面类

* * @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;

/**
 * 

日志切面类

* * @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;

/**
 * 

自动配置类

* * @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、引入依赖


<dependency>
	<groupId>com.tuwergroupId>
	<artifactId>tuwer-encrypt-log-spring-boot-starterartifactId>
	<version>1.0-SNAPSHOTversion>
dependency>

2、配置密钥

application.yml 中添加密钥;16位:超出将截取,不足将补齐

# 密钥
encrypt:
  aes-secret-key: 0123456789123456

3、添加加密注解

  • 在需要加密的api接口上添加 @Encrypt 即可。
  • 关于解密:请求方收到服务方返回的密文后需要先解密。如何自动判断是明文还是密文?明文和密文有明显的区别,通过二者的区别,可以判断是否加密了:
    • 明文:就是Result对象的json字符串。可以直接看到Result对象的code等特征信息。
    • 密文:表面上看就是一串随机的字符,看不到Result对象的特征信息。

用starter实现api接口的加密与日志功能_第3张图片

4、日志功能

关于日志功能,只要引入了依赖后,自动生效,不需要额外的配置。

默认的切入点:com.tuwer.controllercom.tuwer.api 包下的所有方法。

你可能感兴趣的:(#,SpringBoot,#,SpringCloud,Spring,spring,boot,java,spring)