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 ASTERISK = '*';
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 AT = '@';
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.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
@@ -34,9 +35,11 @@ import we.plugin.auth.AuthPluginFilter;
import we.plugin.stat.StatPluginFilter;
import we.proxy.Route;
import we.util.ReactorUtils;
import we.util.Result;
import we.util.WebUtils;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -74,23 +77,28 @@ public class PreprocessFilter extends FizzWebFilter {
return process(exchange, chain, eas, vm);
}
// TODO
// TODO: improve
private Mono<Void> process(ServerWebExchange exchange, WebFilterChain chain, Map<String, Object> eas, Mono vm) {
return chain(exchange, vm, authPluginFilter).defaultIfEmpty(ReactorUtils.NULL)
.flatMap(
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();
if (authRes instanceof ApiConfig) {
ApiConfig ac = (ApiConfig) authRes;
ApiConfig ac = authRes.data;
if (ac == null) {
afterAuth(exchange, null, null);
m = executeFixedPluginFilters(exchange);
return m.defaultIfEmpty(ReactorUtils.NULL).flatMap(func(exchange, chain));
}
Route route = ac.getRoute(exchange);
exchange.getAttributes().put(WebUtils.ROUTE, route);
eas.put(WebUtils.ROUTE, route);
afterAuth(exchange, ac, route);
m = executeFixedPluginFilters(exchange);
m = m.defaultIfEmpty(ReactorUtils.NULL);
if (route.pluginConfigs == null || route.pluginConfigs.isEmpty()) {
if (CollectionUtils.isEmpty(route.pluginConfigs)) {
return m.flatMap(func(exchange, chain));
} else {
return m.flatMap(
@@ -100,20 +108,6 @@ public class PreprocessFilter extends FizzWebFilter {
}
);
}
} else if (authRes == ApiConfigService.Access.YES) {
afterAuth(exchange, null, null);
m = executeFixedPluginFilters(exchange);
return m.defaultIfEmpty(ReactorUtils.NULL).flatMap(func(exchange, chain));
} else {
String err = null;
if (authRes instanceof ApiConfigService.Access) {
ApiConfigService.Access access = (ApiConfigService.Access) authRes;
err = access.getReason();
} 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) {
Iterator<PluginConfig> it = exchange.getAttribute(pluginConfigsIt);
if (it == null) {
List<PluginConfig> pcs = WebUtils.getApiConfig(exchange).pluginConfigs;
List<PluginConfig> pcs = WebUtils.getRoute(exchange).pluginConfigs;
it = pcs.iterator();
Map<String, Object> attris = exchange.getAttributes();
attris.put(pluginConfigsIt, it);

View File

@@ -30,13 +30,13 @@ import java.util.Map;
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 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)
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 FORBID = 'f';
// public static final char FORBID = 'f';
public static final String ALL_METHOD = "AM";
@@ -62,25 +62,36 @@ public class ApiConfig {
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
@JsonProperty(
access = JsonProperty.Access.WRITE_ONLY
)
public int isDeleted = 0; // tb_api_auth.is_deleted
public Set<String> gatewayGroups = Stream.of(GatewayGroup.DEFAULT).collect(Collectors.toSet());
public String service; // a
public String service;
public String backendService;
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
@JsonProperty(
access = JsonProperty.Access.WRITE_ONLY
)
public HttpMethod method;
public Object fizzMethod = ALL_METHOD;
public String path = match_all;
@JsonProperty(
access = JsonProperty.Access.WRITE_ONLY
)
public boolean exactMatch = false;
public String backendPath;

View File

@@ -25,7 +25,9 @@ import org.springframework.data.redis.core.ReactiveStringRedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -41,6 +43,7 @@ import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import java.util.regex.Pattern;
/**
* @author hongqiaowei
@@ -51,11 +54,7 @@ public class ApiConfigService {
private static final Logger log = LoggerFactory.getLogger(ApiConfigService.class);
private static final String mpps = "$mpps";
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";
private static final String macs = "macsT";
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 {
this.init(null);
initPlugin();
}
// TODO: no need like this
private void init(Supplier<Mono<Throwable>> doAfterLoadCache) throws Throwable {
Map<Integer, ApiConfig> apiConfigMapTmp = new HashMap<>(128);
Map<String, ServiceConfig> serviceConfigMapTmp = new HashMap<>(128);
@@ -129,7 +130,7 @@ public class ApiConfigService {
return Flux.just(e);
}
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;
try {
ApiConfig ac = JacksonUtils.readValue(json, ApiConfig.class);
@@ -138,7 +139,7 @@ public class ApiConfigService {
return Flux.just(e);
} catch (Throwable t) {
throwable[0] = t;
log.info(json, t);
log.error("deser {}", json, t);
return Flux.error(t);
}
}).blockLast())).flatMap(
@@ -146,7 +147,6 @@ public class ApiConfigService {
if (throwable[0] != null) {
return Mono.error(throwable[0]);
}
if (doAfterLoadCache != null) {
return doAfterLoadCache.get();
} else {
@@ -157,26 +157,27 @@ public class ApiConfigService {
if (error != ReactorUtils.EMPTY_THROWABLE) {
throw error;
}
this.apiConfigMap = apiConfigMapTmp;
this.serviceConfigMap = serviceConfigMapTmp;
}
// TODO: no need like this
private Mono<Throwable> lsnApiConfigChange() {
final Throwable[] throwable = new Throwable[1];
final boolean[] b = {false};
rt.listenToChannel(apiConfigServiceProperties.getFizzApiConfigChannel()).doOnError(t -> {
String ch = apiConfigServiceProperties.getFizzApiConfigChannel();
rt.listenToChannel(ch).doOnError(t -> {
throwable[0] = t;
b[0] = false;
log.error("lsn " + apiConfigServiceProperties.getFizzApiConfigChannel(), t);
log.error("lsn {}", ch, t);
}).doOnSubscribe(
s -> {
b[0] = true;
log.info("success to lsn on " + apiConfigServiceProperties.getFizzApiConfigChannel());
log.info("success to lsn on {}", ch);
}
).doOnNext(msg -> {
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 {
ApiConfig ac = JacksonUtils.readValue(json, ApiConfig.class);
ApiConfig r = apiConfigMap.remove(ac.id);
@@ -191,7 +192,7 @@ public class ApiConfigService {
apiConifg2appsService.remove(ac.id);
}
} catch (Throwable t) {
log.info(json, t);
log.error("deser {}", json, t);
}
}).subscribe();
Throwable t = throwable[0];
@@ -295,14 +296,11 @@ public class ApiConfigService {
private void updateServiceConfigMap(ApiConfig ac, Map<String, ServiceConfig> serviceConfigMap) {
ServiceConfig sc = serviceConfigMap.get(ac.service);
if (ac.isDeleted == ApiConfig.DELETED) {
if (sc == null) {
log.info("no " + ac.service + " config to delete");
} else {
if (sc != null) {
sc.remove(ac);
if (sc.path2methodToApiConfigMapMap.isEmpty()) {
if (sc.apiConfigMap.isEmpty()) {
serviceConfigMap.remove(ac.service);
}
// apiConifg2appsService.remove(ac.id);
}
} else {
if (sc == null) {
@@ -315,6 +313,9 @@ public class ApiConfigService {
}
}
/**
* @deprecated
*/
public enum Access {
YES (null),
@@ -344,151 +345,164 @@ public class ApiConfigService {
}
}
public ApiConfig getApiConfig(String app, String service, HttpMethod method, String path) {
ApiConfig ac = null;
for (String g : gatewayGroupService.currentGatewayGroupSet) {
ac = getApiConfig(service, method, path, g, app);
if (ac != null) {
return ac;
}
}
return ac;
public Result<ApiConfig> getApiConfig(String app, String service, HttpMethod method, String path) {
return getApiConfig(null, app, service, method, path);
}
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);
if (sc != null) {
List<ApiConfig> apiConfigs = sc.getApiConfigs(method, path, gatewayGroup);
if (!apiConfigs.isEmpty()) {
List<String> matchPathPatterns = ThreadContext.getArrayList(mpps);
if (sc == null) {
return Result.fail("no " + service + " config");
}
if (CollectionUtils.isEmpty(gatewayGroups)) {
gatewayGroups = gatewayGroupService.currentGatewayGroupSet;
}
List<ApiConfig> apiConfigs = sc.getApiConfigs(gatewayGroups, method, path);
if (apiConfigs.isEmpty()) {
return Result.fail(service + " don't have api config matching " + gatewayGroups + " group " + method + " method " + path + " path");
}
List<ApiConfig> appCanAccess = ThreadContext.getArrayList(macs);
for (int i = 0; i < apiConfigs.size(); i++) {
ApiConfig ac = apiConfigs.get(i);
if (ac.checkApp) {
if (apiConifg2appsService.contains(ac.id, app)) {
matchPathPatterns.add(ac.path);
if (StringUtils.isNotBlank(app) && apiConifg2appsService.contains(ac.id, app)) {
appCanAccess.add(ac);
}
} else {
matchPathPatterns.add(ac.path);
appCanAccess.add(ac);
}
}
if (matchPathPatterns.isEmpty()) {
if (app == null) {
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 (!matchPathPatterns.isEmpty()) {
if (matchPathPatterns.size() > 1) {
Collections.sort(matchPathPatterns, UrlTransformUtils.ANT_PATH_MATCHER.getPatternComparator(path));
}
String bestPathPattern = matchPathPatterns.get(0);
for (int i = 0; i < apiConfigs.size(); i++) {
ApiConfig ac = apiConfigs.get(i);
if (StringUtils.equals(ac.path, bestPathPattern)) {
return ac;
}
if (appCanAccess.isEmpty()) {
return Result.fail("app " + app + " can't access " + JacksonUtils.writeValueAsString(apiConfigs));
}
ApiConfig bestOne = appCanAccess.get(0);
if (appCanAccess.size() != 1) {
appCanAccess.sort(new ApiConfigPathPatternComparator(path));
ApiConfig ac0 = appCanAccess.get(0);
bestOne = ac0;
ApiConfig ac1 = appCanAccess.get(1);
if (ac0.path.equals(ac1.path)) {
if (ac0.fizzMethod == ac1.fizzMethod) {
if (StringUtils.isNotBlank(app)) {
if (!ac0.checkApp) {
bestOne = ac1;
}
}
} else {
ThreadContext.set(ApiConfigService.AUTH_MSG, "no " + service + " service config");
if (ac0.fizzMethod == ApiConfig.ALL_METHOD) {
bestOne = ac1;
}
return null;
}
}
}
return Result.succ(bestOne);
}
public Mono<Object> canAccess(ServerWebExchange exchange) {
public Mono<Result<ApiConfig>> auth(ServerWebExchange exchange) {
ServerHttpRequest req = exchange.getRequest();
HttpHeaders hdrs = req.getHeaders();
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));
}
// TODO: improve ...
private Mono<Object> canAccess(ServerWebExchange exchange, String app, String ip, String timestamp, String sign, String service, HttpMethod method, String path) {
private Mono<Result<ApiConfig>> auth(ServerWebExchange exchange, String app, String ip, String timestamp, String sign, String service, HttpMethod method, String path) {
if (!systemConfig.isAggregateTestAuth()) {
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);
if (ac == null) {
String authMsg = (String) ThreadContext.remove(AUTH_MSG);
if (authMsg == null) {
authMsg = deny;
}
if (!apiConfigServiceProperties.isNeedAuth()) {
return Mono.just(Access.YES);
Result<ApiConfig> r = getApiConfig(app, service, method, path);
if (r.code == Result.FAIL) {
if (apiConfigServiceProperties.isNeedAuth()) {
return Mono.just(r);
} else {
return logAndResult(authMsg);
return Mono.just(Result.succ());
}
}
} else if (ac.checkApp) {
ApiConfig ac = r.data;
if (ac.checkApp) {
App a = appService.getApp(app);
if (a.useWhiteList && !a.allow(ip)) {
return logAndResult(ip + " not in " + app + " white list", Access.IP_NOT_IN_WHITE_LIST);
} else if (a.useAuth) {
r.code = Result.FAIL;
r.msg = ip + " not in " + app + " app white list";
return Mono.just(r);
}
if (a.useAuth) {
if (a.authType == App.AUTH_TYPE.SIGN) {
return authSign(ac, a, timestamp, sign);
} else if (a.authType == App.AUTH_TYPE.SECRETKEY) {
return authSecretkey(ac, a, sign);
return authSign(a, timestamp, sign, r);
} else if (a.authType == App.AUTH_TYPE.SECRET_KEY) {
return authSecretKey(a, sign, r);
} else if (customAuth == null) {
return logAndResult(app + " no custom auth", Access.NO_CUSTOM_AUTH);
r.code = Result.FAIL;
r.msg = "no custom auth bean for " + app;
return Mono.just(r);
} else {
return customAuth.auth(exchange, app, ip, timestamp, sign, a).flatMap(v -> {
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(ac);
return Mono.just(r);
} else {
return Mono.just(Access.CUSTOM_AUTH_REJECT);
r.code = Result.FAIL;
r.msg = v.getReason();
return Mono.just(r);
}
});
}
} else {
return Mono.just(ac);
);
}
}
}
}
return Mono.just(r);
}
} else {
return Mono.just(ac);
}
}
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)) {
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)) {
return Mono.just(ac);
} 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) {
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()));
}
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)) {
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)) {
return Mono.just(ac);
} else {
return logAndResult(a.app + " secretkey " + sign + " invalid", Access.SECRETKEY_INVALID);
r.code = Result.FAIL;
r.msg = a.app + " secret key " + sign + " invalid";
}
}
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);
return Mono.just(r);
}
private String getTimestamp(HttpHeaders reqHdrs) {
@@ -512,4 +526,169 @@ public class ApiConfigService {
}
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

@@ -37,7 +37,7 @@ public class App {
static interface AUTH_TYPE {
static final int SIGN = 1;
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
@@ -61,7 +61,7 @@ public class App {
public Map<String, List<String[]>> ips = new HashMap<>();
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;
}
}

View File

@@ -32,6 +32,7 @@ import java.util.Map;
/**
* @author hongqiaowei
* @apiNote unstable.
*/
@Component(AuthPluginFilter.AUTH_PLUGIN_FILTER)
@@ -47,11 +48,11 @@ public class AuthPluginFilter extends PluginFilter {
private ApiConfigService apiConfigService;
@Override
public Mono<Void> doFilter(ServerWebExchange exchange, Map<String, Object> config, String fixedConfig) {
return apiConfigService.canAccess(exchange).flatMap(
public Mono<Void> doFilter(ServerWebExchange exchange, Map<String, Object> config, String pluginConfig) {
return apiConfigService.auth(exchange).flatMap(
r -> {
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);
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;
/**
* extend AbstractCustomAuth instead implement this class.
*
* @author hongqiaowei
*/
@@ -28,6 +30,8 @@ public interface CustomAuth {
/**
* 认证通过返回 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);
}

View File

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

View File

@@ -34,132 +34,123 @@ public class ServiceConfig {
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<Integer, ApiConfig> apiConfigMap = new HashMap<>();
public Map<String, Map<Object, GatewayGroup2apiConfig>> path2methodToApiConfigMapMap = new HashMap<>();
public Map<String/*gateway group*/,
Map<Object/*method*/,
Map<String/*path patten*/, ApiConfig>
>
>
apiConfigMap = new HashMap<>();
public ServiceConfig(String id) {
this.id = id;
}
public void add(ApiConfig ac) {
apiConfigMap.put(ac.id, ac);
Map<Object, GatewayGroup2apiConfig> method2apiConfigMap = path2methodToApiConfigMapMap.get(ac.path);
if (method2apiConfigMap == null) {
method2apiConfigMap = new HashMap<Object, GatewayGroup2apiConfig>();
GatewayGroup2apiConfig gatewayGroup2apiConfig = new GatewayGroup2apiConfig();
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);
for (String gatewayGroup : ac.gatewayGroups) {
Map<Object, Map<String, ApiConfig>> method2pathPattenMap = apiConfigMap.get(gatewayGroup);
if (method2pathPattenMap == null) {
method2pathPattenMap = new HashMap<>();
apiConfigMap.put(gatewayGroup, method2pathPattenMap);
}
gatewayGroup2apiConfig.add(ac);
Map<String, ApiConfig> pathPattern2apiConfigMap = method2pathPattenMap.get(ac.fizzMethod);
if (pathPattern2apiConfigMap == null) {
pathPattern2apiConfigMap = new HashMap<>();
method2pathPattenMap.put(ac.fizzMethod, pathPattern2apiConfigMap);
}
log.info("add " + ac);
pathPattern2apiConfigMap.put(ac.path, ac);
}
log.info("{} service add api config: {}", id, ac);
}
public void remove(ApiConfig ac) {
ApiConfig remove = apiConfigMap.remove(ac.id);
Map<Object, GatewayGroup2apiConfig> method2apiConfigMap = path2methodToApiConfigMapMap.get(ac.path);
if (method2apiConfigMap == null) {
log.info("no config to delete for " + ac.service + ' ' + ac.path);
} else {
GatewayGroup2apiConfig gatewayGroup2apiConfig = method2apiConfigMap.get(ac.fizzMethod);
if (gatewayGroup2apiConfig == null) {
log.info("no config to delete for " + ac.service + ' ' + ac.fizzMethod + ' ' + ac.path);
} else {
log.info(id + " remove " + ac);
gatewayGroup2apiConfig.remove(ac);
if (gatewayGroup2apiConfig.getConfigMap().isEmpty()) {
method2apiConfigMap.remove(ac.fizzMethod);
if (method2apiConfigMap.isEmpty()) {
path2methodToApiConfigMapMap.remove(ac.path);
for (String gatewayGroup : ac.gatewayGroups) {
Map<Object, Map<String, ApiConfig>> method2pathPattenMap = apiConfigMap.get(gatewayGroup);
if (method2pathPattenMap != null) {
Map<String, ApiConfig> pathPattern2apiConfigMap = method2pathPattenMap.get(ac.fizzMethod);
if (pathPattern2apiConfigMap != null) {
pathPattern2apiConfigMap.remove(ac.path);
if (pathPattern2apiConfigMap.isEmpty()) {
method2pathPattenMap.remove(ac.fizzMethod);
if (method2pathPattenMap.isEmpty()) {
apiConfigMap.remove(gatewayGroup);
}
}
}
}
}
log.info("{} service remove api config: {}", id, ac);
}
public void update(ApiConfig ac) {
ApiConfig prev = apiConfigMap.put(ac.id, ac);
log.info(prev + " is updated by " + ac + " in api config map");
Map<Object, GatewayGroup2apiConfig> method2apiConfigMap = path2methodToApiConfigMapMap.get(ac.path);
if (method2apiConfigMap == null) {
method2apiConfigMap = new HashMap<Object, GatewayGroup2apiConfig>();
GatewayGroup2apiConfig gatewayGroup2apiConfig = new GatewayGroup2apiConfig();
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);
ApiConfig prevApiConfig = null;
for (String gatewayGroup : ac.gatewayGroups) {
Map<Object, Map<String, ApiConfig>> method2pathPattenMap = apiConfigMap.get(gatewayGroup);
if (method2pathPattenMap == null) {
method2pathPattenMap = new HashMap<>();
apiConfigMap.put(gatewayGroup, method2pathPattenMap);
}
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
public List<ApiConfig> getApiConfigs(HttpMethod method, String path, String gatewayGroup) {
List<GatewayGroup2apiConfig> matchGatewayGroup2apiConfigs = ThreadContext.getArrayList(gg2acs);
Set<Map.Entry<String, Map<Object, GatewayGroup2apiConfig>>> es = path2methodToApiConfigMapMap.entrySet();
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);
}
public List<ApiConfig> getApiConfigs(Set<String> gatewayGroups, HttpMethod method, String path) {
ArrayList<ApiConfig> result = ThreadContext.getArrayList(gsmpT);
for (String gatewayGroup : gatewayGroups) {
List<ApiConfig> apiConfigs = getApiConfigs(gatewayGroup, method, path);
result.addAll(apiConfigs);
}
return result;
}
if (matchGatewayGroup2apiConfigs.isEmpty()) {
ThreadContext.set(ApiConfigService.AUTH_MSG, id + " no route match " + method + ' ' + path);
@JsonIgnore
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();
} else {
List<ApiConfig> lst = ThreadContext.getArrayList(acs);
for (int i = 0; i < matchGatewayGroup2apiConfigs.size(); i++) {
GatewayGroup2apiConfig gatewayGroup2apiConfig = matchGatewayGroup2apiConfigs.get(i);
Set<ApiConfig> apiConfigs = gatewayGroup2apiConfig.get(gatewayGroup);
if (apiConfigs == null) {
ThreadContext.set(ApiConfigService.AUTH_MSG, "route which match " + id + ' ' + method + ' ' + path + " is not exposed to " + gatewayGroup);
ArrayList<ApiConfig> result = ThreadContext.getArrayList(gmpT);
Map<String, ApiConfig> pathPattern2apiConfigMap = method2pathPattenMap.get(method);
if (pathPattern2apiConfigMap != null) {
checkPathPattern(pathPattern2apiConfigMap, path, result);
}
pathPattern2apiConfigMap = method2pathPattenMap.get(ApiConfig.ALL_METHOD);
if (pathPattern2apiConfigMap != null) {
checkPathPattern(pathPattern2apiConfigMap, path, result);
}
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);
}
} else {
for (ApiConfig ac : apiConfigs) {
if (ac.access == ApiConfig.ALLOW) {
lst.add(ac);
}
}
if (lst.isEmpty()) {
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.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import we.flume.clients.log4j2appender.LogService;
import we.config.AggregateRedisConfig;
import we.flume.clients.log4j2appender.LogService;
import we.plugin.PluginFilter;
import we.plugin.auth.GatewayGroupService;
import we.util.Consts;
import we.util.ThreadContext;
import we.util.WebUtils;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.Iterator;
import java.util.Map;
/**
* @author hongqiaowei
* @apiNote unstable.
*/
@Component(StatPluginFilter.STAT_PLUGIN_FILTER)
@@ -71,39 +70,35 @@ public class StatPluginFilter extends PluginFilter {
@Resource
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
public Mono<Void> doFilter(ServerWebExchange exchange, Map<String, Object> config, String fixedConfig) {
if (statPluginFilterProperties.isStatOpen()) {
StringBuilder b = ThreadContext.getStringBuilder();
b.append(Consts.S.LEFT_BRACE);
b.append(ip); toJsonStringValue(b, WebUtils.getOriginIp(exchange)); 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);
b.append(ip);
toJsonStringValue(b, WebUtils.getOriginIp(exchange));
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);
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(apiPath); toJsonStringValue(b, WebUtils.getClientReqPath(exchange)); b.append(Consts.S.COMMA);
b.append(apiMethod);
toJsonStringValue(b, exchange.getRequest().getMethodValue());
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);

View File

@@ -44,6 +44,7 @@ import we.util.*;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
@@ -205,7 +206,10 @@ public class CallbackService {
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) {
return Mono.just(ReactiveResult.fail("no api config for " + req.path));
}

View File

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

View File

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