对称加密DES、AES、SM4-池化-接入spring项目请求解密响应加密

开发项目使用的spring boot web 项目,需求是 部分接口 需要 支持 报文密文和明文传输 动态切换。 请求解密或明文、响应加密或明文,通过度娘搜索和综合考虑(就自己简单分析一下最简单的实现)。
当前实现的密码算法:DES、3DES、AES、SM4,只展示 DES和SM4 其他类似
使用apache.commons.pool2池化包,加速处理报文
使用 接口 统一 加解密操作
使用RequestBodyAdviceAdapterResponseBodyAdvice<?>处理spring web 接口报文
使用redis 缓存 认证信息-当前文档不会详细说明
参考:

代码实现

pom依赖

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
</dependency>
<!-- 池化包,含 对象池化-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>
<!-- hutool 工具包,4版本不支持sm加解密 -->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.4.5</version>
</dependency>
<!-- 支持SM4的加解密包 -->
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15to18</artifactId>
    <version>1.66</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
</dependency>
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
</dependency>

枚举-算法类型

package **.enums;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 对称加密 默认配置项
 *<pre>
 *     对称加密 SM1/SCB2 SM4/SMS4 DES IDEA AES RC5 RC6
 *     DES(Data Encryption Standard,数据加密标准)
 *     3DES(Triple DES、DESede,进行了三重DES加密的算法)
 *     AES(Advanced Encryption Standard,高级数据加密标准,AES算法可以有效抵制针对DES的攻击算法)
 *     SM4 国家商用密码算法-对称加密,自主设计的分组对称密码算法
 *     SM1、SM7 未公开
 *     算法公开、计算量小、加密速度快、加密效率高,使用同样密钥,安全性得不到保证
 *     ECB(电子密码本)、CBC(密文链接)、CFB(密文反馈)、OFB(输出反馈)、CTR(计数器)
 *</pre>
 * @author z.y
 * @version v1.0
 */
public enum SymEncEcbEnum {
    /** DES 16进制密文 */DES_16(Instance.DES, Code.LOWER_16),
    /** 3DES 16进制密文 */DES3_16(Instance.DES3, Code.LOWER_16),
    /** AES 16进制密文 */AES_16(Instance.AES, Code.LOWER_16),
    /** SM4 16进制密文 */SM4_16(Instance.SM4, Code.LOWER_16),

    /** DES 16进制密文 大写 */DES_UP16(Instance.DES, Code.UPPER_16),
    /** 3DES 16进制密文 大写 */DES3_UP16(Instance.DES3, Code.UPPER_16),
    /** AES 16进制密文 大写 */AES_UP16(Instance.AES, Code.UPPER_16),
    /** SM4 16进制密文 大写 */SM4_UP16(Instance.SM4, Code.UPPER_16),

    /** DES 密文base64编码 */DES_B64(Instance.DES, Code.BASE_64),
    /** 3DES 密文base64编码 */DES3_B64(Instance.DES3, Code.BASE_64),
    /** AES 密文base64编码 */AES_B64(Instance.AES, Code.BASE_64),
    /** SM4 密文base64编码 */SM4_B64(Instance.SM4, Code.BASE_64),
    ;
    /** 对称密码算法 */
    private final Instance instance;
    /** 工作模式 */
    private final String mode;
    /** 别名 保证唯一 数据库存储或者枚举确定的标志 */
    private final String value;
    /** 枚举的中文说明 保证唯一 */
    private final String label;
    /** 密文编码方式  base64、16进制全小写(默认)、全大写 */
    private final Code code;
    SymEncEcbEnum(Instance instance, Code code){
        this.instance = instance;
        //ECB(电子密码本),最常用的,每次加密均产生独立的密文分组,并且对其他的密文分组不会产生影响,也就是相同的明文加密后产生相同的密文
        this.mode = instance.val + "/ECB/PKCS5Padding";
        this.code = code;
        this.value = instance.val +'-'+ code.type;
        this.label = instance.lab +'-'+ code.name;
    }
    get...

    /** 密文编码方式 base64 */
    public boolean isBase64() { return Code.BASE_64 == this.code; }
    /** 密文编码方式 16进制 大写 */
    public boolean isUpper() { return Code.UPPER_16 == this.code; }
    public boolean isLower() { return Code.LOWER_16 == this.code; }
    public boolean isDes(){ return Instance.DES == this.instance; }
    public boolean isDes3(){ return Instance.DES3 == this.instance; }
    public boolean isAes(){ return Instance.AES == this.instance; }
    public boolean isSm4(){ return Instance.SM4 == this.instance; }
    public boolean eq(String value){ return this.value.equals(value); }
    public static Map<String, String> labelValueMap(){
        return Stream.of(SymEncEcbEnum.values())
                .collect(Collectors.toMap(SymEncEcbEnum::getValue,SymEncEcbEnum::getLabel,(k1,k2)-> k2));
    }
    public static Map<String, SymEncEcbEnum> toMap(){
        return Stream.of(SymEncEcbEnum.values())
                .collect(Collectors.toMap(SymEncEcbEnum::getValue,i -> i,(k1,k2)-> k2));
    }
    public enum Instance{
        /** 对称密码算法 DES 数据加密标准 */DES("DES",56,"DES-数据加密标准"),
        /** 对称密码算法 3DES 进行了三重DES加密的算法 */DES3("DESede",168,"3DES-三重DES加密"),
        /** 对称密码算法 AES 高级数据加密标准 */AES("AES",128,"AES-高级数据加密标准"),
        /** 对称密码算法 SM4 国密分组密码算法 */SM4("SM4",128,"SM4-国密分组密码算法"),
        ;
        /** 算法 */
        private final String val;
        /** 密钥长度 */
        private final int keyLen;
        /** 算法名称 */
        private final String lab;
        Instance(String val,int keyLen,String lab){
            this.val = val;this.keyLen = keyLen; this.lab = lab;
        }
        get...

        public boolean eq(String type){ return this.val.equals(type); }
    }
    public enum Code{
        /** 加解密-密文编码 默认 小写 16进制 */LOWER_16("Lower16","小写16进制编码"),
        /** 密文 编码 大写 16进制 */UPPER_16("Upper16","大写16进制编码"),
        /** 密文编码 base64 */BASE_64("Base64","BASE64编码"),
        ;
        /** 密文编码 */
        private final String type;
        private final String name;
        Code(String type,String name){ this.type = type; this.name = name; }
        get...
        public boolean eq(String type){ return this.type.equals(type); }
    }
    //SM2: 国密椭圆曲线算法库
    //. 支持Generate Key, Sign, Verify基础操作
    //. 支持加密和不加密的pem文件格式(加密方法参见RFC5958, 具体实现参加代码)
    //. 支持证书的生成,证书的读写(接口兼容rsa和ecdsa的证书)
    //. 支持证书链的操作(接口兼容rsa和ecdsa)
    //. 支持crypto.Signer接口
    //SM3: 国密Hash算法库(摘要算法)
    //. 支持基础的sm3Sum操作
    //. 支持hash.Hash接口
    //SM4: 国密分组密码算法库
    //. 支持Generate Key, Encrypt, Decrypt基础操作
    //. 提供Cipher.Block接口
    //. 支持加密和不加密的pem文件格式(加密方法为pem block加密, 具体函数为x509.EncryptPEMBlock)
}

