1、材料
jdk 1.8 + springboot 2.7.13 + springCloud 2021.0.8 + fastJson2.0 + Hutool
pom:
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway</artifactId>
<version>3.1.8</version>
2、编写filter:
import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
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.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.PathContainer;
import org.springframework.http.server.reactive.ServerHttpRequest;
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 org.springframework.web.util.pattern.PathPatternParser;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import javax.annotation.Resource;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* 定制密码过滤器
* <p>
* 1、请求解密
* 2、响应加密
* <p>
* 目前优先做 2
*/
@Component
// 触发配置
// @ConditionalOnProperty(value = "security.responseEncrypt", havingValue = "true")
@Slf4j
public class ResponseEncryptFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 获取请求体
ServerHttpRequest request = exchange.getRequest();
MediaType mediaType = exchange.getRequest().getHeaders().getContentType();
// 判定是否为json请求
if (mediaType != null
&& mediaType.includes(MediaType.APPLICATION_JSON) ) {
// 根据具体业务内容,修改响应体
return modifyResponseBody(exchange, chain);
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
// 参看这个类 org.springframework.core.Ordered; 值越小,优先级越高
return -200;
}
/**
* 修改响应体
*
* @param exchange
* @param chain
* @return
*/
private Mono<Void> modifyResponseBody(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpResponse originalResponse = exchange.getResponse();
originalResponse.getHeaders().setContentType(MediaType.APPLICATION_JSON);
DataBufferFactory bufferFactory = originalResponse.bufferFactory();
ServerHttpResponseDecorator response = buildResponse(originalResponse, bufferFactory);
return chain.filter(exchange.mutate().response(response).build());
}
private ServerHttpResponseDecorator buildResponse(ServerHttpResponse originalResponse, DataBufferFactory bufferFactory) {
return new ServerHttpResponseDecorator(originalResponse) {
@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(dataBuffers -> {
DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
DataBuffer join = dataBufferFactory.join(dataBuffers);
byte[] content = new byte[join.readableByteCount()];
join.read(content);
DataBufferUtils.release(join);
// 流转为字符串
String responseData = new String(content, StandardCharsets.UTF_8);
//加密数据
responseData = encryptResponse(responseData);
byte[] uppedContent = responseData.getBytes(StandardCharsets.UTF_8);
originalResponse.getHeaders().setContentLength(uppedContent.length);
return bufferFactory.wrap(uppedContent);
}));
} else {
log.error("获取响应体数据 :" + getStatusCode());
}
return super.writeWith(body);
}
@Override
public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
return writeWith(Flux.from(body).flatMapSequential(p -> p));
}
};
}
private String encryptResponse(String originalResponseBody) {
if (!secureProperties.enableEncryptResponseParam()) {
log.info("响应结果加密,跳过");
return originalResponseBody;
}
Map map = JSON.parseObject(originalResponseBody);
//处理返回的数据
Object data = map.get("data");
if (data != null) {
// 加密
map.put("data",xxxx.encrypt(data.toString()));
}
return JSON.toJSONString(map);
}
}
3、加密算法
完整代码仓库:springcloud-gateway-feng-demo: 使用网关做请求和响应的加解密
SpringCloud Gateway实现请求解密和响应加密_springcloudgateway filter进行请求跟返回参数加解密-CSDN博客
4、不生效问题
一般情况是,Filter Order 的问题。 因为你这个Filter执行的优先级比较低导致被截断
请看这里,比较详细
【Spring Cloud GateWay】ServerHttpResponseDecorator不生效-CSDN博客
5、URL匹配
比如我们只想对某些uri进行处理,处理方式如下:
// 调用
// 获取请求体
ServerHttpRequest request = exchange.getRequest();
isNeedEncrypt(request.getURI())
private boolean isNeedEncrypt(URI uri) {
List<String> encryptUriList = "你配置的待加密的uri列表";
log.info("encryptUriList:{}",encryptUriList);
if(CollUtil.isEmpty(encryptUriList)){
return false;
}
String path = uri.getPath();
for (String pathPattern : encryptUriList) {
if (isMatch(path, pathPattern)) {
return true;
}
}
return false;
}
private boolean isMatch(String path, String pathPattern) {
// 这里需要根据whiteListed的格式来判断和匹配
// 对于简单IP可以直接比较,对于范围和CIDR需要额外逻辑
// ...
return PathPatternParser.defaultInstance.parse(pathPattern).matches(PathContainer.parsePath(path));
}
6、小结
gateway处理加解密,比在其他地方处理更合理,当然,也可以通过RequestBodyAdvice 和 ResponseBodyAdvice进行加解密。还有AOP的方式 等等