这篇文章弃了,想看 请求/响应修改的同学,请点击这里看我最新写的文章

前言:

目前就是对请求的request body ,url param 在网关进行拦截、做解密操作,对 response body 做加密操作
这边初期就打算先用非对称加密对请求响应进行处理,后续如果项目开展顺利,再改成HTTPS通信(节约钱、先看项目起得来不了)。翻阅了一些博文,抄袭了一下,自己稍作修改。下面直接上代码了,自己做个记录吧

参考/抄袭地址:https://www.cnblogs.com/CHWLearningNotes/p/11277293.html

请求体(request body)拦截

import org.springframework.util.MultiValueMap;

/**
 * 网关上下文
 */
public class GatewayContext {

	public static final String CACHE_GATEWAY_CONTEXT = "cacheGatewayContext";

	/**
	 * cache json body
	 */
	private String cacheBody;
	/**
	 * cache formdata
	 */
	private MultiValueMap<String, String> formData;
	/**
	 * cache reqeust path
	 */
	private String path;


	public String getCacheBody() {
		return cacheBody;
	}

	public void setCacheBody(String cacheBody) {
		this.cacheBody = cacheBody;
	}

	public MultiValueMap<String, String> getFormData() {
		return formData;
	}

	public void setFormData(MultiValueMap<String, String> formData) {
		this.formData = formData;
	}

	public String getPath() {
		return path;
	}

	public void setPath(String path) {
		this.path = path;
	}
}
import io.netty.buffer.ByteBufAllocator;
import org.apache.commons.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.cloud.gateway.support.DefaultServerRequest;
import org.springframework.core.Ordered;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;

/**
 * 对所有的request请求的body进行解密
 * @date 2020/12/4 17:34
 * @author wei.heng
 */
@Component
public class GlobalRequestBodyDecodeFilter implements GlobalFilter, Ordered {

	@Value("${gateCustom.privateKey}")
	private String privateKeyStr;

	@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		ServerHttpRequest request = exchange.getRequest();

		// TODO 如果请求的后台管理系统,则直接放行,不做加解密处理

