使用postman的Pre-request Script和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
    这个使用自己的加密方法,不贴代码。

总结

用网关代理加密后转发比较方便使用,网关服务也是独立,只用于开发调试使用,对原来项目没有任何影响。