开发项目使用的spring boot web 项目,需求是 部分接口 需要 支持 报文密文和明文传输 动态切换。 请求解密或明文、响应加密或明文,通过度娘搜索和综合考虑(就自己简单分析一下最简单的实现)。
当前实现的密码算法:DES、3DES、AES、SM4,只展示 DES和SM4 其他类似
使用apache.commons.pool2池化包,加速处理报文
使用 接口 统一 加解密操作
使用RequestBodyAdviceAdapter和ResponseBodyAdvice<?>处理spring web 接口报文
使用redis 缓存 认证信息-当前文档不会详细说明
参考:
- commons-pool2(2.6.2)实现对象池-jdk8
- jdk(java)8 日期格式化工具-使用枚举处理
- 其他参考链接已丢失o(╥﹏╥)o
代码实现
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,并重写方法equals、hashCode
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⊙)…

1041

被折叠的 条评论
为什么被折叠?