		if (request.getMethod() != HttpMethod.POST && request.getMethod() != HttpMethod.PUT && request.getMethod() != HttpMethod.PATCH) {
			// 如果不是post(新增)、put(全量修改)、patch(部分字段修改)操作,则直接放行
			return chain.filter(exchange);
		}
		// 对增/改操作,实现入参解密操作
		return operationExchange(exchange, chain);
	}

	@Override
	public int getOrder() {
		return Integer.MIN_VALUE;
	}

	private Mono<Void> operationExchange(ServerWebExchange exchange, GatewayFilterChain chain) {

		// 缓存请求路径
		ServerHttpRequest request = exchange.getRequest();
		String path = request.getPath().pathWithinApplication().value();
		GatewayContext gatewayContext = new GatewayContext();
		gatewayContext.setPath(path);
		// 保存网关上下文到exchange中
		exchange.getAttributes().put(GatewayContext.CACHE_GATEWAY_CONTEXT, gatewayContext);

		HttpHeaders headers = request.getHeaders();
		MediaType contentType = headers.getContentType();

		Mono<Void> voidMono = null;
		if (MediaType.APPLICATION_JSON.equals(contentType) || MediaType.APPLICATION_JSON_UTF8.equals(contentType)) {
			// 对applicationjson类型数据做解密操作
			voidMono = readBody(exchange, chain);
		}
		if (MediaType.APPLICATION_FORM_URLENCODED.equals(contentType)
			|| (contentType != null && contentType.toString().startsWith(MediaType.MULTIPART_FORM_DATA_VALUE)) ) {
			// form data
			voidMono = readFormData(exchange, chain, gatewayContext);
		}
		if(voidMono == null){
			voidMono = chain.filter(exchange);
		}
		return voidMono;
	}

	private Mono<Void> readFormData(
		ServerWebExchange exchange,
		GatewayFilterChain chain,
		GatewayContext gatewayContext) {
		final ServerHttpRequest request = exchange.getRequest();
		HttpHeaders headers = request.getHeaders();

		return exchange.getFormData()
			.doOnNext(multiValueMap -> {
				gatewayContext.setFormData(multiValueMap);
			})
			.then(Mono.defer(() -> {
				Charset charset = headers.getContentType().getCharset();
				charset = charset == null ? StandardCharsets.UTF_8 : charset;
				String charsetName = charset.name();
				MultiValueMap<String, String> formData =
					gatewayContext.getFormData();
				/** formData is empty just return */
				if (null == formData || formData.isEmpty()) {
					return chain.filter(exchange);
				}
				StringBuilder formDataBodyBuilder = new StringBuilder();
				String entryKey;
				List<String> entryValue;
				try {
					/* repackage form data */
					for (Map.Entry<String, List<String>> entry : formData
						.entrySet()) {
						entryKey = entry.getKey();
						entryValue = entry.getValue();
						if (entryValue.size() > 1) {
							for (String value : entryValue) {
								formDataBodyBuilder.append(entryKey).append("=")
									.append(
										URLEncoder.encode(value, charsetName))
									.append("&");
							}
						} else {
							formDataBodyBuilder
								.append(entryKey).append("=").append(URLEncoder
								.encode(entryValue.get(0), charsetName))
								.append("&");
						}
					}
				} catch (UnsupportedEncodingException e) {
					// ignore URLEncode Exception
				}
				/* substring with the last char '&' */
				String formDataBodyString = "";
				if (formDataBodyBuilder.length() > 0) {
					formDataBodyString = formDataBodyBuilder.substring(0,
						formDataBodyBuilder.length() - 1);
				}

				// date data bytes
				byte[] bodyBytes = formDataBodyString.getBytes(charset);
				int contentLength = bodyBytes.length;
				ServerHttpRequestDecorator decorator =
					new ServerHttpRequestDecorator(
						request) {

						@Override
						public HttpHeaders getHeaders() {
							HttpHeaders httpHeaders = new HttpHeaders();
							httpHeaders.putAll(super.getHeaders());
							if (contentLength > 0) {
								httpHeaders.setContentLength(contentLength);
							} else {
								httpHeaders.set(HttpHeaders.TRANSFER_ENCODING,
									"chunked");
							}
							return httpHeaders;
						}

						@Override
						public Flux<DataBuffer> getBody() {
							return DataBufferUtils
								.read(new ByteArrayResource(bodyBytes),
									new NettyDataBufferFactory(
										ByteBufAllocator.DEFAULT),
									contentLength);
						}
					};
				ServerWebExchange mutateExchange =
					exchange.mutate().request(decorator).build();
                /*   log.info("[GatewayContext]Rewrite Form Data :{}",
                       formDataBodyString);*/

				return chain.filter(mutateExchange);
			}));
	}

	private Mono<Void> readBody(ServerWebExchange exchange, GatewayFilterChain chain) {
		final ServerHttpRequest request = exchange.getRequest();
		MediaType mediaType = request.getHeaders().getContentType();
		// 修改响应体
		ServerRequest serverRequest = new DefaultServerRequest(exchange);
		Mono<String> modifiedBody = serverRequest.bodyToMono(String.class)
			.flatMap(body -> {
//				if (MediaType.APPLICATION_JSON.isCompatibleWith(mediaType)) {
//					// 拿到公钥加密后的数据
//					return Mono.just(decodeBody(body));
//				}
//				return Mono.empty();
				// TODO 加解密暂未联调,先屏蔽掉
				return Mono.just(body);
			});
		BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
		HttpHeaders headers = new HttpHeaders();
		headers.putAll(request.getHeaders());
		headers.remove(HttpHeaders.CONTENT_LENGTH);
		CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
		return bodyInserter.insert(outputMessage, new BodyInserterContext())
			.then(Mono.defer(() -> {
				ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(
					request) {
					@Override
					public HttpHeaders getHeaders() {
						long contentLength = headers.getContentLength();
						HttpHeaders httpHeaders = new HttpHeaders();
						httpHeaders.putAll(super.getHeaders());
						if (contentLength > 0) {
							httpHeaders.setContentLength(contentLength);
						} else {
							httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
						}
						return httpHeaders;
					}

					@Override
					public Flux<DataBuffer> getBody() {
						return outputMessage.getBody();
					}
				};
				return chain.filter(exchange.mutate().request(decorator).build());
			}));
	}

	private String decodeBody(String body){
		// 服务端私钥
		byte[] privateKey = Base64.decodeBase64(privateKeyStr);
		// 拿到公钥加密后的数据
		byte[] decodedBytes = RSACoderUtil.getDecodedBytes(privateKey, body);
		return new String(decodedBytes, StandardCharsets.UTF_8);
	}

}

URL参数拦截


import com.ruoyi.gateway.util.RSACoderUtil;
import org.apache.commons.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.nio.charset.StandardCharsets;

/**
 * 对所有的request请求的url参数进行解密
 * @date 2020/12/9 13:37
 * @author wei.heng
 */
