使用postman的Pre-request Script和zuul网关实现请求体RSA加密后转发
使用zuul网关实现请求体RSA加密后转发
前言
项目的服务端和app端的通信,是先在app端使用rsa的公钥加密后发送到服务端后使用rsa的私钥解密,这样在用postman调试接口的时候,就比较麻烦,需要对请求报文进行rsa加密后发送,不便于调试。刚开始想到是使用postman的Pre-request Script来先对请求体进行加密,也进行实现并且成功了,但是这种方式对请求报文可读性产生很大影响。于是就想到使用网关,请求到网关服务,在网关中对请求报文加密,再把加密后的报文转发到app服务端,这样在postman中就可以直接使用原始数据进行请求,可读性和易用性都得到了满足!!
方式1:postman的Pre-request Script对请求数据的RSA加密
-
环境变量-globals

-
环境变量-environment

-
Pre-request Script的代码如下
// 从环境变量中获取token参数
var tokenId = pm.environment.get("tokenId") ;
var t_token = pm.environment.get("t_token") ;
// 构建要发送的请求数据的json,并放到环境变量param中
pm.environment.set("param",'{"tokenId":"'+tokenId+'","t_token":"'+t_token+'"}');
// 从环境变量中加载rsa加密脚本,该脚本的下载地址为:
// https://raw.githubusercontent.com/loveiset/RSAForPostman/master/forge.js
var forgeJS = pm.globals.get("forgeJS");
eval(forgeJS);
// 从环境变量中获取公钥
var rsa_public_key = '-----BEGIN PUBLIC KEY-----\n' +
pm.globals.get("rsa_public_key") + '\n' +
'-----END PUBLIC KEY-----';
// 开始构造公钥并对请求体param进行加密
var publicKey = forge.pki.publicKeyFromPem(rsa_public_key);
var param = pm.environment.get("param") ;
var buffer = forge.util.createBuffer(param, 'utf8');
var bytes = buffer.getBytes();
var encryptedText = forge.util.encode64(publicKey.encrypt(bytes, 'RSAES-PKCS1-V1_5', {
md: forge.md.sha256.create(),
mgf1: {
md: forge.md.sha1.create()
}
}));
// 将加密完成的数据放置到环境变量
pm.environment.set("param",encodeURIComponent(encryptedText)) ;
console.log(encodeURIComponent(encryptedText));
- 请求体body中的数据从环境变量中取值
param={{param}}

- 问题
不知道为什么,当加密的请求体过长的时候,就会报错说无法加密过长的数据,所以就想到使用网关加密转发的方法。
There was an error in evaluating the Pre-request Script:Error: Message is too long for PKCS#1 v1.5 padding.
方式2: zuul网关对请求体加密后转发
- 项目结构

- pom.xml
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.9.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.zhixiang</groupId>
<artifactId>chat-gateway</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>gateway</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR10</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
- application.properties
# 本服务端口
server.port=80
# zuul网关路由路径
zuul.routes.chat_app.path=/chat_app/**
# zuul网关转发地址
zuul.routes.chat_app.url=http://localhost:8090/chat_app/
- GatewayApplication
package com.zhixiang;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@SpringBootApplication
// 开启zuul网关代理
@EnableZuulProxy
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
- 过滤器RequestBodyRSAFilter
package com.zhixiang.filter;
import java.io.IOException;
import org.springframework.stereotype.Component;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.zhixiang.base.RequestBodyRSAWrapper;
/**
* 对请求体的JSON格式进行RSA加密为param=***的格式
*
* @author Administrator
*
*/
@Component
public class RequestBodyRSAFilter extends ZuulFilter {
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
// 获取请求上下文
RequestContext ctx = RequestContext.getCurrentContext();
try {
// RequestBodyRSAWrapper重新包装请求体
ctx.setRequest(new RequestBodyRSAWrapper(ctx.getRequest()));
} catch (IOException e) {
throw new ZuulException(e, 500, e.getMessage());
}
return null;
}
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 0;
}
}
- RequestBodyRSAWrapper
package com.zhixiang.base;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.springframework.util.StreamUtils;
import com.netflix.zuul.http.ServletInputStreamWrapper;
import com.zhixiang.util.RSACoderUtil;
public class RequestBodyRSAWrapper extends HttpServletRequestWrapper {
private byte[] body;
public RequestBodyRSAWrapper(HttpServletRequest request) throws IOException {
super(request);
// 原来json格式的请求体
String jsonBody = getRequestJsonBody(request);
System.out.println("加密前body:" + jsonBody);
// 加密后的请求体
String newBody = RSACoderUtil.publicEncrypt(jsonBody);
// 再进行URL编码
newBody = URLEncoder.encode(newBody, "UTF-8");
System.out.println("加密后body:" + new String(newBody));
body = ("param=" + newBody).getBytes();
}
/**
* 获取请求体内容
*
* @param request
* @return
*/
private static String getRequestJsonBody(HttpServletRequest request) {
try {
return StreamUtils.copyToString(request.getInputStream(), Charset.forName("UTF-8"));
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
/**
* 在使用@RequestBody注解的时候,其实框架是调用了getInputStream()方法,所以我们要重写这个方法
*
* @return
* @throws IOException
*/
@Override
public ServletInputStream getInputStream() throws IOException {
return new ServletInputStreamWrapper(body);
}
@Override
public int getContentLength() {
return body.length;
}
@Override
public long getContentLengthLong() {
return body.length;
}
}
- RSACoderUtil
这个使用自己的加密方法,不贴代码。
总结
用网关代理加密后转发比较方便使用,网关服务也是独立,只用于开发调试使用,对原来项目没有任何影响。