Pairing controller
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
52
fizz-core/src/main/java/we/api/pairing/PairingUtils.java
Normal file
52
fizz-core/src/main/java/we/api/pairing/PairingUtils.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user