@Component
public class GlobalRequestUrlDecodeFilter implements GlobalFilter, Ordered {

	@Value("${gateCustom.privateKey}")
	private String privateKeyStr;

	@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		ServerHttpRequest request = exchange.getRequest();
		if (request.getMethod() != HttpMethod.POST && request.getMethod() != HttpMethod.PUT && request.getMethod() != HttpMethod.PATCH) {
			// 如果不是post(新增)、put(全量修改)、patch(部分字段修改)操作,则直接放行
			return chain.filter(exchange);
		}

		URI uri = request.getURI();
		String query = uri.getQuery();
		if(StringUtils.isEmpty(query)){
			return chain.filter(exchange);
		}

		byte[] privateKey = Base64.decodeBase64(privateKeyStr);
		// 拿到解密后的参数
		byte[] queryDecodedBytes = RSACoderUtil.getDecodedBytes(privateKey, query);

		// 将解密后的URL参数替换到URL后面
		URI newUri = UriComponentsBuilder.fromUri(uri)
			.replaceQuery(new String(queryDecodedBytes, StandardCharsets.UTF_8))
			.build(true)
			.toUri();
		ServerHttpRequest newRequest = exchange.getRequest().mutate().uri(newUri).build();
		return chain.filter(exchange.mutate().request(newRequest).build());
	}

	@Override
	public int getOrder() {
		return Integer.MIN_VALUE + 1;
	}

}

响应体(response body)拦截


import com.ruoyi.gateway.util.RSACoderUtil;
import org.apache.commons.codec.binary.Base64;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
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.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;

/**
 * 对所有的response请求的body进行加密
 * @date 2020/12/6 10:26
 * @author wei.heng
 */
//@Component
public class GlobalResponseBodyEncodeFilter implements GlobalFilter, Ordered {

	private Logger log = LoggerFactory.getLogger(GlobalResponseBodyEncodeFilter.class);

	@Value("${gateCustom.privateKey}")
	private String privateKeyStr;

	@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		ServerHttpRequest request = exchange.getRequest();
		if (request.getMethod() != HttpMethod.POST && request.getMethod() != HttpMethod.PUT && request.getMethod() != HttpMethod.PATCH) {
			// 如果不是post(新增)、put(全量修改)、patch(部分字段修改)操作,则直接放行
			return chain.filter(exchange);
		}

		ServerHttpResponse originalResponse = exchange.getResponse();
		DataBufferFactory bufferFactory = originalResponse.bufferFactory();
		ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
			@Override
			public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
				if (body instanceof Flux) {
					Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
					return super.writeWith(fluxBody.map(dataBuffer -> {
						byte[] content = new byte[dataBuffer.readableByteCount()];
						dataBuffer.read(content);
						//释放掉内存
						DataBufferUtils.release(dataBuffer);
						// 对响应体进行加密
						byte[] privateKey = Base64.decodeBase64(privateKeyStr);
						// 私钥加密返回的响应体
						byte[] encodedBytes;
						try {
							encodedBytes = RSACoderUtil.encryptByPrivateKey(content, privateKey);
						} catch (Exception e) {
							e.printStackTrace();
							log.error("加密异常:" + e.getMessage());
							throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "响应体加密异常");
						}
						String s = Base64.encodeBase64String(encodedBytes);
						return bufferFactory.wrap(s.getBytes(StandardCharsets.UTF_8));
					}));
				}
				return super.writeWith(body);
			}
		};
		return chain.filter(exchange.mutate().response(decoratedResponse).build());
	}

	@Override
	public int getOrder() {
		return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1;
	}

}

RSA 加解密工具

这个在博客里的Java工具中有,这里就不再粘贴了

updateLog

2021-03-11 当提交的数据量过大时出现以下异常:
springcloud gateway Exceeded limit on max bytes to buffer : 262144
添加类:

@Configuration
public class WebFluxConfiguration implements WebFluxConfigurer {
    @Override
    public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
        configurer.defaultCodecs().maxInMemorySize(16 * 1024 * 1024);
    }
}

修改代码,GlobalRequestBodyDecodeFilter
1、添加codecConfigurer的注入

@Autowired
ServerCodecConfigurer codecConfigurer;

2、修改 GlobalRequestBodyDecodeFilter.readBody ,替换掉 ServerRequest 的实例获取方式

//		ServerRequest serverRequest = new DefaultServerRequest(exchange);
		ServerRequest serverRequest = ServerRequest.create(exchange, codecConfigurer.getReaders());
Logo

快速构建 Web 应用程序

更多推荐