Pairing controller

This commit is contained in:
hongqiaowei
2021-10-20 11:44:03 +08:00
committed by GitHub
parent 085db7be97
commit db6563d3cb
6 changed files with 218 additions and 16 deletions

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<Void> 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<String> 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<String> 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;
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@@ -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<Void> 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);
}
}

View File

@@ -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)) {

View File

@@ -529,9 +529,9 @@ public class ApiConfigService {
private String getTimestamp(HttpHeaders reqHdrs) {
List<String> 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<String> 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;

View File

@@ -106,10 +106,22 @@ public abstract class WebUtils {
public static Set<String> 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;
}