diff --git a/fizz-core/src/main/java/we/api/pairing/FizzApiPairingController.java b/fizz-core/src/main/java/we/api/pairing/FizzApiPairingController.java new file mode 100644 index 0000000..a67630c --- /dev/null +++ b/fizz-core/src/main/java/we/api/pairing/FizzApiPairingController.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2020 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.api.pairing; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; +import we.config.SystemConfig; +import we.plugin.auth.App; +import we.plugin.auth.AppService; +import we.util.DateTimeUtils; +import we.util.WebUtils; + +import javax.annotation.Resource; +import java.time.LocalDateTime; +import java.util.List; + +/** + * @author hongqiaowei + */ + +@RestController +@RequestMapping(SystemConfig.DEFAULT_GATEWAY_PREFIX + "/_fizz-pairing") +public class FizzApiPairingController { + + private static final Logger log = LoggerFactory.getLogger(FizzApiPairingController.class); + + @Resource + private SystemConfig systemConfig; + + @Resource + private AppService appService; + + @Value("${fizz.api.pairing.request.timeliness:300}") + private int timeliness = 300; // unit: sec + + @GetMapping("/pair") + public Mono pair(ServerWebExchange exchange) { + + ServerHttpRequest request = exchange.getRequest(); + ServerHttpResponse response = exchange.getResponse(); + response.setStatusCode(HttpStatus.FORBIDDEN); + response.getHeaders().setContentType(MediaType.TEXT_PLAIN); + HttpHeaders headers = request.getHeaders(); + + String appId = WebUtils.getAppId(exchange); + if (appId == null) { + return WebUtils.buildDirectResponse(response, null, null, "请求无应用信息"); + } + App app = appService.getApp(appId); + if (app == null) { + return WebUtils.buildDirectResponse(response, null, null, "系统无" + appId + "应用信息"); + } + + String timestamp = getTimestamp(headers); + if (timestamp == null) { + return WebUtils.buildDirectResponse(response, null, null, "请求无时间戳"); + } + try { + long ts = Long.parseLong(timestamp); + LocalDateTime now = LocalDateTime.now(); + long start = DateTimeUtils.toMillis(now.minusSeconds(timeliness)); + long end = DateTimeUtils.toMillis(now.plusSeconds (timeliness)); + if (start <= ts && ts <= end) { + // valid + } else { + return WebUtils.buildDirectResponse(response, null, null, "请求时间戳无效"); + } + } catch (NumberFormatException e) { + return WebUtils.buildDirectResponse(response, null, null, "请求时间戳无效"); + } + + String sign = getSign(headers); + if (sign == null) { + return WebUtils.buildDirectResponse(response, null, null, "请求未签名"); + } + + boolean equals = PairingUtils.checkSign(appId, timestamp, app.secretkey, sign); + + if (equals) { + // TODO: 响应文档集 + return Mono.empty(); + } else { + log.warn("request authority: app {}, timestamp {}, sign {} invalid", appId, timestamp, sign); + return WebUtils.buildDirectResponse(response, null, null, "请求签名无效"); + } + } + + private String getSign(HttpHeaders headers) { + List signHdrs = systemConfig.getSignHeaders(); + for (int i = 0; i < signHdrs.size(); i++) { + String v = headers.getFirst(signHdrs.get(i)); + if (v != null) { + return v; + } + } + return null; + } + + private String getTimestamp(HttpHeaders headers) { + List tsHdrs = systemConfig.getTimestampHeaders(); + for (int i = 0; i < tsHdrs.size(); i++) { + String v = headers.getFirst(tsHdrs.get(i)); + if (v != null) { + return v; + } + } + return null; + } +} diff --git a/fizz-core/src/main/java/we/api/pairing/PairingUtils.java b/fizz-core/src/main/java/we/api/pairing/PairingUtils.java new file mode 100644 index 0000000..190636d --- /dev/null +++ b/fizz-core/src/main/java/we/api/pairing/PairingUtils.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2020 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.api.pairing; + +import we.util.Consts; +import we.util.ThreadContext; + +/** + * @author hongqiaowei + */ + +public abstract class PairingUtils extends org.apache.commons.codec.digest.DigestUtils { + + private PairingUtils() { + } + + public static String sign(String app, String timestamp, String secretKey) { + StringBuilder b = ThreadContext.getStringBuilder(ThreadContext.sb0); + b.append(app) .append(Consts.S.UNDER_LINE) + .append(timestamp).append(Consts.S.UNDER_LINE) + .append(secretKey); + return sha256Hex(b.toString()); + } + + public static String sign(String app, long timestamp, String secretKey) { + return sign(app, String.valueOf(timestamp), secretKey); + } + + public static boolean checkSign(String app, String timestamp, String secretKey, String sign) { + String s = sign(app, timestamp, secretKey); + return s.equals(sign); + } + + public static boolean checkSign(String app, long timestamp, String secretKey, String sign) { + return checkSign(app, String.valueOf(timestamp), secretKey, sign); + } +} diff --git a/fizz-core/src/main/java/we/filter/FizzWebFilter.java b/fizz-core/src/main/java/we/filter/FizzWebFilter.java index 428ced3..664fd68 100644 --- a/fizz-core/src/main/java/we/filter/FizzWebFilter.java +++ b/fizz-core/src/main/java/we/filter/FizzWebFilter.java @@ -21,6 +21,7 @@ import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebFilter; import org.springframework.web.server.WebFilterChain; import reactor.core.publisher.Mono; +import we.util.WebUtils; /** * @author hongqiaowei @@ -28,15 +29,12 @@ import reactor.core.publisher.Mono; public abstract class FizzWebFilter implements WebFilter { - private static final String admin = "admin"; - private static final String actuator = "actuator"; - @Override public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { - if (exchange.getAttribute(FlowControlFilter.ADMIN_REQUEST) == null) { - return doFilter(exchange, chain); - } else { + if (WebUtils.isAdminReq(exchange) || WebUtils.isFizzApiReq(exchange)) { return chain.filter(exchange); + } else { + return doFilter(exchange, chain); } } diff --git a/fizz-core/src/main/java/we/filter/FlowControlFilter.java b/fizz-core/src/main/java/we/filter/FlowControlFilter.java index 679725c..5f7a00e 100644 --- a/fizz-core/src/main/java/we/filter/FlowControlFilter.java +++ b/fizz-core/src/main/java/we/filter/FlowControlFilter.java @@ -67,7 +67,8 @@ public class FlowControlFilter extends FizzWebFilter { private static final String defaultFizzTraceIdValueStrategy = "requestId"; - public static final String ADMIN_REQUEST = "$a"; + private static final String _fizz = "_fizz-"; + @Resource private FlowControlFilterProperties flowControlFilterProperties; @@ -96,17 +97,21 @@ public class FlowControlFilter extends FizzWebFilter { return WebUtils.responseError(exchange, HttpStatus.INTERNAL_SERVER_ERROR.value(), "request path should like /optional-prefix/service-name/real-biz-path"); } String service = path.substring(1, secFS); - boolean adminReq = false, proxyTestReq = false; + boolean adminReq = false, proxyTestReq = false, fizzApiReq = false; if (service.equals(admin) || service.equals(actuator)) { adminReq = true; - exchange.getAttributes().put(ADMIN_REQUEST, Consts.S.EMPTY); + exchange.getAttributes().put(WebUtils.ADMIN_REQUEST, Consts.S.EMPTY); } else if (service.equals(SystemConfig.DEFAULT_GATEWAY_TEST)) { proxyTestReq = true; } else { service = WebUtils.getClientService(exchange); + if (service.startsWith(_fizz)) { + fizzApiReq = true; + exchange.getAttributes().put(WebUtils.FIZZ_API_REQUEST, Consts.S.EMPTY); + } } - if (flowControlFilterProperties.isFlowControl() && !adminReq && !proxyTestReq) { + if (flowControlFilterProperties.isFlowControl() && !adminReq && !proxyTestReq && !fizzApiReq) { String traceId = WebUtils.getTraceId(exchange); LogService.setBizId(traceId); if (!apiConfigService.serviceConfigMap.containsKey(service)) { diff --git a/fizz-core/src/main/java/we/plugin/auth/ApiConfigService.java b/fizz-core/src/main/java/we/plugin/auth/ApiConfigService.java index 35f68e1..6a78b32 100644 --- a/fizz-core/src/main/java/we/plugin/auth/ApiConfigService.java +++ b/fizz-core/src/main/java/we/plugin/auth/ApiConfigService.java @@ -529,9 +529,9 @@ public class ApiConfigService { private String getTimestamp(HttpHeaders reqHdrs) { List tsHdrs = systemConfig.getTimestampHeaders(); for (int i = 0; i < tsHdrs.size(); i++) { - String a = reqHdrs.getFirst(tsHdrs.get(i)); - if (a != null) { - return a; + String v = reqHdrs.getFirst(tsHdrs.get(i)); + if (v != null) { + return v; } } return null; @@ -540,9 +540,9 @@ public class ApiConfigService { private String getSign(HttpHeaders reqHdrs) { List signHdrs = systemConfig.getSignHeaders(); for (int i = 0; i < signHdrs.size(); i++) { - String a = reqHdrs.getFirst(signHdrs.get(i)); - if (a != null) { - return a; + String v = reqHdrs.getFirst(signHdrs.get(i)); + if (v != null) { + return v; } } return null; diff --git a/fizz-core/src/main/java/we/util/WebUtils.java b/fizz-core/src/main/java/we/util/WebUtils.java index e8e9450..ebeb159 100644 --- a/fizz-core/src/main/java/we/util/WebUtils.java +++ b/fizz-core/src/main/java/we/util/WebUtils.java @@ -106,10 +106,22 @@ public abstract class WebUtils { public static Set LOG_HEADER_SET = Collections.emptySet(); + public static final String ADMIN_REQUEST = "ar@"; + + public static final String FIZZ_API_REQUEST = "far@"; + private WebUtils() { } + public static boolean isAdminReq(ServerWebExchange exchange) { + return exchange.getAttribute(ADMIN_REQUEST) != null; + } + + public static boolean isFizzApiReq(ServerWebExchange exchange) { + return exchange.getAttribute(FIZZ_API_REQUEST) != null; + } + public static void setGatewayPrefix(String p) { gatewayPrefix = p; }