简单无注册中心本地ip轮训网关实现

简单无注册中心本地ip轮训网关实现

1. 需求

我们公司想新增一个DMZ网关,放置于DMZ区。作为外网的访问入口,网关进行简单的数据解密,转发,然后接收请求服务端数据,将响应数据进行加密,最终返回到客户端,并且我们DMZ区不能随意添加网络策略,所以我们的网关服务只能开通与调用端的策略,不能注册到我们的注册中心了,只能本地配置几个地址进行轮询访问或者配置单个地址,使用nginx进行反向代理,我们暂时使用本地配置多个地址进行轮训访问。

2. 具体实现步骤

我们具体的实现步骤可以进行简单的总结 ,具体执行步骤如下

  • 设置pom信息,主要是gateway以及ribbon部分
  • 设置启动类
  • 编写gateway服务配置类
  • 编写解密过滤器,响应加密过滤器
  • 编写application.yml 配置文件

2.1 编写pom信息

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>bank-credit-sy</artifactId>
        <groupId>cn.git</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>credit-dmz-gateway</artifactId>
    <description>服务网关</description>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!-- 集成oracle、redis和日志 -->
        <dependency>
            <groupId>cn.git</groupId>
            <artifactId>business-common</artifactId>
            <version>1.0-SNAPSHOT</version>
            <exclusions>
                <!-- 不要加入web依赖,否则初始化会报错。 -->
                <exclusion>
                    <artifactId>spring-boot-starter-web</artifactId>
                    <groupId>org.springframework.boot</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>cn.git</artifactId>
                    <groupId>credit-swagger-starter</groupId>
                </exclusion>
                <exclusion>
                    <groupId>cn.git</groupId>
                    <artifactId>credit-oracle-starter</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>cn.git</groupId>
                    <artifactId>credit-kafka-starter</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>cn.git</groupId>
                    <artifactId>credit-redis-starter</artifactId>
                </exclusion>
                <exclusion>
                    <artifactId>credit-swagger-starter</artifactId>
                    <groupId>cn.git</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>credit-discovery-starter</artifactId>
                    <groupId>cn.git</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>cn.git</groupId>
            <artifactId>credit-log-starter</artifactId>
            <version>1.0-SNAPSHOT</version>
            <exclusions>
                <!-- 不要加入web依赖,否则初始化会报错。 -->
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-web</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- 动态路由 健康检查 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
            <!-- 使用log4j 排除冲突包 -->
            <exclusions>
                <exclusion>
                    <artifactId>logback-classic</artifactId>
                    <groupId>ch.qos.logback</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>logback-core</artifactId>
                    <groupId>ch.qos.logback</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>log4j-to-slf4j</artifactId>
                    <groupId>org.apache.logging.log4j</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>cn.git</groupId>
            <artifactId>foreign-api</artifactId>
            <version>1.0-SNAPSHOT</version>
            <exclusions>
                <exclusion>
                    <groupId>cn.git</groupId>
                    <artifactId>business-common</artifactId>
                </exclusion>
                <exclusion>
                    <artifactId>credit-swagger-starter</artifactId>
                    <groupId>cn.git</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>credit-discovery-starter</artifactId>
                    <groupId>cn.git</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>credit-kafka-starter</artifactId>
                    <groupId>cn.git</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>credit-redis-starter</artifactId>
                    <groupId>cn.git</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- 集成监听 -->
        <dependency>
            <groupId>cn.git</groupId>
            <artifactId>credit-monitor-starter</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.apache.skywalking</groupId>
            <artifactId>apm-toolkit-log4j-2.x</artifactId>
            <version>8.6.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <!-- compiler -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.mapstruct</groupId>
                            <artifactId>mapstruct-processor</artifactId>
                            <version>${mapstruct.version}</version>
                        </path>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>${lombok.version}</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
            <!-- package -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

2.2 设置启动类

package cn.git.dmz;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * dmz区信贷外部网关启动类
 * @program: bank-credit-sy
 * @author: lixuchun
 * @create: 2021-09-16
 */
@SpringBootApplication(scanBasePackages = {"cn.git.dmz", "cn.git.elk"})
public class DmzGatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(DmzGatewayApplication.class, args);
    }
}

2.3 编写gateway服务配置类

package cn.git.dmz.config;

import cn.git.dmz.filter.RespEncryptFilter;
import org.springframework.beans.factory.annotation.Autowired;
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 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)
                                .stripPrefix(STRIP_PREFIX)
                        ).uri("lb://synsh-servers")
                ).build();
    }

}

2.4 编写解密验签过滤器

  • DmzCacheFilter
    类的主要作用是在 Spring Cloud Gateway 中缓存 POST 请求的请求体,并确保请求体在后续处理中可以多次读取,此处则是供全局过滤器解密请求体的时候使用。这对于需要多次处理请求体的场景非常有用,例如日志记录、安全检查等
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.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 (!"POST".equalsIgnoreCase(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;
    }
}

  • DmzGlobalFilter 全局过滤器,进行请求数据的解密
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.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 (!"POST".equalsIgnoreCase(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;
    }

}

2.5 响应信息加密

正常的响应信息应该也与请求信息一样进行加密,然后客户端再进行解密,此处则进行简化了,直接使用AES进行对称加密

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

}

2.6 application.yml配置

spring:
  application:
    name: @project.artifactId@
  main:
    #当遇到同样名字的时候,是否允许覆盖注册。
    allow-bean-definition-overriding: true

server:
  port: 11301

# 自定义轮训服务地址
synsh-servers:
  ribbon:
    listOfServers: http://3.2.36.116:11110,http://3.1.19.14:11110
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule

3. 简单测试

我们使用本地post请求模拟外部请求, 设置请求类型都为post类型,具体的实现步骤如下:

package cn.git.foreign;

import cn.git.api.util.NewCoreProperties;
import cn.git.common.constant.CommonConstant;
import cn.git.foreign.dmz.dto.base.LRCBRequestDTO;
import cn.git.foreign.dmz.dto.child.XDTSW000ChildDTO;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.AES;
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 ForeignTest {

    /**
     * 外部系统请求网关加密密匙
     */
    public static final String DMZ_LOCK_KEY = "DMZ:XdXt:loan==,";

    /**
     * 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,正常需要与运营人员沟通获取 18位自定义秘钥值
     */
    public static final String MD5_KEY = "M66!777X%#888.9999CW";

    /**
     * RSA公钥
     * 需要自己生成
     */
    private static final String RSA_PUB_KEY = "";

    /**
     * RSA私钥
     * 需要自己生成
     */
    private static final String RSA_PRI_KEY = "";

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

        // 请求参数
        JSONObject requestJSON = new JSONObject();
        requestJSON.put("name", "jack");
        requestJSON.put("age", 18);
        requestJSON.put("tall", 185);
        requestJSON.put("date", "2023-12-12");

        // 附属信息
        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);

        // 响应信息解密
        AES AES = new AES(Mode.ECB, Padding.PKCS5Padding, CommonConstant.DMZ_LOCK_KEY.getBytes(StandardCharsets.UTF_8));
        JSONObject jsonObject = JSONObject.parseObject(rspJsonStr );
        if (ObjectUtil.isNotEmpty(jsonObject.getString("result"))) {
            String decryptStr = AES.decryptStr(jsonObject.getString("result"));
            System.out.println("响应解密信息为:" + decryptStr);
        }
    }

    /**
     * 获取密钥
     *
     * @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");
    }

}

我们直接调用测试方法,进行观察,第一次请求以及响应信息如下:
在这里插入图片描述
第二次请求以及响应信息如下,本地做了报错,方便区分请求:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值