SpringCloudGateway请求/响应拦截,加/解密
前言:目前就是对请求的request body ,url param 在网关进行拦截、做解密操作,对 response body 做加密操作这边初期就打算先用非对称加密对请求响应进行处理,后续如果项目开展顺利,再改成HTTPS通信(节约钱、先看项目起得来不了)。翻阅了一些博文,抄袭了一下,自己稍作修改。下面直接上代码了,自己做个记录吧请求体(request body)拦截import com.ru
·
这篇文章弃了,想看 请求/响应修改的同学,请点击这里看我最新写的文章
前言:
目前就是对请求的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());
更多推荐
已为社区贡献2条内容
所有评论(0)