From b0f2723b7aeeb138a61bfd4d85fe7e9a904e15cf Mon Sep 17 00:00:00 2001 From: Francis Dong Date: Wed, 20 Oct 2021 10:52:08 +0800 Subject: [PATCH] Add API pairing plugin #353 --- fizz-core/src/main/java/we/util/WebUtils.java | 35 +++++ .../pairing/ApiPairingPluginFilter.java | 127 ++++++++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 fizz-plugin/src/main/java/we/plugin/pairing/ApiPairingPluginFilter.java diff --git a/fizz-core/src/main/java/we/util/WebUtils.java b/fizz-core/src/main/java/we/util/WebUtils.java index ebeb159..ba3aca7 100644 --- a/fizz-core/src/main/java/we/util/WebUtils.java +++ b/fizz-core/src/main/java/we/util/WebUtils.java @@ -83,6 +83,10 @@ public abstract class WebUtils { private static String gatewayPrefix = SystemConfig.DEFAULT_GATEWAY_PREFIX; private static List appHeaders = Stream.of("fizz-appid").collect(Collectors.toList()); + + private static List signHeaders = Stream.of("fizz-sign").collect(Collectors.toList()); + + private static List timestampHeaders = Stream.of("fizz-ts").collect(Collectors.toList()); private static final String app = "app"; @@ -129,6 +133,14 @@ public abstract class WebUtils { public static void setAppHeaders(List hdrs) { appHeaders = hdrs; } + + public static void setSignHeaders(List hdrs) { + signHeaders = hdrs; + } + + public static void setTimestampHeaders(List hdrs) { + timestampHeaders = hdrs; + } public static String getHeaderValue(ServerWebExchange exchange, String header) { return exchange.getRequest().getHeaders().getFirst(header); @@ -152,6 +164,29 @@ public abstract class WebUtils { } return a; } + + public static String getTimestamp(ServerWebExchange exchange) { + HttpHeaders headers = exchange.getRequest().getHeaders(); + for (int i = 0; i < timestampHeaders.size(); i++) { + String a = headers.getFirst(timestampHeaders.get(i)); + if (a != null) { + return a; + } + } + return null; + } + + public static String getSign(ServerWebExchange exchange) { + HttpHeaders headers = exchange.getRequest().getHeaders(); + for (int i = 0; i < signHeaders.size(); i++) { + String a = headers.getFirst(signHeaders.get(i)); + if (a != null) { + return a; + } + } + return null; + } + public static String getClientService(ServerWebExchange exchange) { String svc = exchange.getAttribute(clientService); diff --git a/fizz-plugin/src/main/java/we/plugin/pairing/ApiPairingPluginFilter.java b/fizz-plugin/src/main/java/we/plugin/pairing/ApiPairingPluginFilter.java new file mode 100644 index 0000000..95f7616 --- /dev/null +++ b/fizz-plugin/src/main/java/we/plugin/pairing/ApiPairingPluginFilter.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2021 the original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package we.plugin.pairing; + +import java.util.Map; + +import javax.annotation.Resource; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; + +import reactor.core.publisher.Mono; +import we.config.SystemConfig; +import we.plugin.FizzPluginFilter; +import we.plugin.FizzPluginFilterChain; +import we.plugin.auth.App; +import we.plugin.auth.AppService; +import we.util.DigestUtils; +import we.util.ReactorUtils; +import we.util.WebUtils; + +/** + * + * @author Francis Dong + * + */ +@Component(ApiPairingPluginFilter.API_PAIRING_PLUGIN_FILTER) +public class ApiPairingPluginFilter implements FizzPluginFilter { + + private static final Logger log = LoggerFactory.getLogger(ApiPairingPluginFilter.class); + + public static final String API_PAIRING_PLUGIN_FILTER = "apiPairingPlugin"; + + @Resource + private SystemConfig systemConfig; + + @Resource + private AppService appService; + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public Mono filter(ServerWebExchange exchange, Map config) { + try { + String appid = WebUtils.getAppId(exchange); + App app = appService.getApp(appid); + String ts = WebUtils.getTimestamp(exchange); + String sign = WebUtils.getSign(exchange); + if (validateSign(appid, ts, sign, app)) { + // Go to next plugin + Mono next = FizzPluginFilterChain.next(exchange); + return next.defaultIfEmpty(ReactorUtils.NULL).flatMap(nil -> { + doAfter(); + return Mono.empty(); + }); + } else { + // Auth failed + ServerHttpResponse response = exchange.getResponse(); + response.setStatusCode(HttpStatus.UNAUTHORIZED); + response.getHeaders().setCacheControl("no-store"); + response.getHeaders().setExpires(0); + String respJson = WebUtils.jsonRespBody(HttpStatus.UNAUTHORIZED.value(), + HttpStatus.UNAUTHORIZED.getReasonPhrase(), WebUtils.getTraceId(exchange)); + return WebUtils.buildDirectResponse(exchange, HttpStatus.UNAUTHORIZED, null, respJson); + } + } catch (Exception e) { + log.error("{} Exception", API_PAIRING_PLUGIN_FILTER, e); + String respJson = WebUtils.jsonRespBody(HttpStatus.INTERNAL_SERVER_ERROR.value(), + HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(), WebUtils.getTraceId(exchange)); + return WebUtils.buildDirectResponse(exchange, HttpStatus.INTERNAL_SERVER_ERROR, null, respJson); + } + } + + private boolean validateSign(String appid, String ts, String sign, App app) { + if (StringUtils.isBlank(appid) || StringUtils.isBlank(ts) || StringUtils.isBlank(sign) || app == null + || StringUtils.isBlank(app.secretkey)) { + return false; + } + + // SHA256(appid+_+ts+_+secretkey) + String data = appid + "_" + ts + "_" + app.secretkey; + if (!DigestUtils.sha256Hex(data).equals(sign)) { + return false; + } + + // validate timestamp + long t = 0; + try { + t = Long.valueOf(ts).longValue(); + } catch (Exception e) { + log.warn("invalid timestamp: {}", ts); + return false; + } + long now = System.currentTimeMillis(); + long offset = 5 * 60 * 1000; + if (t < now - offset || t > now + offset) { + log.warn("timestamp expired: {}", ts); + return false; + } + + return true; + } + + public void doAfter() { + + } + +}