原文連接: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