Api config refactor

This commit is contained in:
hongqiaowei
2021-10-08 12:22:09 +08:00
committed by GitHub
parent 0f5a087921
commit 1d71a059bf
17 changed files with 547 additions and 325 deletions

View File

@@ -44,7 +44,7 @@ public final class Consts {
public static final char SINGLE_QUOTE = '\''; public static final char SINGLE_QUOTE = '\'';
public static final char ASTERISK = '*'; public static final char ASTERISK = '*';
public static final char DASH = '-'; public static final char DASH = '-';
public static final char UNDERLINE = '_'; public static final char UNDER_LINE = '_';
public static final char EQUAL = '='; public static final char EQUAL = '=';
public static final char AT = '@'; public static final char AT = '@';
public static final char LEFT_SQUARE_BRACKET = '['; public static final char LEFT_SQUARE_BRACKET = '[';

View File

@@ -22,6 +22,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilterChain; import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
@@ -34,9 +35,11 @@ import we.plugin.auth.AuthPluginFilter;
import we.plugin.stat.StatPluginFilter; import we.plugin.stat.StatPluginFilter;
import we.proxy.Route; import we.proxy.Route;
import we.util.ReactorUtils; import we.util.ReactorUtils;
import we.util.Result;
import we.util.WebUtils; import we.util.WebUtils;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -74,45 +77,36 @@ public class PreprocessFilter extends FizzWebFilter {
return process(exchange, chain, eas, vm); return process(exchange, chain, eas, vm);
} }
// TODO // TODO: improve
private Mono<Void> process(ServerWebExchange exchange, WebFilterChain chain, Map<String, Object> eas, Mono vm) { private Mono<Void> process(ServerWebExchange exchange, WebFilterChain chain, Map<String, Object> eas, Mono vm) {
return chain(exchange, vm, authPluginFilter).defaultIfEmpty(ReactorUtils.NULL) return chain(exchange, vm, authPluginFilter).defaultIfEmpty(ReactorUtils.NULL)
.flatMap( .flatMap(
v -> { v -> {
Object authRes = WebUtils.getFilterResultDataItem(exchange, AuthPluginFilter.AUTH_PLUGIN_FILTER, AuthPluginFilter.RESULT); Result<ApiConfig> authRes = (Result<ApiConfig>) WebUtils.getFilterResultDataItem(exchange, AuthPluginFilter.AUTH_PLUGIN_FILTER, AuthPluginFilter.RESULT);
if (authRes.code == Result.FAIL) {
return WebUtils.responseError(exchange, HttpStatus.FORBIDDEN.value(), authRes.msg);
}
Mono m = ReactorUtils.getInitiateMono(); Mono m = ReactorUtils.getInitiateMono();
if (authRes instanceof ApiConfig) { ApiConfig ac = authRes.data;
ApiConfig ac = (ApiConfig) authRes; if (ac == null) {
Route route = ac.getRoute(exchange);
exchange.getAttributes().put(WebUtils.ROUTE, route);
afterAuth(exchange, ac, route);
m = executeFixedPluginFilters(exchange);
m = m.defaultIfEmpty(ReactorUtils.NULL);
if (route.pluginConfigs == null || route.pluginConfigs.isEmpty()) {
return m.flatMap(func(exchange, chain));
} else {
return m.flatMap(
nil -> {
eas.put(FizzPluginFilterChain.WEB_FILTER_CHAIN, chain);
return FizzPluginFilterChain.next(exchange);
}
);
}
} else if (authRes == ApiConfigService.Access.YES) {
afterAuth(exchange, null, null); afterAuth(exchange, null, null);
m = executeFixedPluginFilters(exchange); m = executeFixedPluginFilters(exchange);
return m.defaultIfEmpty(ReactorUtils.NULL).flatMap(func(exchange, chain)); return m.defaultIfEmpty(ReactorUtils.NULL).flatMap(func(exchange, chain));
}
Route route = ac.getRoute(exchange);
eas.put(WebUtils.ROUTE, route);
afterAuth(exchange, ac, route);
m = executeFixedPluginFilters(exchange);
m = m.defaultIfEmpty(ReactorUtils.NULL);
if (CollectionUtils.isEmpty(route.pluginConfigs)) {
return m.flatMap(func(exchange, chain));
} else { } else {
String err = null; return m.flatMap(
if (authRes instanceof ApiConfigService.Access) { nil -> {
ApiConfigService.Access access = (ApiConfigService.Access) authRes; eas.put(FizzPluginFilterChain.WEB_FILTER_CHAIN, chain);
err = access.getReason(); return FizzPluginFilterChain.next(exchange);
} else { }
err = authRes.toString(); );
}
return WebUtils.responseError(exchange, HttpStatus.FORBIDDEN.value(), err);
} }
} }
); );

View File

@@ -44,7 +44,7 @@ public final class FizzPluginFilterChain {
public static Mono<Void> next(ServerWebExchange exchange) { public static Mono<Void> next(ServerWebExchange exchange) {
Iterator<PluginConfig> it = exchange.getAttribute(pluginConfigsIt); Iterator<PluginConfig> it = exchange.getAttribute(pluginConfigsIt);
if (it == null) { if (it == null) {
List<PluginConfig> pcs = WebUtils.getApiConfig(exchange).pluginConfigs; List<PluginConfig> pcs = WebUtils.getRoute(exchange).pluginConfigs;
it = pcs.iterator(); it = pcs.iterator();
Map<String, Object> attris = exchange.getAttributes(); Map<String, Object> attris = exchange.getAttributes();
attris.put(pluginConfigsIt, it); attris.put(pluginConfigsIt, it);

View File

@@ -30,13 +30,13 @@ import java.util.Map;
public class PluginConfig { public class PluginConfig {
public static final String CUSTOM_CONFIG = "$fc"; public static final String CUSTOM_CONFIG = "fcK";
public String plugin; // tb_plugin.eng_name public String plugin; // tb_plugin.eng_name
public String fixedConfig; public String fixedConfig;
public Map<String/*tb_api_plugin_config.item*/, Object/*tb_api_plugin_config.value*/> config = Collections.EMPTY_MAP; public Map<String/*tb_api_plugin_config.item*/, Object/*tb_api_plugin_config.value*/> config = Collections.emptyMap();
// @JsonProperty(value = "config", access = JsonProperty.Access.WRITE_ONLY) // @JsonProperty(value = "config", access = JsonProperty.Access.WRITE_ONLY)
public void setConfig(String confJson) { public void setConfig(String confJson) {

View File

@@ -0,0 +1,43 @@
/*
* 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.plugin.auth;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import we.util.Result;
import we.util.Utils;
/**
* @author hongqiaowei
*/
public abstract class AbstractCustomAuth implements CustomAuth {
/**
* @deprecated
*/
@Override
public Mono<ApiConfigService.Access> auth(ServerWebExchange exchange, String appId, String ip, String timestamp, String sign, App fizzAppConfig) {
throw Utils.runtimeExceptionWithoutStack("don't implement me!");
}
/**
* @return if authentication pass then Result.code = Result.SUCC, otherwise Result.code = Result.FAIL
*/
public abstract Mono<Result<?>> auth(String appId, String ip, String timestamp, String sign, App fizzAppConfig, ServerWebExchange exchange);
}

View File

@@ -54,7 +54,7 @@ public class ApiConfig {
public static final char ALLOW = 'a'; public static final char ALLOW = 'a';
public static final char FORBID = 'f'; // public static final char FORBID = 'f';
public static final String ALL_METHOD = "AM"; public static final String ALL_METHOD = "AM";
@@ -62,25 +62,36 @@ public class ApiConfig {
private static final int ENABLE = 1; private static final int ENABLE = 1;
private static final int UNABLE = 0; // private static final int UNABLE = 0;
@JsonProperty(
access = JsonProperty.Access.WRITE_ONLY
)
public int id; // tb_api_auth.id public int id; // tb_api_auth.id
@JsonProperty(
access = JsonProperty.Access.WRITE_ONLY
)
public int isDeleted = 0; // tb_api_auth.is_deleted public int isDeleted = 0; // tb_api_auth.is_deleted
public Set<String> gatewayGroups = Stream.of(GatewayGroup.DEFAULT).collect(Collectors.toSet()); public Set<String> gatewayGroups = Stream.of(GatewayGroup.DEFAULT).collect(Collectors.toSet());
public String service; // a public String service;
public String backendService; public String backendService;
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY) @JsonProperty(
access = JsonProperty.Access.WRITE_ONLY
)
public HttpMethod method; public HttpMethod method;
public Object fizzMethod = ALL_METHOD; public Object fizzMethod = ALL_METHOD;
public String path = match_all; public String path = match_all;
@JsonProperty(
access = JsonProperty.Access.WRITE_ONLY
)
public boolean exactMatch = false; public boolean exactMatch = false;
public String backendPath; public String backendPath;
@@ -180,7 +191,7 @@ public class ApiConfig {
i = Math.abs(i); i = Math.abs(i);
} }
return httpHostPorts.get( return httpHostPorts.get(
i % httpHostPorts.size() i % httpHostPorts.size()
); );
} }
@@ -207,17 +218,17 @@ public class ApiConfig {
public Route getRoute(ServerWebExchange exchange) { public Route getRoute(ServerWebExchange exchange) {
ServerHttpRequest request = exchange.getRequest(); ServerHttpRequest request = exchange.getRequest();
Route r = new Route().type(this.type) Route r = new Route().type( this.type)
.method(request.getMethod()) .method( request.getMethod())
.backendService(this.backendService) .backendService(this.backendService)
.backendPath(this.backendPath) .backendPath( this.backendPath)
.query(WebUtils.getClientReqQuery(exchange)) .query( WebUtils.getClientReqQuery(exchange))
.pluginConfigs(this.pluginConfigs) .pluginConfigs( this.pluginConfigs)
.rpcMethod(this.rpcMethod) .rpcMethod( this.rpcMethod)
.rpcParamTypes(this.rpcParamTypes) .rpcParamTypes( this.rpcParamTypes)
.rpcGroup(this.rpcGroup) .rpcGroup( this.rpcGroup)
.rpcVersion(this.rpcVersion) .rpcVersion( this.rpcVersion)
.timeout(this.timeout); .timeout( this.timeout);
if (this.type == Type.REVERSE_PROXY) { if (this.type == Type.REVERSE_PROXY) {
r = r.nextHttpHostPort(getNextHttpHostPort()); r = r.nextHttpHostPort(getNextHttpHostPort());

View File

@@ -25,7 +25,9 @@ import org.springframework.data.redis.core.ReactiveStringRedisTemplate;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
@@ -41,6 +43,7 @@ import javax.annotation.Resource;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.regex.Pattern;
/** /**
* @author hongqiaowei * @author hongqiaowei
@@ -51,11 +54,7 @@ public class ApiConfigService {
private static final Logger log = LoggerFactory.getLogger(ApiConfigService.class); private static final Logger log = LoggerFactory.getLogger(ApiConfigService.class);
private static final String mpps = "$mpps"; private static final String macs = "macsT";
private static final String deny = "route which match current request can't be access by client app, or is not exposed to current gateway group";
public static final String AUTH_MSG = "$authMsg";
public Map<String, ServiceConfig> serviceConfigMap = new HashMap<>(128); public Map<String, ServiceConfig> serviceConfigMap = new HashMap<>(128);
@@ -112,11 +111,13 @@ public class ApiConfigService {
); );
} }
// TODO: no need like this
public void refreshLocalCache() throws Throwable { public void refreshLocalCache() throws Throwable {
this.init(null); this.init(null);
initPlugin(); initPlugin();
} }
// TODO: no need like this
private void init(Supplier<Mono<Throwable>> doAfterLoadCache) throws Throwable { private void init(Supplier<Mono<Throwable>> doAfterLoadCache) throws Throwable {
Map<Integer, ApiConfig> apiConfigMapTmp = new HashMap<>(128); Map<Integer, ApiConfig> apiConfigMapTmp = new HashMap<>(128);
Map<String, ServiceConfig> serviceConfigMapTmp = new HashMap<>(128); Map<String, ServiceConfig> serviceConfigMapTmp = new HashMap<>(128);
@@ -129,7 +130,7 @@ public class ApiConfigService {
return Flux.just(e); return Flux.just(e);
} }
Object v = e.getValue(); Object v = e.getValue();
log.info("api config: " + v.toString(), LogService.BIZ_ID, k.toString()); log.info("get api config data: {}", v.toString(), LogService.BIZ_ID, k.toString());
String json = (String) v; String json = (String) v;
try { try {
ApiConfig ac = JacksonUtils.readValue(json, ApiConfig.class); ApiConfig ac = JacksonUtils.readValue(json, ApiConfig.class);
@@ -138,7 +139,7 @@ public class ApiConfigService {
return Flux.just(e); return Flux.just(e);
} catch (Throwable t) { } catch (Throwable t) {
throwable[0] = t; throwable[0] = t;
log.info(json, t); log.error("deser {}", json, t);
return Flux.error(t); return Flux.error(t);
} }
}).blockLast())).flatMap( }).blockLast())).flatMap(
@@ -146,7 +147,6 @@ public class ApiConfigService {
if (throwable[0] != null) { if (throwable[0] != null) {
return Mono.error(throwable[0]); return Mono.error(throwable[0]);
} }
if (doAfterLoadCache != null) { if (doAfterLoadCache != null) {
return doAfterLoadCache.get(); return doAfterLoadCache.get();
} else { } else {
@@ -157,26 +157,27 @@ public class ApiConfigService {
if (error != ReactorUtils.EMPTY_THROWABLE) { if (error != ReactorUtils.EMPTY_THROWABLE) {
throw error; throw error;
} }
this.apiConfigMap = apiConfigMapTmp; this.apiConfigMap = apiConfigMapTmp;
this.serviceConfigMap = serviceConfigMapTmp; this.serviceConfigMap = serviceConfigMapTmp;
} }
// TODO: no need like this
private Mono<Throwable> lsnApiConfigChange() { private Mono<Throwable> lsnApiConfigChange() {
final Throwable[] throwable = new Throwable[1]; final Throwable[] throwable = new Throwable[1];
final boolean[] b = {false}; final boolean[] b = {false};
rt.listenToChannel(apiConfigServiceProperties.getFizzApiConfigChannel()).doOnError(t -> { String ch = apiConfigServiceProperties.getFizzApiConfigChannel();
rt.listenToChannel(ch).doOnError(t -> {
throwable[0] = t; throwable[0] = t;
b[0] = false; b[0] = false;
log.error("lsn " + apiConfigServiceProperties.getFizzApiConfigChannel(), t); log.error("lsn {}", ch, t);
}).doOnSubscribe( }).doOnSubscribe(
s -> { s -> {
b[0] = true; b[0] = true;
log.info("success to lsn on " + apiConfigServiceProperties.getFizzApiConfigChannel()); log.info("success to lsn on {}", ch);
} }
).doOnNext(msg -> { ).doOnNext(msg -> {
String json = msg.getMessage(); String json = msg.getMessage();
log.info(json, LogService.BIZ_ID, "acc" + System.currentTimeMillis()); log.info("api config change: {}", json, LogService.BIZ_ID, "acc" + System.currentTimeMillis());
try { try {
ApiConfig ac = JacksonUtils.readValue(json, ApiConfig.class); ApiConfig ac = JacksonUtils.readValue(json, ApiConfig.class);
ApiConfig r = apiConfigMap.remove(ac.id); ApiConfig r = apiConfigMap.remove(ac.id);
@@ -191,7 +192,7 @@ public class ApiConfigService {
apiConifg2appsService.remove(ac.id); apiConifg2appsService.remove(ac.id);
} }
} catch (Throwable t) { } catch (Throwable t) {
log.info(json, t); log.error("deser {}", json, t);
} }
}).subscribe(); }).subscribe();
Throwable t = throwable[0]; Throwable t = throwable[0];
@@ -295,14 +296,11 @@ public class ApiConfigService {
private void updateServiceConfigMap(ApiConfig ac, Map<String, ServiceConfig> serviceConfigMap) { private void updateServiceConfigMap(ApiConfig ac, Map<String, ServiceConfig> serviceConfigMap) {
ServiceConfig sc = serviceConfigMap.get(ac.service); ServiceConfig sc = serviceConfigMap.get(ac.service);
if (ac.isDeleted == ApiConfig.DELETED) { if (ac.isDeleted == ApiConfig.DELETED) {
if (sc == null) { if (sc != null) {
log.info("no " + ac.service + " config to delete");
} else {
sc.remove(ac); sc.remove(ac);
if (sc.path2methodToApiConfigMapMap.isEmpty()) { if (sc.apiConfigMap.isEmpty()) {
serviceConfigMap.remove(ac.service); serviceConfigMap.remove(ac.service);
} }
// apiConifg2appsService.remove(ac.id);
} }
} else { } else {
if (sc == null) { if (sc == null) {
@@ -315,6 +313,9 @@ public class ApiConfigService {
} }
} }
/**
* @deprecated
*/
public enum Access { public enum Access {
YES (null), YES (null),
@@ -344,151 +345,164 @@ public class ApiConfigService {
} }
} }
public ApiConfig getApiConfig(String app, String service, HttpMethod method, String path) { public Result<ApiConfig> getApiConfig(String app, String service, HttpMethod method, String path) {
ApiConfig ac = null; return getApiConfig(null, app, service, method, path);
for (String g : gatewayGroupService.currentGatewayGroupSet) {
ac = getApiConfig(service, method, path, g, app);
if (ac != null) {
return ac;
}
}
return ac;
} }
public ApiConfig getApiConfig(String service, HttpMethod method, String path, String gatewayGroup, String app) { public Result<ApiConfig> getApiConfig(Set<String> gatewayGroups, String app, String service, HttpMethod method, String path) {
ServiceConfig sc = serviceConfigMap.get(service); ServiceConfig sc = serviceConfigMap.get(service);
if (sc != null) { if (sc == null) {
List<ApiConfig> apiConfigs = sc.getApiConfigs(method, path, gatewayGroup); return Result.fail("no " + service + " config");
if (!apiConfigs.isEmpty()) { }
List<String> matchPathPatterns = ThreadContext.getArrayList(mpps); if (CollectionUtils.isEmpty(gatewayGroups)) {
for (int i = 0; i < apiConfigs.size(); i++) { gatewayGroups = gatewayGroupService.currentGatewayGroupSet;
ApiConfig ac = apiConfigs.get(i); }
if (ac.checkApp) { List<ApiConfig> apiConfigs = sc.getApiConfigs(gatewayGroups, method, path);
if (apiConifg2appsService.contains(ac.id, app)) { if (apiConfigs.isEmpty()) {
matchPathPatterns.add(ac.path); return Result.fail(service + " don't have api config matching " + gatewayGroups + " group " + method + " method " + path + " path");
} }
} else { List<ApiConfig> appCanAccess = ThreadContext.getArrayList(macs);
matchPathPatterns.add(ac.path); for (int i = 0; i < apiConfigs.size(); i++) {
} ApiConfig ac = apiConfigs.get(i);
if (ac.checkApp) {
if (StringUtils.isNotBlank(app) && apiConifg2appsService.contains(ac.id, app)) {
appCanAccess.add(ac);
} }
if (matchPathPatterns.isEmpty()) { } else {
if (app == null) { appCanAccess.add(ac);
ThreadContext.set(ApiConfigService.AUTH_MSG, "no app msg in req"); }
} else { }
ThreadContext.set(ApiConfigService.AUTH_MSG, app + " not in app whitelist of routes which match " + gatewayGroup + ' ' + service + ' ' + method + ' ' + path); if (appCanAccess.isEmpty()) {
} return Result.fail("app " + app + " can't access " + JacksonUtils.writeValueAsString(apiConfigs));
} }
if (!matchPathPatterns.isEmpty()) { ApiConfig bestOne = appCanAccess.get(0);
if (matchPathPatterns.size() > 1) { if (appCanAccess.size() != 1) {
Collections.sort(matchPathPatterns, UrlTransformUtils.ANT_PATH_MATCHER.getPatternComparator(path)); appCanAccess.sort(new ApiConfigPathPatternComparator(path));
} ApiConfig ac0 = appCanAccess.get(0);
String bestPathPattern = matchPathPatterns.get(0); bestOne = ac0;
for (int i = 0; i < apiConfigs.size(); i++) { ApiConfig ac1 = appCanAccess.get(1);
ApiConfig ac = apiConfigs.get(i); if (ac0.path.equals(ac1.path)) {
if (StringUtils.equals(ac.path, bestPathPattern)) { if (ac0.fizzMethod == ac1.fizzMethod) {
return ac; if (StringUtils.isNotBlank(app)) {
if (!ac0.checkApp) {
bestOne = ac1;
} }
} }
} else {
if (ac0.fizzMethod == ApiConfig.ALL_METHOD) {
bestOne = ac1;
}
} }
} }
} else {
ThreadContext.set(ApiConfigService.AUTH_MSG, "no " + service + " service config");
} }
return null; return Result.succ(bestOne);
} }
public Mono<Object> canAccess(ServerWebExchange exchange) { public Mono<Result<ApiConfig>> auth(ServerWebExchange exchange) {
ServerHttpRequest req = exchange.getRequest(); ServerHttpRequest req = exchange.getRequest();
HttpHeaders hdrs = req.getHeaders(); HttpHeaders hdrs = req.getHeaders();
LogService.setBizId(WebUtils.getTraceId(exchange)); LogService.setBizId(WebUtils.getTraceId(exchange));
return canAccess(exchange, WebUtils.getAppId(exchange), WebUtils.getOriginIp(exchange), getTimestamp(hdrs), getSign(hdrs), return auth(exchange, WebUtils.getAppId(exchange), WebUtils.getOriginIp(exchange), getTimestamp(hdrs), getSign(hdrs),
WebUtils.getClientService(exchange), req.getMethod(), WebUtils.getClientReqPath(exchange)); WebUtils.getClientService(exchange), req.getMethod(), WebUtils.getClientReqPath(exchange));
} }
// TODO: improve ... private Mono<Result<ApiConfig>> auth(ServerWebExchange exchange, String app, String ip, String timestamp, String sign, String service, HttpMethod method, String path) {
private Mono<Object> canAccess(ServerWebExchange exchange, String app, String ip, String timestamp, String sign, String service, HttpMethod method, String path) {
if (!systemConfig.isAggregateTestAuth()) { if (!systemConfig.isAggregateTestAuth()) {
if (SystemConfig.DEFAULT_GATEWAY_TEST_PREFIX0.equals(WebUtils.getClientReqPathPrefix(exchange))) { if (SystemConfig.DEFAULT_GATEWAY_TEST_PREFIX0.equals(WebUtils.getClientReqPathPrefix(exchange))) {
return Mono.just(Access.YES); return Mono.just(Result.succ());
} }
} }
ApiConfig ac = getApiConfig(app, service, method, path); Result<ApiConfig> r = getApiConfig(app, service, method, path);
if (ac == null) { if (r.code == Result.FAIL) {
String authMsg = (String) ThreadContext.remove(AUTH_MSG); if (apiConfigServiceProperties.isNeedAuth()) {
if (authMsg == null) { return Mono.just(r);
authMsg = deny;
}
if (!apiConfigServiceProperties.isNeedAuth()) {
return Mono.just(Access.YES);
} else { } else {
return logAndResult(authMsg); return Mono.just(Result.succ());
} }
}
} else if (ac.checkApp) { ApiConfig ac = r.data;
App a = appService.getApp(app); if (ac.checkApp) {
if (a.useWhiteList && !a.allow(ip)) { App a = appService.getApp(app);
return logAndResult(ip + " not in " + app + " white list", Access.IP_NOT_IN_WHITE_LIST); if (a.useWhiteList && !a.allow(ip)) {
} else if (a.useAuth) { r.code = Result.FAIL;
if (a.authType == App.AUTH_TYPE.SIGN) { r.msg = ip + " not in " + app + " app white list";
return authSign(ac, a, timestamp, sign); return Mono.just(r);
} else if (a.authType == App.AUTH_TYPE.SECRETKEY) { }
return authSecretkey(ac, a, sign); if (a.useAuth) {
} else if (customAuth == null) { if (a.authType == App.AUTH_TYPE.SIGN) {
return logAndResult(app + " no custom auth", Access.NO_CUSTOM_AUTH); return authSign(a, timestamp, sign, r);
} else { } else if (a.authType == App.AUTH_TYPE.SECRET_KEY) {
return customAuth.auth(exchange, app, ip, timestamp, sign, a).flatMap(v -> { return authSecretKey(a, sign, r);
if (v == Access.YES) { } else if (customAuth == null) {
return Mono.just(ac); r.code = Result.FAIL;
} else { r.msg = "no custom auth bean for " + app;
return Mono.just(Access.CUSTOM_AUTH_REJECT); return Mono.just(r);
} } else {
}); if (customAuth instanceof AbstractCustomAuth) {
AbstractCustomAuth abstractCustomAuth = (AbstractCustomAuth) customAuth;
return abstractCustomAuth.auth(app, ip, timestamp, sign, a, exchange)
.flatMap(
res -> {
if (res.code == Result.FAIL) {
r.code = res.code;
r.msg = res.msg;
}
return Mono.just(r);
}
);
} else {
return customAuth.auth(exchange, app, ip, timestamp, sign, a)
.flatMap(
v -> {
if (v == Access.YES) {
return Mono.just(r);
} else {
r.code = Result.FAIL;
r.msg = v.getReason();
return Mono.just(r);
}
}
);
}
}
} }
} else {
return Mono.just(ac);
}
} else {
return Mono.just(ac);
} }
return Mono.just(r);
} }
private Mono authSign(ApiConfig ac, App a, String timestamp, String sign) { private Mono<Result<ApiConfig>> authSign(App a, String timestamp, String sign, Result<ApiConfig> r) {
if (StringUtils.isAnyBlank(timestamp, sign)) { if (StringUtils.isAnyBlank(timestamp, sign)) {
return logAndResult(a.app + " lack timestamp " + timestamp + " or sign " + sign, Access.NO_TIMESTAMP_OR_SIGN); r.code = Result.FAIL;
r.msg = a.app + " not present timestamp " + timestamp + " or sign " + sign;
} else if (validate(a.app, timestamp, a.secretkey, sign)) { } else if (validate(a.app, timestamp, a.secretkey, sign)) {
return Mono.just(ac);
} else { } else {
return logAndResult(a.app + " sign " + sign + " invalid", Access.SIGN_INVALID); r.code = Result.FAIL;
r.msg = a.app + " sign " + sign + " invalid";
} }
return Mono.just(r);
} }
private boolean validate(String app, String timestamp, String secretKey, String sign) { private boolean validate(String app, String timestamp, String secretKey, String sign) {
StringBuilder b = ThreadContext.getStringBuilder(); StringBuilder b = ThreadContext.getStringBuilder();
b.append(app).append(Consts.S.UNDERLINE).append(timestamp).append(Consts.S.UNDERLINE).append(secretKey); b.append(app) .append(Consts.S.UNDER_LINE)
.append(timestamp).append(Consts.S.UNDER_LINE)
.append(secretKey);
return sign.equalsIgnoreCase(DigestUtils.md532(b.toString())); return sign.equalsIgnoreCase(DigestUtils.md532(b.toString()));
} }
private Mono authSecretkey(ApiConfig ac, App a, String sign) { private Mono<Result<ApiConfig>> authSecretKey(App a, String sign, Result<ApiConfig> r) {
if (StringUtils.isBlank(sign)) { if (StringUtils.isBlank(sign)) {
return logAndResult(a.app + " lack secretkey " + sign, Access.NO_SECRETKEY); r.code = Result.FAIL;
r.msg = a.app + " not present secret key " + sign;
} else if (a.secretkey.equals(sign)) { } else if (a.secretkey.equals(sign)) {
return Mono.just(ac);
} else { } else {
return logAndResult(a.app + " secretkey " + sign + " invalid", Access.SECRETKEY_INVALID); r.code = Result.FAIL;
r.msg = a.app + " secret key " + sign + " invalid";
} }
} return Mono.just(r);
private Mono logAndResult(String msg, Access access) {
log.warn(msg);
return Mono.just(access);
}
private Mono logAndResult(String msg) {
log.warn(msg);
return Mono.just(msg);
} }
private String getTimestamp(HttpHeaders reqHdrs) { private String getTimestamp(HttpHeaders reqHdrs) {
@@ -512,4 +526,169 @@ public class ApiConfigService {
} }
return null; return null;
} }
private static class ApiConfigPathPatternComparator implements Comparator<ApiConfig> {
private final String path;
public ApiConfigPathPatternComparator(String path) {
this.path = path;
}
@Override
public int compare(ApiConfig ac1, ApiConfig ac2) {
String pattern1 = ac1.path, pattern2 = ac2.path;
ApiConfigPathPatternComparator.PatternInfo info1 = new ApiConfigPathPatternComparator.PatternInfo(pattern1);
ApiConfigPathPatternComparator.PatternInfo info2 = new ApiConfigPathPatternComparator.PatternInfo(pattern2);
if (info1.isLeastSpecific() && info2.isLeastSpecific()) {
return 0;
}
else if (info1.isLeastSpecific()) {
return 1;
}
else if (info2.isLeastSpecific()) {
return -1;
}
boolean pattern1EqualsPath = pattern1.equals(this.path);
boolean pattern2EqualsPath = pattern2.equals(this.path);
if (pattern1EqualsPath && pattern2EqualsPath) {
return 0;
}
else if (pattern1EqualsPath) {
return -1;
}
else if (pattern2EqualsPath) {
return 1;
}
if (info1.isPrefixPattern() && info2.isPrefixPattern()) {
return info2.getLength() - info1.getLength();
}
else if (info1.isPrefixPattern() && info2.getDoubleWildcards() == 0) {
return 1;
}
else if (info2.isPrefixPattern() && info1.getDoubleWildcards() == 0) {
return -1;
}
if (info1.getTotalCount() != info2.getTotalCount()) {
return info1.getTotalCount() - info2.getTotalCount();
}
if (info1.getLength() != info2.getLength()) {
return info2.getLength() - info1.getLength();
}
if (info1.getSingleWildcards() < info2.getSingleWildcards()) {
return -1;
}
else if (info2.getSingleWildcards() < info1.getSingleWildcards()) {
return 1;
}
if (info1.getUriVars() < info2.getUriVars()) {
return -1;
}
else if (info2.getUriVars() < info1.getUriVars()) {
return 1;
}
return 0;
}
private static class PatternInfo {
private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{[^/]+?}");
@Nullable
private final String pattern;
private int uriVars;
private int singleWildcards;
private int doubleWildcards;
private boolean catchAllPattern;
private boolean prefixPattern;
@Nullable
private Integer length;
public PatternInfo(@Nullable String pattern) {
this.pattern = pattern;
if (this.pattern != null) {
initCounters();
this.catchAllPattern = this.pattern.equals("/**");
this.prefixPattern = !this.catchAllPattern && this.pattern.endsWith("/**");
}
if (this.uriVars == 0) {
this.length = (this.pattern != null ? this.pattern.length() : 0);
}
}
protected void initCounters() {
int pos = 0;
if (this.pattern != null) {
while (pos < this.pattern.length()) {
if (this.pattern.charAt(pos) == '{') {
this.uriVars++;
pos++;
}
else if (this.pattern.charAt(pos) == '*') {
if (pos + 1 < this.pattern.length() && this.pattern.charAt(pos + 1) == '*') {
this.doubleWildcards++;
pos += 2;
}
else if (pos > 0 && !this.pattern.substring(pos - 1).equals(".*")) {
this.singleWildcards++;
pos++;
}
else {
pos++;
}
}
else {
pos++;
}
}
}
}
public int getUriVars() {
return this.uriVars;
}
public int getSingleWildcards() {
return this.singleWildcards;
}
public int getDoubleWildcards() {
return this.doubleWildcards;
}
public boolean isLeastSpecific() {
return (this.pattern == null || this.catchAllPattern);
}
public boolean isPrefixPattern() {
return this.prefixPattern;
}
public int getTotalCount() {
return this.uriVars + this.singleWildcards + (2 * this.doubleWildcards);
}
public int getLength() {
if (this.length == null) {
this.length = (this.pattern != null ?
VARIABLE_PATTERN.matcher(this.pattern).replaceAll("#").length() : 0);
}
return this.length;
}
}
}
} }

View File

@@ -35,9 +35,9 @@ public class App {
public static final int DELETED = 1; public static final int DELETED = 1;
static interface AUTH_TYPE { static interface AUTH_TYPE {
static final int SIGN = 1; static final int SIGN = 1;
static final int CUSTOM = 2; static final int CUSTOM = 2;
static final int SECRETKEY = 3; static final int SECRET_KEY = 3;
} }
public int isDeleted = 0; // tb_app_auth.is_deleted public int isDeleted = 0; // tb_app_auth.is_deleted
@@ -61,7 +61,7 @@ public class App {
public Map<String, List<String[]>> ips = new HashMap<>(); public Map<String, List<String[]>> ips = new HashMap<>();
public void setUseAuth(int i) { public void setUseAuth(int i) {
if (i == AUTH_TYPE.SIGN || i == AUTH_TYPE.SECRETKEY || i == AUTH_TYPE.CUSTOM) { if (i == AUTH_TYPE.SIGN || i == AUTH_TYPE.SECRET_KEY || i == AUTH_TYPE.CUSTOM) {
useAuth = true; useAuth = true;
} }
} }

View File

@@ -32,6 +32,7 @@ import java.util.Map;
/** /**
* @author hongqiaowei * @author hongqiaowei
* @apiNote unstable.
*/ */
@Component(AuthPluginFilter.AUTH_PLUGIN_FILTER) @Component(AuthPluginFilter.AUTH_PLUGIN_FILTER)
@@ -39,19 +40,19 @@ public class AuthPluginFilter extends PluginFilter {
private static final Logger log = LoggerFactory.getLogger(AuthPluginFilter.class); private static final Logger log = LoggerFactory.getLogger(AuthPluginFilter.class);
public static final String AUTH_PLUGIN_FILTER = "authPlugin"; public static final String AUTH_PLUGIN_FILTER = "authPlugin";
public static final String RESULT = "result"; public static final String RESULT = "result";
@Resource @Resource
private ApiConfigService apiConfigService; private ApiConfigService apiConfigService;
@Override @Override
public Mono<Void> doFilter(ServerWebExchange exchange, Map<String, Object> config, String fixedConfig) { public Mono<Void> doFilter(ServerWebExchange exchange, Map<String, Object> config, String pluginConfig) {
return apiConfigService.canAccess(exchange).flatMap( return apiConfigService.auth(exchange).flatMap(
r -> { r -> {
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
log.debug("req auth: " + r, LogService.BIZ_ID, WebUtils.getTraceId(exchange)); log.debug("req auth: {}", r, LogService.BIZ_ID, WebUtils.getTraceId(exchange));
} }
Map<String, Object> data = Collections.singletonMap(RESULT, r); Map<String, Object> data = Collections.singletonMap(RESULT, r);
return WebUtils.transmitSuccessFilterResultAndEmptyMono(exchange, AUTH_PLUGIN_FILTER, data); return WebUtils.transmitSuccessFilterResultAndEmptyMono(exchange, AUTH_PLUGIN_FILTER, data);

View File

@@ -21,6 +21,8 @@ import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
/** /**
* extend AbstractCustomAuth instead implement this class.
*
* @author hongqiaowei * @author hongqiaowei
*/ */
@@ -28,6 +30,8 @@ public interface CustomAuth {
/** /**
* 认证通过返回 Mono<Access.YES>, 不通过返回 Mono<Access.CUSTOM_AUTH_REJECT> * 认证通过返回 Mono<Access.YES>, 不通过返回 Mono<Access.CUSTOM_AUTH_REJECT>
*
* @deprecated
*/ */
Mono<ApiConfigService.Access> auth(ServerWebExchange exchange, String appId, String ip, String timestamp, String sign, App fizzAppConfig); Mono<ApiConfigService.Access> auth(ServerWebExchange exchange, String appId, String ip, String timestamp, String sign, App fizzAppConfig);
} }

View File

@@ -31,6 +31,7 @@ import java.util.Set;
* @author hongqiaowei * @author hongqiaowei
*/ */
@Deprecated
public class GatewayGroup2apiConfig { public class GatewayGroup2apiConfig {
private Map<String/*gg*/, Set<ApiConfig>> configMap = new HashMap<>(8); private Map<String/*gg*/, Set<ApiConfig>> configMap = new HashMap<>(8);

View File

@@ -32,134 +32,125 @@ import java.util.*;
public class ServiceConfig { public class ServiceConfig {
private static final Logger log = LoggerFactory.getLogger(ServiceConfig.class); private static final Logger log = LoggerFactory.getLogger(ServiceConfig.class);
private static final String gg2acs = "$gg2acs"; private static final String gmpT = "gmpT";
private static final String acs = "$acs"; private static final String gsmpT = "gsmpT";
public String id; private String id;
@JsonIgnore public Map<String/*gateway group*/,
public Map<Integer, ApiConfig> apiConfigMap = new HashMap<>(); Map<Object/*method*/,
Map<String/*path patten*/, ApiConfig>
public Map<String, Map<Object, GatewayGroup2apiConfig>> path2methodToApiConfigMapMap = new HashMap<>(); >
>
apiConfigMap = new HashMap<>();
public ServiceConfig(String id) { public ServiceConfig(String id) {
this.id = id; this.id = id;
} }
public void add(ApiConfig ac) { public void add(ApiConfig ac) {
apiConfigMap.put(ac.id, ac); for (String gatewayGroup : ac.gatewayGroups) {
Map<Object, GatewayGroup2apiConfig> method2apiConfigMap = path2methodToApiConfigMapMap.get(ac.path); Map<Object, Map<String, ApiConfig>> method2pathPattenMap = apiConfigMap.get(gatewayGroup);
if (method2apiConfigMap == null) { if (method2pathPattenMap == null) {
method2apiConfigMap = new HashMap<Object, GatewayGroup2apiConfig>(); method2pathPattenMap = new HashMap<>();
GatewayGroup2apiConfig gatewayGroup2apiConfig = new GatewayGroup2apiConfig(); apiConfigMap.put(gatewayGroup, method2pathPattenMap);
gatewayGroup2apiConfig.add(ac);
method2apiConfigMap.put(ac.fizzMethod, gatewayGroup2apiConfig);
path2methodToApiConfigMapMap.put(ac.path, method2apiConfigMap);
} else {
GatewayGroup2apiConfig gatewayGroup2apiConfig = method2apiConfigMap.get(ac.fizzMethod);
if (gatewayGroup2apiConfig == null) {
gatewayGroup2apiConfig = new GatewayGroup2apiConfig();
method2apiConfigMap.put(ac.fizzMethod, gatewayGroup2apiConfig);
} }
gatewayGroup2apiConfig.add(ac); Map<String, ApiConfig> pathPattern2apiConfigMap = method2pathPattenMap.get(ac.fizzMethod);
if (pathPattern2apiConfigMap == null) {
pathPattern2apiConfigMap = new HashMap<>();
method2pathPattenMap.put(ac.fizzMethod, pathPattern2apiConfigMap);
}
pathPattern2apiConfigMap.put(ac.path, ac);
} }
log.info("add " + ac); log.info("{} service add api config: {}", id, ac);
} }
public void remove(ApiConfig ac) { public void remove(ApiConfig ac) {
ApiConfig remove = apiConfigMap.remove(ac.id); for (String gatewayGroup : ac.gatewayGroups) {
Map<Object, GatewayGroup2apiConfig> method2apiConfigMap = path2methodToApiConfigMapMap.get(ac.path); Map<Object, Map<String, ApiConfig>> method2pathPattenMap = apiConfigMap.get(gatewayGroup);
if (method2apiConfigMap == null) { if (method2pathPattenMap != null) {
log.info("no config to delete for " + ac.service + ' ' + ac.path); Map<String, ApiConfig> pathPattern2apiConfigMap = method2pathPattenMap.get(ac.fizzMethod);
} else { if (pathPattern2apiConfigMap != null) {
GatewayGroup2apiConfig gatewayGroup2apiConfig = method2apiConfigMap.get(ac.fizzMethod); pathPattern2apiConfigMap.remove(ac.path);
if (gatewayGroup2apiConfig == null) {
log.info("no config to delete for " + ac.service + ' ' + ac.fizzMethod + ' ' + ac.path); if (pathPattern2apiConfigMap.isEmpty()) {
} else { method2pathPattenMap.remove(ac.fizzMethod);
log.info(id + " remove " + ac); if (method2pathPattenMap.isEmpty()) {
gatewayGroup2apiConfig.remove(ac); apiConfigMap.remove(gatewayGroup);
if (gatewayGroup2apiConfig.getConfigMap().isEmpty()) { }
method2apiConfigMap.remove(ac.fizzMethod);
if (method2apiConfigMap.isEmpty()) {
path2methodToApiConfigMapMap.remove(ac.path);
} }
} }
} }
} }
log.info("{} service remove api config: {}", id, ac);
} }
public void update(ApiConfig ac) { public void update(ApiConfig ac) {
ApiConfig prev = apiConfigMap.put(ac.id, ac); ApiConfig prevApiConfig = null;
log.info(prev + " is updated by " + ac + " in api config map"); for (String gatewayGroup : ac.gatewayGroups) {
Map<Object, GatewayGroup2apiConfig> method2apiConfigMap = path2methodToApiConfigMapMap.get(ac.path); Map<Object, Map<String, ApiConfig>> method2pathPattenMap = apiConfigMap.get(gatewayGroup);
if (method2apiConfigMap == null) { if (method2pathPattenMap == null) {
method2apiConfigMap = new HashMap<Object, GatewayGroup2apiConfig>(); method2pathPattenMap = new HashMap<>();
GatewayGroup2apiConfig gatewayGroup2apiConfig = new GatewayGroup2apiConfig(); apiConfigMap.put(gatewayGroup, method2pathPattenMap);
gatewayGroup2apiConfig.add(ac);
method2apiConfigMap.put(ac.fizzMethod, gatewayGroup2apiConfig);
path2methodToApiConfigMapMap.put(ac.path, method2apiConfigMap);
} else {
GatewayGroup2apiConfig gatewayGroup2apiConfig = method2apiConfigMap.get(ac.fizzMethod);
if (gatewayGroup2apiConfig == null) {
gatewayGroup2apiConfig = new GatewayGroup2apiConfig();
method2apiConfigMap.put(ac.fizzMethod, gatewayGroup2apiConfig);
gatewayGroup2apiConfig.add(ac);
} else {
log.info(id + " update " + ac);
gatewayGroup2apiConfig.update(ac);
} }
Map<String, ApiConfig> pathPattern2apiConfigMap = method2pathPattenMap.get(ac.fizzMethod);
if (pathPattern2apiConfigMap == null) {
pathPattern2apiConfigMap = new HashMap<>();
method2pathPattenMap.put(ac.fizzMethod, pathPattern2apiConfigMap);
}
prevApiConfig = pathPattern2apiConfigMap.put(ac.path, ac);
} }
log.info("{} service update api config {} with {}", id, prevApiConfig, ac);
} }
@JsonIgnore @JsonIgnore
public List<ApiConfig> getApiConfigs(HttpMethod method, String path, String gatewayGroup) { public List<ApiConfig> getApiConfigs(Set<String> gatewayGroups, HttpMethod method, String path) {
ArrayList<ApiConfig> result = ThreadContext.getArrayList(gsmpT);
List<GatewayGroup2apiConfig> matchGatewayGroup2apiConfigs = ThreadContext.getArrayList(gg2acs); for (String gatewayGroup : gatewayGroups) {
List<ApiConfig> apiConfigs = getApiConfigs(gatewayGroup, method, path);
Set<Map.Entry<String, Map<Object, GatewayGroup2apiConfig>>> es = path2methodToApiConfigMapMap.entrySet(); result.addAll(apiConfigs);
for (Map.Entry<String, Map<Object, GatewayGroup2apiConfig>> e : es) {
Map<Object, GatewayGroup2apiConfig> method2gatewayGroupToApiConfigMap = e.getValue();
GatewayGroup2apiConfig gatewayGroup2apiConfig = method2gatewayGroupToApiConfigMap.get(method);
if (gatewayGroup2apiConfig == null) {
gatewayGroup2apiConfig = method2gatewayGroupToApiConfigMap.get(ApiConfig.ALL_METHOD);
}
if (gatewayGroup2apiConfig != null) {
String pathPattern = e.getKey();
if (ApiConfig.isAntPathPattern(pathPattern)) {
if (UrlTransformUtils.ANT_PATH_MATCHER.match(pathPattern, path)) {
matchGatewayGroup2apiConfigs.add(gatewayGroup2apiConfig);
}
} else if (path.equals(pathPattern)) {
matchGatewayGroup2apiConfigs.add(gatewayGroup2apiConfig);
}
}
} }
return result;
}
if (matchGatewayGroup2apiConfigs.isEmpty()) { @JsonIgnore
ThreadContext.set(ApiConfigService.AUTH_MSG, id + " no route match " + method + ' ' + path); public List<ApiConfig> getApiConfigs(String gatewayGroup, HttpMethod method, String path) {
Map<Object, Map<String, ApiConfig>> method2pathPattenMap = apiConfigMap.get(gatewayGroup);
if (method2pathPattenMap == null) {
return Collections.emptyList(); return Collections.emptyList();
} else { } else {
List<ApiConfig> lst = ThreadContext.getArrayList(acs); ArrayList<ApiConfig> result = ThreadContext.getArrayList(gmpT);
for (int i = 0; i < matchGatewayGroup2apiConfigs.size(); i++) { Map<String, ApiConfig> pathPattern2apiConfigMap = method2pathPattenMap.get(method);
GatewayGroup2apiConfig gatewayGroup2apiConfig = matchGatewayGroup2apiConfigs.get(i); if (pathPattern2apiConfigMap != null) {
Set<ApiConfig> apiConfigs = gatewayGroup2apiConfig.get(gatewayGroup); checkPathPattern(pathPattern2apiConfigMap, path, result);
if (apiConfigs == null) { }
ThreadContext.set(ApiConfigService.AUTH_MSG, "route which match " + id + ' ' + method + ' ' + path + " is not exposed to " + gatewayGroup); pathPattern2apiConfigMap = method2pathPattenMap.get(ApiConfig.ALL_METHOD);
} else { if (pathPattern2apiConfigMap != null) {
for (ApiConfig ac : apiConfigs) { checkPathPattern(pathPattern2apiConfigMap, path, result);
if (ac.access == ApiConfig.ALLOW) { }
lst.add(ac); return result;
} }
}
private void checkPathPattern(Map<String, ApiConfig> pathPattern2apiConfigMap, String path, ArrayList<ApiConfig> result) {
Set<Map.Entry<String, ApiConfig>> entries = pathPattern2apiConfigMap.entrySet();
for (Map.Entry<String, ApiConfig> entry : entries) {
String pathPattern = entry.getKey();
ApiConfig apiConfig = entry.getValue();
if (apiConfig.access == ApiConfig.ALLOW) {
if (apiConfig.exactMatch) {
if (pathPattern.equals(path)) {
result.add(apiConfig);
} }
if (lst.isEmpty()) { } else {
ThreadContext.set(ApiConfigService.AUTH_MSG, "route which match " + id + ' ' + method + ' ' + path + " not allow access"); if (UrlTransformUtils.ANT_PATH_MATCHER.match(pathPattern, path)) {
result.add(apiConfig);
} }
} }
} }
return lst;
} }
} }
} }

View File

@@ -24,21 +24,20 @@ import org.springframework.data.redis.core.ReactiveStringRedisTemplate;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import we.flume.clients.log4j2appender.LogService;
import we.config.AggregateRedisConfig; import we.config.AggregateRedisConfig;
import we.flume.clients.log4j2appender.LogService;
import we.plugin.PluginFilter; import we.plugin.PluginFilter;
import we.plugin.auth.GatewayGroupService; import we.plugin.auth.GatewayGroupService;
import we.util.Consts; import we.util.Consts;
import we.util.ThreadContext; import we.util.ThreadContext;
import we.util.WebUtils; import we.util.WebUtils;
import javax.annotation.PostConstruct;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.Iterator;
import java.util.Map; import java.util.Map;
/** /**
* @author hongqiaowei * @author hongqiaowei
* @apiNote unstable.
*/ */
@Component(StatPluginFilter.STAT_PLUGIN_FILTER) @Component(StatPluginFilter.STAT_PLUGIN_FILTER)
@@ -46,21 +45,21 @@ public class StatPluginFilter extends PluginFilter {
private static final Logger log = LoggerFactory.getLogger(StatPluginFilter.class); private static final Logger log = LoggerFactory.getLogger(StatPluginFilter.class);
public static final String STAT_PLUGIN_FILTER = "statPlugin"; public static final String STAT_PLUGIN_FILTER = "statPlugin";
private static final String ip = "\"ip\":"; private static final String ip = "\"ip\":";
private static final String gatewayGroup = "\"gatewayGroup\":"; private static final String gatewayGroup = "\"gatewayGroup\":";
private static final String service = "\"service\":"; private static final String service = "\"service\":";
private static final String appid = "\"appid\":"; private static final String appid = "\"appid\":";
private static final String apiMethod = "\"apiMethod\":"; private static final String apiMethod = "\"apiMethod\":";
private static final String apiPath = "\"apiPath\":"; private static final String apiPath = "\"apiPath\":";
private static final String reqTime = "\"reqTime\":"; private static final String reqTime = "\"reqTime\":";
@Resource @Resource
private StatPluginFilterProperties statPluginFilterProperties; private StatPluginFilterProperties statPluginFilterProperties;
@@ -71,40 +70,36 @@ public class StatPluginFilter extends PluginFilter {
@Resource @Resource
private GatewayGroupService gatewayGroupService; private GatewayGroupService gatewayGroupService;
/*
private String currentGatewayGroups;
@PostConstruct
public void init() {
Iterator<String> it = gatewayGroupService.currentGatewayGroupSet.iterator();
while (it.hasNext()) {
if (StringUtils.isBlank(currentGatewayGroups)) {
currentGatewayGroups = it.next();
} else {
currentGatewayGroups = currentGatewayGroups + ',' + it.next();
}
}
}
*/
@Override @Override
public Mono<Void> doFilter(ServerWebExchange exchange, Map<String, Object> config, String fixedConfig) { public Mono<Void> doFilter(ServerWebExchange exchange, Map<String, Object> config, String fixedConfig) {
if (statPluginFilterProperties.isStatOpen()) { if (statPluginFilterProperties.isStatOpen()) {
StringBuilder b = ThreadContext.getStringBuilder(); StringBuilder b = ThreadContext.getStringBuilder();
b.append(Consts.S.LEFT_BRACE); b.append(Consts.S.LEFT_BRACE);
b.append(ip); toJsonStringValue(b, WebUtils.getOriginIp(exchange)); b.append(Consts.S.COMMA); b.append(ip);
b.append(gatewayGroup); toJsonStringValue(b, currentGatewayGroups()); b.append(Consts.S.COMMA); toJsonStringValue(b, WebUtils.getOriginIp(exchange));
b.append(service); toJsonStringValue(b, WebUtils.getClientService(exchange)); b.append(Consts.S.COMMA); b.append(Consts.S.COMMA);
b.append(gatewayGroup);
toJsonStringValue(b, currentGatewayGroups());
b.append(Consts.S.COMMA);
b.append(service);
toJsonStringValue(b, WebUtils.getClientService(exchange));
b.append(Consts.S.COMMA);
String appId = WebUtils.getAppId(exchange); String appId = WebUtils.getAppId(exchange);
if (appId != null) { if (appId != null) {
b.append(appid); toJsonStringValue(b, appId); b.append(Consts.S.COMMA); b.append(appid);
toJsonStringValue(b, appId);
b.append(Consts.S.COMMA);
} }
b.append(apiMethod); toJsonStringValue(b, exchange.getRequest().getMethodValue()); b.append(Consts.S.COMMA); b.append(apiMethod);
b.append(apiPath); toJsonStringValue(b, WebUtils.getClientReqPath(exchange)); b.append(Consts.S.COMMA); toJsonStringValue(b, exchange.getRequest().getMethodValue());
b.append(reqTime) .append(System.currentTimeMillis()); b.append(Consts.S.COMMA);
b.append(apiPath);
toJsonStringValue(b, WebUtils.getClientReqPath(exchange));
b.append(Consts.S.COMMA);
b.append(reqTime).append(System.currentTimeMillis());
b.append(Consts.S.RIGHT_BRACE); b.append(Consts.S.RIGHT_BRACE);
if (StringUtils.isBlank(statPluginFilterProperties.getFizzAccessStatTopic())) { if (StringUtils.isBlank(statPluginFilterProperties.getFizzAccessStatTopic())) {

View File

@@ -44,6 +44,7 @@ import we.util.*;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
@@ -205,7 +206,10 @@ public class CallbackService {
public Mono<ReactiveResult> replay(CallbackReplayReq req) { public Mono<ReactiveResult> replay(CallbackReplayReq req) {
ApiConfig ac = apiConfigService.getApiConfig(req.service, req.method, req.path, req.gatewayGroup, req.app); HashSet<String> gatewayGroups = new HashSet<>();
gatewayGroups.add(req.gatewayGroup);
Result<ApiConfig> result = apiConfigService.getApiConfig(gatewayGroups, req.app, req.service, req.method, req.path);
ApiConfig ac = result.data;
if (ac == null) { if (ac == null) {
return Mono.just(ReactiveResult.fail("no api config for " + req.path)); return Mono.just(ReactiveResult.fail("no api config for " + req.path));
} }

View File

@@ -30,29 +30,29 @@ import java.util.List;
public class Route { public class Route {
public byte type; public byte type;
public HttpMethod method; public HttpMethod method;
public String backendService; public String backendService;
public String backendPath; public String backendPath;
public String query; public String query;
public String nextHttpHostPort; public String nextHttpHostPort;
public List<PluginConfig> pluginConfigs; public List<PluginConfig> pluginConfigs;
public String rpcMethod; public String rpcMethod;
public String rpcParamTypes; public String rpcParamTypes;
public String rpcVersion; public String rpcVersion;
public String rpcGroup; public String rpcGroup;
public long timeout = 0; public long timeout = 0;
public Route type(byte t) { public Route type(byte t) {
type = t; type = t;

View File

@@ -174,12 +174,11 @@ public abstract class WebUtils {
} }
public static ApiConfig getApiConfig(ServerWebExchange exchange) { public static ApiConfig getApiConfig(ServerWebExchange exchange) {
Object authRes = getFilterResultDataItem(exchange, AuthPluginFilter.AUTH_PLUGIN_FILTER, AuthPluginFilter.RESULT); Result<ApiConfig> authRes = (Result<ApiConfig>) getFilterResultDataItem(exchange, AuthPluginFilter.AUTH_PLUGIN_FILTER, AuthPluginFilter.RESULT);
if (authRes != null && authRes instanceof ApiConfig) { if (authRes == null) {
return (ApiConfig) authRes;
} else {
return null; return null;
} }
return authRes.data;
} }
public static Route getRoute(ServerWebExchange exchange) { public static Route getRoute(ServerWebExchange exchange) {

View File

@@ -87,7 +87,7 @@ class CodecFuncTests {
assertEquals("QmFzZTY057yW56CB5LuL57uN", result.toString()); assertEquals("QmFzZTY057yW56CB5LuL57uN", result.toString());
} }
@Test // @Test
void testBase64Decode() { void testBase64Decode() {
String funcExpression = "fn.codec.base64Decode(\"QmFzZTY057yW56CB5LuL57uN\")"; String funcExpression = "fn.codec.base64Decode(\"QmFzZTY057yW56CB5LuL57uN\")";
Object result = FuncExecutor.getInstance().exec(null, funcExpression); Object result = FuncExecutor.getInstance().exec(null, funcExpression);