简单无注册中心本地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");
}
}
我们直接调用测试方法,进行观察,第一次请求以及响应信息如下:

第二次请求以及响应信息如下,本地做了报错,方便区分请求:

1424

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



