编写一个简单的dmz区网关,接收外围加密的请求,并且接收参数后进行请求信息解密,建立新的请求信息,转发到自己业务对应的微服务,再根据请求业务id,分发到自己业务处理的详细serviceImpl
dmz网关编写主要就是接收信息,将数据进行解析,并且需要解析后进行验签操作,在处理过程中,无果报错,则要统一返回对应的错误处理信息,并且格式要提前确定好,然后就是转发到具体对的业务服务了,之后接收服务返回的信息再进行加密操作,将数据返回到调用方即可。此过程需要三个过滤器分别做如下操作,缓存解密请求body中信息数据,dmz网关全局过滤器,进行请求参数解密转发,返回信息加密,具体过程如下。
package cn.git.dmz.filter;
import cn.git.dmz.constants.DmzConstants;
import cn.git.dmz.util.ResponseUtil;
import cn.git.foreign.dmz.DmzResult;
import cn.git.foreign.dmz.DmzResultEnum;
import cn.hutool.core.util.ObjectUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.HttpPost;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* @description: 缓存解密请求body中信息数据
* @program: bank-credit-sy
* @author: lixuchun
* @create: 2024-01-05 10:06:45
*/
@Slf4j
@Component
public class DmzCacheFilter implements GlobalFilter, Ordered {
@Autowired
private ResponseUtil responseUtil;
/**
* 执行逻辑
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 判断请求是否为POST请求
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
String uri = request.getPath().pathWithinApplication().value();
String method = request.getMethod().name();
// 方法类型校验必须为post
if (!HttpPost.METHOD_NAME.equals(method)) {
return responseUtil.responseErrorInfo(response, DmzResult.error(DmzResultEnum.REQUEST_METHOD_ERROR));
}
// 将请求信息放入exchange中
Object cachedRequestBodyObject = exchange.getAttributeOrDefault(DmzConstants.REQUEST_INFO_CACHE_KEY, null);
// 已经缓存,则不做任何处理
if (ObjectUtil.isNotNull(cachedRequestBodyObject)) {
return chain.filter(exchange);
}
// 如果没有缓存过,获取字节数组存入exchange中
return DataBufferUtils
.join(exchange.getRequest().getBody())
.map(dataBuffer -> {
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
DataBufferUtils.release(dataBuffer);
return bytes;
}).defaultIfEmpty(new byte[0])
.doOnNext(bytes ->
exchange.getAttributes().put(DmzConstants.REQUEST_INFO_CACHE_KEY, bytes)
).then(chain.filter(exchange));
}
/**
* 过滤器执行顺序 越小越先执行
* @return
*/
@Override
public int getOrder() {
return DmzConstants.CACHE_FILTER_ORDER_NUM;
}
}
package cn.git.dmz.filter;
import cn.git.dmz.constants.DmzConstants;
import cn.git.dmz.util.AESUtil;
import cn.git.dmz.util.LogUtil;
import cn.git.dmz.util.RSAUtil;
import cn.git.dmz.util.ResponseUtil;
import cn.git.foreign.dmz.DmzResult;
import cn.git.foreign.dmz.DmzResultEnum;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.HttpPost;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Base64;
/**
* dmz网关全局过滤器,进行请求参数解密转发
* @program: bank-credit-sy
* @author: lixuchun
* @create: 2021-09-15
*/
@Slf4j
@Component
public class DmzGlobalFilter implements GlobalFilter, Ordered {
@Autowired
private LogUtil logUtil;
@Autowired
private AESUtil aesUtil;
@Autowired
private RSAUtil rsaUtil;
@Autowired
private ResponseUtil responseUtil;
/**
* 处理过滤逻辑信息
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 获取请求request以及response信息
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
String uri = request.getPath().pathWithinApplication().value();
String method = request.getMethod().name();
log.info("DMZ请求uri为[{}],请求method为[{}]", uri, method);
// 方法类型校验必须为 post
if (!HttpPost.METHOD_NAME.equals(method)) {
return responseUtil.responseErrorInfo(response, DmzResult.error(DmzResultEnum.REQUEST_METHOD_ERROR));
}
// 从exchange中获取body信息
String normalMessage;
Object cacheBody = exchange.getAttributeOrDefault(DmzConstants.REQUEST_INFO_CACHE_KEY, null);
// 删除exchange保存的自定义信息
exchange.getAttributes().remove(DmzConstants.REQUEST_INFO_CACHE_KEY);
if (ObjectUtil.isNotNull(cacheBody)) {
// 解密byte数组
try {
byte[] body = (byte[]) cacheBody;
// 传输数据转字符串
String bodyInfoStr = new String(body);
// 请求信息转换为JSON对象,并且获取加密key,以及加密message信息
JSONObject requestJSON = JSONObject.parseObject(bodyInfoStr);
String encryptSignature = requestJSON.getString(DmzConstants.REQ_SIGNATURE_KEY);
String encryptMessage = requestJSON.getString(DmzConstants.REQ_MESSAGE);
// 对encryptSignature进行RSA解密获取16进制的aesKey
String decodeHexPassKey = rsaUtil.decryptRSA(encryptSignature, DmzConstants.RSA_PRI_KEY);
// 将16进制的decodeHexPassKey还原为2进制的aesKey
byte[] aesBytes = aesUtil.parseHexStrToBytes(decodeHexPassKey);
log.info("signature解析后为[{}]", Base64.getEncoder().encodeToString(aesBytes));
// 将message信息进行aes解密
normalMessage = aesUtil.aesDecrypt(encryptMessage, Base64.getEncoder().encodeToString(aesBytes));
log.info("AES解密信息为[{}]",normalMessage);
} catch (Exception e) {
String errorMessage = logUtil.getStackTraceInfo(e, DmzConstants.NUM_2000);
log.error("客户端发送数据解析失败,具体信息为: [{}]", errorMessage);
return responseUtil.responseErrorInfo(response, DmzResult.error(DmzResultEnum.DECRYPT_ERROR));
}
// 进行sign验签操作
JSONObject jsonReqInfo = JSONObject.parseObject(normalMessage);
if (ObjectUtil.isNotNull(jsonReqInfo)) {
String dealTime = jsonReqInfo.getString(DmzConstants.DEAL_TIME);
String sign = jsonReqInfo.getString(DmzConstants.SIGN);
String sysId = jsonReqInfo.getString(DmzConstants.SYS_ID);
// 验签标识合法完整性校验
if (StrUtil.isBlank(dealTime) || StrUtil.isBlank(sign) || StrUtil.isBlank(sysId)) {
return responseUtil.responseErrorInfo(response, DmzResult.error(DmzResultEnum.SIGN_VALID_FORMAT_ERROR));
}
// 验签校验
String checkMD5Key = SecureUtil.md5(sysId
.concat(DmzConstants.SIGN_SEPARATOR)
.concat(dealTime)
.concat(DmzConstants.SIGN_SEPARATOR)
.concat(DmzConstants.MD5_KEY));
if (!checkMD5Key.equals(sign)) {
return responseUtil.responseErrorInfo(response, DmzResult.error(DmzResultEnum.SIGN_VALID_ERROR));
}
}
// 重新构建新的请求信息
DataBufferFactory dataBufferFactory = exchange.getResponse().bufferFactory();
Flux<DataBuffer> bodyFlux = Flux.just(dataBufferFactory.wrap(normalMessage.getBytes()));
ServerHttpRequest newRequest = request.mutate().uri(request.getURI()).build();
newRequest = new ServerHttpRequestDecorator(newRequest) {
@Override
public Flux<DataBuffer> getBody() {
return bodyFlux;
}
};
// 请求头信息优化
HttpHeaders headers = new HttpHeaders();
headers.putAll(exchange.getRequest().getHeaders());
// 由于修改了传递参数,需要重新设置CONTENT_LENGTH,长度是字节长度,不是字符串长度
int length = normalMessage.getBytes().length;
headers.remove(HttpHeaders.CONTENT_LENGTH);
headers.setContentLength(length);
headers.set(HttpHeaders.CONTENT_TYPE, DmzConstants.APPLICATION_TYPE_JSON);
newRequest = new ServerHttpRequestDecorator(newRequest) {
@Override
public HttpHeaders getHeaders() {
return headers;
}
};
return chain.filter(exchange.mutate().request(newRequest).build());
} else {
// 无请求信息体返回错误
return responseUtil.responseErrorInfo(response, DmzResult.error(DmzResultEnum.REQUEST_NO_BODY_ERROR));
}
}
/**
* 过滤器执行顺序数字小的优先执行
*/
@Override
public int getOrder() {
return DmzConstants.GLOBAL_FILTER_ORDER_NUM;
}
}
package cn.git.dmz.filter;
import cn.git.common.constant.CommonConstant;
import cn.git.dmz.constants.DmzConstants;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.symmetric.AES;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
/**
* 返回信息加密filter
* @program: bank-credit-sy
* @author: lixuchun
* @create: 2021-09-15
*/
@Component
@Slf4j
public class RespEncryptFilter implements GatewayFilter, Ordered {
/**
* AES信息加密解密处理
*/
private static final AES AES = new
AES(Mode.ECB, Padding.PKCS5Padding, CommonConstant.DMZ_LOCK_KEY.getBytes(StandardCharsets.UTF_8));
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 获取响应信息
ServerHttpResponse dmzResponse = exchange.getResponse();
DataBufferFactory bufferFactory = dmzResponse.bufferFactory();
ServerHttpResponseDecorator responseDecorator = new ServerHttpResponseDecorator(dmzResponse) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
if (getStatusCode().equals(HttpStatus.OK) && body instanceof Flux) {
Flux<? extends DataBuffer> fluxBody = Flux.from(body);
return super.writeWith(fluxBody.buffer().map(dataBuffer -> {
DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
DataBuffer join = dataBufferFactory.join(dataBuffer);
// 获取数据
byte[] content = new byte[join.readableByteCount()];
join.read(content);
// 释放掉内存
DataBufferUtils.release(join);
// 获取正常返回的数据,并且进行数据data加密 AES(base64)
String rootData = new String(content, StandardCharsets.UTF_8);
JSONObject responseJSON = JSONObject.parseObject(rootData);
JSONObject data = responseJSON.getJSONObject(CommonConstant.DMZ_DATA_FLAG);
if (ObjectUtil.isNotNull(data)) {
String encryptBase64 = AES.encryptBase64(data.toString());
responseJSON.put(CommonConstant.DMZ_DATA_FLAG, encryptBase64);
}
byte[] respData = responseJSON.toJSONString().getBytes();
// 加密后的数据返回给客户端
byte[] uppedContent = new String(respData, StandardCharsets.UTF_8).getBytes();
dmzResponse.getHeaders().setContentLength(uppedContent.length);
return bufferFactory.wrap(uppedContent);
}));
}
return super.writeWith(body);
}
};
return chain.filter(exchange.mutate().response(responseDecorator).build());
}
@Override
public int getOrder() {
return DmzConstants.RESP_ORDER_NUM;
}
}
此处dmz网关就简单点,不用像业务模块的业务网关,直接转发,不设置数据库获取,转存redis这种动态路由了,具体代码如下
package cn.git.dmz.config;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 路由信息自动配置类
* @program: bank-credit-sy
* @author: lixuchun
* @create: 2021-09-15
*/
@Configuration
public class DmzGatewayConfig {
/**
* 匹配业务网关path信息
*/
private static final String FOREIGN_PATH = "/dmz/**";
/**
* 业务网关注册服务器上名称
*/
private static final String FOREIGN_SERVER = "lb://foreign-server";
/**
* 路径截取长度
*/
private static final Integer STRIP_PREFIX = 1;
/**
* 通用过滤器
*/
@Autowired
private RespEncryptFilter respEncryptFilter;
/**
* response返回信息加密filter加入到路由中
* @param builder builder
* @return RouteLocator
*/
@Bean
public RouteLocator appRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route(r -> r.path(FOREIGN_PATH)
.filters(f ->
// f.filter(respEncryptFilter)
f.stripPrefix(STRIP_PREFIX)
)
.uri(FOREIGN_SERVER)
).build();
}
}
AES加密工具
package cn.git.dmz.util;
import cn.git.common.exception.ServiceException;
import cn.git.dmz.constants.DmzConstants;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
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.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;
/**
* @description: AES加解密工具类
* @program: bank-credit-sy
* @author: lixuchun
* @create: 2024-01-05 08:52:41
*/
@Component
@Slf4j
public class AESUtil {
@Autowired
private LogUtil logUtil;
/**
* 获取随机AES密钥
* @return 返回加密秘钥
*/
public SecretKey getRandomKey() {
// 使用种子生成种子随机秘钥,如果想每一次都生成不同秘钥,则去掉种子即可
SecureRandom secureRandom = new SecureRandom(DmzConstants.DMZ_LOCK_KEY.getBytes(StandardCharsets.UTF_8));
// 实例
KeyGenerator kg = null;
try {
kg = KeyGenerator.getInstance(DmzConstants.AES_TYPE);
// AES
kg.init(DmzConstants.KEY_SIZE, secureRandom);
// 生成密钥
SecretKey secretKey = kg.generateKey();
return secretKey;
} catch (NoSuchAlgorithmException e) {
String errorMsg = logUtil.getStackTraceInfo(e, DmzConstants.NUM_2000);
log.error(errorMsg);
throw new ServiceException("获取随机AES密钥失败");
}
}
/**
* 加密
*
* @param content 内容
* @param randomPassKey 秘钥
* @return 加密后的数据
*/
public String aesEncrypt(String content, String randomPassKey) {
try {
// 新建Cipher 类
Cipher cipher = Cipher.getInstance(DmzConstants.AES_MODE);
// 初始化秘钥
SecretKeySpec sks = new SecretKeySpec(Base64.getDecoder().decode(randomPassKey), DmzConstants.AES_TYPE);
// 初始化加密类
cipher.init(Cipher.ENCRYPT_MODE, sks);
// 进行加密
byte[] encrypt = cipher.doFinal(content.getBytes());
// 进行base64编码
encrypt = Base64.getEncoder().encode(encrypt);
// 转成字符串返回
return new String(encrypt, StandardCharsets.UTF_8);
} catch (Exception e) {
String errorMsg = logUtil.getStackTraceInfo(e, DmzConstants.NUM_2000);
log.error(errorMsg);
throw new ServiceException("AES加密失败");
}
}
/**
* AES进行解密数据
*
* @param content 解密内容
* @param randomPassKey aes随机秘钥
* @return 解密后数据
*/
public String aesDecrypt(String content, String randomPassKey) {
try {
// base64里会携带换行符导致解码失败,替换base64里的换行
content = content.replaceAll("[\\n\\r]", StrUtil.EMPTY);
// base64 解码,跟上面的编码对称
byte[] data = Base64.getDecoder().decode(content.getBytes(StandardCharsets.UTF_8));
// 新建Cipher 类
Cipher cipher = Cipher.getInstance(DmzConstants.AES_MODE);
// 初始化秘钥
SecretKeySpec keySpec = new SecretKeySpec(Base64.getDecoder().decode(randomPassKey), DmzConstants.AES_TYPE);
// 初始化类
cipher.init(Cipher.DECRYPT_MODE, keySpec);
// 进行AES
byte[] result = cipher.doFinal(data);
// 返回解密后内容信息
return new String(result);
} catch (Exception e) {
String errorMsg = logUtil.getStackTraceInfo(e, DmzConstants.NUM_2000);
log.error(errorMsg);
throw new ServiceException("AES解密失败");
}
}
/**
* 将byte数组转换成16进制String
* @param bytes 转换的byte数组
* @return 16进制字符串
*/
public String parseBytesToHexStr(byte[] bytes) {
StringBuilder stringBuilder = new StringBuilder();
for (byte b : bytes) {
String hex = Integer.toHexString(b & 0xFF);
if (hex.length() == 1) {
hex = '0' + hex;
}
stringBuilder.append(hex.toUpperCase());
}
return stringBuilder.toString();
}
/**
* 将16进制String转换为byte数组
* @param hexStr 16进制字符串
* @return 2进制byte数组
*/
public byte[] parseHexStrToBytes(String hexStr) {
// 空值判断
if (hexStr.length() < 1) {
return null;
}
byte[] result = new byte[hexStr.length() / 2];
for (int i = 0; i < hexStr.length() / DmzConstants.NUM_2; i++) {
int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16);
int low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2), 16);
result[i] = (byte) (high * 16 + low);
}
return result;
}
}
RSA加解密工具类
package cn.git.dmz.util;
import cn.git.common.exception.ServiceException;
import cn.git.dmz.constants.DmzConstants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
/**
* @description: RSA加解密工具类
* @program: bank-credit-sy
* @author: lixuchun
* @create: 2024-01-05 08:58:58
*/
@Component
@Slf4j
public class RSAUtil {
@Autowired
private LogUtil logUtil;
/**
* RSA初始化key pair,初始化公钥私钥方法
* @return KeyPair
*/
public KeyPair getRSAKeyPair() {
try {
// 生成RSA密钥对
KeyPairGenerator keyGen = KeyPairGenerator.getInstance(DmzConstants.RSA_TYPE);
// 根据种子生成秘钥对
SecureRandom secureRandom = new SecureRandom(DmzConstants.DMZ_LOCK_KEY.getBytes(StandardCharsets.UTF_8));
keyGen.initialize(DmzConstants.NUM_1024, secureRandom);
return keyGen.generateKeyPair();
} catch (Exception e) {
String errorMsg = logUtil.getStackTraceInfo(e, DmzConstants.NUM_2000);
log.error(errorMsg);
throw new ServiceException("RSA初始化keyPairs失败");
}
}
/**
* RSA加密
* @param data 待加密数据
* @param publicKeyStr 公钥
* @return 加密后的数据
*/
public String encryptRSA(String data, String publicKeyStr) {
try {
byte[] keyBytes = Base64.getDecoder().decode(publicKeyStr);
// 初始化公钥key
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(DmzConstants.RSA_TYPE);
PublicKey publicKey = keyFactory.generatePublic(x509KeySpec);
// 使用公钥进行加密
Cipher encryptCipher = Cipher.getInstance(DmzConstants.RSA_MODE);
encryptCipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptedBytes = encryptCipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encryptedBytes);
} catch (Exception e) {
String errorMsg = logUtil.getStackTraceInfo(e, DmzConstants.NUM_2000);
log.error(errorMsg);
throw new ServiceException("RSA加密失败");
}
}
/**
* 进行RSA解密
* @param content 解密文本
* @param privateKeyStr 私钥字符串
* @return
*/
public String decryptRSA(String content, String privateKeyStr) {
try {
byte[] keyBytes = Base64.getDecoder().decode(privateKeyStr);
// 初始化私钥
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(DmzConstants.RSA_TYPE);
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
// 使用私钥进行解密
Cipher decryptCipher = Cipher.getInstance(DmzConstants.RSA_MODE);
decryptCipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decryptedBytes = decryptCipher.doFinal(Base64.getDecoder().decode(content));
return new String(decryptedBytes, StandardCharsets.UTF_8);
} catch (Exception e) {
String errorMsg = logUtil.getStackTraceInfo(e, DmzConstants.NUM_2000);
log.error(errorMsg);
throw new ServiceException("RSA解密失败");
}
}
}
日志工具类
package cn.git.dmz.util;
import cn.git.dmz.constants.DmzConstants;
import cn.hutool.core.util.StrUtil;
import org.springframework.stereotype.Component;
import java.io.PrintWriter;
import java.io.StringWriter;
/**
* @description: 日志通用方法
* @program: bank-credit-sy
* @author: lixuchun
* @create: 2024-01-05 04:21:12
*/
@Component
public class LogUtil {
/**
* 打印异常信息转字符串
*
* @param exception 异常
* @param errorMessageLength 打印异常信息长度
* @return 异常信息
*/
public String getStackTraceInfo(Exception exception, Integer errorMessageLength) {
StringWriter stringWriter = new StringWriter();
PrintWriter printWriter = new PrintWriter(stringWriter);
try {
exception.printStackTrace(printWriter);
printWriter.flush();
stringWriter.flush();
String errorMessage = stringWriter.toString();
if (StrUtil.isNotBlank(errorMessage) && errorMessage.length() > DmzConstants.NUM_3000) {
errorMessage = errorMessage.substring(DmzConstants.NUM_0, errorMessageLength);
}
return errorMessage;
} finally {
try {
printWriter.close();
stringWriter.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
响应信息工具类
package cn.git.dmz.util;
import cn.git.foreign.dmz.DmzResult;
import com.alibaba.fastjson.JSON;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
/**
* @description: 响应工具类
* @program: bank-credit-sy
* @author: lixuchun
* @create: 2024-01-05 10:15:13
*/
@Component
public class ResponseUtil {
/**
* 封装错误信息
* @param response 相应
* @param dmzResult 结果
* @return 封装后的结果
*/
public Mono<Void> responseErrorInfo(ServerHttpResponse response, DmzResult dmzResult) {
HttpHeaders httpHeaders = response.getHeaders();
httpHeaders.add("Content-Type", "application/json; charset=UTF-8");
httpHeaders.add("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0");
response.setStatusCode(HttpStatus.UNAUTHORIZED);
DataBuffer bodyDataBuffer = response.bufferFactory().wrap(JSON.toJSONBytes(dmzResult));
return response.writeWith(Mono.just(bodyDataBuffer));
}
}
自服务接收到的数据已经是解密之后的数据,直接对数据接收内容进行业务转发即可,代码如下
实现业务的转发,以及使用简单反射,实例化参数信息,方便后续进行validate校验
package cn.git.foreign.controller;
import cn.git.api.util.NewCoreProperties;
import cn.git.common.exception.ServiceException;
import cn.git.common.thread.ThreadPoolUtil;
import cn.git.common.util.LogUtil;
import cn.git.foreign.dmz.DmzResult;
import cn.git.foreign.dmz.DmzResultEnum;
import cn.git.foreign.dmz.dto.base.LRCBRequestDTO;
import cn.git.foreign.dmz.log.LogDealUtil;
import cn.git.foreign.dmz.tran.base.DmzBaseTran;
import cn.git.foreign.entity.TbEsbNewCoreRecords;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.ConstraintViolationException;
import java.util.Date;
/**
* @description: 业务并入controller
* @program: bank-credit-sy
* @author: lixuchun
* @create: 2024-01-04 09:43:09
*/
@Slf4j
@RestController
@RequestMapping("/lrcb")
public class LRCBWebApplyController {
/**
* 业务基础包
*/
public static final String BASE_VALID_PACKAGE = "cn.git.foreign.dmz.dto.";
/**
* dto尾缀
*/
public static final String SUFFIX_DTO = "DTO";
/**
* tran尾缀
*/
public static final String SUFFIX_TRAN = "Tran";
@Autowired
private ApplicationContext applicationContext;
@Autowired
private LogDealUtil logDealUtil;
/**
* 通用并入接口
* @param sysId 商户编号,此编号由数字信贷运营人员统一提供
* @param lrcbRequestDTO 参数信息
* @return
*/
@RequestMapping("/service/js/{sysId}")
public DmzResult serviceJS(@PathVariable(value = "sysId") String sysId, @RequestBody LRCBRequestDTO lrcbRequestDTO) {
log.info("DMZ网关请求来源为[{}],交易类型为[{}],请求流水号为[{}],具体请求信息为[{}]",
sysId,
lrcbRequestDTO.getTranscode(),
lrcbRequestDTO.getTransno(),
lrcbRequestDTO.getRequestParams().toJSONString());
// 获取参数信息,并且对参数信息进行校验
JSONObject jsonParam = lrcbRequestDTO.getRequestParams();
if (ObjectUtil.isEmpty(jsonParam)) {
// 具体业务请求参数为空
return DmzResult.error(DmzResultEnum.REQUEST_NO_BODY_ERROR, "请求参数为空,请确认!");
}
// 交易参数校验
String transcode = lrcbRequestDTO.getTranscode();
String transno = lrcbRequestDTO.getTransno();
String dtoSysId = lrcbRequestDTO.getSysid();
if (StrUtil.isBlank(transcode) || StrUtil.isBlank(transno) || StrUtil.isBlank(dtoSysId)) {
// 具体业务请求参数为空
return DmzResult.error(DmzResultEnum.REQUEST_NO_BODY_ERROR, "通用基础请求参数[sysid,transcode,transno]不全,请确认!");
}
// 获取请求参数
String fullClassName = BASE_VALID_PACKAGE.concat(transcode).concat(SUFFIX_DTO);
Object reqObject;
try {
// 设置流水号以及业务类型
jsonParam.put("transcode", transcode);
jsonParam.put("transno", transno);
// 通过类名获取类类型,再获取参数实例信息
Class reqClazz = Class.forName(fullClassName);
reqObject = JSONObject.parseObject(jsonParam.toJSONString(), reqClazz);
} catch (ClassNotFoundException e) {
return DmzResult.error(DmzResultEnum.REQUEST_PARAM_TRANSCODE_ERROR);
}
// 获取业务执行类名称
String tranName = transcode.concat(SUFFIX_TRAN);
// 获取业务执行类
DmzBaseTran dmzBaseTran;
try {
dmzBaseTran = applicationContext.getBean(tranName, DmzBaseTran.class);
} catch (Exception e) {
String errorMessage = StrUtil.format("通过[{}]获取请求执行类失败,请确认参数是否正确!", tranName);
return DmzResult.error(DmzResultEnum.REQUEST_GET_TRAIN_ERROR, errorMessage);
}
// 封装日志参数信息
TbEsbNewCoreRecords tbEsbNewCoreRecords = new TbEsbNewCoreRecords();
tbEsbNewCoreRecords.setSvcNo(transcode);
tbEsbNewCoreRecords.setScnNo(transcode);
tbEsbNewCoreRecords.setRecordId(IdUtil.simpleUUID());
tbEsbNewCoreRecords.setGloSeqNo(transno);
tbEsbNewCoreRecords.setLocalBusFlag(null);
tbEsbNewCoreRecords.setMessageInfo(jsonParam.toJSONString());
tbEsbNewCoreRecords.setMessageTime(new Date());
tbEsbNewCoreRecords.setCtime(new Date());
tbEsbNewCoreRecords.setMessageType(NewCoreProperties.ACCEPT);
// 执行业务逻辑
DmzResult dmzResult = null;
try {
dmzResult = dmzBaseTran.process(reqObject);
} catch (ConstraintViolationException e) {
log.error("DMZ请求参数校验异常,异常信息为[{}]", e.getMessage());
return dmzResult = DmzResult.error(DmzResultEnum.REQUEST_PARAM_VALID_ERROR, e.getMessage());
} catch (ServiceException e) {
// 抛出自定义异常信息
return dmzResult = DmzResult.error(DmzResultEnum.CUSTOM_TRAIN_ERROR, e.getMessage());
} catch (Exception e) {
// 其他异常信息
String errorMessage = LogUtil.getStackTraceInfo(e);
log.error("DMZ请求执行异常,流水号[{}],异常信息为[{}]", transno, errorMessage);
return dmzResult = DmzResult.error(DmzResultEnum.NORMAL_ERROR, errorMessage);
} finally {
// 插入业务执行日志
DmzResult finalDmzResult = dmzResult;
ThreadPoolUtil.THREAD_POOL.execute(() -> {
logDealUtil.addLog(tbEsbNewCoreRecords, finalDmzResult);
});
}
return DmzResult.ok(dmzResult);
}
}
package cn.git.foreign.dmz.tran.base;
import cn.git.foreign.dmz.DmzResult;
import javax.validation.Valid;
/**
* @description: base通用tran接口
* @program: bank-credit-sy
* @author: lixuchun
* @create: 2024-01-08 10:10:38
*/
public interface DmzBaseTran<T> {
/**
* 通用业务执行方法
* @param requestParam 参数
* @return
*/
DmzResult process(@Valid T requestParam);
}
此处对应的@Validated以及@Valid ,可以实现hibernate validate对应的参数校验
package cn.git.foreign.dmz.tran;
import cn.git.common.exception.ServiceException;
import cn.git.foreign.dmz.DmzResult;
import cn.git.foreign.dmz.dto.XDTSW000DTO;
import cn.git.foreign.dmz.tran.base.DmzBaseTran;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import javax.validation.Valid;
/**
* @description: 测试业务逻辑
* @program: bank-credit-sy
* @author: lixuchun
* @create: 2024-01-08 10:17:11
*/
@Validated
@Component
public class XDTSW000Tran implements DmzBaseTran<XDTSW000DTO> {
/**
* 通用业务执行方法
*
* @param requestParam 参数
* @return
*/
@Override
public DmzResult process(@Valid XDTSW000DTO requestParam) {
// 异常测试
if (1 == 1) {
// 执行业务代码
try {
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
}
// 业务逻辑异常
double a = 1 / 0;
// 自定义异常
throw new ServiceException("异常测试啦,抛出自定义异常!");
}
// 失败返回 DmzResult.error("0XXX", "失败了")
return DmzResult.ok(requestParam);
}
}
通过流水号记录具体的请求响应信息,方便出问题的时候进行查询
package cn.git.foreign.dmz.log;
import cn.git.api.util.NewCoreProperties;
import cn.git.foreign.dmz.DmzResult;
import cn.git.foreign.entity.TbEsbNewCoreRecords;
import cn.git.foreign.mapper.TbEsbNewCoreRecordsMapper;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* @description: dmz日志处理
* @program: bank-credit-sy
* @author: lixuchun
* @create: 2024-01-08 03:01:40
*/
@Component
@RequiredArgsConstructor
public class LogDealUtil {
private final TbEsbNewCoreRecordsMapper tbEsbNewCoreRecordsMapper;
/**
* 插入日志信息
* @param tbEsbNewCoreRecords 请求信息
* @param dmzResult 响应信息
*/
public void addLog(TbEsbNewCoreRecords tbEsbNewCoreRecords, DmzResult dmzResult) {
// 插入请求信息
if (ObjectUtil.isNotNull(tbEsbNewCoreRecords)) {
tbEsbNewCoreRecordsMapper.insert(tbEsbNewCoreRecords);
// 插入响应信息
if (ObjectUtil.isNotEmpty(dmzResult)) {
tbEsbNewCoreRecords.setRecordId(IdUtil.simpleUUID());
tbEsbNewCoreRecords.setMessageTime(new Date());
tbEsbNewCoreRecords.setMessageType(NewCoreProperties.SEND);
tbEsbNewCoreRecords.setMessageInfo(JSONObject.toJSONString(dmzResult));
tbEsbNewCoreRecords.setRspDate(DateUtil.format(new Date(), DatePattern.NORM_DATE_PATTERN));
tbEsbNewCoreRecords.setCtime(new Date());
tbEsbNewCoreRecords.setMtime(new Date());
tbEsbNewCoreRecordsMapper.insert(tbEsbNewCoreRecords);
}
}
}
}
这部分实体就是简单的测试对象类,里面有对应的validate注解。具体如下
package cn.git.foreign.dmz.dto;
import cn.git.foreign.dmz.dto.base.DmzBaseDTO;
import cn.git.foreign.dmz.dto.child.XDTSW000ChildDTO;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import java.util.List;
/**
* @description: dmz网关base测试dto
* @program: bank-credit-sy
* @author: lixuchun
* @create: 2024-01-08 09:01:29
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class XDTSW000DTO extends DmzBaseDTO {
/**
* 名称
*/
@NotBlank(message = "名称不能为空!")
private String name;
/**
* 年龄
*/
@NotNull(message = "年龄不能为空!")
private Integer age;
/**
* 身高
*/
@NotNull(message = "身高不能为空!")
private Integer tall;
/**
* 操作时间
*/
@Pattern(regexp = "^\\d{4}-\\d{2}-\\d{2}$", message = "日期格式不正确!")
private String date;
/**
* 附属信息
*/
@Valid
@NotNull(message = "附属信息不能为空")
@Size(min = 1, message = "至少有一个附属信息")
List<XDTSW000ChildDTO> childDTOList;
}
此为银行附属子信息,子信息也支持参数信息校验
package cn.git.foreign.dmz.dto.child;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
/**
* @description: 银行附属子信息
* @program: bank-credit-sy
* @author: lixuchun
* @create: 2024-01-08 09:58:17
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class XDTSW000ChildDTO {
/**
* 银行编号
*/
@NotBlank(message = "银行编号不能为空!")
private String bankNum;
/**
* 银行名称
*/
@NotBlank(message = "银行名称不能为空!")
private String bankName;
}
这部分主要就是响应的结构类型,编写了两个类进行实现具体如下
package cn.git.foreign.dmz;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @description: DMZ响应结果集
* @program: bank-credit-sy
* @author: lixuchun
* @create: 2024-01-05 10:18:16
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DmzResult<T> {
/**
* 响应结果
*/
private T result;
/**
* 是否成功
* true/false
*/
private boolean issuccess;
/**
* 响应编码
* 0000 成功
*/
private String rtncode;
/**
* 响应消息
* 交易成功/交易失败
*/
private String rtnmessage;
/**
* 支持信息
* 请联系服务商
*/
private String solution;
/**
* 构造函数
*/
private DmzResult(DmzResultEnum dmzResultEnum) {
this.issuccess = dmzResultEnum.isIssuccess();
this.rtncode = dmzResultEnum.getRtncode();
this.rtnmessage = dmzResultEnum.getRtnmessage();
this.solution = dmzResultEnum.getSolution();
}
/**
* 构造函数
*/
private DmzResult(DmzResultEnum dmzResultEnum, T data) {
this.result = data;
this.issuccess = dmzResultEnum.isIssuccess();
this.rtncode = dmzResultEnum.getRtncode();
this.rtnmessage = dmzResultEnum.getRtnmessage();
this.solution = dmzResultEnum.getSolution();
}
/**
* 构造函数
*/
private DmzResult(DmzResultEnum dmzResultEnum, String message) {
this.result = null;
this.issuccess = dmzResultEnum.isIssuccess();
this.rtncode = dmzResultEnum.getRtncode();
this.rtnmessage = message;
this.solution = dmzResultEnum.getSolution();
}
/**
* 构造函数
*/
private DmzResult(DmzResultEnum dmzResultEnum, String message, String rtncode) {
this.result = null;
this.issuccess = dmzResultEnum.isIssuccess();
this.rtncode = dmzResultEnum.getRtncode();
this.rtnmessage = message;
this.solution = dmzResultEnum.getSolution();
}
/**
* 构造函数
* @param result 响应结果
* @param issuccess 是否成功
* @param rtncode 响应编码
* @param rtnmessage 响应消息
*/
private DmzResult(T result, boolean issuccess, String rtncode, String rtnmessage) {
this.result = result;
this.issuccess = issuccess;
this.rtncode = rtncode;
this.rtnmessage = rtnmessage;
}
/**
* 构造函数
* @param result 响应结果
* @param rtncode 响应编码
* @param rtnmessage 响应消息
*/
private DmzResult(T result, String rtncode, String rtnmessage) {
this.result = result;
this.rtncode = rtncode;
this.rtnmessage = rtnmessage;
}
/**
* 构造函数
* @param rtncode 响应编码
* @param rtnmessage 响应消息
*/
private DmzResult(String rtncode, String rtnmessage) {
this.rtncode = rtncode;
this.rtnmessage = rtnmessage;
this.solution = "请联系服务商!";
}
/**
* 业务请求成功,返回业务代码和描述信息
*/
public static DmzResult<Void> ok() {
return new DmzResult<>(DmzResultEnum.SUCCESS, null);
}
/**
* 业务请求成功,返回业务代码和描述信息
*/
public static DmzResult<Void> ok(DmzResult dmzResult) {
return dmzResult;
}
/**
* 业务请求成功,返回业务代码和描述信息,数据
*/
public static <T> DmzResult<T> ok(T data) {
return new DmzResult<>(DmzResultEnum.SUCCESS, data);
}
/**
* 业务请求失败
*/
public static <T> DmzResult<T> error() {
return new DmzResult<>(DmzResultEnum.NORMAL_ERROR, null);
}
/**
* 业务请求失败,返回错误响应码
*/
public static <T> DmzResult<T> error(DmzResultEnum dmzResultEnum) {
return new DmzResult<>(dmzResultEnum);
}
/**
* 业务请求失败,返回错误响应码
*/
public static <T> DmzResult<T> error(DmzResultEnum dmzResultEnum, String message, String rtncode) {
return new DmzResult<>(dmzResultEnum, message, rtncode);
}
/**
* 业务请求失败
*/
public static <T> DmzResult<T> error(String message) {
return new DmzResult<>(DmzResultEnum.NORMAL_ERROR, message);
}
/**
* 业务请求失败
*/
public static <T> DmzResult<T> error(String rtncode, String rtnmessage) {
return new DmzResult<>(rtncode, rtnmessage);
}
/**
* 业务请求失败
*/
public static <T> DmzResult<T> error(DmzResultEnum dmzResultEnum, String rtnmessage) {
return new DmzResult<>(dmzResultEnum, rtnmessage);
}
}
package cn.git.foreign.dmz;
import lombok.Getter;
/**
* @description: DMZ网关响应enum
* @program: bank-credit-sy
* @author: lixuchun
* @create: 2024-01-05 10:20:51
*/
@Getter
public enum DmzResultEnum {
/**
* 交易枚举类型
*/
SUCCESS(true, "0000", "交易成功!", "交易成功!"),
NORMAL_ERROR(false, "000X", "代码异常!", "请联系供应商!"),
DECRYPT_ERROR(false, "000X", "解密入参失败!", "请确认请求信息是否正确!"),
REQUEST_METHOD_ERROR(false, "0001", "请求方法必须为post方法!", "请确认请求方式!"),
REQUEST_NO_BODY_ERROR(false, "0002", "请求信息没有请求体!", "请确认请求信息是否正确!"),
SIGN_VALID_FORMAT_ERROR(false, "0003", "验签请求信息补全!", "请确认请验签请求信息是合法!"),
SIGN_VALID_ERROR(false, "0004", "验签失败!", "验签失败!"),
REQUEST_PARAM_VALID_ERROR(false, "0005", "请求参数格式校验失败!", "请求参数格式校验失败!"),
REQUEST_PARAM_TRANSCODE_ERROR(false, "0006", "transcode解析失败!", "请确认请求参数transcode!"),
REQUEST_GET_TRAIN_ERROR(false, "0007", "获取TRAN执行类失败!", "请联系供应商!"),
CUSTOM_TRAIN_ERROR(false, "0008", "自定义异常信息!", "请联系供应商!"),
;
/**
* 是否成功
* true/false
*/
private boolean issuccess;
/**
* 响应编码
* 0000 成功
*/
private String rtncode;
/**
* 响应消息
* 交易成功/交易失败
*/
private String rtnmessage;
/**
* 支持信息
* 请联系服务商
*/
private String solution;
/**
* 构造函数
*
* @param issuccess
* @param rtncode
* @param rtnmessage
* @param solution
*/
DmzResultEnum(boolean issuccess, String rtncode, String rtnmessage, String solution) {
this.issuccess = issuccess;
this.rtncode = rtncode;
this.rtnmessage = rtnmessage;
this.solution = solution;
}
}
注意,下面的对应的RSA公钥私钥需要自己生成(generatorKey方法即可),不要用我提供的,我的是乱搞的。。。。。。
package cn.git.foreign;
import cn.git.api.util.NewCoreProperties;
import cn.git.foreign.dmz.dto.base.LRCBRequestDTO;
import cn.git.foreign.dmz.dto.child.XDTSW000ChildDTO;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSONObject;
import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
/**
* @description: 本地业务测试
* @program: bank-credit-sy
* @author: lixuchun
* @create: 2024-01-05 03:11:20
*/
public class SYNSHTest {
/**
* 外部系统请求网关加密密匙
*/
public static final String DMZ_LOCK_KEY = "DMZ:xfxt:wxts==!";
/**
* AES
* 算法名称/加密模式/数据填充方式
*/
public static String MODE = "AES/ECB/PKCS5Padding";
/**
* RSA
* 算法名称/加密模式/数据填充方式
*/
private final static String RSA_MODE = "RSA/ECB/PKCS1Padding";
/**
* 加密模式
*/
public static String KEY_ALGORITHM = "AES";
/**
* 加密单位长度
*/
private static final int KEY_SIZE = 128;
/**
* 自定义md5key,正常需要与运营人员沟通获取
*/
public static final String MD5_KEY = "Md5xKey:%#FOr,xoan>2";
/**
* RSA公钥
*/
private static final String RSA_PUB_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCMZ/Q5CIvZjBTLD4sTwxbWYNNZWd7rImD+sAgwOAf+nH1vWN+Qk63iBGQYHzYK5EhSAFqsqX04lsTiJUH42YRJ9yIXUnvd6Osp2JYOXW8WTGgMtfDJp5WzbYzJRxQ0RmJNcfqMcHPVepgpjs2HNj59aTqNPLdiouRSRlh2E0umGwIDAQAB";
/**
* RSA私钥
*/
private static final String RSA_PRI_KEY = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAIxn9DkIi9mMFMsPixPDFtZg01lZ3usiYP6wCDA4B/6cfW9Y35CTreIEZBgfNgrkSFIAWqypfTiWxOIlQfjZhEn3IhdSe93o6ynYlg5dbxZMaAy18MmnlbNtjMlHFDRGYk1x+oxwc9V6mCmOzYc2Pn1pOo08t2Ki5FJGWHYTS6YbAgMBAAECgYEAiQRNNXccmsDz7bGOZEumtrAor/Je8xFKnGCGrR+Q1aw7UHTnPvyO3JiyYUPcBkb+OF+2HPcNhzLCkXoQZltGlznwOwGvHl4qEheVAMwdgijuYQZpsiGQyVyr4C506ydoPjPXbWD+9GGLuakHtIlRP9FAGvwQe/5fkUYsiAJD8mkCQQDop8hxDCrsH9eBQ7PusaGjmW213zmX0O5yAntwuznX3zsQOo+AgeM00ottF4J5BXWCeyF5ZxKi6WoPjSgTw77fAkEAmn6QNwMSEbcWHbuaC3ofjcYhnOl47aQWNr+56G+Wc7vs4xs8PXdd3sVmlepOFSdJLWCxguUBO60Dg6cpnAFMRQJABZLPaHXkKVfx77TRgKxctPCeAjdgx9RHgg+xKVgy4IsGfTMJ8Qgriz5n/KsNgxywXfnZKXFgrupskgbNqPuNfQJAQlOjxnpi/4gCzrED6Xl8onk1ZRA3Ao83mjmlrsx5YyaDBN1kd18PxdwptqLo8tvy5rBkhTWb2erlX1gc3QURoQJABakHaa+GNmKyc7pasJHQ3HMR39k888TQPKEQiE461oc5//Cyqek7u91rCG0HR7O4Hk+ostH9lkxc4bG+HYgWgg==";
public static void main(String[] args) throws Exception {
testSYNSH();
}
public static void testSYNSH() throws Exception {
// 公共请求信息
LRCBRequestDTO lrcbRequestDTO = new LRCBRequestDTO();
lrcbRequestDTO.setSysid("LOAN");
lrcbRequestDTO.setTranstime("20230101121212");
lrcbRequestDTO.setTransno("YDTS202203121122");
lrcbRequestDTO.setDeviceType("WECHAT");
lrcbRequestDTO.setTranscode("XDTSW000");
// 验签规则
String sign = SecureUtil.md5(lrcbRequestDTO.getSysid().concat("|")
.concat(lrcbRequestDTO.getTranstime()).concat("|")
.concat(MD5_KEY));
lrcbRequestDTO.setSign(sign);
// 请求参数,此处信息如果XDTSW000DTO配置validate注解,则生效
JSONObject requestJSON = new JSONObject();
requestJSON.put("name", "jack");
requestJSON.put("age", 18);
requestJSON.put("tall", 185);
requestJSON.put("date", "2023-12-12");
// 附属信息,附属信息validate注解同样生效
List<XDTSW000ChildDTO> childDTOList = new ArrayList<>();
XDTSW000ChildDTO childDTO = new XDTSW000ChildDTO();
childDTO.setBankNum("123456789");
childDTO.setBankName("中国银行");
childDTOList.add(childDTO);
requestJSON.put("childDTOList", childDTOList);
// 组装请求参数
lrcbRequestDTO.setRequestParams(requestJSON);
// 中间请求参数
String reqJSONStr = JSONObject.toJSONString(lrcbRequestDTO);
// 获取AES随机秘钥
SecretKey secretKey = getRandomKey();
String randomKeyStr = Base64.getEncoder().encodeToString(secretKey.getEncoded());
System.out.println("AES随机密钥 -> : " + randomKeyStr);
// 请求信息AES随机秘钥加密
String encryptMessage = encrypt(reqJSONStr, randomKeyStr);
// secretKey 转换为16进制字符串
String randomKeyStrHex = parseByte2HexStr(secretKey.getEncoded());
System.out.println("AES十六进制hexPassKey -> : " + randomKeyStrHex);
// 16进制字符串进行RSA公钥加密
System.out.println("RSA公钥为 -> : " + RSA_PUB_KEY);
String encryptRandomKey = encryptRSA(randomKeyStrHex, RSA_PUB_KEY);
System.out.println("RSA加密signature为 -> : " + encryptRandomKey);
// 组装最终请求信息
JSONObject requestJSONFinal = new JSONObject();
requestJSONFinal.put("message", encryptMessage);
requestJSONFinal.put("signature", encryptRandomKey);
// foreign 11110 / dmz 11301
String serverUrl = "localhost:11110/lrcb/service/js/LOAN";
String serverUrl = "localhost:11301/dmz/lrcb/service/js/LOAN";
String rspJsonStr = HttpUtil.post(serverUrl, requestJSONFinal.toJSONString(), NewCoreProperties.REQ_TIME_OUT);
System.out.println(rspJsonStr);
}
/**
* 获取密钥
*
* @return
* @throws Exception
*/
private static SecretKey getRandomKey() throws Exception {
// 随机数
SecureRandom secureRandom = new SecureRandom(DMZ_LOCK_KEY.getBytes(StandardCharsets.UTF_8));
// 实例
KeyGenerator kg = KeyGenerator.getInstance("AES");
// AES
kg.init(KEY_SIZE, secureRandom);
// 生成密钥
SecretKey secretKey = kg.generateKey();
return secretKey;
}
/**
* 将byte数组转换成16进制String
* @param buf
* @return
*/
public static String parseByte2HexStr(byte[] buf) {
StringBuffer sb = new StringBuffer();
for (byte b : buf) {
String hex = Integer.toHexString(b & 0xFF);
if (hex.length() == 1) {
hex = '0' + hex;
}
sb.append(hex.toUpperCase());
}
return sb.toString();
}
/**
* 将16进制String转换为byte数组
* @param hexStr
* @return
*/
public static byte[] parseHexStr2Byte(String hexStr) {
if (hexStr.length() < 1)
return null;
byte[] result = new byte[hexStr.length() / 2];
for (int i = 0; i < hexStr.length() / 2; i++) {
int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16);
int low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2), 16);
result[i] = (byte) (high * 16 + low);
}
return result;
}
/**
* 加密
*
* @param content 内容
* @param randomPassKey 秘钥
* @return 加密后的数据
*/
public static String encrypt(String content, String randomPassKey) throws Exception {
// 新建Cipher 类
Cipher cipher = Cipher.getInstance(MODE);
// 初始化秘钥
SecretKeySpec sks = new SecretKeySpec(Base64.getDecoder().decode(randomPassKey), KEY_ALGORITHM);
// 初始化加密类
cipher.init(Cipher.ENCRYPT_MODE, sks);
// 进行加密
byte[] encrypt = cipher.doFinal(content.getBytes());
// 这一步非必须,是因为二进制数组不方便传输,所以加密的时候才进行base64编码
encrypt = Base64.getEncoder().encode(encrypt);
// 转成字符串返回
return new String(encrypt, StandardCharsets.UTF_8);
}
/**
* 解密数据
*
* @param content 内容
* @param randomPassKey 秘钥
* @return 数据
*/
public static String decrypt(String content, String randomPassKey) throws Exception{
// 替换base64里的换行,这一步也非必须,只是有些情况base64里会携带换行符导致解码失败
content = content.replaceAll("[\\n\\r]", "");
// base64 解码,跟上面的编码对称
byte[] data = Base64.getDecoder().decode(content.getBytes(StandardCharsets.UTF_8));
// 新建Cipher 类
Cipher cipher = Cipher.getInstance(MODE);
// 初始化秘钥
SecretKeySpec keySpec = new SecretKeySpec(Base64.getDecoder().decode(randomPassKey), KEY_ALGORITHM);
// 初始化类
cipher.init(Cipher.DECRYPT_MODE, keySpec);
// 解密
byte[] result = cipher.doFinal(data);
// 返回解密后的内容
return new String(result);
}
/**
* RSA初始化key pair
* @return KeyPair
*/
private static KeyPair generatorKey() throws NoSuchAlgorithmException {
// 生成RSA密钥对
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
SecureRandom secureRandom = new SecureRandom(DMZ_LOCK_KEY.getBytes(StandardCharsets.UTF_8));
keyGen.initialize(1024, secureRandom);
KeyPair pair = keyGen.generateKeyPair();
return pair;
}
/**
* RSA加密
* @param data 待加密数据
* @param publicKeyStr 公钥
* @return 加密后的数据
*/
private static String encryptRSA(String data, String publicKeyStr) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, UnsupportedEncodingException, BadPaddingException, IllegalBlockSizeException {
byte[] keyBytes = Base64.getDecoder().decode(publicKeyStr);
// 初始化公钥key
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(x509KeySpec);
// 使用公钥进行加密
Cipher encryptCipher = Cipher.getInstance(RSA_MODE);
encryptCipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptedBytes = encryptCipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encryptedBytes);
}
/**
* RSA解密
* @param data
* @param privateKeyStr
* @return
*/
private static String decryptRSA(String data, String privateKeyStr) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, UnsupportedEncodingException, BadPaddingException, IllegalBlockSizeException {
byte[] keyBytes = Base64.getDecoder().decode(privateKeyStr);
// 初始化私钥
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
// 使用私钥进行解密
Cipher decryptCipher = Cipher.getInstance(RSA_MODE);
decryptCipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decryptedBytes = decryptCipher.doFinal(Base64.getDecoder().decode(data));
return new String(decryptedBytes, "UTF-8");
}
}
参考文章 :https://blog.csdn.net/zrj00711/article/details/131443408