SCG 请求body解密后业务封装、验签|过滤白名单等、对响应结果的加密以及返回
SCG踩坑总结
版本选择
spring-cloud-starter-gateway 2.2.2.RELEASE
spring-cloud-dependencies Hoxton.SR3
spring-boot-dependencies 2.2.6.RELEASE
直接进入正题,查了很多大神提供的资料(踩坑过来,都是泪),总结出一套ok。
全局post请求解密+修改请求体
/**
* 请求体的封装处理
*/
@Component
@Slf4j
public class RequestBodyFilter implements GlobalFilter, Ordered {
private final List<HttpMessageReader<?>> messageReaders = HandlerStrategies.withDefaults().messageReaders();
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("处理请求体------------order为:" + this.getOrder());
ServerHttpRequest request = exchange.getRequest();
//对post的请求体处理
if (request.getMethod() == HttpMethod.POST) {
return operationExchange(exchange, chain);
}
return chain.filter(exchange);
}
private Mono<Void> operationExchange(ServerWebExchange exchange, GatewayFilterChain chain) {
MediaType mediaType = exchange.getRequest().getHeaders().getContentType();
// read & modify body
ServerRequest serverRequest = ServerRequest.create(exchange, messageReaders);
// ServerRequest serverRequest = new DefaultServerRequest(exchange);
Mono<String> modifiedBody = serverRequest.bodyToMono(String.class).flatMap(body -> {
if (MediaType.APPLICATION_JSON.isCompatibleWith(mediaType)) {
// 对原先的body进行操作,处理解密,添加参数等等
JSONObject jsonObject = JSON.parseObject(body);
jsonObject.put("demo", "zw");
//将处理后的数据保存到属性里,后面业务逻辑使用
exchange.getAttributes().put("newBody", jsonObject);
return Mono.just(jsonObject.toJSONString());
}
return Mono.empty();
});
BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
HttpHeaders headers = new HttpHeaders();
headers.putAll(exchange.getRequest().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(exchange.getRequest()) {
@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());
}));
}
@Override
public int getOrder() {
return -100;
}
}
业务处理,验签|白名单|等等
/**
* 验签
*/
@Component
public class TestSignCheckFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//RequestBodyFilter处理后的请求参数json,直接获取用于业务
Object postBody = exchange.getAttribute("newBody");
//todo 验签
if (true) {
//mock签名验证失败,直接返回错误。下面是个例子,自行封装吧
ServerHttpResponse response = exchange.getResponse();
JSONObject jsonObject = new JSONObject();
jsonObject.put("code", 100002);
jsonObject.put("msg", "签名有误!");
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
return response.writeWith(Mono.just(exchange.getResponse().bufferFactory().wrap(jsonObject.toJSONString().getBytes())));
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return -4;
}
微服务端的响应加密
参考的
https://blog.csdn.net/suchahaerkang/article/details/108204840
到这,肯定会了吧,处理响应体加密后返回
/**
微服务端的响应加密
*/
@Component
public class GlobalResponseFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("处理响应体------------");
Object postBody = exchange.getAttribute("postBody");
System.out.println("封装的请求体还在吗??" + postBody);
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 -> {
// probably should reuse buffers
byte[] content = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(content);
//释放掉内存
DataBufferUtils.release(dataBuffer);
String s = new String(content, StandardCharsets.UTF_8);
//修改response之后的字符串
System.out.println("获取到响应体:" + s);
String lastStr = s;
//修改response的值
if (StrUtil.containsIgnoreCase(s, "code") && JSONUtil.isJson(s)) {
GlobalResponse globalResponse = getGlobalResponse(originalResponse, s);
if (globalResponse != null) {
lastStr = JSONUtil.toJsonStr(globalResponse);
}
}
byte[] uppedContent = lastStr.getBytes();
originalResponse.getHeaders().setContentLength(uppedContent.length);
return bufferFactory.wrap(uppedContent);
}));
}
// if body is not a flux. never got there.
return super.writeWith(body);
}
};
// replace response with decorator
return chain.filter(exchange.mutate().response(decoratedResponse).build());
}
@Override
public int getOrder() {
//必须小于默认过滤器 NettyWriteResponseFilter(-1)
return -2;
}
/**
* @param originalResponse
* @param originalResponseStr
* @return
*/
private GlobalResponse getGlobalResponse(ServerHttpResponse originalResponse, String originalResponseStr) {
JSONObject jsonObject = JSONUtil.parseObj(originalResponseStr, false);
if (jsonObject.containsKey("code")) {
if (jsonObject.getInt("code") == 200 || jsonObject.getInt("code") == 0) {
GlobalResponse globalResponse = new GlobalResponse();
globalResponse.setCode(HttpStatus.OK.value());
globalResponse.setMsg("成功");
globalResponse.setSuccess(true);
if (jsonObject.containsKey("count")) {
Map<String, Object> map = new HashMap<>();
map.put("total", jsonObject.getInt("count"));
map.put("list", Optional.ofNullable(jsonObject.getObj("data")).orElse("[]"));
globalResponse.setData(map);
} else {
globalResponse.setData(Optional.ofNullable(jsonObject.getObj("data")).orElse("[]"));
}
return globalResponse;
} else {
GlobalResponse globalResponse = new GlobalResponse();
if (jsonObject.getInt("code") == 401) {
globalResponse.setCode(403);
globalResponse.setMsg("处方平台token失效");
globalResponse.setData("[]");
} else {
globalResponse.setCode(originalResponse.getStatusCode().value());
globalResponse.setMsg(Optional.ofNullable(jsonObject.getStr("msg")).orElse(""));
globalResponse.setData(Optional.ofNullable(jsonObject.getObj("data")).orElse("[]"));
}
return globalResponse;
}
}
return null;
}
/**
* @param
* @description: 返回给前端的统一格式
* @return:
* @author: sukang
* @date: 2020/8/24 15:36
*/
@Data
private class GlobalResponse implements Serializable {
private static final long serialVersionUID = 1L;
private int code;
private boolean success = false;
private String msg = "";
private Object data;
}
}
web微服务端全局异常处理
@ControllerAdvice
@Slf4j
public class GlobeExceptionHandler {
@ExceptionHandler
@ResponseBody
public JsonResult exceptionHandler(HttpServletRequest req, HttpServletResponse res, Exception e) {
//自己的业务异常处理,返回一个json
if (e instanceof RuntimeException) {
return new JsonResult(e) ;
}
//非业务抛错通用json
return JsonResult.commonError();
}
}
其他说明
- 网关的路由配置,策略,动态路由。网上资料很多,我没遇到问题,一步就成功了,就不赘述了
- 网关实在不适合作全局异常处理,为了方便统一处理响应体,微服务端要作全局异常封装处理,返回一个包含错误信息的json响应体,上面已经贴出来
- 网关结合redis,防范措施等,可以自行百度查,自定义一个网关的filter即可
- filter按照order顺序执行,过滤器业务处理中,拦截请求并返回相应消息(比如签名错误),就不会再走后面的filter
- 自定义过滤器权重值的协定,不能乱写哦,自定义的全局过滤器必须和官方默认的兼容一起用。整明白的可以自由发挥,还不是很明白的可以参考我上面例子写的权重值
**由于不熟悉weblux,以前都是用Zuul作为网关,不熟悉gateway2,一路踩了不少坑。 写这篇博客,让后面的同行者少走弯路,加油,共勉!
springcloud版本的选择很重要