diff --git a/README.en-us.md b/README.en-us.md index 8246652..84bcc38 100644 --- a/README.en-us.md +++ b/README.en-us.md @@ -27,7 +27,7 @@ http://demo.fizzgate.com/ account/password:`admin`/`Aa123!` -health checking url:http://demo.fizzgate.com/serviceConfigs +health checking url:http://demo.fizzgate.com/admin/health API access:http://demo.fizzgate.com/proxy/[Service Name]/[API Path] diff --git a/README.md b/README.md index 8b3b148..0765cb5 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ http://demo.fizzgate.com/ 账号/密码:`admin`/`Aa123!` -健康检查地址:http://demo.fizzgate.com/serviceConfigs +健康检查地址:http://demo.fizzgate.com/admin/health API地址:http://demo.fizzgate.com/proxy/[服务名]/[API Path] diff --git a/src/main/java/we/controller/ConfigController.java b/src/main/java/we/controller/ConfigController.java index fe0e39d..953d82c 100644 --- a/src/main/java/we/controller/ConfigController.java +++ b/src/main/java/we/controller/ConfigController.java @@ -34,7 +34,7 @@ import java.nio.charset.StandardCharsets; */ @RestController -@RequestMapping(value = "/config") +@RequestMapping(value = "/admin/config") public class ConfigController { @Resource diff --git a/src/main/java/we/controller/HealthCheckController.java b/src/main/java/we/controller/HealthCheckController.java new file mode 100644 index 0000000..e1e6765 --- /dev/null +++ b/src/main/java/we/controller/HealthCheckController.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 the original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package we.controller; + +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.plugin.auth.ApiConfigService; +import we.plugin.auth.AppService; +import we.plugin.auth.GatewayGroupService; +import we.stats.ratelimit.ResourceRateLimitConfigService; +import we.util.Constants; +import we.util.DateTimeUtils; +import we.util.JacksonUtils; + +import javax.annotation.Resource; +import java.time.LocalDateTime; + +/** + * @author hongqiaowei + */ + +@RestController +@RequestMapping("/admin") +public class HealthCheckController { + + @GetMapping("/health") + public Mono health(ServerWebExchange exchange) { + long mills = System.currentTimeMillis(); + String now = DateTimeUtils.toDate(mills, Constants.DatetimePattern.DP23); + return Mono.just(now + " ok"); + } +} diff --git a/src/main/java/we/controller/ManagerConfigController.java b/src/main/java/we/controller/ManagerConfigController.java index eb2929b..9ec623a 100644 --- a/src/main/java/we/controller/ManagerConfigController.java +++ b/src/main/java/we/controller/ManagerConfigController.java @@ -43,7 +43,7 @@ import java.util.stream.Collectors; * @author zhongjie */ @RestController -@RequestMapping(value = "/managerConfig") +@RequestMapping(value = "/admin/managerConfig") public class ManagerConfigController { @NacosValue(value = "${fizz.manager.config.key:fizz-manager-key}", autoRefreshed = true) @Value("${fizz.manager.config.key:fizz-manager-key}") diff --git a/src/main/java/we/plugin/auth/ApiConfig.java b/src/main/java/we/plugin/auth/ApiConfig.java index 21b3798..8a0794c 100644 --- a/src/main/java/we/plugin/auth/ApiConfig.java +++ b/src/main/java/we/plugin/auth/ApiConfig.java @@ -53,10 +53,12 @@ public class ApiConfig { private static final String match_all = "/**"; - // @JsonIgnore + private static final int ENABLE = 1; + + private static final int UNABLE = 0; + public int id; // tb_api_auth.id - // @JsonIgnore public int isDeleted = 0; // tb_api_auth.is_deleted public Set gatewayGroups = Stream.of(GatewayGroup.DEFAULT).collect(Collectors.toSet()); @@ -67,28 +69,27 @@ public class ApiConfig { public HttpMethod method = HttpMethod.X; -// public String path = String.valueOf(Constants.Symbol.FORWARD_SLASH); public String path = match_all; public boolean exactMatch = false; public String backendPath; - public Set apps = Stream.of(App.ALL_APP).collect(Collectors.toSet()); +// public Set apps = Stream.of(App.ALL_APP).collect(Collectors.toSet()); @JsonProperty("proxyMode") public byte type = Type.SERVICE_DISCOVERY; private AtomicInteger counter = new AtomicInteger(-1); -// public List backendUrls; - public List httpHostPorts; public char access = ALLOW; public List pluginConfigs; + public boolean checkApp = false; + public static boolean isAntPathPattern(String path) { boolean uriVar = false; for (int i = 0; i < path.length(); i++) { @@ -120,18 +121,18 @@ public class ApiConfig { } } - public void setApp(String as) { - apps.remove(App.ALL_APP); - if (StringUtils.isBlank(as)) { - apps.add("*"); - } else { - Arrays.stream(StringUtils.split(as, ',')).forEach( - a -> { - apps.add(a.trim()); - } - ); - } - } + // public void setApp(String as) { + // apps.remove(App.ALL_APP); + // if (StringUtils.isBlank(as)) { + // apps.add("*"); + // } else { + // Arrays.stream(StringUtils.split(as, ',')).forEach( + // a -> { + // apps.add(a.trim()); + // } + // ); + // } + // } public void setPath(String p) { if (StringUtils.isNotBlank(p)) { @@ -155,15 +156,13 @@ public class ApiConfig { } } - // @JsonIgnore - // public String getNextBackendUrl() { - // int idx = counter.incrementAndGet(); - // if (idx < 0) { - // counter.set(0); - // idx = 0; - // } - // return backendUrls.get(idx % backendUrls.size()); - // } + public void setAppEnable(int v) { + if (v == ENABLE) { + checkApp = true; + } else { + checkApp = false; + } + } @JsonIgnore public String getNextHttpHostPort() { @@ -182,6 +181,20 @@ public class ApiConfig { return UrlTransformUtils.transform(path, backendPath, reqPath); } + @Override + public int hashCode() { + return id; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof ApiConfig) { + ApiConfig that = (ApiConfig) obj; + return this.id == that.id; + } + return false; + } + @Override public String toString() { return JacksonUtils.writeValueAsString(this); diff --git a/src/main/java/we/plugin/auth/ApiConfig2apps.java b/src/main/java/we/plugin/auth/ApiConfig2apps.java new file mode 100644 index 0000000..ae11afe --- /dev/null +++ b/src/main/java/we/plugin/auth/ApiConfig2apps.java @@ -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 . + */ + +package we.plugin.auth; + +import we.util.JacksonUtils; + +import java.util.Collections; +import java.util.List; + +/** + * @author hongqiaowei + */ + +public class ApiConfig2apps { + + public static final int DELETED = 1; + + public int id; + + public int isDeleted = 0; + + public List apps; + + @Override + public String toString() { + return JacksonUtils.writeValueAsString(this); + } +} diff --git a/src/main/java/we/plugin/auth/ApiConfigService.java b/src/main/java/we/plugin/auth/ApiConfigService.java index 8935d2d..c546e3d 100644 --- a/src/main/java/we/plugin/auth/ApiConfigService.java +++ b/src/main/java/we/plugin/auth/ApiConfigService.java @@ -18,9 +18,6 @@ package we.plugin.auth; import com.alibaba.nacos.api.config.annotation.NacosValue; -import com.ctrip.framework.apollo.model.ConfigChange; -import com.ctrip.framework.apollo.model.ConfigChangeEvent; -import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,12 +28,11 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.stereotype.Service; -import org.springframework.util.ObjectUtils; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import we.flume.clients.log4j2appender.LogService; import we.config.AggregateRedisConfig; +import we.flume.clients.log4j2appender.LogService; import we.util.*; import javax.annotation.PostConstruct; @@ -71,48 +67,6 @@ public class ApiConfigService { private Map apiConfigMap = new HashMap<>(128); - // TODO XXX - @Value("${serviceWhiteList:x}") - private String serviceWhiteList; - private Set whiteListSet = new HashSet<>(196); - @ApolloConfigChangeListener - private void configChangeListter(ConfigChangeEvent cce) { - cce.changedKeys().forEach( - k -> { - ConfigChange cc = cce.getChange(k); - if (cc.getPropertyName().equalsIgnoreCase("serviceWhiteList")) { - this.updateServiceWhiteList(cc.getOldValue(), cc.getNewValue()); - } - } - ); - } - - private void updateServiceWhiteList(String oldValue, String newValue) { - if (ObjectUtils.nullSafeEquals(oldValue, newValue)) { - return; - } - log.info("old service white list: " + oldValue); - serviceWhiteList = newValue; - afterServiceWhiteListSet(); - } - - @NacosValue(value = "${serviceWhiteList:x}", autoRefreshed = true) - public void setServiceWhiteList(String serviceWhiteList) { - this.updateServiceWhiteList(this.serviceWhiteList, serviceWhiteList); - } - - public void afterServiceWhiteListSet() { - if (StringUtils.isNotBlank(serviceWhiteList)) { - whiteListSet.clear(); - Arrays.stream(StringUtils.split(serviceWhiteList, Constants.Symbol.COMMA)).forEach(s -> { - whiteListSet.add(s); - }); - log.info("new service white list: " + whiteListSet.toString()); - } else { - log.info("no service white list"); - } - } - @NacosValue(value = "${need-auth:false}", autoRefreshed = true) @Value("${need-auth:false}") private boolean needAuth; @@ -123,6 +77,9 @@ public class ApiConfigService { @Resource private AppService appService; + @Resource + private ApiConifg2appsService apiConifg2appsService; + @Resource private GatewayGroupService gatewayGroupService; @@ -136,8 +93,6 @@ public class ApiConfigService { @PostConstruct public void init() throws Throwable { - afterServiceWhiteListSet(); // TODO XXX - final Throwable[] throwable = new Throwable[1]; Throwable error = Mono.just(Objects.requireNonNull(rt.opsForHash().entries(fizzApiConfig) .defaultIfEmpty(new AbstractMap.SimpleEntry<>(ReactorUtils.OBJ, ReactorUtils.OBJ)).onErrorStop().doOnError(t -> { @@ -226,6 +181,7 @@ public class ApiConfigService { log.info("no " + ac.service + " config to delete"); } else { sc.remove(ac); + apiConifg2appsService.remove(ac.id); } } else { if (sc == null) { @@ -260,8 +216,6 @@ public class ApiConfigService { CUSTOM_AUTH_REJECT ("custom auth reject"), - SERVICE_NOT_OPEN ("service not open"), - CANT_ACCESS_SERVICE_API ("cant access service api"); private String reason; @@ -275,6 +229,21 @@ public class ApiConfigService { } } + public ApiConfig getApiConfig(String service, HttpMethod method, String path, String gatewayGroup, String app) { + ServiceConfig sc = serviceConfigMap.get(service); + if (sc != null) { + Set acs = sc.getApiConfigs(method, path, gatewayGroup); + if (acs != null) { + for (ApiConfig ac : acs) { + if (apiConifg2appsService.contains(ac.id, app)) { + return ac; + } + } + } + } + return null; + } + public Mono canAccess(ServerWebExchange exchange) { ServerHttpRequest req = exchange.getRequest(); HttpHeaders hdrs = req.getHeaders(); @@ -286,11 +255,6 @@ public class ApiConfigService { private Mono canAccess(ServerWebExchange exchange, String app, String ip, String timestamp, String sign, String secretKey, String service, HttpMethod method, String path) { - // if (openServiceWhiteList) { - // if (!whiteListSet.contains(service)) { // TODO XXX - // return Mono.just(Access.SERVICE_NOT_OPEN); - // } - // } ServiceConfig sc = serviceConfigMap.get(service); if (sc == null) { if (!needAuth) { @@ -301,8 +265,8 @@ public class ApiConfigService { } else { String api = ThreadContext.getStringBuilder().append(service).append(Constants.Symbol.BLANK).append(method.name()).append(Constants.Symbol.BLANK + path).toString(); ApiConfig ac0 = null; - for (String g : gatewayGroupService.currentGatewayGroupSet) { // compatible - ac0 = sc.getApiConfig(method, path, g, app); + for (String g : gatewayGroupService.currentGatewayGroupSet) { + ac0 = getApiConfig(service, method, path, g, app); if (ac0 != null) { break; } @@ -315,9 +279,9 @@ public class ApiConfigService { return logWarnAndResult(api + " no api config", Access.NO_API_CONFIG); } } else if (gatewayGroupService.currentGatewayGroupIn(ac.gatewayGroups)) { - if (ac.apps.contains(App.ALL_APP)) { + if (!ac.checkApp) { return allow(api, ac); - } else if (app != null && ac.apps.contains(app)) { + } else if (app != null && apiConifg2appsService.contains(ac.id, app) ) { if (ac.access == ApiConfig.ALLOW) { App a = appService.getApp(app); if (a.useWhiteList && !a.allow(ip)) { diff --git a/src/main/java/we/plugin/auth/ApiConifg2appsService.java b/src/main/java/we/plugin/auth/ApiConifg2appsService.java new file mode 100644 index 0000000..f5b7068 --- /dev/null +++ b/src/main/java/we/plugin/auth/ApiConifg2appsService.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2020 the original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package we.plugin.auth; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.redis.core.ReactiveStringRedisTemplate; +import org.springframework.stereotype.Service; +import we.config.AggregateRedisConfig; +import we.flume.clients.log4j2appender.LogService; +import we.util.Constants; +import we.util.JacksonUtils; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * @author hongqiaowei + */ + +@Service +public class ApiConifg2appsService { + + private static final Logger log = LoggerFactory.getLogger(ApiConifg2appsService.class); + + private static final String fizzApiConfigAppChannel = "fizz_api_config_app_channel"; + + private Map> apiConfig2appsMap = new HashMap<>(128); + + @Resource(name = AggregateRedisConfig.AGGREGATE_REACTIVE_REDIS_TEMPLATE) + private ReactiveStringRedisTemplate rt; + + @PostConstruct + public void init() throws Throwable { + rt.listenToChannel(fizzApiConfigAppChannel) + .doOnError( + t -> { + log.error("lsn api config 2 apps channel", t); + } + ) + .doOnComplete( + () -> { + log.info("success to lsn on api config 2 apps channel"); + } + ) + .doOnNext( + msg -> { + String json = msg.getMessage(); + log.info(json, LogService.BIZ_ID, "ac2as" + System.currentTimeMillis()); + try { + ApiConfig2apps data = JacksonUtils.readValue(json, ApiConfig2apps.class); + updateApiConfig2appsMap(data); + } catch (Throwable t) { + log.error(Constants.Symbol.EMPTY, t); + } + } + ) + .subscribe() + ; + } + + private void updateApiConfig2appsMap(ApiConfig2apps data) { + Set apps = apiConfig2appsMap.get(data.id); + if (data.isDeleted == ApiConfig2apps.DELETED) { + if (apps != null) { + apps.removeAll(data.apps); + log.info("remove " + data.apps); + } + } else { + if (apps == null) { + apps = new HashSet<>(32); + apiConfig2appsMap.put(data.id, apps); + } + apps.addAll(data.apps); + log.info("add " + data.apps); + } + } + + public boolean contains(int api, String app) { + Set apps = apiConfig2appsMap.get(api); + if (apps == null) { + return false; + } else { + return apps.contains(app); + } + } + + public Set remove(int id) { + return apiConfig2appsMap.remove(id); + } +} diff --git a/src/main/java/we/plugin/auth/GatewayGroup2apiConfig.java b/src/main/java/we/plugin/auth/GatewayGroup2apiConfig.java index 5bbb085..31dd1b8 100644 --- a/src/main/java/we/plugin/auth/GatewayGroup2apiConfig.java +++ b/src/main/java/we/plugin/auth/GatewayGroup2apiConfig.java @@ -22,7 +22,9 @@ import org.slf4j.LoggerFactory; import we.util.JacksonUtils; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; /** * @author hongqiaowei @@ -30,65 +32,86 @@ import java.util.Map; public class GatewayGroup2apiConfig { - private static final Logger log = LoggerFactory.getLogger(GatewayGroup2apiConfig.class); + // private static final Logger log = LoggerFactory.getLogger(GatewayGroup2apiConfig.class); - private Map> configMap = new HashMap<>(6); + // private Map> configMap = new HashMap<>(6); + private Map> configMap = new HashMap<>(6); - public Map> getConfigMap() { - return configMap; - } + // public Map> getConfigMap() { + // return configMap; + // } - public void setConfigMap(Map> configMap) { - this.configMap = configMap; - } + // public void setConfigMap(Map> configMap) { + // this.configMap = configMap; + // } public void add(ApiConfig ac) { for (String gg : ac.gatewayGroups) { - Map app2apiConfigMap = configMap.get(gg); - if (app2apiConfigMap == null) { - app2apiConfigMap = new HashMap<>(); - configMap.put(gg, app2apiConfigMap); - } - for (String a : ac.apps) { - app2apiConfigMap.put(a, ac); - log.info("expose " + ac + " to " + gg + " group and " + a + " app"); + // Map app2apiConfigMap = configMap.get(gg); + // if (app2apiConfigMap == null) { + // app2apiConfigMap = new HashMap<>(); + // configMap.put(gg, app2apiConfigMap); + // } + // for (String a : ac.apps) { + // app2apiConfigMap.put(a, ac); + // log.info("expose " + ac + " to " + gg + " group and " + a + " app"); + // } + Set acs = configMap.get(gg); + if (acs == null) { + acs = new HashSet<>(6); + configMap.put(gg, acs); } + acs.add(ac); } } public void remove(ApiConfig ac) { for (String gg : ac.gatewayGroups) { - Map app2apiConfigMap = configMap.get(gg); - if (app2apiConfigMap != null) { - for (String a : ac.apps) { - ApiConfig r = app2apiConfigMap.remove(a); - log.info("remove " + r + " from " + gg + " group and " + a + " app"); - } + // Map app2apiConfigMap = configMap.get(gg); + // if (app2apiConfigMap != null) { + // for (String a : ac.apps) { + // ApiConfig r = app2apiConfigMap.remove(a); + // log.info("remove " + r + " from " + gg + " group and " + a + " app"); + // } + // } + Set acs = configMap.get(gg); + if (acs != null) { + acs.remove(ac); } } } public void update(ApiConfig ac) { for (String gg : ac.gatewayGroups) { - Map app2apiConfigMap = configMap.get(gg); - if (app2apiConfigMap == null) { - app2apiConfigMap = new HashMap<>(); - configMap.put(gg, app2apiConfigMap); - } - for (String a : ac.apps) { - ApiConfig old = app2apiConfigMap.put(a, ac); - log.info(gg + " group and " + a + " app update " + old + " with " + ac); + // Map app2apiConfigMap = configMap.get(gg); + // if (app2apiConfigMap == null) { + // app2apiConfigMap = new HashMap<>(); + // configMap.put(gg, app2apiConfigMap); + // } + // for (String a : ac.apps) { + // ApiConfig old = app2apiConfigMap.put(a, ac); + // log.info(gg + " group and " + a + " app update " + old + " with " + ac); + // } + Set acs = configMap.get(gg); + if (acs == null) { + acs = new HashSet<>(6); + configMap.put(gg, acs); } + acs.add(ac); } } - public ApiConfig get(String gatewayGroup, String app) { - Map app2apiConfigMap = configMap.get(gatewayGroup); - if (app2apiConfigMap == null) { - return null; - } else { - return app2apiConfigMap.get(app); - } + // public ApiConfig get(String gatewayGroup, String app) { + // Map app2apiConfigMap = configMap.get(gatewayGroup); + // if (app2apiConfigMap == null) { + // return null; + // } else { + // return app2apiConfigMap.get(app); + // } + // } + + public Set get(String gatewayGroup) { + return configMap.get(gatewayGroup); } public String toString() { diff --git a/src/main/java/we/plugin/auth/ServiceConfig.java b/src/main/java/we/plugin/auth/ServiceConfig.java index f747a9e..6bdbfe2 100644 --- a/src/main/java/we/plugin/auth/ServiceConfig.java +++ b/src/main/java/we/plugin/auth/ServiceConfig.java @@ -111,17 +111,25 @@ public class ServiceConfig { } } + // @JsonIgnore + // public ApiConfig getApiConfig(HttpMethod method, String path, String gatewayGroup, String app) { + // GatewayGroup2apiConfig r = getApiConfig(method, path); + // if (r == null) { + // return null; + // } + // if (StringUtils.isBlank(app)) { + // app = App.ALL_APP; + // } + // return r.get(gatewayGroup, app); + // } + @JsonIgnore - public ApiConfig getApiConfig(HttpMethod method, String path, String gatewayGroup, String app) { -// GatewayGroup2appsToApiConfig r = getApiConfig0(method, path); + public Set getApiConfigs(HttpMethod method, String path, String gatewayGroup) { GatewayGroup2apiConfig r = getApiConfig(method, path); if (r == null) { return null; } - if (StringUtils.isBlank(app)) { - app = App.ALL_APP; - } - return r.get(gatewayGroup, app); + return r.get(gatewayGroup); } private GatewayGroup2apiConfig getApiConfig(HttpMethod method, String reqPath) { diff --git a/src/test/java/we/util/WebUtilsTests.java b/src/test/java/we/util/WebUtilsTests.java index d3f861a..c1a3c85 100644 --- a/src/test/java/we/util/WebUtilsTests.java +++ b/src/test/java/we/util/WebUtilsTests.java @@ -24,8 +24,8 @@ public class WebUtilsTests { MockServerHttpRequest mr = MockServerHttpRequest.get("http://127.0.0.1:8600/proxytest/test/ybiz").build(); MockServerWebExchange me = MockServerWebExchange.from(mr); String cs = WebUtils.getClientService(me); - System.err.println(cs); + // System.err.println(cs); String crpp = WebUtils.getClientReqPathPrefix(me); - System.err.println(crpp); + // System.err.println(crpp); } }