简单例子gateway网关加密解密子服务分发请求

简单例子gateway网关加密解密子服务分发请求

1.需求

编写一个简单的dmz区网关,接收外围加密的请求,并且接收参数后进行请求信息解密,建立新的请求信息,转发到自己业务对应的微服务,再根据请求业务id,分发到自己业务处理的详细serviceImpl

2.具体的执行流程

  1. 使用AES生成随机加密秘钥转换为base64字符串
  2. 生成网关验签的sign信息(md5[系统标识|业务时间|约定md5随机key])
  3. 使用AES随机秘钥对请求参数Body进行AES对称加密
  4. 将AES随机秘钥byte[]转换为16进制字符串
  5. 将16进制的秘钥串进行RSA非对称加密[提前自己生成需要的私钥和公钥]既signature
  6. 网关接收到信息,进行数据解密,将解密内容按照约定格式发送到微服务集群
  7. 微服务接收到请求信息,按照约定进行hibernate validate参数校验
  8. 校验通过进行业务转发,到各自业务serviceImpl进行执行业务

3. 详情实现

3.1 dmz网关编写

dmz网关编写主要就是接收信息,将数据进行解析,并且需要解析后进行验签操作,在处理过程中,无果报错,则要统一返回对应的错误处理信息,并且格式要提前确定好,然后就是转发到具体对的业务服务了,之后接收服务返回的信息再进行加密操作,将数据返回到调用方即可。此过程需要三个过滤器分别做如下操作,缓存解密请求body中信息数据,dmz网关全局过滤器,进行请求参数解密转发,返回信息加密,具体过程如下。

3.1.1 缓存body请求信息DmzCacheFilter
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;
    }
}

3.1.2 dmz网关全局过滤器,进行请求参数解密转发
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;
    }
}
3.1.3 响应filter,没时间写意思意思,看看得了
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;
    }

}
3.1.4 网关转发配置

此处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();
    }
}
3.1.5 过程中的工具类

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));
    }
}

3.2 自服务接收数据编写

自服务接收到的数据已经是解密之后的数据,直接对数据接收内容进行业务转发即可,代码如下

3.2.1 自服务接口入口

实现业务的转发,以及使用简单反射,实例化参数信息,方便后续进行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);
    }

}

3.2.2 策略类
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);

}

3.2.3 具体业务执行类

此处对应的@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);
    }
}

3.2.4 操作流水记录类

通过流水号记录具体的请求响应信息,方便出问题的时候进行查询

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);
            }
        }
    }

}

3.2.5 请求参数的实体类

这部分实体就是简单的测试对象类,里面有对应的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;

}

3.3 网关以及子模块微服务通用类

这部分主要就是响应的结构类型,编写了两个类进行实现具体如下

3.3.1 DmzResult
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);
    }
}

3.3.2 DmzResultEnum
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;
    }

}

4. 最后测试类

注意,下面的对应的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

  • 12
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值