原文连接:https://blog.csdn.net/autfish/article/details/90637957redis
在引入网关后,一般会把每一个服务都要作的工做,诸如日志、安全验证等转移到网关处理以减小重复开发。spring
1 加入log4j2安全
这里使用log4j2做为日志组件,首先添加log4j2的依赖并排除SpringBoot默认日志组件的依赖app
在resources目录下建立log4j2-spring.xmldom
在application.yml中增长配置告知log4j2文件路径ide
2 获取POST的Bodyspring-boot
记录日志时一般关注请求URI、Method、QueryString、POST请求的Body、响应信息和来源IP等。对于Spring Cloud Gateway这其中的POST请求的Body获取比较复杂,这里添加一个全局过滤器预先获取并存入请求的Attributes中。测试
CachePostBodyFilterui
@Component public class CachePostBodyFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest serverHttpRequest = exchange.getRequest(); String method = serverHttpRequest.getMethodValue(); if("POST".equalsIgnoreCase(method)) { ServerRequest serverRequest = new DefaultServerRequest(exchange); Mono<String> bodyToMono = serverRequest.bodyToMono(String.class); return bodyToMono.flatMap(body -> { exchange.getAttributes().put("cachedRequestBody", body); ServerHttpRequest newRequest = new ServerHttpRequestDecorator(serverHttpRequest) { @Override public HttpHeaders getHeaders() { HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.putAll(super.getHeaders()); httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked"); return httpHeaders; } @Override public Flux<DataBuffer> getBody() { NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(new UnpooledByteBufAllocator(false)); DataBuffer bodyDataBuffer = nettyDataBufferFactory.wrap(body.getBytes()); return Flux.just(bodyDataBuffer); } }; return chain.filter(exchange.mutate().request(newRequest).build()); }); } return chain.filter(exchange); } @Override public int getOrder() { return -21; } }
3 记录日志spa
接下来再建立一个过滤器用于记录日志
4 鉴权
对请求的安全验证方案视各自项目需求而定,没有固定的作法,这里仅演示检查签名的处理。规则是:对除sign外全部请求参数按字典顺序排序后组成key1=value1&key2=value2的字符串,而后计算MD5码并与sign参数值比较,一致即认为经过。
这里面一样要处理QueryString和POST方法的Body,所以和日志过滤器合并为在一块儿。
@Component public class AuthAndLogFilter implements GlobalFilter, Ordered { static final Logger logger = LogManager.getLogger("request"); @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest serverHttpRequest = exchange.getRequest(); ServerHttpResponse serverHttpResponse = exchange.getResponse(); StringBuilder logBuilder = new StringBuilder(); Map<String, String> params = parseRequest(exchange, logBuilder); boolean r = checkSignature(params, serverHttpRequest); if(!r) { Map map = new HashMap<>(); map.put("code", 2); map.put("message", "签名验证失败"); String resp = JSON.toJSONString(map); logBuilder.append(",resp=").append(resp); logger.info(logBuilder.toString()); DataBuffer bodyDataBuffer = serverHttpResponse.bufferFactory().wrap(resp.getBytes()); serverHttpResponse.getHeaders().add("Content-Type", "text/plain;charset=UTF-8"); return serverHttpResponse.writeWith(Mono.just(bodyDataBuffer)); } DataBufferFactory bufferFactory = serverHttpResponse.bufferFactory(); ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(serverHttpResponse) { @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); String resp = new String(content, Charset.forName("UTF-8")); logBuilder.append(",resp=").append(resp); logger.info(logBuilder.toString()); byte[] uppedContent = new String(content, Charset.forName("UTF-8")).getBytes(); return bufferFactory.wrap(uppedContent); })); } return super.writeWith(body); } }; return chain.filter(exchange.mutate().response(decoratedResponse).build()); } private Map<String, String> parseRequest(ServerWebExchange exchange, StringBuilder logBuilder) { ServerHttpRequest serverHttpRequest = exchange.getRequest(); String method = serverHttpRequest.getMethodValue().toUpperCase(); logBuilder.append(method).append(",").append(serverHttpRequest.getURI()); MultiValueMap<String, String> query = serverHttpRequest.getQueryParams(); Map<String, String> params = new HashMap<>(); query.forEach((k, v) -> { params.put(k, v.get(0)); }); if("POST".equals(method)) { String body = exchange.getAttributeOrDefault("cachedRequestBody", ""); if(StringUtils.isNotBlank(body)) { logBuilder.append(",body=").append(body); String[] kvArray = body.split("&"); for (String kv : kvArray) { if (kv.indexOf("=") >= 0) { String k = kv.split("=")[0]; String v = kv.split("=")[1]; if(!params.containsKey(k)) { try { params.put(k, URLDecoder.decode(v, "UTF-8")); } catch (UnsupportedEncodingException e) { } } } } } } return params; } private boolean checkSignature(Map<String, String> params, ServerHttpRequest serverHttpRequest) { String sign = params.get("sign"); if(StringUtils.isBlank(sign)) { return false; } //检查签名 Map<String, String> sorted = new TreeMap<>(); params.forEach( (k, v) -> { if(!"sign".equals(k)) { sorted.put(k, v); } }); StringBuilder builder = new StringBuilder(); sorted.forEach((k, v) -> { builder.append(k).append("=").append(v).append("&"); }); String value = builder.toString(); value = value.substring(0, value.length() - 1); if(!sign.equalsIgnoreCase(MD5Utils.MD5(value))) { return false; } return true; } @Override public int getOrder() { return -20; } }
测试
A:无签名
B:带签名GET请求
C:POST请求
本期源码
连接:https://pan.baidu.com/s/1Vfg9Apnl1OgL8pzeBqHYmw 提取码:jfkl