1.构建请求对象实体
Java是面向对象的语言,所以这一步是理所当然的,没有为什么。根据请求报文字段,可以创建请求对象如下:
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @Title: CommonReq
* @author: HOLiC
* @date: 2020/7/24
*/
@Data
@NoArgsConstructor
public class CommonReq {
private String encType = "aes";
private String signType = "md5";
private String signData;
private String busiName;
private String busiData;
private String timeStamp;
private String nonce;
private String clientId = "holic_01";
}
有些字段的值是固定的,所以在实体类中先行赋值了。
2.创建AES加解密工具类
AES是一种对称加密算法,感兴趣可以自行百度更多细节。这里我们用AES对业务报文进行加密处理。
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.Key;
/**
* AES对称加密算法
* */
public class AESCoder {
private static final String KEY_ALGORITHM = "AES";
private static final String CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";
/* *
* @Description: 解密
* @author: HOLiC
* @date: 2020/7/24
* @Param: [data, key]
* @return: java.lang.String
**/
public static String decrypt(String data, String key) throws Exception {
// 还原密钥
Key k = toKey(key);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
// 初始化,设置为解密模式
cipher.init(Cipher.DECRYPT_MODE, k);
// data转为字节数组
byte[] dataByte = Base64.decodeBase64(data);
// 执行操作
byte[] decryptByte = cipher.doFinal(dataByte);
return new String(decryptByte, StandardCharsets.UTF_8);
}
/* *
* @Description: 加密
* @author: HOLiC
* @date: 2020/7/24
* @Param: [data, key]
* @return: java.lang.String
**/
public static String encrypt(String data, String key) throws Exception {
// 还原密钥
Key k = toKey(key);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
// 初始化,设置为加密模式
cipher.init(Cipher.ENCRYPT_MODE, k);
// 执行操作
byte[] encryptByt = cipher.doFinal(data.getBytes());
return Base64.encodeBase64String(encryptByt);
}
/**
* 生成密钥
* @return byte[] 二进制密钥
* */
public static byte[] initKey() throws Exception {
// 实例化密钥生成器
KeyGenerator kg = KeyGenerator.getInstance(KEY_ALGORITHM);
// 初始化密钥生成器,AES要求密钥长度为128位、192位、256位
// AES/ECB/PKCS5Padding的填充方式不支持256位秘钥
kg.init(192);
// 生成密钥
SecretKey secretKey = kg.generateKey();
// 获取二进制密钥编码形式
return secretKey.getEncoded();
}
/* *
* @Description: 秘钥转换
* @author: HOLiC
* @date: 2020/7/24
* @Param: [key]
* @return: java.security.Key
**/
private static Key toKey(String key) {
byte[] keyBytes = key.getBytes(StandardCharsets.US_ASCII);
return new SecretKeySpec(keyBytes,KEY_ALGORITHM);
}
}
工具类亲测可用,绝对无害。
3.创建字符串和实体对象转换工具类
加密工具只操作字符串,我们的请求报文是个实体,所以需要这个工具类来实现转换。 具体的实现很简单:
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* @Title: JsonUtils
* @author: HOLiC
* @date: 2020/7/24
*/
public class JsonUtils {
private static final ObjectMapper mapper = new ObjectMapper();
/* *
* @Description: 字符串转实体对象
* @author: HOLiC
* @date: 2020/7/24
* @Param: [jsonStr, clazz]
* @return: T
**/
public static <T> T jsonStrToEntity(String jsonStr, Class<T> clazz) throws JsonProcessingException {
return mapper.readValue(jsonStr, clazz);
}
public static String entityToJsonStr(Object data) throws JsonProcessingException {
return mapper.writeValueAsString(data);
}
}
有编译异常直接在方法签名里抛出,让service层去处理。
4.报文字段处理
工具都准备好了,可以正式开工了。
import com.holic.blog.entity.CommonReq;
import org.apache.commons.codec.digest.DigestUtils;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Random;
import java.util.TreeMap;
/**
* @title DealCommonReq
* @author HOLiC
* @date 2020/7/24
*/
public class DealCommonReq {
private DealCommonReq() {
}
private static final String aesKey = "9dLY2oHO0dKHFxG8XdJY/PPuk+wQVa7H";
private static final String md5Key = "2oO0dG8JPukQVa7H";
public static CommonReq getCommonReq(Object data, String busiName) throws Exception {
// 1.先将真正的业务报文转成字符串;异常直接抛出,让业务层去处理
String jsonStr = JsonUtils.entityToJsonStr(data);
// 2.加密;
String encrypt = AESCoder.encrypt(jsonStr, aesKey);
// 3.请求报文字段排序;使用Treemap即可,默认就是ASCII的升序。需要降序可重写Comparator。
CommonReq req = new CommonReq();
req.setBusiData(encrypt);
req.setBusiName(busiName);
req.setTimeStamp(""+System.currentTimeMillis());
req.setNonce(""+new Random().nextInt(99999999)); // 8位的随机数
TreeMap<String, Object> map = new TreeMap<>();
Class<?> reqClass = req.getClass();
Field[] fields = reqClass.getDeclaredFields(); //反射拿到所有字段
for (Field obj : fields ) {
obj.setAccessible(true);
String key = obj.getName();
Object value = obj.get(req);
if (value == null) continue;
map.put(key, value);
}
// 4.请求报文拼接,形如key1=value1&key2=value2&......
StringBuffer buffer = new StringBuffer();
for (Map.Entry field: map.entrySet()) {
buffer.append(field.getKey()).append("=").append(field.getValue()).append("&");
}
String temp = buffer.substring(0, buffer.length() - 1);
// 5.签名
String text = temp + md5Key;
String hex = DigestUtils.md5Hex(text.getBytes(StandardCharsets.UTF_8));
req.setSignData(hex);
return req;
}
}
对外的接口,aesKey和md5Key的值通常由双方来确定,直接给定或者规定生成规则,肯定是一致的。需要注意,这两个key需要绝对保密。
大概就这么多吧,写这篇一是希望帮到别人,二就是万一自己下次遇到类似的处理,能回想起来,不用从头再来。