gateway获取请求体和响应体
GateWay 获取响应结果,处理大数据分段传输问题
package com.yymt.project.filter;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.yymt.project.config.SwaggerProvider;
import com.yymt.project.feign.AuthFeignClient;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
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.http.HttpHeaders;
import org.springframework.http.MediaType;
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 reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @calssName AppCacheRequestBodyFilter
* @Description 将 request 参数 中的内容 copy 一份,记录到 exchange 的一个自定义属性中
*/
@Slf4j
@Component
public class GlobalCacheRequestFilter implements GlobalFilter, Ordered {
public final static String CACHED_REQUEST_PARAM_KEY = "CACHED_REQUEST_PARAM_KEY";
public final static String SYS_LOG_ID = "sysLogId";
public final static Snowflake snowflake = IdUtil.getSnowflake(1, 1);
private int order;
@Resource
AuthFeignClient authFeignClient;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// String path = exchange.getRequest().getPath().toString();
ServerHttpRequest request = exchange.getRequest();
// String method = request.getMethodValue();
HttpHeaders headers = request.getHeaders();
String sysLogId = String.valueOf(snowflake.nextId());
// 将 request body 中的内容 copy 一份,记录到 exchange 的一个自定义属性中
Object cachedRequestBodyObject = exchange.getAttributeOrDefault(CACHED_REQUEST_PARAM_KEY,
null);
// 如果已经缓存过,或者白名单略过
if (cachedRequestBodyObject != null) {
return chain.filter(exchange);
}
// 如果没有缓存过,获取字节数组存入 exchange 的自定义属性中
String contentType = exchange.getRequest().getHeaders().getFirst("Content-Type");
if (StrUtil.isNotBlank(contentType) && contentType.contains(MediaType.APPLICATION_JSON_VALUE)) {
return DataBufferUtils.join(exchange.getRequest().getBody()).map(dataBuffer -> {
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
DataBufferUtils.release(dataBuffer);
return bytes;
}).defaultIfEmpty(new byte[0])
.doOnNext(bytes -> {
String param = new String(bytes, StandardCharsets.UTF_8);
logtrace(exchange, param);
exchange.getAttributes().put(CACHED_REQUEST_PARAM_KEY, param);
exchange.getAttributes().put(SYS_LOG_ID, sysLogId);
// }).then(chain.filter(exchange));
}).then(chain.filter(exchange.mutate().response(recordResponseLog(exchange)).build()));
}
Map m = request.getQueryParams();
String param = Convert.toStr(m);
logtrace(exchange, param);
exchange.getAttributes().put(CACHED_REQUEST_PARAM_KEY, param);
exchange.getAttributes().put(SYS_LOG_ID, sysLogId);
return chain.filter(exchange.mutate().response(recordResponseLog(exchange)).build());
// return chain.filter(exchange.mutate().request(recordRequestLog(exchange)).response(recordResponseLog(exchange)).build());
// return chain.filter(exchange);
}
@Override
public int getOrder() {
return this.order;
}
public GlobalCacheRequestFilter() {
this.order = -30;
}
public GlobalCacheRequestFilter(int order) {
this.order = order;
}
/**
* 日志信息
*
* @param exchange
* @param param 请求参数
*/
private void logtrace(ServerWebExchange exchange, String param) {
ServerHttpRequest serverHttpRequest = exchange.getRequest();
String path = serverHttpRequest.getURI().getPath();
String method = serverHttpRequest.getMethodValue();
String headers = serverHttpRequest.getHeaders().entrySet()
.stream()
.map(entry -> " " + entry.getKey() + ": [" + String.join(";", entry.getValue()) + "]")
.collect(Collectors.joining("\n"));
log.debug("\n" + "------------------------------------------------>>\n" +
"HttpMethod : {}\n" +
"Uri : {}\n" +
"Param : {}\n" +
"Headers : \n" +
"{}\n" +
"\"<<------------------------------------------------"
, method, path, param, headers);
}
/**
* 记录响应日志
*/
private ServerHttpResponseDecorator recordResponseLog(ServerWebExchange exchange) {
ServerHttpResponse response = exchange.getResponse();
HttpHeaders headers = exchange.getRequest().getHeaders();
MediaType mediaType = headers.getContentType();
String url = exchange.getRequest().getURI().getPath();
DataBufferFactory bufferFactory = response.bufferFactory();
ServerHttpResponseDecorator decoratorResponse = new ServerHttpResponseDecorator(response) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
if (body instanceof Flux) {
// 没有token的不会记录日志,所以不记录响应
if (StrUtil.isBlank(headers.getFirst("Authorization"))) {
return super.writeWith(body);
}
// 过滤上传附件请求
if ((mediaType != null && mediaType.equals(MediaType.MULTIPART_FORM_DATA))
|| (mediaType != null && mediaType.equals(MediaType.APPLICATION_FORM_URLENCODED))) {
return super.writeWith(body);
}
if (url.contains(SwaggerProvider.API_URI)) {
return super.writeWith(body);
}
Flux<? extends DataBuffer> fluxBody = Flux.from(body);
return super.writeWith(fluxBody.buffer().map(dataBuffers -> {// 解决返回体分段传输获取完整响应数据问题
StringBuilder bodyString = new StringBuilder();
dataBuffers.forEach(dataBuffer -> {
byte[] content = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(content);
DataBufferUtils.release(dataBuffer);
bodyString.append(new String(content, CharsetUtil.UTF_8));
});
// 拿到返回数据,可以进行做一些修改
String result = bodyString.toString();
byte[] uppedContent = new String(result.getBytes(), CharsetUtil.UTF_8).getBytes();
// 保存响应日志
sysLogResAdd(exchange, result);
response.getHeaders().setContentLength(uppedContent.length);
return bufferFactory.wrap(uppedContent);
}));
}
return super.writeWith(body);
}
};
return decoratorResponse;
}
/**
* 保存响应日志
*
* @param exchange
* @param result
*/
private void sysLogResAdd(ServerWebExchange exchange, String result) {
// 根据结果处理自己的逻辑
String sysLogId = exchange.getAttributeOrDefault(GlobalCacheRequestFilter.SYS_LOG_ID, "");
log.info("===============sysLogId:{},responseResult:{}", sysLogId, result);
Map<String, Object> sysLogResMap = new HashMap<>();
sysLogResMap.put("id", sysLogId);
sysLogResMap.put("result", result);
authFeignClient.sysLogResAdd(sysLogResMap);
}
}
package com.yymt.project.filter;
import cn.hutool.core.convert.Convert;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.yymt.project.dto.HasPermissionReqDTO;
import com.yymt.project.entity.User;
import com.yymt.project.entity.common.CodeEnum;
import com.yymt.project.entity.common.Result;
import com.yymt.project.feign.AuthFeignClient;
import com.yymt.project.filter.util.ModifiedRequestDecorator;
import com.yymt.project.filter.util.RecorderServerHttpRequestDecorator;
import com.yymt.project.filter.util.RewriteConfig;
import com.yymt.project.filter.util.SensitiveWordUtils;
import com.yymt.project.util.IpUtil;
import com.yymt.project.util.JwtUtil;
import com.yymt.project.util.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
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.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
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.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import javax.annotation.Resource;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
/**
* 自定义网关权限全局过滤器
*
* @author: hubin
* @date: 2020/12/17 16:21
*/
@Slf4j
@Component
public class AuthorityFilter implements GlobalFilter, Ordered {
/**
* 注入权限远程用
*/
@Resource
AuthFeignClient authFeignClient;
/**
* 注入权限远程用
*/
@Resource
SensitiveWordUtils sensitiveWordUtils;
/**
* 访问白名单
*/
@Value("${access.whitelist}")
private String whitelist;
/**
* 敏感词过滤url
*/
@Value("${access.sensitiveWordFilterUrl:}")
private String sensitiveWordFilterUrl;
private static final String OPTIONS = "OPTIONS";
/**
* 注入redis工具类
*/
@Resource
RedisUtils redisUtils;
/**
* ip名单列表
*/
public static String REDIS_IP_LIST_KEY = "ip_list_key:";
/**
* ip名单类型
*/
public static String REDIS_IP_LIST_TYPE_KEY = "ip_list_type_key:";
/**
* 重写过滤器
*
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 放行不需要鉴权请求
String path = exchange.getRequest().getPath().toString();
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
HttpHeaders headers = exchange.getRequest().getHeaders();
// 获取token
String token = headers.getFirst("Authorization");
String requestHeaderVersion = headers.getFirst("version");
String contentType = headers.getFirst("Content-Type");
Integer version = null;
// 请求体对象
JSONObject bodyObject = null;
if ((request.getMethod() == HttpMethod.POST || request.getMethod() == HttpMethod.PUT) && "application/json".equals(contentType)) {
AtomicReference<String> requestBody = new AtomicReference<>("");
RecorderServerHttpRequestDecorator requestDecorator = new RecorderServerHttpRequestDecorator(request);
Flux<DataBuffer> body = requestDecorator.getBody();
body.subscribe(buffer -> {
CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
requestBody.set(charBuffer.toString());
});
//获取body参数
String bodyString = requestBody.get();
if (StringUtils.isNotBlank(bodyString) && bodyString.startsWith("{")) {
bodyObject = JSONObject.parseObject(bodyString);
}
}
if (StringUtils.isNotBlank(requestHeaderVersion)) {
// 如果请求头设置了版本号,优先设置请求头版本号
version = Integer.valueOf(requestHeaderVersion);
} else if (bodyObject != null) {
// 请求头未设置版本号,从请求体获取版本号参数
if (bodyObject.getInteger("version") != null) {
version = bodyObject.getInteger("version");
}
}
if (StringUtils.isNotBlank(token)) {
// 请求头和请求体都没有版本号参数,从token获取版本号
version = JwtUtil.getVersion(token);
}
if (version == null) {
// 无版本参数数据,设置为默认1
version = 1;
}
// 获取ip
String ip = IpUtil.getIpAddress(exchange.getRequest());
// 如果是web端使用,则需要处理ip禁用逻辑
boolean needCheckIp = false;
// loginMode 登录方式:1表示Web端登录,2表示移动端登录,3表示3D客户端登录,4客户端登录
if (StringUtils.isNotBlank(token)) {
if (JwtUtil.getUserId(token).longValue() != 1 && JwtUtil.getLoginMode(token) == 1) {
// 非admin账号又是web端接口需要校验ip
needCheckIp = true;
}
} else if (bodyObject != null) {
// 判断body数据是否携带loginMode参数
if (bodyObject.getInteger("loginMode") != null) {
if (bodyObject.getInteger("loginMode") == 1) {
needCheckIp = true;
}
}
}
if (needCheckIp) {
// 判断黑白名单
String ipListType = redisUtils.get(REDIS_IP_LIST_TYPE_KEY + version);
String ipList = redisUtils.get(REDIS_IP_LIST_KEY + version);
if (StringUtils.isNotBlank(ipListType) && StringUtils.isNotBlank(ipList)) {
String[] ipRuleArr = ipList.split(",");
if ("1".equals(ipListType)) {
// 判断黑名单
for (String ipRule : ipRuleArr) {
if (IpUtil.checkIpByRule(ip, ipRule)) {
// 属于黑名单ip
response.setStatusCode(HttpStatus.LOCKED);
Result result = new Result().setCode(HttpStatus.LOCKED.value()).setMsg("ip已被禁用");
DataBuffer buffer = response.bufferFactory().wrap(JSON.toJSONString(result).getBytes());
return response.writeWith(Mono.just(buffer));
}
}
}
if ("0".equals(ipListType)) {
// 是否属于白名单
boolean isAllow = false;
for (String ipRule : ipRuleArr) {
if (IpUtil.checkIpByRule(ip, ipRule)) {
// 属于白名单ip
isAllow = true;
break;
}
}
if (!isAllow) {
// 不属于白名单
response.setStatusCode(HttpStatus.LOCKED);
Result result = new Result().setCode(HttpStatus.LOCKED.value()).setMsg("ip已被禁用");
DataBuffer buffer = response.bufferFactory().wrap(JSON.toJSONString(result).getBytes());
return response.writeWith(Mono.just(buffer));
}
}
}
}
// 获取参数
String params = "none";
String sysLogId = "";
try {
// 从 exchange 的自定义属性中取出缓存到的 body
params = exchange.getAttributeOrDefault(GlobalCacheRequestFilter.CACHED_REQUEST_PARAM_KEY, "none");
sysLogId = exchange.getAttributeOrDefault(GlobalCacheRequestFilter.SYS_LOG_ID, "");
if (params != null) {
// 取完数据清空缓存
exchange.getAttributes().remove(params);
}
} catch (Exception e) {
log.error("get params error!");
}
// log.info("====>request path:{} ,\n params:{}", path, params);
String[] whites = whitelist.split(",");
boolean flag = false;
for (String white : whites) {
if (path.indexOf(white) != -1) {
flag = true;
break;
}
}
List<String> filterUrlList = Arrays.asList(sensitiveWordFilterUrl.split(","));
if (flag) {
// 判断是否敏感词过滤url
if (filterUrlList.contains(path)) {
if (request.getMethod() == HttpMethod.POST || request.getMethod() == HttpMethod.PUT) {
return new ModifiedRequestDecorator(exchange, new RewriteConfig()
.setRewriteFunction(String.class, String.class, (ex, requestData)
-> Mono.just(sensitiveWordUtils.replaceWord(requestData))
)).filter(exchange, chain);
} else {
return chain.filter(exchange);
}
} else {
return chain.filter(exchange);
}
}
// 响应类型
// response.getHeaders().add("Content-Type", "application/json; charset=utf-8");
if (StringUtils.isNotBlank(token)) {
// 远程调用鉴权服务
Result successResult = null;
try {
HasPermissionReqDTO reqDTO = new HasPermissionReqDTO().setToken(token).setUrl(path)
.setServiceName(path.split("/")[1]).setIp(ip).setParams(params)
.setSysLogId(Convert.toLong(sysLogId));
successResult = authFeignClient.hasPermission(reqDTO);
} catch (Exception e) {
log.error("鉴权服务异常!", e);
// 其他异常
response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
// 响应内容
Result result = new Result().setCode(HttpStatus.INTERNAL_SERVER_ERROR.value()).setMsg("鉴权服务异常");
DataBuffer buffer = response.bufferFactory().wrap(JSON.toJSONString(result).getBytes());
return response.writeWith(Mono.just(buffer));
}
// 鉴权不通过
if (ObjectUtils.isEmpty(successResult) || CodeEnum.TOKEN_EXPIRE.getCode().equals(successResult.getCode())) {
// 响应状态码,HTTP 401 错误代表用户没有访问权限
response.setStatusCode(HttpStatus.UNAUTHORIZED);
// 响应内容
Result result = new Result().setCode(HttpStatus.UNAUTHORIZED.value()).setMsg("登录已过期,请重新登录");
DataBuffer buffer = response.bufferFactory().wrap(JSON.toJSONString(result).getBytes());
return response.writeWith(Mono.just(buffer));
} else if (CodeEnum.SUCCESS.getCode().equals(successResult.getCode())) {
User data = JSON.parseObject(JSON.toJSONString(successResult.getData()), User.class);
// 向headers中放文件,记得build
ServerHttpRequest host = exchange.getRequest().mutate().header("x-user-id", data.getUserId().toString())
.header("x-user-mobile", data.getMobile()).header("Authorization", token)
.header("admin", data.getAdmin().toString())
.header("user-type", data.getUserType() == null ? "" : data.getUserType().toString())
.header("system-version", String.valueOf(version))
.header("communityId", data.getCommunityId() == null ? "" : data.getCommunityId().toString())
// .header("secondCall","true")
.header("communityIds", StringUtils.join(data.getCommunityIds().toArray(), ",")).build();
// 将现在的request 变成 change对象
ServerWebExchange build = exchange.mutate().request(host).build();
// 判断是否敏感词过滤url
// AuthFeignClient feignClient = SpringUtil.getBean(AuthFeignClient.class);
if (filterUrlList.contains(path)) {
if (request.getMethod() == HttpMethod.POST || request.getMethod() == HttpMethod.PUT) {
return new ModifiedRequestDecorator(build, new RewriteConfig()
.setRewriteFunction(String.class, String.class, (ex, requestData)
-> Mono.just(requestData == null ? "" : sensitiveWordUtils.replaceWord(requestData))
)).filter(build, chain);
} else {
return chain.filter(build);
}
} else {
return chain.filter(build);
}
} else if (CodeEnum.FORBIDDEN.getCode().equals(successResult.getCode())) {
// 响应状态码,HTTP 401 错误代表用户没有访问权限
response.setStatusCode(HttpStatus.FORBIDDEN);
// 响应内容
Result result = new Result().setCode(HttpStatus.FORBIDDEN.value()).setMsg("无权限,请联系管理员!");
DataBuffer buffer = response.bufferFactory().wrap(JSON.toJSONString(result).getBytes());
return response.writeWith(Mono.just(buffer));
} else if (CodeEnum.REMOTE_LOGIN.getCode().equals(successResult.getCode())) {
// 响应状态码,HTTP 401 错误代表用户异地登录
response.setStatusCode(HttpStatus.UNAUTHORIZED);
// 响应内容
Result result = new Result().setCode(HttpStatus.UNAUTHORIZED.value())
.setMsg(CodeEnum.REMOTE_LOGIN.getMsg());
DataBuffer buffer = response.bufferFactory().wrap(JSON.toJSONString(result).getBytes());
return response.writeWith(Mono.just(buffer));
} else {
// 其他异常
response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
// 响应内容
Result result = new Result().setCode(HttpStatus.INTERNAL_SERVER_ERROR.value())
.setMsg(CodeEnum.COMMON_ERROR.getMsg());
DataBuffer buffer = response.bufferFactory().wrap(JSON.toJSONString(result).getBytes());
return response.writeWith(Mono.just(buffer));
}
}
// 响应状态码,HTTP 401 错误代表用户没有访问权限
response.setStatusCode(HttpStatus.UNAUTHORIZED);
// 响应内容
Result result = new Result().setCode(HttpStatus.UNAUTHORIZED.value())
.setMsg("登录已过期,请重新登录");
DataBuffer buffer = response.bufferFactory().wrap(JSON.toJSONString(result).getBytes());
return response.writeWith(Mono.just(buffer));
}
/**
* 过滤器执行顺序,数值越小,优先级越高
*
* @return
*/
@Override
public int getOrder() {
return 0;
}
}