算法-统一接口

package **.ciphers;

import **.enums.SymEncEcbEnum;

import java.nio.charset.StandardCharsets;
import java.util.Base64;

/**
 * 对称密码算法 基类,不要用单例模式,最好通过new生成(如果key固定建议使用对象池管理)
 * <pre>
 *     对象池化涉及类:
 *     **.beans.EncEcbKeyBean
 *     **.ciphers.*
 *     **.enums.SymEncEcb
 *     **.exceptions.SymEncEcbException
 *     **.pools.SymEncEcbPool
 *     **.pools.SymEncEcbFactory
 *     **.configs.ApiEncEcbCfg
 * </pre>
 * @author z.y
 * @version v1.0
 */
public interface AbsSymEncEcb {
    char[] HEX_CHARS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
    String TEST_STR = "2021/12/34,hyJK-测试文本";

    /**
     * 校验 秘钥-单独使用
     * @param symEncEcb 密码算法,秘钥长度,秘钥字符串类型(16进制 大、小写;base64)
     * @param keyStr 秘钥字符串
     * @return 是否正确的秘钥,内部通过加解密测试验证,先加密在解密 最后解密的内容和测试文本比较
     */
    boolean checkKeyStr(SymEncEcbEnum symEncEcb, String keyStr);

    /**
     * 初始化 加解密 对象
     * @param symEncEcb 密码算法,秘钥长度,秘钥字符串类型(16进制 大、小写;base64)
     * @param keyStr 固定秘钥
     * @return this 链式调用
     * @throws Exception 异常
     */
    AbsSymEncEcb init(SymEncEcbEnum symEncEcb, String keyStr)throws Exception;
    /**
     * 初始化 加解密 对象, 随机秘钥 需要通过 getKeyStr获取
     * @param symEncEcb 密码算法,秘钥长度,秘钥字符串类型(16进制 大、小写;base64)
     * @return this 链式调用
     * @throws Exception 异常
     */
    AbsSymEncEcb init(SymEncEcbEnum symEncEcb)throws Exception;
    /**
     * 获取 秘钥,必须先用init(..)初始化过
     * @return 秘钥(16进制 大、小写;base64)
     */
    String getKeyStr();
    /**
     * 加密 字符串,必须先用init(..)初始化过
     * @param text 待加密 字符串
     * @return 加密 字符串
     * @throws Exception 异常
     */
    String encrypt(String text) throws Exception;

    /**
     * 解密 字符串,必须先用init(..)初始化过
     * @param text 待解密 字符串
     * @return 解密 字符串
     * @throws Exception 异常
     */
    String decrypt(String text) throws Exception;

    /**
     * 增加销毁方法
     */
    void destroy();

    /**
     * 是否初始化
     * @return true 已初始化;false 未初始化
     */
    boolean isInit();
    /**
     * 字节数组 转 base64字符串
     * @param bytes 字节数组
     * @return base64 字符串
     */
    default String encToBase64Str(byte[] bytes) {
        if (bytes.length == 0) { return ""; }
        return new String(Base64.getEncoder().encode(bytes), StandardCharsets.UTF_8);
    }
    /**
     * base64字符串 转 字节数组
     * @param base64 base64字符串
     * @return 字节数组
     */
    default byte[] decFromBase64Str(String base64) {
        if (base64.isEmpty()) { return new byte[0]; }
        return Base64.getDecoder().decode(base64.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * 字节数组 转 16进制字符串
     * @param bytes 字节数组
     * @return 16进制字符串
     */
    default String toHexString(byte[] bytes) {
        StringBuilder result = new StringBuilder();
        for (byte b : bytes) {
            result.append(HEX_CHARS[(b >>> 4) & 0x0f]);
            result.append(HEX_CHARS[b & 0x0f]);
        }
        return result.toString();
    }
    /**
     * 16进制字符串 转 字节数组
     * @param hexStr 16进制字符串
     * @return 字节数组
     */
    default byte[] fromHexString(String hexStr) {
        char[] rawChars = hexStr.toUpperCase().toCharArray();
        int hexChars = 0;
        for (char rawChar : rawChars) {
            if (rawChar >= '0' && rawChar <= '9') {
                hexChars++;
            }else if (rawChar >= 'A' && rawChar <= 'F') {
                hexChars++;
            }
        }
        byte[] byteString = new byte[(hexChars + 1) >> 1];
        int pos = hexChars & 1;
        for (char rawChar : rawChars) {
            if (rawChar >= '0' && rawChar <= '9') {
                byteString[pos >> 1] <<= 4;
                byteString[pos >> 1] |= rawChar - '0';
            } else if (rawChar >= 'A' && rawChar <= 'F') {
                byteString[pos >> 1] <<= 4;
                byteString[pos >> 1] |= rawChar - 'A' + 10;
            } else {
                continue;
            }
            pos++;
        }
        return byteString;
    }
}

算法实现-抽象类-内部公共方法

package **.ciphers;

import **.enums.SymEncEcbEnum;

import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.security.Key;

/**
 * 封装内部方法
 *
 * @author z.y
 * @version v1.0
 */
public abstract class SymEncEcb implements AbsSymEncEcb{

    /**
     * 校验 秘钥-实现类内部使用
     * @param mode 算法工作模式
     * @param key 加解密key
     * @return 是否一致
     * @throws Exception 异常
     */
    protected boolean checkKey(String mode, Key key) throws Exception{
        //加密
        Cipher cipher = Cipher.getInstance(mode);
        cipher.init(Cipher.ENCRYPT_MODE,key);
        byte[] result = cipher.doFinal(TEST_STR.getBytes(StandardCharsets.UTF_8));
        //解密
        cipher.init(Cipher.DECRYPT_MODE,key);
        result = cipher.doFinal(result);
        // 判断是否一致,如果一致 算法和秘钥匹配
        return TEST_STR.equals(new String(result));
    }

    /**
     * key字节数组 转 key字符串
     * @param keyBytes key字节数组
     * @param symEncEcb 密码算法,秘钥长度,秘钥字符串类型(16进制 大、小写;base64)
     * @return key字符串
     */
    protected String toKey(byte[] keyBytes,SymEncEcbEnum symEncEcb){
        if(symEncEcb.isBase64()){
            return encToBase64Str(keyBytes);
        }else if (symEncEcb.isUpper()){
            return toHexString(keyBytes).toUpperCase();
        }else if (symEncEcb.isLower()){
            return toHexString(keyBytes);
        }
        return null;
    }
    /**
     * 字节数组 转 字符串---实现类内部使用
     * @param result 字节数组
     * @param symEncEcb 密码算法,秘钥长度,秘钥字符串类型(16进制 大、小写;base64)
     * @return 字符串
     */
    protected String bytesToStr(byte[] result, SymEncEcbEnum symEncEcb){
        if(symEncEcb.isBase64()){
            return encToBase64Str(result);
        }else if (symEncEcb.isUpper()){
            return toHexString(result).toUpperCase();
        }else if (symEncEcb.isLower()){
            return toHexString(result).toLowerCase();
        }
        return null;
    }
    /**
     * 字符串 转 字节数组---实现类内部使用
     * @param src 字符串
     * @param symEncEcb 密码算法,秘钥长度,秘钥字符串类型(16进制 大、小写;base64)
     * @return 字节数组
     */
    protected byte[] strToBytes(String src,SymEncEcbEnum symEncEcb){
        if(symEncEcb.isBase64()){
            return decFromBase64Str(src);
        }else if (symEncEcb.isUpper()){
            return fromHexString(src);
        }else if (symEncEcb.isLower()){
            return fromHexString(src);
        }
        return null;
    }
    /**
     * 初始化检查,算法是否匹配,是否初始化---实现类内部使用
     * @param symEncEcb 密码算法,秘钥长度,秘钥字符串类型(16进制 大、小写;base64)
     * @param instance 比对的固定算法,不一致报错
     * @param isInit 是否初始化,已初始化过报错
     */
    protected void initCheck(SymEncEcbEnum symEncEcb,SymEncEcbEnum.Instance instance,boolean isInit){
        String val = symEncEcb.getInstance().getVal();
        if(symEncEcb.getInstance() != instance){
            throw new RuntimeException("Symmetric cryptographic algorithm " + val + " is not " + instance.getVal());
        }
        if(isInit){
            throw new RuntimeException("Symmetric cryptographic algorithm " + val + " initialized");
        }
    }

    /**
     * 判断是否未初始化,未初始化报错---实现类内部使用
     * @param instance 提示信息
     * @param isInit 是否初始化,未初始化报错
     */
    protected void isInitThrow(SymEncEcbEnum.Instance instance,boolean isInit){
        if(!isInit){
            throw new RuntimeException("The symmetric cryptographic algorithm " + instance.getVal() + " is not initialized");
        }
    }
}

算法实现类

DES

package **.ciphers;

import **.enums.SymEncEcbEnum;
import org.apache.commons.lang3.StringUtils;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.Key;

/**
 * DES(Data Encryption Standard,数据加密标准)ECB(电子密码本)
 *<pre>
 *     DES 密钥长度:56
 *     工作模式:ECB、CBC、PCBC、CTR、CTS、CFB、CFB8-128、OFB、OFB8-128
 *     NoPadding、PKCS5Padding、ISO10126Padding
 *</pre>
 * @author z.y
 * @version v1.0
 */
public class SymEncEcbDes extends SymEncEcb{
    private boolean isInit = false;
    private SymEncEcbEnum encEcb;
    /** 加密对象 */
    private Cipher encCipher;
    /** 解密对象 */
    private Cipher decCipher;
    /** 加解密key 字符串 */
    private String keyStr;
    @Override
    public boolean checkKeyStr(SymEncEcbEnum symEncEcb, String keyStr) {
        try {
            if(null == symEncEcb || !symEncEcb.isDes() || StringUtils.isEmpty(keyStr)){ return false; }
            //生成一个密钥
            byte[] keyBytes = symEncEcb.isBase64() ? decFromBase64Str(keyStr) : fromHexString(keyStr);
            //实例化DES密钥规则
            DESKeySpec keySpec = new DESKeySpec(keyBytes);
            //实例化密钥工厂-生成密钥 加解密key 对象
            Key key = SecretKeyFactory.getInstance(symEncEcb.getInstance().getVal()).generateSecret(keySpec);
            return checkKey(symEncEcb.getMode(),key);
        }catch (Exception e){
            return false;
        }
    }

    @Override
    public AbsSymEncEcb init(SymEncEcbEnum symEncEcb, String keyStr) throws Exception {
        initCheck(symEncEcb, SymEncEcbEnum.Instance.DES,isInit);
        //DES DES/ECB/PKCS5Padding
        this.encEcb = symEncEcb;
        //生成一个密钥
        byte[] keyBytes = symEncEcb.isBase64() ? decFromBase64Str(keyStr) : fromHexString(keyStr);
        this.keyStr = keyStr;
        init(keyBytes,this.encEcb.getInstance().getVal(),this.encEcb.getMode());
        return this;
    }

    @Override
    public AbsSymEncEcb init(SymEncEcbEnum symEncEcb) throws Exception {
        initCheck(symEncEcb, SymEncEcbEnum.Instance.DES,this.isInit);
        //DES DES/ECB/PKCS5Padding
        this.encEcb = symEncEcb;
        int len = this.encEcb.getInstance().getKeyLen();
        String instance = symEncEcb.getInstance().getVal() ;
        //生成指定算法密钥的KeyGenerator对象
        KeyGenerator keyGen = KeyGenerator.getInstance(instance);
        //初始化此密钥生成器,使其具有确定的密钥大小
        keyGen.init(len);
        //生成一个密钥
        byte[] keyBytes = keyGen.generateKey().getEncoded();
        this.keyStr = toKey(keyBytes,symEncEcb);
        init(keyBytes,instance,this.encEcb.getMode());
        return this;
    }

    @Override
    public String getKeyStr() {
        isInitThrow(SymEncEcbEnum.Instance.DES,this.isInit);
        return this.keyStr;
    }

    @Override
    public String encrypt(String text) throws Exception {
        isInitThrow(SymEncEcbEnum.Instance.DES,this.isInit);
        byte[] result = encCipher.doFinal(text.getBytes(StandardCharsets.UTF_8));
        return bytesToStr(result,this.encEcb);
    }

    @Override
    public String decrypt(String text) throws Exception{
        isInitThrow(SymEncEcbEnum.Instance.DES,this.isInit);
        byte[] bytes = strToBytes(text, this.encEcb);
        byte[] result = decCipher.doFinal(bytes);
        return new String(result, StandardCharsets.UTF_8);
    }

    @Override
    public void destroy() {
        this.isInit = false;
        this.decCipher = null;
        this.encCipher = null;
        this.keyStr = null;
    }
    @Override
    public boolean isInit() { return this.isInit; }

    private void init(byte[] keyBytes, String instance, String mode)throws Exception{
        //实例化DES密钥规则
        DESKeySpec keySpec = new DESKeySpec(keyBytes);
        //实例化密钥工厂-生成密钥
        //加解密key 对象
        Key key = SecretKeyFactory.getInstance(instance).generateSecret(keySpec);
        // 加密
        this.encCipher = Cipher.getInstance(mode);
        this.encCipher.init(Cipher.ENCRYPT_MODE, key);
        // 解密
        this.decCipher = Cipher.getInstance(mode);
        this.decCipher.init(Cipher.DECRYPT_MODE, key);
        this.isInit = true;
    }
}

SM4

package **.ciphers;

import **.enums.SymEncEcbEnum;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.SecureRandom;

/**
 * SM4 国家商用密码算法 ECB(电子密码本)
 *<pre>
 *     SM4 密钥长度:128
 *     工作模式:ECB、CBC、PCBC、CTR、CTS、CFB、CFB8-128、OFB、OFB8-128
 *     NoPadding、PKCS5Padding、ISO10126Padding
 *</pre>
 * @author z.y
 * @version v1.0
 */
public class SymEncEcbSm4 extends SymEncEcb{
    private boolean isInit = false;
    private SymEncEcbEnum encEcb;
    /** 加密对象 */
    private Cipher encCipher;
    /** 解密对象 */
    private Cipher decCipher;
    /** 加解密key 字符串 */
    private String keyStr;
    @Override
    public boolean checkKeyStr(SymEncEcbEnum symEncEcb, String keyStr) {
        try {
            if(null == symEncEcb || !symEncEcb.isSm4() || StringUtils.isEmpty(keyStr)){ return false; }
            //生成一个密钥
            byte[] keyBytes = symEncEcb.isBase64() ? decFromBase64Str(keyStr) : fromHexString(keyStr);
            //实例化密钥工厂-生成密钥 加解密key 对象
            Key key = new SecretKeySpec(keyBytes,symEncEcb.getInstance().getVal());
            return checkKey(symEncEcb.getMode(),key);
        }catch (Exception e){
            return false;
        }
    }

    @Override
    public AbsSymEncEcb init(SymEncEcbEnum symEncEcb, String keyStr) throws Exception {
        initCheck(symEncEcb, SymEncEcbEnum.Instance.SM4,isInit);
        //DES DES/ECB/PKCS5Padding
        this.encEcb = symEncEcb;
        //生成一个密钥
        byte[] keyBytes = symEncEcb.isBase64() ? decFromBase64Str(keyStr) : fromHexString(keyStr);
        this.keyStr = keyStr;
        init(keyBytes,this.encEcb.getInstance().getVal(),this.encEcb.getMode());
        return this;
    }

    @Override
    public AbsSymEncEcb init(SymEncEcbEnum symEncEcb) throws Exception {
        initCheck(symEncEcb, SymEncEcbEnum.Instance.SM4,this.isInit);
        //AES AES/ECB/PKCS5Padding
        this.encEcb = symEncEcb;
        String instance = this.encEcb.getInstance().getVal() ;
        //随机数
        SecureRandom random = new SecureRandom();
        //生成指定算法密钥的KeyGenerator对象
        KeyGenerator keyGen = KeyGenerator.getInstance(instance, BouncyCastleProvider.PROVIDER_NAME);
        //初始化此密钥生成器,使其具有确定的密钥大小
        keyGen.init(this.encEcb.getInstance().getKeyLen(),random);
        //生成一个密钥
        byte[] keyBytes = keyGen.generateKey().getEncoded();
        this.keyStr = toKey(keyBytes,symEncEcb);
        init(keyBytes,instance,this.encEcb.getMode());
        return this;
    }

    @Override
    public String getKeyStr() {
        isInitThrow(SymEncEcbEnum.Instance.SM4,this.isInit);
        return this.keyStr;
    }

    @Override
    public String encrypt(String text) throws Exception {
        isInitThrow(SymEncEcbEnum.Instance.SM4,this.isInit);
        byte[] result = encCipher.doFinal(text.getBytes(StandardCharsets.UTF_8));
        return bytesToStr(result,this.encEcb);
    }

    @Override
    public String decrypt(String text) throws Exception{
        isInitThrow(SymEncEcbEnum.Instance.SM4,this.isInit);
        byte[] bytes = strToBytes(text, this.encEcb);
        byte[] result = decCipher.doFinal(bytes);
        return new String(result, StandardCharsets.UTF_8);
    }

    @Override
    public void destroy() {
        this.isInit = false;
        this.decCipher = null;
        this.encCipher = null;
        this.keyStr = null;
    }
    @Override
    public boolean isInit() { return this.isInit; }

    private void init(byte[] keyBytes, String instance, String mode)throws Exception{
        //实例化密钥工厂-生成密钥 加解密key 对象
        Key key = new SecretKeySpec(keyBytes,instance);
        // 加密
        this.encCipher = Cipher.getInstance(mode);
        this.encCipher.init(Cipher.ENCRYPT_MODE, key);
        // 解密
        this.decCipher = Cipher.getInstance(mode);
        this.decCipher.init(Cipher.DECRYPT_MODE, key);
        this.isInit = true;
    }
}

3DES、AES略

参考代码

package **.utils;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
import org.springframework.util.Base64Utils;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.*;
import java.security.Key;
import java.security.SecureRandom;
import java.security.Security;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.KeySpec;

/**
 * CipherTest 类说明:
 *<pre>
 *     对称加密
 *     算法公开、计算量小、加密速度快、加密效率高,使用同样密钥,安全性得不到保证
 *     ECB(电子密码本)、CBC(密文链接)、CFB(密文反馈)、OFB(输出反馈)、CTR(计数器)
 *</pre>
 * @author z.y
 * @version v1.0
 */
public class CipherTest {
    static {
        try {
            // 解决报错 java.security.NoSuchProviderException: no such provider: BC
            Security.addProvider(new BouncyCastleProvider());
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public static String des(String text)throws Exception{
        String instance = "DES";
        int len = 56;
        String mode = "DES/ECB/PKCS5Padding";
        // DES 密钥长度:56
        // 工作模式:ECB、CBC、PCBC、CTR、CTS、CFB、CFB8-128、OFB、OFB8-128
        // NoPadding、PKCS5Padding、ISO10126Padding
        byte[] bs = initKey(instance,len);
        //实例化DES密钥规则
        DESKeySpec desKeySpec = new DESKeySpec(bs);
        //生成密钥
        Key key = createKey(desKeySpec,instance);
        // 加密
        Cipher cipher = Cipher.getInstance(mode);
        cipher.init(Cipher.ENCRYPT_MODE,key);
        byte[] result = cipher.doFinal(text.getBytes());
        System.out.println(instance + "-加密:" + ByteUtils.toHexString(result));
        System.out.println(instance + "-加密:" + Base64Utils.encodeToString(result));
        //解密
        cipher.init(Cipher.DECRYPT_MODE,key);
        result = cipher.doFinal(result);
        System.out.println(instance + "-解密:" + new String(result));
        return new String(result);
    }
    public static String des3(String text)throws Exception{
        String instance = "DESede";
        int len = 168;
        String mode = "DESede/ECB/PKCS5Padding";
        // 3DES 密钥长度:168
        // 工作模式:ECB、CBC、PCBC、CTR、CTS、CFB、CFB8-128、OFB、OFB8-128
        // NoPadding、PKCS5Padding、ISO10126Padding
        byte[] bs = initKey(instance,len);
        //实例化3DES密钥规则
        DESedeKeySpec keySpec = new DESedeKeySpec(bs);
        //生成密钥
        Key key = createKey(keySpec,instance);
        // 加密
        Cipher cipher = Cipher.getInstance(mode);
        cipher.init(Cipher.ENCRYPT_MODE,key);
        byte[] result = cipher.doFinal(text.getBytes());
        System.out.println(instance + "-加密:" + ByteUtils.toHexString(result));
        System.out.println(instance + "-加密 :" + Base64Utils.encodeToString(result));
        //解密
        cipher.init(Cipher.DECRYPT_MODE,key);
        result = cipher.doFinal(result);
        System.out.println(instance + "-解密:" + new String(result));
        return new String(result);
    }
    public static String aes(String text)throws Exception{
        String instance = "AES";
        int len = 128;
        String mode = "AES/ECB/PKCS5Padding";
        // AES 密钥长度:128
        // 工作模式:ECB、CBC、PCBC、CTR、CTS、CFB、CFB8-128、OFB、OFB8-128
        // NoPadding、PKCS5Padding、ISO10126Padding
        byte[] bs = initKey(instance,len);
        //AES key转换
        Key key = new SecretKeySpec(bs,instance);
        // 加密
        Cipher cipher = Cipher.getInstance(mode);
        cipher.init(Cipher.ENCRYPT_MODE,key);
        byte[] result = cipher.doFinal(text.getBytes());
        System.out.println(instance + "-加密 :" + ByteUtils.toHexString(result));
        System.out.println(instance + "-加密:" + Base64Utils.encodeToString(result));
        //解密
        cipher.init(Cipher.DECRYPT_MODE,key);
        result = cipher.doFinal(result);
        System.out.println(instance + "-解密:" + new String(result));
        return new String(result);
    }
    public static String sm4(String text)throws Exception{
        int len = 128;
        String instance = "SM4";
        String mode = "SM4/ECB/PKCS5Padding";
        //加密算法/分组加密模式/分组填充方式;<br>PKCS5Padding-以8个字节为一组进行分组加密;<br>定义分组加密模式使用:PKCS5Padding
        //128-32位16进制;256-64位16进制
        //随机数
        SecureRandom random = new SecureRandom();
        KeyGenerator keyGen = KeyGenerator.getInstance(instance, BouncyCastleProvider.PROVIDER_NAME);
        keyGen.init(len,random);
        //生成一个密钥
        byte[] bs = keyGen.generateKey().getEncoded();
        System.out.println(instance + "-key:" + ByteUtils.toHexString(bs));
        System.out.println(instance + "-key:" + Base64Utils.encodeToString(bs));
        Key key = new SecretKeySpec(bs, instance);
        // 加密
        Cipher cipher = Cipher.getInstance(mode);
        cipher.init(Cipher.ENCRYPT_MODE,key);
        byte[] result = cipher.doFinal(text.getBytes());
        System.out.println(instance + "- 加密:" + ByteUtils.toHexString(result));
        System.out.println(instance + "-加密 :" + Base64Utils.encodeToString(result));
        //解密
        cipher.init(Cipher.DECRYPT_MODE,key);
        result = cipher.doFinal(result);
        System.out.println(instance + "-解密:" + new String(result));
        return new String(result);
    }
    private static Key createKey(KeySpec keySpec, String instance)throws Exception{
        //实例化密钥工厂
        SecretKeyFactory factory = SecretKeyFactory.getInstance(instance);
        //生成密钥
        return factory.generateSecret(keySpec);
    }
    private static byte[] initKey(String instance,int len)throws Exception{
        //生成指定算法密钥的KeyGenerator对象
        KeyGenerator keyGen = KeyGenerator.getInstance(instance);
        //初始化此密钥生成器,使其具有确定的密钥大小
        keyGen.init(len);
        //生成一个密钥
        byte[] bs = keyGen.generateKey().getEncoded();
        System.out.println(instance + "-key:" + new String(bs));
        System.out.println(instance + "-key:" + ByteUtils.toHexString(bs));
        System.out.println(instance + "-key:" + Base64Utils.encodeToString(bs));
        return bs;
    }
}

对象池化

池化对象的key类

考虑到 初始化只能 获取到 key,如果只是String 需要拼接内容较多,故 就自定义类做池化的key,并重写方法equalshashCode

package **.beans;

import **.enums.DateFormatEnum;
import **.enums.SymEncEcbEnum;

/**
 * 对称加解密 配置实体类
 *
 * @author z.y
 * @version v1.0
 */
public class EncEcbKeyBean {
    /** 对称加密 id,每组加解密对象 固定(如:api系统编码),必填 且不能为空 */
    private String id;
    /** 对称加密类型 默认 SM4 */
    private SymEncEcbEnum enc;
    /** 对称加密秘钥,为空 项目启动生成或每次登陆生成 */
    private String encKey;
    private String time;

    public EncEcbKeyBean(String id) {
        this(id,SymEncEcbEnum.SM4_16);
    }
    public EncEcbKeyBean(String id, SymEncEcbEnum enc) {
        this(id,enc,null);
    }
    public EncEcbKeyBean(String id, SymEncEcbEnum enc, String encKey) {
        this.enc = enc;
        this.encKey = encKey;
        this.setId(id);
        this.time = DateFormatEnum.y_M_d_H_m_s_S.format();
    }
    public void setId(String id) {
        if( null == id || id.trim().length() <= 0){
            throw new IllegalArgumentException("EncEcbKeyBean id is null");
        }
        this.id = id;
    }
    setGet...

    @Override
    public boolean equals(Object o) {
        if (this == o){ return true; }
        if (o == null || getClass() != o.getClass()) { return false; }
        EncEcbKeyBean that = (EncEcbKeyBean) o;
        return getId().equals(that.getId());
    }
    @Override
    public int hashCode() { return getId().hashCode(); }

    toString...
}

池化工厂类

package **.pools;

import **.beans.EncEcbKeyBean;
import **.ciphers.AbsSymEncEcb;
import **.ciphers.SymEncEcbBuilder;
import **.exceptions.SymEncEcbException;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.pool2.KeyedPooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * api 加解密 工厂类-通过key获取
 *
 * @author z.y
 * @version v1.0
 */
public class SymEncEcbFactory implements KeyedPooledObjectFactory<EncEcbKeyBean, AbsSymEncEcb> {
    private static final Logger logger = LoggerFactory.getLogger(SymEncEcbFactory.class);
    /** 构建-对象 */
    @Override
    public PooledObject<AbsSymEncEcb> makeObject(EncEcbKeyBean key) throws Exception {
        logger.debug("加解密-构建,{}",key);
        if(StringUtils.isEmpty(key.getId()) || null == key.getEnc()){
            throw new SymEncEcbException("加解密-生成,id、enc为空");
        }
        AbsSymEncEcb build = SymEncEcbBuilder.build(key.getEnc());
        if( null == build ){
            throw new SymEncEcbException("加解密-生成,构建失败");
        }
        if(StringUtils.isEmpty(key.getEncKey())){
            build.init(key.getEnc());
        }else{
            build.init(key.getEnc(),key.getEncKey());
        }
        if( build.isInit() ){
            logger.debug("加解密-生成,类初始化完成");
        }else {
            throw new SymEncEcbException("加解密-生成,类初始化未完成");
        }
        return new DefaultPooledObject<>(build);
    }

    /** 销毁对象 */
    @Override
    public void destroyObject(EncEcbKeyBean key, PooledObject<AbsSymEncEcb> p) throws Exception {
        logger.debug("加解密-销毁,{}",key);
        if(StringUtils.isEmpty(key.getId())){
            throw new SymEncEcbException("加解密-销毁失败,id为空");
        }
        AbsSymEncEcb object = p.getObject();
        object.destroy();
    }
    /** 验证-对象是否可用 */
    @Override
    public boolean validateObject(EncEcbKeyBean key, PooledObject<AbsSymEncEcb> p) {
        logger.debug("加解密-验证,{}",key);
        if(StringUtils.isEmpty(key.getId())){
            logger.error("加解密-验证对象是否可用,id为空");
            return false;
        }
        AbsSymEncEcb object = p.getObject();
        return object.isInit();
    }
    /** 激活-对象 */
    @Override
    public void activateObject(EncEcbKeyBean key, PooledObject<AbsSymEncEcb> p) throws Exception {
        logger.debug("加解密-激活,{}",key);
        if(StringUtils.isEmpty(key.getId()) || null == key.getEnc()){
            throw new SymEncEcbException("加解密-激活失败,id、enc为空");
        }
        AbsSymEncEcb object = p.getObject();
        if(!object.isInit()){
            logger.debug("加解密-激活-初始化,{}",key);
            object.init(key.getEnc(),key.getEncKey());
        }
    }
    /** 钝化-对象 将对象缓存 */
    @Override
    public void passivateObject(EncEcbKeyBean key, PooledObject<AbsSymEncEcb> p) throws Exception {
        logger.debug("加解密-钝化,{}",key);
        if(StringUtils.isEmpty(key.getId()) || null == key.getEnc()){
            throw new SymEncEcbException("加解密-钝化失败,id、enc为空");
        }
        // 暂未实现 对象钝化。好像是在销毁前,会执行钝化操作
    }
}

对象池

因为使用池中对象加解密都是固定的代码,所以就封装两个方法,简化外部使用

package **.pools;

import **.beans.EncEcbKeyBean;
import **.ciphers.AbsSymEncEcb;
import **.ciphers.SymEncEcb;
import **.exceptions.SymEncEcbException;
import org.apache.commons.pool2.KeyedPooledObjectFactory;
import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * api 加解密 对象池-通过key获取
 *
 * @author z.y
 * @version v1.0
 */
public class SymEncEcbPool extends GenericKeyedObjectPool<EncEcbKeyBean, AbsSymEncEcb> {
    private static final Logger logger = LoggerFactory.getLogger(SymEncEcbPool.class);

    public SymEncEcbPool(KeyedPooledObjectFactory<EncEcbKeyBean, AbsSymEncEcb> factory) {
        super(factory);
    }
    public SymEncEcbPool(KeyedPooledObjectFactory<EncEcbKeyBean, AbsSymEncEcb> factory, GenericKeyedObjectPoolConfig<AbsSymEncEcb> config) {
        super(factory, config);
    }
    /**
     * 加密 字符串,从对象池中获取 加解密对象,使用完后 归还对象
     * @param text 待加密 字符串
     * @param key 从对象池中获取 加解密对象 的key
     * @return 加密 字符串
     * @throws Exception 异常
     */
    public String encrypt(String text,EncEcbKeyBean key) throws Exception{
        logger.debug("加解密池-加密,{}",key);
        AbsSymEncEcb ecb = null;
        try{
            ecb = this.borrowObject(key);
            return ecb.encrypt(text);
        }catch (Exception e){
            logger.error("加解密池-加密报错",e);
            throw new SymEncEcbException("加密错误:" + e.getMessage());
        }
        finally {
            if( null != ecb ){ this.returnObject(key,ecb); }
        }
    }
    /**
     * 解密 字符串,从对象池中获取 加解密对象,使用完后 归还对象
     * @param text 待解密 字符串
     * @param key 从对象池中获取 加解密对象 的key
     * @return 解密 字符串
     * @throws Exception 异常
     */
    public String decrypt(String text,EncEcbKeyBean key) throws Exception{
        logger.debug("加解密池-解密,{}",key);
        AbsSymEncEcb ecb = null;
        try{
            ecb = this.borrowObject(key);
            return ecb.decrypt(text);
        }catch (Exception e){
            logger.error("加解密池-解密报错",e);
            throw new SymEncEcbException("解密错误:" + e.getMessage());
        }
        finally {
            if( null != ecb ){ this.returnObject(key,ecb); }
        }
    }
}

对象池注入spring略

请查看参考链接

处理请求和响应

处理请求

package **.handlers;

import **.annotation.SymEncDec;
import **.beans.EncEcbKeyBean;
import **.cache.CacheCodeService;
import **.ciphers.AbsSymEncEcb;
import **.exceptions.SymEncEcbException;
import **.pools.SymEncEcbPool;
import **.utils.ReqUtils;
import cn.hutool.core.io.IoUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Sets;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.Set;

/**
 * 在HttpMessageConverter处理request body的前做一些处理,然后处理处理完的数据才会进入到controller。可以通过覆写 beforeBodyRead 来统一的对请求数据体进行修改
 *
 * @author z.y
 * @version v1.0
 */
@ControllerAdvice("**.controller")
public class ApiDecReqBodyAdvice extends RequestBodyAdviceAdapter {
    private static final Logger logger = LoggerFactory.getLogger(ApiDecReqBodyAdvice.class);
    private static final String ERR_MSG_1 = "HttpServletRequest is null";
    private static final String ERR_MSG_2 = "code is empty";
    private static final String ERR_MSG_3 = "EncEcbKeyBean is null";
    private static final Set<String> IGNORE_FIELDS = Sets.newHashSet("pageNum","pageSize");
    @Resource
    private CacheCodeService cacheCodeService;
    private SymEncEcbPool symEncEcbPool;
    @Autowired
    public void setSymEncEcbPool(SymEncEcbPool symEncEcbPool) { this.symEncEcbPool = symEncEcbPool; }
    /**
     * 该方法用于判断当前请求,是否要执行beforeBodyRead方法
     *
     * @param mp handler方法的参数对象
     * @param type      handler方法的参数类型
     * @param converter   将会使用到的Http消息转换器类类型
     * @return 返回true则会执行beforeBodyRead
     */
    @Override
    public boolean supports(MethodParameter mp, Type type, Class<? extends HttpMessageConverter<?>> converter) {
        //该方法用于判断当前请求,是否要执行beforeBodyRead方法
        logger.debug("-----,{}",mp.getMethod());
        return mp.hasMethodAnnotation(SymEncDec.class);
    }
    /**
     * 在Http消息转换器执转换,之前执行
     *
     * @param inputMsg    客户端的请求数据
     * @param parameter handler方法的参数对象
     * @param type      handler方法的参数类型
     * @param converter   将会使用到的Http消息转换器类类型
     * @return 返回 一个自定义的HttpInputMessage
     */
    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMsg, MethodParameter parameter,
                                           Type type, Class<? extends HttpMessageConverter<?>> converter) throws IOException {
        // 请求上下文获取失败,直接返回
        if(null == RequestContextHolder.getRequestAttributes()){ return errorLog(inputMsg,parameter,type,converter,ERR_MSG_1); }
        HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String code = ReqUtils.requestGet(req, "code");
        // 编码为空
        if(StringUtils.isEmpty(code)){ return errorLog(inputMsg,parameter,type,converter,ERR_MSG_2); }
        EncEcbKeyBean symKey = cacheCodeService.getCacheByCode(code);
        if( null == symKey ){ return errorLog(inputMsg,parameter,type,converter,ERR_MSG_3); }
        //加解密方式为空 或 秘钥为空,不进行 加解密操作
        if( null == symKey.getEnc() || StringUtils.isEmpty(symKey.getEncKey())){
            logger.debug("in-请求拦截,code:{},加解密方式为空 或 秘钥为空", code);
            return super.beforeBodyRead(inputMsg, parameter, type, converter);
        }
        logger.debug("in-请求拦截,code:{},解密-start", code);
        AbsSymEncEcb ecb;
        try{
            // 获取加解密 对象
            ecb = symEncEcbPool.borrowObject(symKey);
            if( null == ecb || !ecb.isInit() ){
                throw new SymEncEcbException("请求未找到加解密配置");
            }
            InputStream is = inputMsg.getBody();
            //输入流为空,暂不处理
            if(is.available() <= 0){ return newInput(is,inputMsg); }
            String body = IoUtil.read(is, StandardCharsets.UTF_8);
            if(StringUtils.isEmpty(body) || "{}".equals(body) || "[]".equals(body)){
                return newInput(new ByteArrayInputStream(body.getBytes()),inputMsg);
            }

            logger.debug("读取请求的内容-1,{}",body);
            JSON json;
            if(body.startsWith("{") && body.endsWith("}")){
                logger.debug("in-请求拦截,sysCode:{},字符串是对象", sysCode);
                json = JSON.parseObject(body);
            }else if(body.startsWith("[") && body.endsWith("]")){
                logger.debug("in-请求拦截,sysCode:{},字符串是对象", sysCode);
                json = JSON.parseArray(body);
            }else{
                throw new SymEncEcbException("请求内容非法");
            }
            // json 对象 解密内容
            decJson(json,ecb);
            logger.debug("读取请求的内容-2,{}",json);
            final InputStream input = IoUtil.toStream(json.toJSONString(), StandardCharsets.UTF_8);
            return newInput(input,inputMsg);
        }catch (Exception e){
            logger.error("请求解密异常",e);
            throw new SymEncEcbException("请求解密异常:"+e.getMessage());
        }
    }

    private HttpInputMessage errorLog(HttpInputMessage inputMsg, MethodParameter parameter,
                                            Type type, Class<? extends HttpMessageConverter<?>> converter,String msg) throws IOException{
        logger.error("in-请求拦截异常,{}",msg);
        return super.beforeBodyRead(inputMsg, parameter, type, converter);
    }
    private HttpInputMessage newInput(InputStream input,HttpInputMessage inputMsg){
        return new HttpInputMessage() {
            @Override
            public InputStream getBody() throws IOException { return input; }
            @Override
            public HttpHeaders getHeaders() { return inputMsg.getHeaders(); }
        };
    }
    /**
     * json 解密内容
     * @param json json 对象-数据
     * @param ecb 解密方式
     * @throws Exception 异常
     */
    private void decJson(JSON json, AbsSymEncEcb ecb)throws Exception{
        if( null == json ){ return; }
        if(json instanceof JSONObject){
            JSONObject object = (JSONObject)json;
            for (String key : object.keySet()) {
                Object v = object.get(key);
                if( null == v ){ continue; }
                if( IGNORE_FIELDS.contains(key) ){ continue; }
                if(v instanceof JSONObject){
                    decJson(object.getJSONObject(key),ecb);
                }else if(v instanceof JSONArray){
                    decJson(object.getJSONArray(key),ecb);
                }else{
                    object.put(key, dec(ecb,v));
                }
            }
        }else if (json instanceof JSONArray){
            JSONArray array = (JSONArray)json;
            for (int i = 0; i < array.size(); i++) {
                JSONObject object = array.getJSONObject(i);
                decJson(object,ecb);
            }
        }
    }
    /**
     * 数据 解密
     * @param ecb 解密方式
     * @param data 数据
     * @return 解密后内容-文本(只有密文转换为明文,下一步spring将数据转 入参实体类的字段时候才不报错)
     * @throws Exception 异常
     */
    private Object dec(AbsSymEncEcb ecb, Object data)throws Exception{
        if( null == data){ return null; }
        if(data instanceof CharSequence){ return ecb.decrypt(data.toString()); }
        return data;
    }
}

处理响应

package **.handlers;

import **.annotation.SymEncDec;
import **.beans.EncEcbKeyBean;
import **.cache.CacheCodeService;
import **.ciphers.AbsSymEncEcb;
import **.enums.DateFormatEnum;
import **.exceptions.SymEncEcbException;
import **.pools.SymEncEcbPool;
import **.utils.ReqUtils;
import **.web.ApiResponse;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;

/**
 * 在 Controller 方法返回数据,并且匹配到了 HttpMessageConverter 之后。HttpMessageConverter 进行序列化之前执行。可以通过覆写 beforeBodyWrite 来统一的对响应体进行修改
 *
 * @author z.y
 * @version v1.0
 */
@ControllerAdvice("**.controller")
public class ApiDecResBodyAdvice implements ResponseBodyAdvice<ApiResponse<?>> {
    private static final Logger logger = LoggerFactory.getLogger(ApiDecResBodyAdvice.class);
    @Resource
    private CacheCodeService cacheCodeService;
    private static final String ERR_MSG_0 = "ApiResponse is null or is fail";
    private static final String ERR_MSG_1 = "HttpServletRequest is null";
    private static final String ERR_MSG_2 = "code is empty";
    private static final String ERR_MSG_3 = "EncEcbKeyBean is null";
    private static final String ERR_MSG_4 = "AbsSymEncEcb is null or not init";
    private static final String ERR_MSG_5 = "data is null";
    private SymEncEcbPool symEncEcbPool;
    @Autowired
    public void setSymEncEcbPool(SymEncEcbPool symEncEcbPool) { this.symEncEcbPool = symEncEcbPool; }
    /**
     * 该方法用于判断当前请求的返回值,是否要执行beforeBodyWrite方法
     *
     * @param mp handler方法的参数对象
     * @param converter   将会使用到的Http消息转换器类类型
     * @return 返回true则会执行beforeBodyWrite
     */
    @Override
    public boolean supports(MethodParameter mp, Class<? extends HttpMessageConverter<?>> converter) {
        //该方法用于判断当前请求的返回值,是否要执行beforeBodyWrite方法
        return mp.hasMethodAnnotation(SymEncDec.class);
    }
    /**
     * 在Http消息转换器执转换,之前执行
     *
     * @param body               服务端的响应数据
     * @param parameter    handler方法的参数对象
     * @param mediaType          响应的ContentType
     * @param converter      将会使用到的Http消息转换器类类型
     * @param req  serverHttpRequest
     * @param resp serverHttpResponse
     * @return 返回 一个自定义的HttpInputMessage,可以为null,表示没有任何响应
     */
    @Override
    public ApiResponse<?> beforeBodyWrite(ApiResponse<?> body, MethodParameter parameter,
                                              MediaType mediaType, Class<? extends HttpMessageConverter<?>> converter,
                                              ServerHttpRequest req, ServerHttpResponse resp) {
        if(null == body || !body.isSuccess()){ return errorLog(body,ERR_MSG_0); }
        // 请求上下文获取失败,直接返回
        if(null == RequestContextHolder.getRequestAttributes()){ return errorLog(body,ERR_MSG_1); }
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String code = ReqUtils.requestGet(request, "code");
        // 编码为空
        if(StringUtils.isEmpty(code)){ return errorLog(body,ERR_MSG_2); }
        EncEcbKeyBean symKey = cacheCodeService.getCacheByCode(code);
        if( null == symKey ){ return errorLog(body,ERR_MSG_3); }
        //加解密方式为空 或 秘钥为空,不进行 加解密操作
        if( null == symKey.getEnc() || StringUtils.isEmpty(symKey.getEncKey())) {
            logger.debug("out-响应拦截,code:{},加解密方式为空 或 秘钥为空", code);
            return body;
        }
        logger.debug("out-响应拦截,code:{},加密-start", code);
        AbsSymEncEcb ecb = null;
        try {
            // 获取加解密 对象
            ecb = symEncEcbPool.borrowObject(symKey);
            if( null == ecb || !ecb.isInit() ){ return errorLog(body,ERR_MSG_4); }
            logger.debug("out-响应拦截,code:{},获取加解密对象", code);
            return encObj(body,ecb,code);
        } catch (Exception e) {
            throw new SymEncEcbException("响应-加密失败",e.getMessage());
        }
        finally {
            // 归还 加解密对象
            if( null != ecb ){
                logger.debug("out-响应拦截,归还 加解密对象");
                symEncEcbPool.returnObject(symKey,ecb);
            }
        }
    }
    private ApiResponse<?> errorLog(ApiResponse<?> body, String msg) {
        logger.error("out-响应拦截,{}",msg);
        if( null == body ){
            // 报文为空
            return ApiResponse.fail("失败",null);
        }else if (!body.isSuccess()){
            // 接口失败
            return body;
        }
        // 接口成功,本类处理失败
        body.setMessage( "ERR:" + msg );
        return body;
    }
    private ApiResponse<?> encObj(ApiResponse<?> body, AbsSymEncEcb ecb,String code)throws Exception{
        // data 转成 map,在处理里面的值 加密
        Object data = body.getData();
        // null 不处理
        if( null == data ){ return errorLog(body,ERR_MSG_5); }
        JSON json;
        if ( data instanceof Collection){
            // 集合
            json = JSONArray.parseObject(JSON.toJSONString(data));
            logger.debug("out-响应拦截,code:{},data为list", code);
            encJson(json,ecb);
            return ApiResponse.success(json);
        }else if ( data instanceof String || data instanceof BigDecimal || data instanceof Date || data.getClass().isPrimitive()){
            // 字符串、金额、日期、基本类型
            return ApiResponse.success(this.enc(ecb,data));
        }else{
            // 其他 按照 对象处理
            json = JSONObject.parseObject(JSON.toJSONString(data));
            logger.debug("out-响应拦截,code:{},data为其他", code);
            encJson(json,ecb);
            return ApiResponse.success(json);
        }
    }

    /**
     * json 加密内容
     * @param json json 对象-数据
     * @param ecb 加密方式
     * @throws Exception 异常
     */
    private void encJson(JSON json, AbsSymEncEcb ecb)throws Exception{
        if( null == json ){ return; }
        if(json instanceof JSONObject){
            JSONObject object = (JSONObject)json;
            for (String key : object.keySet()) {
                Object v = object.get(key);
                if( null == v ){ continue; }
                if(v instanceof JSONObject){
                    encJson(object.getJSONObject(key),ecb);
                }else if(v instanceof JSONArray){
                    encJson(object.getJSONArray(key),ecb);
                }else{
                    object.put(key,enc(ecb,v));
                }
            }
        }else if (json instanceof JSONArray){
            JSONArray array = (JSONArray)json;
            for (int i = 0; i < array.size(); i++) {
                JSONObject object = array.getJSONObject(i);
                encJson(object,ecb);
            }
        }
    }
    /**
     * 数据 加密
     * @param ecb 加密方式
     * @param data 数据
     * @return 加密后内容-文本
     * @throws Exception 异常
     */
    private String enc(AbsSymEncEcb ecb,Object data)throws Exception{
        if( null == data){ return null; }
        String res;
        if(data instanceof CharSequence){ res = data.toString(); }
        else if(data instanceof BigDecimal){ res = ((BigDecimal)data).toPlainString(); }
        else if(data instanceof Date){ res = DateFormatEnum.y_M_d_H_m_s_2.format((Date)data); }
        else if(data instanceof Long){ res = Long.toString((Long)data); }
        else if(data instanceof Integer){ res = Integer.toString((Integer)data); }
        else if(data instanceof Double){ res = Double.toString((Double)data); }
        else if(data instanceof Float){ res = Float.toString((Float)data); }
        else if(data instanceof Short){ res = Short.toString((Short)data); }
        else { return null; }
        return ecb.encrypt(res);
    }
}

其他类

自定义异常

package **.exceptions;
/**
 * 自定义异常-加解密
 *
 * @author z.y
 * @version v1.0
 */
public class SymEncEcbException extends Exception{
    public SymEncEcbException() { }
    public SymEncEcbException(String message) { super(message); }
}

工具类

package **.utils;

import org.apache.commons.lang3.StringUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

/**
 * 请求工具类
 *
 * @author z.y.l
 * @version v1.0
 * @date 2022/4/14
 */
public class ReqUtils {
    public static String requestGet(HttpServletRequest request,String key){
        return StringUtils.isNotBlank(request.getHeader(key)) ? request.getHeader(key) : sessionGet(request.getSession(),key,"");
    }
    public static String sessionGet(HttpSession session,String key,String def){
        Object o = session.getAttribute(key);
        return null == o ? def : o.toString();
    }
}

自定义响应

package **.web;

import java.io.Serializable;

/**
 * 接口响应实体类
 *
 * @author z.y
 * @version v1.0
 */
public class ApiResponse<T> implements Serializable {
    private int code = 0;
    private T data;
    private String message;

    public ApiResponse() {}
    public ApiResponse(int code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }

    public static <T> ApiResponse<T> success(T data) {
        return new ApiResponse<>(0, "成功", data);
    }
    public static <T> ApiResponse<T> fail(String message, T data) {
        if (message == null || message.length() <= 0) {
            message = "失败";
        }
        return new ApiResponse<>(-1, message, data);
    }

    @JsonIgnore
    public boolean isSuccess(){
        return 0 == this.code;
    }
    setGet...
}

测试-暂无

寄语

工具类当前只是自用,如果觉得方法不全可以自己在添加。代码比较简陋,请手下留情(⊙o⊙)…

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值