diff --git a/.travis.yml b/.travis.yml index b048e4b9b..b8a83104a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,6 @@ notifications: email: recipients: - - xchaos8@126.com - - nacos_dev@linux.alibaba.com - dev-nacos@googlegroups.com - mw_configcenter@list.alibaba-inc.com on_success: change @@ -30,7 +28,7 @@ before_install: script: - mvn -B clean package apache-rat:check findbugs:findbugs -Dmaven.test.skip=true - - mvn -Prelease-nacos clean install -U + - mvn -Prelease-nacos -Dmaven.test.skip=true clean install -U - mvn clean package -Pit-test after_success: - mvn clean package -Pit-test diff --git a/BUILDING b/BUILDING index e7e968b8b..214f330ff 100644 --- a/BUILDING +++ b/BUILDING @@ -35,4 +35,4 @@ Build Instructions for NACOS Execute the following command in order to build the tar.gz packages and install JAR into local repository: #build nacos - $ mvn -Prelease-nacos -DskipTests clean install -U \ No newline at end of file + $ mvn -Prelease-nacos -Dmaven.test.skip=true clean install -U diff --git a/api/src/main/java/com/alibaba/nacos/api/PropertyKeyConst.java b/api/src/main/java/com/alibaba/nacos/api/PropertyKeyConst.java index 9205112cb..ba31d0c49 100644 --- a/api/src/main/java/com/alibaba/nacos/api/PropertyKeyConst.java +++ b/api/src/main/java/com/alibaba/nacos/api/PropertyKeyConst.java @@ -32,6 +32,10 @@ public class PropertyKeyConst { public final static String NAMESPACE = "namespace"; + public final static String USERNAME = "username"; + + public final static String PASSWORD = "password"; + public final static String ACCESS_KEY = "accessKey"; public final static String SECRET_KEY = "secretKey"; diff --git a/api/src/main/java/com/alibaba/nacos/api/common/Constants.java b/api/src/main/java/com/alibaba/nacos/api/common/Constants.java index db8c79f4e..b93031753 100644 --- a/api/src/main/java/com/alibaba/nacos/api/common/Constants.java +++ b/api/src/main/java/com/alibaba/nacos/api/common/Constants.java @@ -72,6 +72,16 @@ public class Constants { public static final String CONFIG_CONTROLLER_PATH = BASE_PATH + "/configs"; + public static final String TOKEN = "token"; + + public static final String ACCESS_TOKEN = "accessToken"; + + public static final String TOKEN_TTL = "tokenTtl"; + + public static final String GLOBAL_ADMIN = "globalAdmin"; + + public static final String TOKEN_REFRESH_WINDOW = "tokenRefreshWindow"; + /** * second */ @@ -171,4 +181,6 @@ public class Constants { public static final String SNOWFLAKE_INSTANCE_ID_GENERATOR = "snowflake"; + public static final String HTTP_PREFIX = "http"; + } diff --git a/api/src/main/java/com/alibaba/nacos/api/naming/utils/NamingUtils.java b/api/src/main/java/com/alibaba/nacos/api/naming/utils/NamingUtils.java index bb9677fc4..932b460b0 100644 --- a/api/src/main/java/com/alibaba/nacos/api/naming/utils/NamingUtils.java +++ b/api/src/main/java/com/alibaba/nacos/api/naming/utils/NamingUtils.java @@ -16,6 +16,7 @@ package com.alibaba.nacos.api.naming.utils; import com.alibaba.nacos.api.common.Constants; +import org.apache.commons.lang3.StringUtils; /** * @author nkorange @@ -32,6 +33,9 @@ public class NamingUtils { } public static String getServiceName(String serviceNameWithGroup) { + if (StringUtils.isBlank(serviceNameWithGroup)) { + return StringUtils.EMPTY; + } if (!serviceNameWithGroup.contains(Constants.SERVICE_INFO_SPLITER)) { return serviceNameWithGroup; } @@ -39,6 +43,9 @@ public class NamingUtils { } public static String getGroupName(String serviceNameWithGroup) { + if (StringUtils.isBlank(serviceNameWithGroup)) { + return StringUtils.EMPTY; + } if (!serviceNameWithGroup.contains(Constants.SERVICE_INFO_SPLITER)) { return Constants.DEFAULT_GROUP; } diff --git a/client/src/main/java/com/alibaba/nacos/client/config/NacosConfigService.java b/client/src/main/java/com/alibaba/nacos/client/config/NacosConfigService.java index e05751776..c25f557c2 100644 --- a/client/src/main/java/com/alibaba/nacos/client/config/NacosConfigService.java +++ b/client/src/main/java/com/alibaba/nacos/client/config/NacosConfigService.java @@ -16,7 +16,6 @@ package com.alibaba.nacos.client.config; import com.alibaba.nacos.api.PropertyKeyConst; -import com.alibaba.nacos.api.SystemPropertyKeyConst; import com.alibaba.nacos.api.common.Constants; import com.alibaba.nacos.api.config.ConfigService; import com.alibaba.nacos.api.config.listener.Listener; @@ -33,8 +32,7 @@ import com.alibaba.nacos.client.config.impl.LocalConfigInfoProcessor; import com.alibaba.nacos.client.config.utils.ContentUtils; import com.alibaba.nacos.client.config.utils.ParamUtils; import com.alibaba.nacos.client.utils.LogUtils; -import com.alibaba.nacos.client.utils.TemplateUtils; -import com.alibaba.nacos.client.utils.TenantUtil; +import com.alibaba.nacos.client.utils.ParamUtil; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; @@ -44,7 +42,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Properties; -import java.util.concurrent.Callable; /** * Config Impl @@ -86,34 +83,7 @@ public class NacosConfigService implements ConfigService { } private void initNamespace(Properties properties) { - String namespaceTmp = null; - - String isUseCloudNamespaceParsing = - properties.getProperty(PropertyKeyConst.IS_USE_CLOUD_NAMESPACE_PARSING, - System.getProperty(SystemPropertyKeyConst.IS_USE_CLOUD_NAMESPACE_PARSING, - String.valueOf(Constants.DEFAULT_USE_CLOUD_NAMESPACE_PARSING))); - - if (Boolean.parseBoolean(isUseCloudNamespaceParsing)) { - namespaceTmp = TemplateUtils.stringBlankAndThenExecute(namespaceTmp, new Callable() { - @Override - public String call() { - return TenantUtil.getUserTenantForAcm(); - } - }); - - namespaceTmp = TemplateUtils.stringBlankAndThenExecute(namespaceTmp, new Callable() { - @Override - public String call() { - String namespace = System.getenv(PropertyKeyConst.SystemEnv.ALIBABA_ALIWARE_NAMESPACE); - return StringUtils.isNotBlank(namespace) ? namespace : EMPTY; - } - }); - } - - if (StringUtils.isBlank(namespaceTmp)) { - namespaceTmp = properties.getProperty(PropertyKeyConst.NAMESPACE); - } - namespace = StringUtils.isNotBlank(namespaceTmp) ? namespaceTmp.trim() : EMPTY; + namespace = ParamUtil.parseNamespace(properties); properties.put(PropertyKeyConst.NAMESPACE, namespace); } diff --git a/client/src/main/java/com/alibaba/nacos/client/config/http/ServerHttpAgent.java b/client/src/main/java/com/alibaba/nacos/client/config/http/ServerHttpAgent.java index eedb6b0d1..5a521493c 100644 --- a/client/src/main/java/com/alibaba/nacos/client/config/http/ServerHttpAgent.java +++ b/client/src/main/java/com/alibaba/nacos/client/config/http/ServerHttpAgent.java @@ -23,6 +23,7 @@ import com.alibaba.nacos.client.config.impl.HttpSimpleClient.HttpResult; import com.alibaba.nacos.client.config.impl.ServerListManager; import com.alibaba.nacos.client.config.impl.SpasAdapter; import com.alibaba.nacos.client.identify.STSConfig; +import com.alibaba.nacos.client.security.SecurityProxy; import com.alibaba.nacos.client.utils.JSONUtils; import com.alibaba.nacos.client.utils.LogUtils; import com.alibaba.nacos.client.utils.ParamUtil; @@ -43,7 +44,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Properties; -import java.util.concurrent.Callable; +import java.util.concurrent.*; /** * Server Agent @@ -54,6 +55,12 @@ public class ServerHttpAgent implements HttpAgent { private static final Logger LOGGER = LogUtils.logger(ServerHttpAgent.class); + private SecurityProxy securityProxy; + + private String namespaceId; + + private long securityInfoRefreshIntervalMills = TimeUnit.SECONDS.toMillis(5); + /** * @param path 相对于web应用根,以/开头 * @param headers @@ -68,7 +75,7 @@ public class ServerHttpAgent implements HttpAgent { long readTimeoutMs) throws IOException { final long endTime = System.currentTimeMillis() + readTimeoutMs; final boolean isSSL = false; - + injectSecurityInfo(paramValues); String currentServerAddr = serverListMgr.getCurrentServerAddr(); int maxRetry = this.maxRetry; @@ -121,7 +128,7 @@ public class ServerHttpAgent implements HttpAgent { long readTimeoutMs) throws IOException { final long endTime = System.currentTimeMillis() + readTimeoutMs; boolean isSSL = false; - + injectSecurityInfo(paramValues); String currentServerAddr = serverListMgr.getCurrentServerAddr(); int maxRetry = this.maxRetry; @@ -176,7 +183,7 @@ public class ServerHttpAgent implements HttpAgent { long readTimeoutMs) throws IOException { final long endTime = System.currentTimeMillis() + readTimeoutMs; boolean isSSL = false; - + injectSecurityInfo(paramValues); String currentServerAddr = serverListMgr.getCurrentServerAddr(); int maxRetry = this.maxRetry; @@ -243,7 +250,39 @@ public class ServerHttpAgent implements HttpAgent { public ServerHttpAgent(Properties properties) throws NacosException { serverListMgr = new ServerListManager(properties); + securityProxy = new SecurityProxy(properties); + namespaceId = properties.getProperty(PropertyKeyConst.NAMESPACE); init(properties); + securityProxy.login(serverListMgr.getServerUrls()); + + ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1, new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r); + t.setName("com.alibaba.nacos.client.config.security.updater"); + t.setDaemon(true); + return t; + } + }); + + executorService.scheduleWithFixedDelay(new Runnable() { + @Override + public void run() { + securityProxy.login(serverListMgr.getServerUrls()); + } + }, 0, securityInfoRefreshIntervalMills, TimeUnit.MILLISECONDS); + } + + private void injectSecurityInfo(List params) { + ArrayList list = (ArrayList) params; + if (StringUtils.isNotBlank(securityProxy.getAccessToken())) { + list.add(Constants.ACCESS_TOKEN); + list.add(securityProxy.getAccessToken()); + } + if (StringUtils.isNotBlank(namespaceId)) { + list.add("tenant"); + list.add(namespaceId); + } } private void init(Properties properties) { diff --git a/client/src/main/java/com/alibaba/nacos/client/config/impl/ClientWorker.java b/client/src/main/java/com/alibaba/nacos/client/config/impl/ClientWorker.java index e1495d06a..31c34040e 100644 --- a/client/src/main/java/com/alibaba/nacos/client/config/impl/ClientWorker.java +++ b/client/src/main/java/com/alibaba/nacos/client/config/impl/ClientWorker.java @@ -223,9 +223,9 @@ public class ClientWorker { try { List params = null; if (StringUtils.isBlank(tenant)) { - params = Arrays.asList("dataId", dataId, "group", group); + params = new ArrayList(Arrays.asList("dataId", dataId, "group", group)); } else { - params = Arrays.asList("dataId", dataId, "group", group, "tenant", tenant); + params = new ArrayList(Arrays.asList("dataId", dataId, "group", group, "tenant", tenant)); } result = agent.httpGet(Constants.CONFIG_CONTROLLER_PATH, null, params, agent.getEncode(), readTimeout); } catch (IOException e) { diff --git a/client/src/main/java/com/alibaba/nacos/client/config/impl/HttpSimpleClient.java b/client/src/main/java/com/alibaba/nacos/client/config/impl/HttpSimpleClient.java index e655784da..cf7398fae 100644 --- a/client/src/main/java/com/alibaba/nacos/client/config/impl/HttpSimpleClient.java +++ b/client/src/main/java/com/alibaba/nacos/client/config/impl/HttpSimpleClient.java @@ -29,10 +29,7 @@ import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Map; +import java.util.*; /** * Http tool diff --git a/client/src/main/java/com/alibaba/nacos/client/config/impl/ServerListManager.java b/client/src/main/java/com/alibaba/nacos/client/config/impl/ServerListManager.java index 2d830983c..2962cd988 100644 --- a/client/src/main/java/com/alibaba/nacos/client/config/impl/ServerListManager.java +++ b/client/src/main/java/com/alibaba/nacos/client/config/impl/ServerListManager.java @@ -234,6 +234,10 @@ public class ServerListManager { isStarted = true; } + public List getServerUrls() { + return serverUrls; + } + Iterator iterator() { if (serverUrls.isEmpty()) { LOGGER.error("[{}] [iterator-serverlist] No server address defined!", name); diff --git a/client/src/main/java/com/alibaba/nacos/client/identify/CredentialService.java b/client/src/main/java/com/alibaba/nacos/client/identify/CredentialService.java index f238345fc..db61f1b67 100644 --- a/client/src/main/java/com/alibaba/nacos/client/identify/CredentialService.java +++ b/client/src/main/java/com/alibaba/nacos/client/identify/CredentialService.java @@ -53,7 +53,7 @@ public final class CredentialService implements SpasCredentialLoader { } public static CredentialService getInstance(String appName) { - String key = appName != null ? appName : Constants.NO_APP_NAME; + String key = appName != null ? appName : IdentifyConstants.NO_APP_NAME; CredentialService instance = instances.get(key); if (instance == null) { instance = new CredentialService(appName); @@ -70,7 +70,7 @@ public final class CredentialService implements SpasCredentialLoader { } public static CredentialService freeInstance(String appName) { - String key = appName != null ? appName : Constants.NO_APP_NAME; + String key = appName != null ? appName : IdentifyConstants.NO_APP_NAME; CredentialService instance = instances.remove(key); if (instance != null) { instance.free(); diff --git a/client/src/main/java/com/alibaba/nacos/client/identify/CredentialWatcher.java b/client/src/main/java/com/alibaba/nacos/client/identify/CredentialWatcher.java index 103891e79..97fdf24ea 100644 --- a/client/src/main/java/com/alibaba/nacos/client/identify/CredentialWatcher.java +++ b/client/src/main/java/com/alibaba/nacos/client/identify/CredentialWatcher.java @@ -94,7 +94,7 @@ public class CredentialWatcher { private void loadCredential(boolean init) { boolean logWarn = init; if (propertyPath == null) { - URL url = ClassLoader.getSystemResource(Constants.PROPERTIES_FILENAME); + URL url = ClassLoader.getSystemResource(IdentifyConstants.PROPERTIES_FILENAME); if (url != null) { propertyPath = url.getPath(); } @@ -105,7 +105,7 @@ public class CredentialWatcher { propertyPath = value; } if (propertyPath == null || propertyPath.isEmpty()) { - propertyPath = Constants.CREDENTIAL_PATH + (appName == null ? Constants.CREDENTIAL_DEFAULT + propertyPath = IdentifyConstants.CREDENTIAL_PATH + (appName == null ? IdentifyConstants.CREDENTIAL_DEFAULT : appName); } else { if (logWarn) { @@ -115,7 +115,7 @@ public class CredentialWatcher { } else { if (logWarn) { SpasLogger.info("[{}] Load credential file from classpath: {}", appName, - Constants.PROPERTIES_FILENAME); + IdentifyConstants.PROPERTIES_FILENAME); } } } @@ -125,13 +125,13 @@ public class CredentialWatcher { try { propertiesIS = new FileInputStream(propertyPath); } catch (FileNotFoundException e) { - if (appName != null && !appName.equals(Constants.CREDENTIAL_DEFAULT) && propertyPath.equals( - Constants.CREDENTIAL_PATH + appName)) { - propertyPath = Constants.CREDENTIAL_PATH + Constants.CREDENTIAL_DEFAULT; + if (appName != null && !appName.equals(IdentifyConstants.CREDENTIAL_DEFAULT) && propertyPath.equals( + IdentifyConstants.CREDENTIAL_PATH + appName)) { + propertyPath = IdentifyConstants.CREDENTIAL_PATH + IdentifyConstants.CREDENTIAL_DEFAULT; continue; } - if (!Constants.DOCKER_CREDENTIAL_PATH.equals(propertyPath)) { - propertyPath = Constants.DOCKER_CREDENTIAL_PATH; + if (!IdentifyConstants.DOCKER_CREDENTIAL_PATH.equals(propertyPath)) { + propertyPath = IdentifyConstants.DOCKER_CREDENTIAL_PATH; continue; } } @@ -143,8 +143,8 @@ public class CredentialWatcher { String tenantId = null; if (propertiesIS == null) { propertyPath = null; - accessKey = System.getenv(Constants.ENV_ACCESS_KEY); - secretKey = System.getenv(Constants.ENV_SECRET_KEY); + accessKey = System.getenv(IdentifyConstants.ENV_ACCESS_KEY); + secretKey = System.getenv(IdentifyConstants.ENV_SECRET_KEY); if (accessKey == null && secretKey == null) { if (logWarn) { SpasLogger.info("{} No credential found", appName); @@ -173,26 +173,26 @@ public class CredentialWatcher { SpasLogger.info("[{}] Load credential file {}", appName, propertyPath); } - if (!Constants.DOCKER_CREDENTIAL_PATH.equals(propertyPath)) { - if (properties.containsKey(Constants.ACCESS_KEY)) { - accessKey = properties.getProperty(Constants.ACCESS_KEY); + if (!IdentifyConstants.DOCKER_CREDENTIAL_PATH.equals(propertyPath)) { + if (properties.containsKey(IdentifyConstants.ACCESS_KEY)) { + accessKey = properties.getProperty(IdentifyConstants.ACCESS_KEY); } - if (properties.containsKey(Constants.SECRET_KEY)) { - secretKey = properties.getProperty(Constants.SECRET_KEY); + if (properties.containsKey(IdentifyConstants.SECRET_KEY)) { + secretKey = properties.getProperty(IdentifyConstants.SECRET_KEY); } - if (properties.containsKey(Constants.TENANT_ID)) { - tenantId = properties.getProperty(Constants.TENANT_ID); + if (properties.containsKey(IdentifyConstants.TENANT_ID)) { + tenantId = properties.getProperty(IdentifyConstants.TENANT_ID); } } else { - if (properties.containsKey(Constants.DOCKER_ACCESS_KEY)) { - accessKey = properties.getProperty(Constants.DOCKER_ACCESS_KEY); + if (properties.containsKey(IdentifyConstants.DOCKER_ACCESS_KEY)) { + accessKey = properties.getProperty(IdentifyConstants.DOCKER_ACCESS_KEY); } - if (properties.containsKey(Constants.DOCKER_SECRET_KEY)) { - secretKey = properties.getProperty(Constants.DOCKER_SECRET_KEY); + if (properties.containsKey(IdentifyConstants.DOCKER_SECRET_KEY)) { + secretKey = properties.getProperty(IdentifyConstants.DOCKER_SECRET_KEY); } - if (properties.containsKey(Constants.DOCKER_TENANT_ID)) { - tenantId = properties.getProperty(Constants.DOCKER_TENANT_ID); + if (properties.containsKey(IdentifyConstants.DOCKER_TENANT_ID)) { + tenantId = properties.getProperty(IdentifyConstants.DOCKER_TENANT_ID); } } } @@ -211,7 +211,7 @@ public class CredentialWatcher { Credentials credential = new Credentials(accessKey, secretKey, tenantId); if (!credential.valid()) { SpasLogger.warn("[1] Credential file missing required property {} Credential file missing {} or {}", - appName, Constants.ACCESS_KEY, Constants.SECRET_KEY); + appName, IdentifyConstants.ACCESS_KEY, IdentifyConstants.SECRET_KEY); propertyPath = null; // return; } diff --git a/client/src/main/java/com/alibaba/nacos/client/identify/Constants.java b/client/src/main/java/com/alibaba/nacos/client/identify/IdentifyConstants.java similarity index 98% rename from client/src/main/java/com/alibaba/nacos/client/identify/Constants.java rename to client/src/main/java/com/alibaba/nacos/client/identify/IdentifyConstants.java index a94110720..e56b46416 100644 --- a/client/src/main/java/com/alibaba/nacos/client/identify/Constants.java +++ b/client/src/main/java/com/alibaba/nacos/client/identify/IdentifyConstants.java @@ -20,7 +20,7 @@ package com.alibaba.nacos.client.identify; * * @author Nacos */ -public class Constants { +public class IdentifyConstants { public static final String ACCESS_KEY = "accessKey"; public static final String SECRET_KEY = "secretKey"; diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/NacosNamingMaintainService.java b/client/src/main/java/com/alibaba/nacos/client/naming/NacosNamingMaintainService.java index c3652e75f..65440df1c 100644 --- a/client/src/main/java/com/alibaba/nacos/client/naming/NacosNamingMaintainService.java +++ b/client/src/main/java/com/alibaba/nacos/client/naming/NacosNamingMaintainService.java @@ -63,9 +63,7 @@ public class NacosNamingMaintainService implements NamingMaintainService { namespace = InitUtils.initNamespaceForNaming(properties); initServerAddr(properties); InitUtils.initWebRootContext(); - - serverProxy = new NamingProxy(namespace, endpoint, serverList); - serverProxy.setProperties(properties); + serverProxy = new NamingProxy(namespace, endpoint, serverList, properties); } private void initServerAddr(Properties properties) { diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/NacosNamingService.java b/client/src/main/java/com/alibaba/nacos/client/naming/NacosNamingService.java index 3c46ed1ad..f5d95dd63 100644 --- a/client/src/main/java/com/alibaba/nacos/client/naming/NacosNamingService.java +++ b/client/src/main/java/com/alibaba/nacos/client/naming/NacosNamingService.java @@ -34,6 +34,7 @@ import com.alibaba.nacos.client.naming.net.NamingProxy; import com.alibaba.nacos.client.naming.utils.CollectionUtils; import com.alibaba.nacos.client.naming.utils.InitUtils; import com.alibaba.nacos.client.naming.utils.UtilAndComs; +import com.alibaba.nacos.client.security.SecurityProxy; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; @@ -45,15 +46,15 @@ import java.util.Properties; import java.util.concurrent.TimeUnit; /** + * Nacos Naming Service + * * @author nkorange */ @SuppressWarnings("PMD.ServiceOrDaoClassShouldEndWithImplRule") public class NacosNamingService implements NamingService { - private static final String DEFAULT_PORT = "8080"; - private static final long DEFAULT_HEART_BEAT_INTERVAL = TimeUnit.SECONDS.toMillis(5); /** - * Each Naming instance should have different namespace. + * Each Naming service should have different namespace. */ private String namespace; @@ -92,8 +93,7 @@ public class NacosNamingService implements NamingService { initLogName(properties); eventDispatcher = new EventDispatcher(); - serverProxy = new NamingProxy(namespace, endpoint, serverList); - serverProxy.setProperties(properties); + serverProxy = new NamingProxy(namespace, endpoint, serverList, properties); beatReactor = new BeatReactor(serverProxy, initClientBeatThreadCount(properties)); hostReactor = new HostReactor(eventDispatcher, serverProxy, cacheDir, isLoadCacheAtStart(properties), initPollingThreadCount(properties)); diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/net/HttpClient.java b/client/src/main/java/com/alibaba/nacos/client/naming/net/HttpClient.java index bae072e54..5cef3cb64 100644 --- a/client/src/main/java/com/alibaba/nacos/client/naming/net/HttpClient.java +++ b/client/src/main/java/com/alibaba/nacos/client/naming/net/HttpClient.java @@ -45,9 +45,6 @@ public class HttpClient { private static final boolean ENABLE_HTTPS = Boolean .getBoolean("com.alibaba.nacos.client.naming.tls.enable"); - private static final String POST = "POST"; - private static final String PUT = "PUT"; - static { // limit max redirection System.setProperty("http.maxRedirects", "5"); @@ -80,7 +77,6 @@ public class HttpClient { conn.setRequestMethod(method); conn.setDoOutput(true); if (StringUtils.isNotBlank(body)) { - // fix: apache http nio framework must set some content to request body byte[] b = body.getBytes(); conn.setRequestProperty("Content-Length", String.valueOf(b.length)); conn.getOutputStream().write(b, 0, b.length); @@ -88,7 +84,9 @@ public class HttpClient { conn.getOutputStream().close(); } conn.connect(); - NAMING_LOGGER.debug("Request from server: " + url); + if (NAMING_LOGGER.isDebugEnabled()) { + NAMING_LOGGER.debug("Request from server: " + url); + } return getResult(conn); } catch (Exception e) { try { diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/net/NamingProxy.java b/client/src/main/java/com/alibaba/nacos/client/naming/net/NamingProxy.java index a554ba26f..32e42a447 100644 --- a/client/src/main/java/com/alibaba/nacos/client/naming/net/NamingProxy.java +++ b/client/src/main/java/com/alibaba/nacos/client/naming/net/NamingProxy.java @@ -20,6 +20,7 @@ import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.TypeReference; import com.alibaba.nacos.api.PropertyKeyConst; import com.alibaba.nacos.api.SystemPropertyKeyConst; +import com.alibaba.nacos.api.common.Constants; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.naming.CommonParams; import com.alibaba.nacos.api.naming.pojo.Instance; @@ -35,6 +36,7 @@ import com.alibaba.nacos.client.naming.utils.CollectionUtils; import com.alibaba.nacos.client.naming.utils.NetUtils; import com.alibaba.nacos.client.naming.utils.SignUtil; import com.alibaba.nacos.client.naming.utils.UtilAndComs; +import com.alibaba.nacos.client.security.SecurityProxy; import com.alibaba.nacos.client.utils.AppNameUtils; import com.alibaba.nacos.client.utils.TemplateUtils; import com.alibaba.nacos.common.constant.HttpHeaderConsts; @@ -71,14 +73,21 @@ public class NamingProxy { private List serversFromEndpoint = new ArrayList(); + private SecurityProxy securityProxy; + private long lastSrvRefTime = 0L; private long vipSrvRefInterMillis = TimeUnit.SECONDS.toMillis(30); + private long securityInfoRefreshIntervalMills = TimeUnit.SECONDS.toMillis(5); + private Properties properties; - public NamingProxy(String namespaceId, String endpoint, String serverList) { + public NamingProxy(String namespaceId, String endpoint, String serverList, Properties properties) { + securityProxy = new SecurityProxy(properties); + this.properties = properties; + this.setServerPort(DEFAULT_SERVER_PORT); this.namespaceId = namespaceId; this.endpoint = endpoint; if (StringUtils.isNotEmpty(serverList)) { @@ -88,19 +97,16 @@ public class NamingProxy { } } - initRefreshSrvIfNeed(); + initRefreshTask(); } - private void initRefreshSrvIfNeed() { - if (StringUtils.isEmpty(endpoint)) { - return; - } + private void initRefreshTask() { - ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1, new ThreadFactory() { + ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(2, new ThreadFactory() { @Override public Thread newThread(Runnable r) { Thread t = new Thread(r); - t.setName("com.alibaba.nacos.client.naming.serverlist.updater"); + t.setName("com.alibaba.nacos.client.naming.updater"); t.setDaemon(true); return t; } @@ -113,6 +119,14 @@ public class NamingProxy { } }, 0, vipSrvRefInterMillis, TimeUnit.MILLISECONDS); + + executorService.scheduleWithFixedDelay(new Runnable() { + @Override + public void run() { + securityProxy.login(getServerList()); + } + }, 0, securityInfoRefreshIntervalMills, TimeUnit.MILLISECONDS); + refreshSrvIfNeed(); } @@ -377,13 +391,15 @@ public class NamingProxy { } public String reqAPI(String api, Map params, String body, String method) throws NacosException { + return reqAPI(api, params, body, getServerList(), method); + } + private List getServerList() { List snapshot = serversFromEndpoint; if (!CollectionUtils.isEmpty(serverList)) { snapshot = serverList; } - - return reqAPI(api, params, body, snapshot, method); + return snapshot; } public String callServer(String api, Map params, String body, String curServer) throws NacosException { @@ -394,7 +410,7 @@ public class NamingProxy { throws NacosException { long start = System.currentTimeMillis(); long end = 0; - checkSignature(params); + injectSecurityInfo(params); List headers = builderHeaders(); String url; @@ -456,7 +472,7 @@ public class NamingProxy { if (StringUtils.isNotBlank(nacosDomain)) { for (int i = 0; i < UtilAndComs.REQUEST_DOMAIN_RETRY_COUNT; i++) { try { - return callServer(api, params, body, nacosDomain); + return callServer(api, params, body, nacosDomain, method); } catch (NacosException e) { exception = e; if (NAMING_LOGGER.isDebugEnabled()) { @@ -474,22 +490,27 @@ public class NamingProxy { } - private void checkSignature(Map params) { + private void injectSecurityInfo(Map params) { + + // Inject token if exist: + if (StringUtils.isNotBlank(securityProxy.getAccessToken())) { + params.put(Constants.ACCESS_TOKEN, securityProxy.getAccessToken()); + } + + // Inject ak/sk if exist: String ak = getAccessKey(); String sk = getSecretKey(); params.put("app", AppNameUtils.getAppName()); - if (StringUtils.isEmpty(ak) && StringUtils.isEmpty(sk)) { - return; - } - - try { - String signData = getSignData(params.get("serviceName")); - String signature = SignUtil.sign(signData, sk); - params.put("signature", signature); - params.put("data", signData); - params.put("ak", ak); - } catch (Exception e) { - e.printStackTrace(); + if (StringUtils.isNotBlank(ak) && StringUtils.isNotBlank(sk)) { + try { + String signData = getSignData(params.get("serviceName")); + String signature = SignUtil.sign(signData, sk); + params.put("signature", signature); + params.put("data", signData); + params.put("ak", ak); + } catch (Exception e) { + NAMING_LOGGER.error("inject ak/sk failed.", e); + } } } diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/utils/Chooser.java b/client/src/main/java/com/alibaba/nacos/client/naming/utils/Chooser.java index 2dba32671..46c75c1b6 100644 --- a/client/src/main/java/com/alibaba/nacos/client/naming/utils/Chooser.java +++ b/client/src/main/java/com/alibaba/nacos/client/naming/utils/Chooser.java @@ -20,7 +20,7 @@ import java.util.Arrays; import java.util.List; /** - * @author nkorange + * @author alibaba */ public class Chooser { diff --git a/client/src/main/java/com/alibaba/nacos/client/security/SecurityProxy.java b/client/src/main/java/com/alibaba/nacos/client/security/SecurityProxy.java new file mode 100644 index 000000000..2bc33643f --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/security/SecurityProxy.java @@ -0,0 +1,142 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.nacos.client.security; + + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.nacos.api.PropertyKeyConst; +import com.alibaba.nacos.api.common.Constants; +import com.alibaba.nacos.client.naming.net.HttpClient; +import com.alibaba.nacos.common.utils.HttpMethod; +import org.apache.commons.codec.Charsets; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.HttpURLConnection; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Properties; +import java.util.concurrent.TimeUnit; + +/** + * Security proxy to update security information + * + * @author nkorange + * @since 1.2.0 + */ +public class SecurityProxy { + + private static final Logger SECURITY_LOGGER = LoggerFactory.getLogger(SecurityProxy.class); + + private static final String LOGIN_URL = "/v1/auth/users/login"; + + private String contextPath; + + /** + * User's name + */ + private String username; + + /** + * User's password + */ + private String password; + + /** + * A token to take with when sending request to Nacos server + */ + private String accessToken; + + /** + * TTL of token in seconds + */ + private long tokenTtl; + + /** + * Last timestamp refresh security info from server + */ + private long lastRefreshTime; + + /** + * time window to refresh security info in seconds + */ + private long tokenRefreshWindow; + + /** + * Construct from properties, keeping flexibility + * + * @param properties a bunch of properties to read + */ + public SecurityProxy(Properties properties) { + username = properties.getProperty(PropertyKeyConst.USERNAME, StringUtils.EMPTY); + password = properties.getProperty(PropertyKeyConst.PASSWORD, StringUtils.EMPTY); + contextPath = properties.getProperty(PropertyKeyConst.CONTEXT_PATH, "/nacos"); + } + + public boolean login(List servers) { + + try { + if ((System.currentTimeMillis() - lastRefreshTime) < TimeUnit.SECONDS.toMillis(tokenTtl - tokenRefreshWindow)) { + return true; + } + + for (String server : servers) { + if (login(server)) { + lastRefreshTime = System.currentTimeMillis(); + return true; + } + } + } catch (Throwable t) { + } + + return false; + } + + public boolean login(String server) { + + if (StringUtils.isNotBlank(username)) { + String body = "username=" + username + "&password=" + password; + String url = "http://" + server + contextPath + LOGIN_URL; + + if (server.contains(Constants.HTTP_PREFIX)) { + url = server + contextPath + LOGIN_URL; + } + + HttpClient.HttpResult result = HttpClient.request(url, new ArrayList(2), + new HashMap(2), body, Charsets.UTF_8.name(), HttpMethod.POST); + + if (result.code != HttpURLConnection.HTTP_OK) { + SECURITY_LOGGER.error("login failed: {}", JSON.toJSONString(result)); + return false; + } + + JSONObject obj = JSON.parseObject(result.content); + if (obj.containsKey(Constants.ACCESS_TOKEN)) { + accessToken = obj.getString(Constants.ACCESS_TOKEN); + tokenTtl = obj.getIntValue(Constants.TOKEN_TTL); + tokenRefreshWindow = tokenTtl / 10; + } + } + return true; + } + + public String getAccessToken() { + return accessToken; + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/utils/ParamUtil.java b/client/src/main/java/com/alibaba/nacos/client/utils/ParamUtil.java index 7f8f6004c..f3d017fb3 100644 --- a/client/src/main/java/com/alibaba/nacos/client/utils/ParamUtil.java +++ b/client/src/main/java/com/alibaba/nacos/client/utils/ParamUtil.java @@ -16,6 +16,8 @@ package com.alibaba.nacos.client.utils; import com.alibaba.nacos.api.PropertyKeyConst; +import com.alibaba.nacos.api.SystemPropertyKeyConst; +import com.alibaba.nacos.api.common.Constants; import com.alibaba.nacos.client.config.impl.HttpSimpleClient; import org.slf4j.Logger; @@ -152,6 +154,36 @@ public class ParamUtil { ParamUtil.defaultNodesPath = defaultNodesPath; } + public static String parseNamespace(Properties properties) { + String namespaceTmp = null; + + String isUseCloudNamespaceParsing = + properties.getProperty(PropertyKeyConst.IS_USE_CLOUD_NAMESPACE_PARSING, + System.getProperty(SystemPropertyKeyConst.IS_USE_CLOUD_NAMESPACE_PARSING, + String.valueOf(Constants.DEFAULT_USE_CLOUD_NAMESPACE_PARSING))); + + if (Boolean.parseBoolean(isUseCloudNamespaceParsing)) { + namespaceTmp = TemplateUtils.stringBlankAndThenExecute(namespaceTmp, new Callable() { + @Override + public String call() { + return TenantUtil.getUserTenantForAcm(); + } + }); + + namespaceTmp = TemplateUtils.stringBlankAndThenExecute(namespaceTmp, new Callable() { + @Override + public String call() { + String namespace = System.getenv(PropertyKeyConst.SystemEnv.ALIBABA_ALIWARE_NAMESPACE); + return org.apache.commons.lang3.StringUtils.isNotBlank(namespace) ? namespace : StringUtils.EMPTY; + } + }); + } + + if (org.apache.commons.lang3.StringUtils.isBlank(namespaceTmp)) { + namespaceTmp = properties.getProperty(PropertyKeyConst.NAMESPACE); + } + return StringUtils.isNotBlank(namespaceTmp) ? namespaceTmp.trim() : StringUtils.EMPTY; + } public static String parsingEndpointRule(String endpointUrl) { // 配置文件中输入的话,以 ENV 中的优先, diff --git a/config/src/main/java/com/alibaba/nacos/config/server/auth/ConfigResourceParser.java b/config/src/main/java/com/alibaba/nacos/config/server/auth/ConfigResourceParser.java new file mode 100644 index 000000000..d60d5bb0f --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/auth/ConfigResourceParser.java @@ -0,0 +1,64 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.nacos.config.server.auth; + +import com.alibaba.nacos.api.common.Constants; +import com.alibaba.nacos.core.auth.Resource; +import com.alibaba.nacos.core.auth.ResourceParser; +import org.apache.commons.lang3.StringUtils; + +import javax.servlet.http.HttpServletRequest; + +/** + * Config resource parser + * + * @author nkorange + * @since 1.2.0 + */ +public class ConfigResourceParser implements ResourceParser { + + private static final String AUTH_CONFIG_PREFIX = "config/"; + + @Override + public String parseName(Object request) { + HttpServletRequest req = (HttpServletRequest) request; + String namespaceId = req.getParameter("tenant"); + String groupName = req.getParameter("group"); + String dataId = req.getParameter("dataId"); + + if (StringUtils.isBlank(namespaceId)) { + namespaceId = Constants.DEFAULT_NAMESPACE_ID; + } + + StringBuilder sb = new StringBuilder(); + + sb.append(namespaceId).append(Resource.SPLITTER); + + if (StringUtils.isBlank(dataId)) { + sb.append("*") + .append(Resource.SPLITTER) + .append(AUTH_CONFIG_PREFIX) + .append("*"); + } else { + sb.append(groupName) + .append(Resource.SPLITTER) + .append(AUTH_CONFIG_PREFIX) + .append(dataId); + } + + return sb.toString(); + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/auth/PermissionInfo.java b/config/src/main/java/com/alibaba/nacos/config/server/auth/PermissionInfo.java new file mode 100644 index 000000000..3f6c15f56 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/auth/PermissionInfo.java @@ -0,0 +1,62 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.nacos.config.server.auth; + +/** + * @author nkorange + * @since 1.2.0 + */ +public class PermissionInfo { + + /** + * Role name + */ + private String role; + + /** + * Resource + */ + private String resource; + + /** + * Action on resource + */ + private String action; + + public String getRole() { + return role; + } + + public void setRole(String role) { + this.role = role; + } + + public String getResource() { + return resource; + } + + public void setResource(String resource) { + this.resource = resource; + } + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/auth/PermissionPersistService.java b/config/src/main/java/com/alibaba/nacos/config/server/auth/PermissionPersistService.java new file mode 100644 index 000000000..e87b638a1 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/auth/PermissionPersistService.java @@ -0,0 +1,111 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.nacos.config.server.auth; + + +import com.alibaba.nacos.config.server.model.Page; +import com.alibaba.nacos.config.server.service.PersistService; +import com.alibaba.nacos.config.server.utils.PaginationHelper; +import org.apache.commons.lang3.StringUtils; +import org.springframework.jdbc.CannotGetJdbcConnectionException; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Service; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; + +import static com.alibaba.nacos.config.server.utils.LogUtil.fatalLog; + +/** + * Permission CRUD service + * + * @author nkorange + * @since 1.2.0 + */ +@Service +public class PermissionPersistService extends PersistService { + + public Page getPermissions(String role, int pageNo, int pageSize) { + PaginationHelper helper = new PaginationHelper<>(); + + String sqlCountRows = "select count(*) from permissions where "; + String sqlFetchRows + = "select role,resource,action from permissions where "; + + String where = " role='" + role + "' "; + + if (StringUtils.isBlank(role)) { + where = " 1=1 "; + } + + try { + Page pageInfo = helper.fetchPage(jt, sqlCountRows + + where, sqlFetchRows + where, new ArrayList().toArray(), pageNo, + pageSize, PERMISSION_ROW_MAPPER); + + if (pageInfo==null) { + pageInfo = new Page<>(); + pageInfo.setTotalCount(0); + pageInfo.setPageItems(new ArrayList<>()); + } + + return pageInfo; + + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + public void addPermission(String role, String resource, String action) { + + String sql = "INSERT into permissions (role, resource, action) VALUES (?, ?, ?)"; + + try { + jt.update(sql, role, resource, action); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + public void deletePermission(String role, String resource, String action) { + + String sql = "DELETE from permissions WHERE role=? and resource=? and action=?"; + try { + jt.update(sql, role, resource, action); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + private static final class PermissionRowMapper implements + RowMapper { + @Override + public PermissionInfo mapRow(ResultSet rs, int rowNum) + throws SQLException { + PermissionInfo info = new PermissionInfo(); + info.setResource(rs.getString("resource")); + info.setAction(rs.getString("action")); + info.setRole(rs.getString("role")); + return info; + } + } + + private static final PermissionRowMapper PERMISSION_ROW_MAPPER = new PermissionRowMapper(); +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/auth/RoleInfo.java b/config/src/main/java/com/alibaba/nacos/config/server/auth/RoleInfo.java new file mode 100644 index 000000000..269e977b4 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/auth/RoleInfo.java @@ -0,0 +1,45 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.nacos.config.server.auth; + +/** + * Role Info + * + * @author nkorange + * @since 1.2.0 + */ +public class RoleInfo { + + private String role; + + private String username; + + public String getRole() { + return role; + } + + public void setRole(String role) { + this.role = role; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/auth/RolePersistService.java b/config/src/main/java/com/alibaba/nacos/config/server/auth/RolePersistService.java new file mode 100644 index 000000000..83d1e5d2f --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/auth/RolePersistService.java @@ -0,0 +1,139 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.nacos.config.server.auth; + +import com.alibaba.nacos.config.server.model.Page; +import com.alibaba.nacos.config.server.model.User; +import com.alibaba.nacos.config.server.service.PersistService; +import com.alibaba.nacos.config.server.utils.PaginationHelper; +import org.apache.commons.lang3.StringUtils; +import org.springframework.jdbc.CannotGetJdbcConnectionException; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Service; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; + +import static com.alibaba.nacos.config.server.utils.LogUtil.fatalLog; + + +/** + * Role CRUD service + * + * @author nkorange + * @since 1.2.0 + */ +@Service +public class RolePersistService extends PersistService { + + + public Page getRoles(int pageNo, int pageSize) { + + PaginationHelper helper = new PaginationHelper<>(); + + String sqlCountRows = "select count(*) from (select distinct role from roles) roles where "; + String sqlFetchRows + = "select role,username from roles where "; + + String where = " 1=1 "; + + try { + Page pageInfo = helper.fetchPage(jt, sqlCountRows + + where, sqlFetchRows + where, new ArrayList().toArray(), pageNo, + pageSize, ROLE_INFO_ROW_MAPPER); + if (pageInfo == null) { + pageInfo = new Page<>(); + pageInfo.setTotalCount(0); + pageInfo.setPageItems(new ArrayList<>()); + } + return pageInfo; + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + public Page getRolesByUserName(String username, int pageNo, int pageSize) { + + PaginationHelper helper = new PaginationHelper<>(); + + String sqlCountRows = "select count(*) from roles where "; + String sqlFetchRows + = "select role,username from roles where "; + + String where = " username='" + username + "' "; + + if (StringUtils.isBlank(username)) { + where = " 1=1 "; + } + + try { + return helper.fetchPage(jt, sqlCountRows + + where, sqlFetchRows + where, new ArrayList().toArray(), pageNo, + pageSize, ROLE_INFO_ROW_MAPPER); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + public void addRole(String role, String userName) { + + String sql = "INSERT into roles (role, username) VALUES (?, ?)"; + + try { + jt.update(sql, role, userName); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + public void deleteRole(String role) { + String sql = "DELETE from roles WHERE role=?"; + try { + jt.update(sql, role); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + public void deleteRole(String role, String username) { + String sql = "DELETE from roles WHERE role=? and username=?"; + try { + jt.update(sql, role, username); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + private static final class RoleInfoRowMapper implements + RowMapper { + @Override + public RoleInfo mapRow(ResultSet rs, int rowNum) + throws SQLException { + RoleInfo roleInfo = new RoleInfo(); + roleInfo.setRole(rs.getString("role")); + roleInfo.setUsername(rs.getString("username")); + return roleInfo; + } + } + + private static final RoleInfoRowMapper ROLE_INFO_ROW_MAPPER = new RoleInfoRowMapper(); +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/auth/UserPersistService.java b/config/src/main/java/com/alibaba/nacos/config/server/auth/UserPersistService.java new file mode 100644 index 000000000..bf4218f06 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/auth/UserPersistService.java @@ -0,0 +1,130 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.nacos.config.server.auth; + +import com.alibaba.nacos.config.server.model.Page; +import com.alibaba.nacos.config.server.model.User; +import com.alibaba.nacos.config.server.service.PersistService; +import com.alibaba.nacos.config.server.utils.PaginationHelper; +import org.apache.commons.lang3.StringUtils; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.CannotGetJdbcConnectionException; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Service; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; + +import static com.alibaba.nacos.config.server.utils.LogUtil.fatalLog; + +/** + * User CRUD service + * + * @author nkorange + * @since 1.2.0 + */ +@Service +public class UserPersistService extends PersistService { + + public void createUser(String username, String password) { + String sql = "INSERT into users (username, password, enabled) VALUES (?, ?, ?)"; + + try { + jt.update(sql, username, password, true); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + public void deleteUser(String username) { + String sql = "DELETE from users WHERE username=?"; + try { + jt.update(sql, username); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + public void updateUserPassword(String username, String password) { + try { + jt.update( + "UPDATE users SET password = ? WHERE username=?", + password, username); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + public User findUserByUsername(String username) { + String sql = "SELECT username,password FROM users WHERE username=? "; + try { + return this.jt.queryForObject(sql, new Object[]{username}, USER_ROW_MAPPER); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } catch (EmptyResultDataAccessException e) { + return null; + } catch (Exception e) { + fatalLog.error("[db-other-error]" + e.getMessage(), e); + throw new RuntimeException(e); + } + } + + public Page getUsers(int pageNo, int pageSize) { + + PaginationHelper helper = new PaginationHelper<>(); + + String sqlCountRows = "select count(*) from users where "; + String sqlFetchRows + = "select username,password from users where "; + + String where = " 1=1 "; + + try { + Page pageInfo = helper.fetchPage(jt, sqlCountRows + + where, sqlFetchRows + where, new ArrayList().toArray(), pageNo, + pageSize, USER_ROW_MAPPER); + if (pageInfo == null) { + pageInfo = new Page<>(); + pageInfo.setTotalCount(0); + pageInfo.setPageItems(new ArrayList<>()); + } + return pageInfo; + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + private static final class UserRowMapper implements + RowMapper { + @Override + public User mapRow(ResultSet rs, int rowNum) + throws SQLException { + User info = new User(); + info.setUsername(rs.getString("username")); + info.setPassword(rs.getString("password")); + return info; + } + } + + private static final UserRowMapper USER_ROW_MAPPER = new UserRowMapper(); + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/controller/ConfigController.java b/config/src/main/java/com/alibaba/nacos/config/server/controller/ConfigController.java index 3f05e8891..d366a3e8c 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/controller/ConfigController.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/controller/ConfigController.java @@ -15,6 +15,7 @@ */ package com.alibaba.nacos.config.server.controller; +import com.alibaba.nacos.config.server.auth.ConfigResourceParser; import com.alibaba.nacos.config.server.constant.Constants; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.config.server.controller.parameters.SameNamespaceCloneConfigBean; @@ -28,6 +29,8 @@ import com.alibaba.nacos.config.server.service.PersistService; import com.alibaba.nacos.config.server.service.trace.ConfigTraceService; import com.alibaba.nacos.config.server.utils.*; import com.alibaba.nacos.config.server.utils.event.EventDispatcher; +import com.alibaba.nacos.core.auth.ActionTypes; +import com.alibaba.nacos.core.auth.Secured; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.time.DateFormatUtils; import org.slf4j.Logger; @@ -90,6 +93,7 @@ public class ConfigController { * @throws NacosException */ @PostMapping + @Secured(action = ActionTypes.WRITE, parser = ConfigResourceParser.class) public Boolean publishConfig(HttpServletRequest request, HttpServletResponse response, @RequestParam("dataId") String dataId, @RequestParam("group") String group, @RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY) @@ -166,6 +170,7 @@ public class ConfigController { * @throws NacosException */ @GetMapping + @Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class) public void getConfig(HttpServletRequest request, HttpServletResponse response, @RequestParam("dataId") String dataId, @RequestParam("group") String group, @RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY) @@ -186,6 +191,7 @@ public class ConfigController { * @throws NacosException */ @GetMapping(params = "show=all") + @Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class) public ConfigAllInfo detailConfigInfo(HttpServletRequest request, HttpServletResponse response, @RequestParam("dataId") String dataId, @RequestParam("group") String group, @RequestParam(value = "tenant", required = false, @@ -202,6 +208,7 @@ public class ConfigController { * @throws NacosException */ @DeleteMapping + @Secured(action = ActionTypes.WRITE, parser = ConfigResourceParser.class) public Boolean deleteConfig(HttpServletRequest request, HttpServletResponse response, @RequestParam("dataId") String dataId, // @RequestParam("group") String group, // @@ -231,6 +238,7 @@ public class ConfigController { * @Param [request, response, dataId, group, tenant, tag] */ @DeleteMapping(params = "delType=ids") + @Secured(action = ActionTypes.WRITE, parser = ConfigResourceParser.class) public RestResult deleteConfigs(HttpServletRequest request, HttpServletResponse response, @RequestParam(value = "ids") List ids) { String clientIp = RequestUtil.getRemoteIp(request); @@ -249,6 +257,7 @@ public class ConfigController { } @GetMapping("/catalog") + @Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class) public RestResult getConfigAdvanceInfo(@RequestParam("dataId") String dataId, @RequestParam("group") String group, @RequestParam(value = "tenant", required = false, @@ -264,6 +273,7 @@ public class ConfigController { * 比较MD5 */ @PostMapping("/listener") + @Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class) public void listener(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true); @@ -289,6 +299,7 @@ public class ConfigController { * 订阅改配置的客户端信息 */ @GetMapping("/listener") + @Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class) public GroupkeyListenserStatus getListeners(@RequestParam("dataId") String dataId, @RequestParam("group") String group, @RequestParam(value = "tenant", required = false) String tenant, @@ -308,6 +319,7 @@ public class ConfigController { * 查询配置信息,返回JSON格式。 */ @GetMapping(params = "search=accurate") + @Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class) public Page searchConfig(@RequestParam("dataId") String dataId, @RequestParam("group") String group, @RequestParam(value = "appName", required = false) String appName, @@ -337,6 +349,7 @@ public class ConfigController { * 模糊查询配置信息。不允许只根据内容模糊查询,即dataId和group都为NULL,但content不是NULL。这种情况下,返回所有配置。 */ @GetMapping(params = "search=blur") + @Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class) public Page fuzzySearchConfig(@RequestParam("dataId") String dataId, @RequestParam("group") String group, @RequestParam(value = "appName", required = false) String appName, @@ -363,6 +376,7 @@ public class ConfigController { } @DeleteMapping(params = "beta=true") + @Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class) public RestResult stopBeta(@RequestParam(value = "dataId") String dataId, @RequestParam(value = "group") String group, @RequestParam(value = "tenant", required = false, @@ -385,6 +399,7 @@ public class ConfigController { } @GetMapping(params = "beta=true") + @Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class) public RestResult queryBeta(@RequestParam(value = "dataId") String dataId, @RequestParam(value = "group") String group, @RequestParam(value = "tenant", required = false, @@ -405,6 +420,7 @@ public class ConfigController { } @GetMapping(params = "export=true") + @Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class) public ResponseEntity exportConfig(@RequestParam(value = "dataId", required = false) String dataId, @RequestParam(value = "group", required = false) String group, @RequestParam(value = "appName", required = false) String appName, @@ -444,6 +460,7 @@ public class ConfigController { } @PostMapping(params = "import=true") + @Secured(action = ActionTypes.WRITE, parser = ConfigResourceParser.class) public RestResult> importAndPublishConfig(HttpServletRequest request, @RequestParam(value = "src_user", required = false) String srcUser, @RequestParam(value = "namespace", required = false) String namespace, diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/PersistService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/PersistService.java index ed5440dfa..96c1f7b4a 100755 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/PersistService.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/PersistService.java @@ -3237,36 +3237,6 @@ public class PersistService { } } - public User findUserByUsername(String username) { - String sql = "SELECT username,password FROM users WHERE username=? "; - try { - return this.jt.queryForObject(sql, new Object[]{username}, USER_ROW_MAPPER); - } catch (CannotGetJdbcConnectionException e) { - fatalLog.error("[db-error] " + e.toString(), e); - throw e; - } catch (EmptyResultDataAccessException e) { - return null; - } catch (Exception e) { - fatalLog.error("[db-other-error]" + e.getMessage(), e); - throw new RuntimeException(e); - } - } - - /** - * 更新用户密码 - */ - public void updateUserPassword(String username, String password) { - try { - jt.update( - "UPDATE users SET password = ? WHERE username=?", - password, username); - } catch (CannotGetJdbcConnectionException e) { - fatalLog.error("[db-error] " + e.toString(), e); - throw e; - } - } - - private List convertDeletedConfig(List> list) { List configs = new ArrayList(); for (Map map : list) { @@ -3572,7 +3542,7 @@ public class PersistService { static final TenantInfoRowMapper TENANT_INFO_ROW_MAPPER = new TenantInfoRowMapper(); - static final UserRowMapper USER_ROW_MAPPER = new UserRowMapper(); + protected static final UserRowMapper USER_ROW_MAPPER = new UserRowMapper(); static final ConfigInfoWrapperRowMapper CONFIG_INFO_WRAPPER_ROW_MAPPER = new ConfigInfoWrapperRowMapper(); @@ -3605,7 +3575,7 @@ public class PersistService { private static String PATTERN_STR = "*"; private final static int QUERY_LIMIT_SIZE = 50; - private JdbcTemplate jt; - private TransactionTemplate tjt; + protected JdbcTemplate jt; + protected TransactionTemplate tjt; } diff --git a/config/src/main/resources/META-INF/nacos-db.sql b/config/src/main/resources/META-INF/nacos-db.sql index 73bef7b76..14a2e2b74 100644 --- a/config/src/main/resources/META-INF/nacos-db.sql +++ b/config/src/main/resources/META-INF/nacos-db.sql @@ -189,6 +189,13 @@ CREATE TABLE roles ( role varchar(50) NOT NULL ); +CREATE TABLE permissions ( + role varchar(50) NOT NULL, + resource varchar(512) NOT NULL, + action varchar(8) NOT NULL, + constraint uk_role_permission UNIQUE (role,resource,action) +); + INSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE); -INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN'); +INSERT INTO roles (username, role) VALUES ('nacos', 'GLOBAL_ADMIN'); diff --git a/config/src/main/resources/META-INF/schema.sql b/config/src/main/resources/META-INF/schema.sql index 5489d69fa..43249fd5c 100644 --- a/config/src/main/resources/META-INF/schema.sql +++ b/config/src/main/resources/META-INF/schema.sql @@ -184,6 +184,13 @@ CREATE TABLE roles ( role varchar(50) NOT NULL ); +CREATE TABLE permissions ( + role varchar(50) NOT NULL, + resource varchar(512) NOT NULL, + action varchar(8) NOT NULL, + constraint uk_role_permission UNIQUE (role,resource,action) +); + INSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE); -INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN'); +INSERT INTO roles (username, role) VALUES ('nacos', 'GLOBAL_ADMIN'); diff --git a/console/src/main/java/com/alibaba/nacos/console/config/CorsConfig.java b/console/src/main/java/com/alibaba/nacos/console/config/ConsoleConfig.java similarity index 62% rename from console/src/main/java/com/alibaba/nacos/console/config/CorsConfig.java rename to console/src/main/java/com/alibaba/nacos/console/config/ConsoleConfig.java index d42b18048..0a47b45b6 100644 --- a/console/src/main/java/com/alibaba/nacos/console/config/CorsConfig.java +++ b/console/src/main/java/com/alibaba/nacos/console/config/ConsoleConfig.java @@ -13,24 +13,40 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.alibaba.nacos.console.config; +import com.alibaba.nacos.core.code.ControllerMethodsCache; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.stereotype.Component; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; -/** - * Spring cors config - * - * @author yshen - */ -@Configuration -public class CorsConfig { +import javax.annotation.PostConstruct; +/** + * @author yshen + * @author nkorange + * @since 1.2.0 + */ +@Component +@EnableScheduling +@PropertySource("/application.properties") +public class ConsoleConfig { + + @Autowired + private ControllerMethodsCache methodsCache; + + @PostConstruct + public void init() { + methodsCache.initClassMethod("com.alibaba.nacos.naming.controllers"); + methodsCache.initClassMethod("com.alibaba.nacos.console.controller"); + methodsCache.initClassMethod("com.alibaba.nacos.config.server.controller"); + } @Bean public CorsFilter corsFilter() { @@ -44,5 +60,4 @@ public class CorsConfig { source.registerCorsConfiguration("/**", config); return new CorsFilter(source); } - } diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/AuthController.java b/console/src/main/java/com/alibaba/nacos/console/controller/AuthController.java deleted file mode 100644 index 03f4d2328..000000000 --- a/console/src/main/java/com/alibaba/nacos/console/controller/AuthController.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 1999-2018 Alibaba Group Holding Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.alibaba.nacos.console.controller; - -import com.alibaba.nacos.config.server.model.RestResult; -import com.alibaba.nacos.console.config.WebSecurityConfig; -import com.alibaba.nacos.console.security.CustomUserDetailsServiceImpl; -import com.alibaba.nacos.console.utils.JwtTokenUtils; -import com.alibaba.nacos.console.utils.PasswordEncoderUtil; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.web.bind.annotation.*; - -import javax.servlet.http.HttpServletResponse; - -/** - * auth - * - * @author wfnuser - */ -@RestController("auth") -@RequestMapping("/v1/auth") -public class AuthController { - - @Autowired - private JwtTokenUtils jwtTokenUtils; - @Autowired - private AuthenticationManager authenticationManager; - @Autowired - private CustomUserDetailsServiceImpl userDetailsService; - - /** - * Whether the Nacos is in broken states or not, and cannot recover except by being restarted - * - * @return HTTP code equal to 200 indicates that Nacos is in right states. HTTP code equal to 500 indicates that - * Nacos is in broken states. - */ - - @PostMapping("login") - public RestResult login(@RequestParam String username, @RequestParam String password, HttpServletResponse response) { - - // 通过用户名和密码创建一个 Authentication 认证对象,实现类为 UsernamePasswordAuthenticationToken - UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password); - RestResult rr = new RestResult(); - - try { - //通过 AuthenticationManager(默认实现为ProviderManager)的authenticate方法验证 Authentication 对象 - Authentication authentication = authenticationManager.authenticate(authenticationToken); - //将 Authentication 绑定到 SecurityContext - SecurityContextHolder.getContext().setAuthentication(authentication); - //生成Token - String token = jwtTokenUtils.createToken(authentication); - //将Token写入到Http头部 - response.addHeader(WebSecurityConfig.AUTHORIZATION_HEADER, "Bearer " + token); - rr.setCode(200); - rr.setData("Bearer " + token); - return rr; - } catch (BadCredentialsException authentication) { - rr.setCode(401); - rr.setMessage("Login failed"); - return rr; - } - } - - @PutMapping("password") - public RestResult updatePassword(@RequestParam(value = "oldPassword") String oldPassword, - @RequestParam(value = "newPassword") String newPassword) { - - RestResult rr = new RestResult(); - Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); - String username = ((UserDetails) principal).getUsername(); - UserDetails userDetails = userDetailsService.loadUserByUsername(username); - String password = userDetails.getPassword(); - - // TODO: throw out more fine grained exceptions - try { - if (PasswordEncoderUtil.matches(oldPassword, password)) { - userDetailsService.updateUserPassword(username, PasswordEncoderUtil.encode(newPassword)); - rr.setCode(200); - rr.setMessage("Update password success"); - } else { - rr.setCode(401); - rr.setMessage("Old password is invalid"); - } - } catch (Exception e) { - rr.setCode(500); - rr.setMessage("Update userpassword failed"); - } - return rr; - } -} diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/PermissionController.java b/console/src/main/java/com/alibaba/nacos/console/controller/PermissionController.java new file mode 100644 index 000000000..71339d4c5 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/controller/PermissionController.java @@ -0,0 +1,86 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.nacos.console.controller; + + +import com.alibaba.nacos.config.server.model.RestResult; +import com.alibaba.nacos.console.security.nacos.NacosAuthConfig; +import com.alibaba.nacos.console.security.nacos.roles.NacosRoleServiceImpl; +import com.alibaba.nacos.core.auth.ActionTypes; +import com.alibaba.nacos.core.auth.Secured; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + + +/** + * Permission operation controller + * + * @author nkorange + * @since 1.2.0 + */ +@RestController +@RequestMapping("/v1/auth/permissions") +public class PermissionController { + + @Autowired + private NacosRoleServiceImpl nacosRoleService; + + /** + * Query permissions of a role + * + * @param role the role + * @param pageNo page index + * @param pageSize page size + * @return permission of a role + */ + @GetMapping + @Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "permissions", action = ActionTypes.READ) + public Object getPermissions(@RequestParam int pageNo, @RequestParam int pageSize, + @RequestParam(name = "role", defaultValue = StringUtils.EMPTY) String role) { + return nacosRoleService.getPermissionsFromDatabase(role, pageNo, pageSize); + } + + /** + * Add a permission to a role + * + * @param role the role + * @param resource the related resource + * @param action the related action + * @return ok if succeed + */ + @PostMapping + @Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "permissions", action = ActionTypes.WRITE) + public Object addPermission(@RequestParam String role, @RequestParam String resource, @RequestParam String action) { + nacosRoleService.addPermission(role, resource, action); + return new RestResult<>(200, "add permission ok!"); + } + + /** + * Delete a permission from a role + * + * @param role the role + * @param resource the related resource + * @param action the related action + * @return ok if succeed + */ + @DeleteMapping + @Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "permissions", action = ActionTypes.WRITE) + public Object deletePermission(@RequestParam String role, @RequestParam String resource, @RequestParam String action) { + nacosRoleService.deletePermission(role, resource, action); + return new RestResult<>(200, "delete permission ok!"); + } +} diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/RoleController.java b/console/src/main/java/com/alibaba/nacos/console/controller/RoleController.java new file mode 100644 index 000000000..69d720f5c --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/controller/RoleController.java @@ -0,0 +1,93 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.nacos.console.controller; + + +import com.alibaba.nacos.config.server.model.RestResult; +import com.alibaba.nacos.console.security.nacos.NacosAuthConfig; +import com.alibaba.nacos.console.security.nacos.roles.NacosRoleServiceImpl; +import com.alibaba.nacos.core.auth.ActionTypes; +import com.alibaba.nacos.core.auth.Secured; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +/** + * Role operation controller + * + * @author nkorange + * @since 1.2.0 + */ +@RestController +@RequestMapping("/v1/auth/roles") +public class RoleController { + + @Autowired + private NacosRoleServiceImpl roleService; + + /** + * Get roles list + * + * @param pageNo number index of page + * @param pageSize page size + * @param username optional, username of user + * @return role list + */ + @GetMapping + @Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "roles", action = ActionTypes.READ) + public Object getRoles(@RequestParam int pageNo, @RequestParam int pageSize, + @RequestParam(name = "username", defaultValue = "") String username) { + return roleService.getRolesFromDatabase(username, pageNo, pageSize); + } + + /** + * Add a role to a user + *

+ * This method is used for 2 functions: + * 1. create a role and bind it to GLOBAL_ADMIN. + * 2. bind a role to an user. + * + * @param role + * @param username + * @return + */ + @PostMapping + @Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "roles", action = ActionTypes.WRITE) + public Object addRole(@RequestParam String role, @RequestParam String username) { + roleService.addRole(role, username); + return new RestResult<>(200, "add role ok!"); + } + + /** + * Delete a role. If no username is specified, all users under this role are deleted + * + * @param role role + * @param username username + * @return ok if succeed + */ + @DeleteMapping + @Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "roles", action = ActionTypes.WRITE) + public Object deleteRole(@RequestParam String role, + @RequestParam(name = "username", defaultValue = StringUtils.EMPTY) String username) { + if (StringUtils.isBlank(username)) { + roleService.deleteRole(role); + } else { + roleService.deleteRole(role, username); + } + return new RestResult<>(200, "delete role of user " + username + " ok!"); + } + +} diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/UserController.java b/console/src/main/java/com/alibaba/nacos/console/controller/UserController.java new file mode 100644 index 000000000..523bb0f43 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/controller/UserController.java @@ -0,0 +1,219 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.nacos.console.controller; + +import com.alibaba.fastjson.JSONObject; +import com.alibaba.nacos.api.common.Constants; +import com.alibaba.nacos.config.server.model.RestResult; +import com.alibaba.nacos.config.server.model.User; +import com.alibaba.nacos.console.security.nacos.NacosAuthConfig; +import com.alibaba.nacos.console.security.nacos.NacosAuthManager; +import com.alibaba.nacos.console.security.nacos.users.NacosUser; +import com.alibaba.nacos.console.security.nacos.users.NacosUserDetailsServiceImpl; +import com.alibaba.nacos.console.utils.JwtTokenUtils; +import com.alibaba.nacos.console.utils.PasswordEncoderUtil; +import com.alibaba.nacos.core.auth.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * User related methods entry + * + * @author wfnuser + * @author nkorange + */ +@RestController("user") +@RequestMapping({"/v1/auth", "/v1/auth/users"}) +public class UserController { + + @Autowired + private JwtTokenUtils jwtTokenUtils; + + @Autowired + private AuthenticationManager authenticationManager; + + @Autowired + private NacosUserDetailsServiceImpl userDetailsService; + + @Autowired + private AuthConfigs authConfigs; + + @Autowired + private NacosAuthManager authManager; + + /** + * Create a new user + * + * @param username username + * @param password password + * @return ok if create succeed + * @throws IllegalArgumentException if user already exist + * @since 1.2.0 + */ + @Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "users", action = ActionTypes.WRITE) + @PostMapping + public Object createUser(@RequestParam String username, @RequestParam String password) { + + User user = userDetailsService.getUserFromDatabase(username); + if (user != null) { + throw new IllegalArgumentException("user '" + username + "' already exist!"); + } + userDetailsService.createUser(username, PasswordEncoderUtil.encode(password)); + return new RestResult<>(200, "create user ok!"); + } + + /** + * Delete an existed user + * + * @param username username of user + * @return ok if deleted succeed, keep silent if user not exist + * @since 1.2.0 + */ + @DeleteMapping + @Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "users", action = ActionTypes.WRITE) + public Object deleteUser(@RequestParam String username) { + + userDetailsService.deleteUser(username); + return new RestResult<>(200, "delete user ok!"); + } + + /** + * Update an user + * + * @param username username of user + * @param newPassword new password of user + * @return ok if update succeed + * @throws IllegalArgumentException if user not exist or oldPassword is incorrect + * @since 1.2.0 + */ + @PutMapping + @Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "users", action = ActionTypes.WRITE) + public Object updateUser(@RequestParam String username, @RequestParam String newPassword) { + + User user = userDetailsService.getUserFromDatabase(username); + if (user == null) { + throw new IllegalArgumentException("user " + username + " not exist!"); + } + + userDetailsService.updateUserPassword(username, PasswordEncoderUtil.encode(newPassword)); + + return new RestResult<>(200, "update user ok!"); + } + + /** + * Get paged users + * + * @param pageNo number index of page + * @param pageSize size of page + * @return A collection of users, empty set if no user is found + * @since 1.2.0 + */ + @GetMapping + @Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "users", action = ActionTypes.READ) + public Object getUsers(@RequestParam int pageNo, @RequestParam int pageSize) { + return userDetailsService.getUsersFromDatabase(pageNo, pageSize); + } + + /** + * Login to Nacos + *

+ * This methods uses username and password to require a new token. + * + * @param username username of user + * @param password password + * @param response http response + * @param request http request + * @return new token of the user + * @throws AccessException if user info is incorrect + */ + @PostMapping("/login") + public Object login(@RequestParam String username, @RequestParam String password, + HttpServletResponse response, HttpServletRequest request) throws AccessException { + + + if (AuthSystemTypes.NACOS.name().equalsIgnoreCase(authConfigs.getNacosAuthSystemType())) { + NacosUser user = (NacosUser) authManager.login(request); + + response.addHeader(NacosAuthConfig.AUTHORIZATION_HEADER, + NacosAuthConfig.TOKEN_PREFIX + user.getToken()); + + JSONObject result = new JSONObject(); + result.put(Constants.ACCESS_TOKEN, user.getToken()); + result.put(Constants.TOKEN_TTL, authConfigs.getTokenValidityInSeconds()); + result.put(Constants.GLOBAL_ADMIN, user.isGlobalAdmin()); + return result; + } + + // 通过用户名和密码创建一个 Authentication 认证对象,实现类为 UsernamePasswordAuthenticationToken + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password); + + RestResult rr = new RestResult(); + try { + //通过 AuthenticationManager(默认实现为ProviderManager)的authenticate方法验证 Authentication 对象 + Authentication authentication = authenticationManager.authenticate(authenticationToken); + //将 Authentication 绑定到 SecurityContext + SecurityContextHolder.getContext().setAuthentication(authentication); + //生成Token + String token = jwtTokenUtils.createToken(authentication); + //将Token写入到Http头部 + response.addHeader(NacosAuthConfig.AUTHORIZATION_HEADER, "Bearer " + token); + rr.setCode(200); + rr.setData("Bearer " + token); + return rr; + } catch (BadCredentialsException authentication) { + rr.setCode(401); + rr.setMessage("Login failed"); + return rr; + } + } + + @PutMapping("/password") + @Deprecated + public RestResult updatePassword(@RequestParam(value = "oldPassword") String oldPassword, + @RequestParam(value = "newPassword") String newPassword) { + + RestResult rr = new RestResult(); + Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + String username = ((UserDetails) principal).getUsername(); + User user = userDetailsService.getUserFromDatabase(username); + String password = user.getPassword(); + + // TODO: throw out more fine grained exceptions + try { + if (PasswordEncoderUtil.matches(oldPassword, password)) { + userDetailsService.updateUserPassword(username, PasswordEncoderUtil.encode(newPassword)); + rr.setCode(200); + rr.setMessage("Update password success"); + } else { + rr.setCode(401); + rr.setMessage("Old password is invalid"); + } + } catch (Exception e) { + rr.setCode(500); + rr.setMessage("Update userpassword failed"); + } + return rr; + } +} diff --git a/console/src/main/java/com/alibaba/nacos/console/exception/ConsoleExceptionHandler.java b/console/src/main/java/com/alibaba/nacos/console/exception/ConsoleExceptionHandler.java new file mode 100644 index 000000000..9d9d285a3 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/exception/ConsoleExceptionHandler.java @@ -0,0 +1,52 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.nacos.console.exception; + +import com.alibaba.nacos.core.auth.AccessException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +/** + * Exception handler for console module + * + * @author nkorange + * @since 1.2.0 + */ +@ControllerAdvice +public class ConsoleExceptionHandler { + + private static final Logger logger = LoggerFactory.getLogger(ConsoleExceptionHandler.class); + + @ExceptionHandler(AccessException.class) + private ResponseEntity handleAccessException(AccessException e) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).body(e.getErrMsg()); + } + + @ExceptionHandler(IllegalArgumentException.class) + private ResponseEntity handleIllegalArgumentException(IllegalArgumentException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.toString()); + } + + @ExceptionHandler(Exception.class) + private ResponseEntity handleException(Exception e) { + logger.error("CONSOLE", e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.toString()); + } +} diff --git a/console/src/main/java/com/alibaba/nacos/console/filter/JwtAuthenticationTokenFilter.java b/console/src/main/java/com/alibaba/nacos/console/filter/JwtAuthenticationTokenFilter.java index b760d6d89..e4ba2c547 100644 --- a/console/src/main/java/com/alibaba/nacos/console/filter/JwtAuthenticationTokenFilter.java +++ b/console/src/main/java/com/alibaba/nacos/console/filter/JwtAuthenticationTokenFilter.java @@ -15,11 +15,12 @@ */ package com.alibaba.nacos.console.filter; -import com.alibaba.nacos.console.config.WebSecurityConfig; -import com.alibaba.nacos.console.utils.JwtTokenUtils; +import com.alibaba.nacos.api.common.Constants; +import com.alibaba.nacos.console.security.nacos.JwtTokenManager; +import com.alibaba.nacos.console.security.nacos.NacosAuthConfig; +import org.apache.commons.lang3.StringUtils; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; @@ -37,29 +38,23 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { private static final String TOKEN_PREFIX = "Bearer "; - private JwtTokenUtils tokenProvider; + private JwtTokenManager tokenManager; - public JwtAuthenticationTokenFilter(JwtTokenUtils tokenProvider) { - this.tokenProvider = tokenProvider; + public JwtAuthenticationTokenFilter(JwtTokenManager tokenManager) { + this.tokenManager = tokenManager; } @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws IOException, ServletException { + String jwt = resolveToken(request); - if (jwt != null && !"".equals(jwt.trim()) && SecurityContextHolder.getContext().getAuthentication() == null) { - if (this.tokenProvider.validateToken(jwt)) { - /** - * get auth info - */ - Authentication authentication = this.tokenProvider.getAuthentication(jwt); - /** - * save user info to securityContext - */ - SecurityContextHolder.getContext().setAuthentication(authentication); - } + if (StringUtils.isNotBlank(jwt) && SecurityContextHolder.getContext().getAuthentication() == null) { + this.tokenManager.validateToken(jwt); + Authentication authentication = this.tokenManager.getAuthentication(jwt); + SecurityContextHolder.getContext().setAuthentication(authentication); } - chain.doFilter(request, response); } @@ -67,12 +62,12 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { * Get token from header */ private String resolveToken(HttpServletRequest request) { - String bearerToken = request.getHeader(WebSecurityConfig.AUTHORIZATION_HEADER); - if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(TOKEN_PREFIX)) { - return bearerToken.substring(7, bearerToken.length()); + String bearerToken = request.getHeader(NacosAuthConfig.AUTHORIZATION_HEADER); + if (StringUtils.isNotBlank(bearerToken) && bearerToken.startsWith(TOKEN_PREFIX)) { + return bearerToken.substring(7); } - String jwt = request.getParameter(WebSecurityConfig.AUTHORIZATION_TOKEN); - if (StringUtils.hasText(jwt)) { + String jwt = request.getParameter(Constants.ACCESS_TOKEN); + if (StringUtils.isNotBlank(jwt)) { return jwt; } return null; diff --git a/console/src/main/java/com/alibaba/nacos/console/security/CustomUserDetailsServiceImpl.java b/console/src/main/java/com/alibaba/nacos/console/security/CustomUserDetailsServiceImpl.java deleted file mode 100644 index e814c09eb..000000000 --- a/console/src/main/java/com/alibaba/nacos/console/security/CustomUserDetailsServiceImpl.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 1999-2018 Alibaba Group Holding Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.alibaba.nacos.console.security; - - -import com.alibaba.nacos.config.server.model.User; -import com.alibaba.nacos.config.server.service.PersistService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.stereotype.Service; - -/** - * Custem user service - * - * @author wfnuser - */ -@Service -public class CustomUserDetailsServiceImpl implements UserDetailsService { - - @Autowired - private transient PersistService persistService; - - @Override - public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException { - User user = persistService.findUserByUsername(userName); - if (user == null) { - throw new UsernameNotFoundException(userName); - } - return new CustomUserDetails(user); - } - - public void updateUserPassword(String username, String password) throws Exception { - persistService.updateUserPassword(username, password); - } -} diff --git a/console/src/main/java/com/alibaba/nacos/console/security/CustomAuthenticationProvider.java b/console/src/main/java/com/alibaba/nacos/console/security/nacos/CustomAuthenticationProvider.java similarity index 90% rename from console/src/main/java/com/alibaba/nacos/console/security/CustomAuthenticationProvider.java rename to console/src/main/java/com/alibaba/nacos/console/security/nacos/CustomAuthenticationProvider.java index 893cc313d..c54db33f7 100644 --- a/console/src/main/java/com/alibaba/nacos/console/security/CustomAuthenticationProvider.java +++ b/console/src/main/java/com/alibaba/nacos/console/security/nacos/CustomAuthenticationProvider.java @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alibaba.nacos.console.security; +package com.alibaba.nacos.console.security.nacos; +import com.alibaba.nacos.console.security.nacos.users.NacosUserDetailsServiceImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -32,7 +33,7 @@ import org.springframework.stereotype.Component; public class CustomAuthenticationProvider implements AuthenticationProvider { @Autowired - private CustomUserDetailsServiceImpl userDetailsService; + private NacosUserDetailsServiceImpl userDetailsService; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { diff --git a/console/src/main/java/com/alibaba/nacos/console/security/JwtAuthenticationEntryPoint.java b/console/src/main/java/com/alibaba/nacos/console/security/nacos/JwtAuthenticationEntryPoint.java similarity index 82% rename from console/src/main/java/com/alibaba/nacos/console/security/JwtAuthenticationEntryPoint.java rename to console/src/main/java/com/alibaba/nacos/console/security/nacos/JwtAuthenticationEntryPoint.java index 9fe911c85..dad99758c 100644 --- a/console/src/main/java/com/alibaba/nacos/console/security/JwtAuthenticationEntryPoint.java +++ b/console/src/main/java/com/alibaba/nacos/console/security/nacos/JwtAuthenticationEntryPoint.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alibaba.nacos.console.security; +package com.alibaba.nacos.console.security.nacos; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,10 +37,10 @@ public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationEntryPoint.class); @Override - public void commence(HttpServletRequest httpServletRequest, - HttpServletResponse httpServletResponse, + public void commence(HttpServletRequest request, + HttpServletResponse response, AuthenticationException e) throws IOException, ServletException { - logger.error("Responding with unauthorized error. Message - {}", e.getMessage()); - httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); + logger.error("Responding with unauthorized error. Message:{}, url:{}", e.getMessage(), request.getRequestURI()); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); } } diff --git a/console/src/main/java/com/alibaba/nacos/console/security/nacos/JwtTokenManager.java b/console/src/main/java/com/alibaba/nacos/console/security/nacos/JwtTokenManager.java new file mode 100644 index 000000000..0ef8fce1f --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/security/nacos/JwtTokenManager.java @@ -0,0 +1,104 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.nacos.console.security.nacos; + +import com.alibaba.nacos.core.auth.AuthConfigs; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.core.userdetails.User; +import org.springframework.stereotype.Component; + +import java.util.Collection; +import java.util.Date; +import java.util.List; + +/** + * JWT token manager + * + * @author wfnuser + * @author nkorange + */ +@Component +public class JwtTokenManager { + + private static final String AUTHORITIES_KEY = "auth"; + + @Autowired + private AuthConfigs authConfigs; + + /** + * Create token + * + * @param authentication auth info + * @return token + */ + public String createToken(Authentication authentication) { + return createToken(authentication.getName()); + } + + public String createToken(String userName) { + + long now = (new Date()).getTime(); + + Date validity; + validity = new Date(now + authConfigs.getTokenValidityInSeconds() * 1000L); + + Claims claims = Jwts.claims().setSubject(userName); + + return Jwts.builder() + .setClaims(claims) + .setExpiration(validity) + .signWith(SignatureAlgorithm.HS256, authConfigs.getSecretKey()) + .compact(); + } + + /** + * Get auth Info + * + * @param token token + * @return auth info + */ + public Authentication getAuthentication(String token) { + /** + * parse the payload of token + */ + Claims claims = Jwts.parser() + .setSigningKey(authConfigs.getSecretKey()) + .parseClaimsJws(token) + .getBody(); + + List authorities = AuthorityUtils.commaSeparatedStringToAuthorityList((String) claims.get(AUTHORITIES_KEY)); + + User principal = new User(claims.getSubject(), "", authorities); + return new UsernamePasswordAuthenticationToken(principal, "", authorities); + } + + /** + * validate token + * + * @param token token + * @return whether valid + */ + public void validateToken(String token) { + Jwts.parser().setSigningKey(authConfigs.getSecretKey()).parseClaimsJws(token); + } +} diff --git a/console/src/main/java/com/alibaba/nacos/console/config/WebSecurityConfig.java b/console/src/main/java/com/alibaba/nacos/console/security/nacos/NacosAuthConfig.java similarity index 55% rename from console/src/main/java/com/alibaba/nacos/console/config/WebSecurityConfig.java rename to console/src/main/java/com/alibaba/nacos/console/security/nacos/NacosAuthConfig.java index b4b9647de..06e283201 100644 --- a/console/src/main/java/com/alibaba/nacos/console/config/WebSecurityConfig.java +++ b/console/src/main/java/com/alibaba/nacos/console/security/nacos/NacosAuthConfig.java @@ -13,12 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alibaba.nacos.console.config; +package com.alibaba.nacos.console.security.nacos; + import com.alibaba.nacos.console.filter.JwtAuthenticationTokenFilter; -import com.alibaba.nacos.console.security.CustomUserDetailsServiceImpl; -import com.alibaba.nacos.console.security.JwtAuthenticationEntryPoint; -import com.alibaba.nacos.console.utils.JwtTokenUtils; +import com.alibaba.nacos.console.security.nacos.users.NacosUserDetailsServiceImpl; +import com.alibaba.nacos.core.auth.AuthConfigs; +import com.alibaba.nacos.core.auth.AuthSystemTypes; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -43,64 +45,95 @@ import org.springframework.web.cors.CorsUtils; */ @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) -public class WebSecurityConfig extends WebSecurityConfigurerAdapter { +public class NacosAuthConfig extends WebSecurityConfigurerAdapter { public static final String AUTHORIZATION_HEADER = "Authorization"; - public static final String AUTHORIZATION_TOKEN = "access_token"; - public static final String SECURITY_IGNORE_URLS_SPILT_CHAR = ","; - @Autowired - private CustomUserDetailsServiceImpl userDetailsService; + public static final String LOGIN_ENTRY_POINT = "/v1/auth/login"; - @Autowired - private JwtAuthenticationEntryPoint unauthorizedHandler; + public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/v1/auth/**"; - @Autowired - private JwtTokenUtils tokenProvider; + public static final String TOKEN_PREFIX = "Bearer "; + + public static final String CONSOLE_RESOURCE_NAME_PREFIX = "console/"; @Autowired private Environment env; + @Autowired + private JwtTokenManager tokenProvider; + + @Autowired + private AuthConfigs authConfigs; + + @Autowired + private NacosUserDetailsServiceImpl userDetailsService; + @Bean(name = BeanIds.AUTHENTICATION_MANAGER) @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } + @Override + public void configure(WebSecurity web) { + + String ignoreURLs = null; +// + if (AuthSystemTypes.NACOS.name().equalsIgnoreCase(authConfigs.getNacosAuthSystemType())) { + ignoreURLs = "/**"; + } +// + if (StringUtils.isBlank(authConfigs.getNacosAuthSystemType())) { + ignoreURLs = env.getProperty("nacos.security.ignore.urls", "/**"); + } + + if (StringUtils.isNotBlank(ignoreURLs)) { + for (String ignoreURL : ignoreURLs.trim().split(SECURITY_IGNORE_URLS_SPILT_CHAR)) { + web.ignoring().antMatchers(ignoreURL.trim()); + } + } + + } + @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); } - @Override - public void configure(WebSecurity web) { - String ignoreURLs = env.getProperty("nacos.security.ignore.urls", "/**"); - for (String ignoreURL : ignoreURLs.trim().split(SECURITY_IGNORE_URLS_SPILT_CHAR)) { - web.ignoring().antMatchers(ignoreURL.trim()); - } - } - @Override protected void configure(HttpSecurity http) throws Exception { - http - //open cors - .cors().and() - .authorizeRequests() - .requestMatchers(CorsUtils::isPreFlightRequest).permitAll() - .anyRequest().authenticated().and() - // custom token authorize exception handler - .exceptionHandling() - .authenticationEntryPoint(unauthorizedHandler).and() - // since we use jwt, session is not necessary - .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() - // since we use jwt, csrf is not necessary - .csrf().disable(); - http.addFilterBefore(new JwtAuthenticationTokenFilter(tokenProvider), UsernamePasswordAuthenticationFilter.class); - // disable cache - http.headers().cacheControl(); + if (StringUtils.isBlank(authConfigs.getNacosAuthSystemType())) { + http + + .csrf().disable() + .cors() // We don't need CSRF for JWT based authentication + + .and() + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + + .and() + .authorizeRequests() + .requestMatchers(CorsUtils::isPreFlightRequest).permitAll() + .antMatchers(LOGIN_ENTRY_POINT).permitAll() + + .and() + .authorizeRequests() + .antMatchers(TOKEN_BASED_AUTH_ENTRY_POINT).authenticated() + + .and() + .exceptionHandling() + .authenticationEntryPoint(new JwtAuthenticationEntryPoint()); + + // disable cache + http.headers().cacheControl(); + + http.addFilterBefore(new JwtAuthenticationTokenFilter(tokenProvider), UsernamePasswordAuthenticationFilter.class); + } } @Bean diff --git a/console/src/main/java/com/alibaba/nacos/console/security/nacos/NacosAuthManager.java b/console/src/main/java/com/alibaba/nacos/console/security/nacos/NacosAuthManager.java new file mode 100644 index 000000000..3386a5efb --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/security/nacos/NacosAuthManager.java @@ -0,0 +1,133 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.nacos.console.security.nacos; + +import com.alibaba.nacos.api.common.Constants; +import com.alibaba.nacos.config.server.auth.RoleInfo; +import com.alibaba.nacos.console.security.nacos.roles.NacosRoleServiceImpl; +import com.alibaba.nacos.console.security.nacos.users.NacosUser; +import com.alibaba.nacos.core.auth.AccessException; +import com.alibaba.nacos.core.auth.AuthManager; +import com.alibaba.nacos.core.auth.Permission; +import com.alibaba.nacos.core.auth.User; +import com.alibaba.nacos.core.utils.Loggers; +import io.jsonwebtoken.ExpiredJwtException; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; + +/** + * Builtin access control entry of Nacos + * + * @author nkorange + * @since 1.2.0 + */ +@Component +public class NacosAuthManager implements AuthManager { + + private static final String TOKEN_PREFIX = "Bearer "; + + @Autowired + private JwtTokenManager tokenManager; + + @Autowired + private AuthenticationManager authenticationManager; + + @Autowired + private NacosRoleServiceImpl roleService; + + @Override + public User login(Object request) throws AccessException { + HttpServletRequest req = (HttpServletRequest) request; + String token = resolveToken(req); + if (StringUtils.isBlank(token)) { + throw new AccessException("user not found!"); + } + + try { + tokenManager.validateToken(token); + } catch (ExpiredJwtException e) { + throw new AccessException("token expired!"); + } catch (Exception e) { + throw new AccessException("token invalid!"); + } + + Authentication authentication = tokenManager.getAuthentication(token); + SecurityContextHolder.getContext().setAuthentication(authentication); + + String username = authentication.getName(); + NacosUser user = new NacosUser(); + user.setUserName(username); + user.setToken(token); + List roleInfoList = roleService.getRoles(username); + for (RoleInfo roleInfo : roleInfoList) { + if (roleInfo.getRole().equals(NacosRoleServiceImpl.GLOBAL_ADMIN_ROLE)) { + user.setGlobalAdmin(true); + break; + } + } + return user; + } + + @Override + public void auth(Permission permission, User user) throws AccessException { + if (Loggers.AUTH.isDebugEnabled()) { + Loggers.AUTH.debug("auth permission: {}, user: {}", permission, user); + } + + if (!roleService.hasPermission(user.getUserName(), permission)) { + throw new AccessException("authorization failed!"); + } + } + + /** + * Get token from header + */ + private String resolveToken(HttpServletRequest request) throws AccessException { + String bearerToken = request.getHeader(NacosAuthConfig.AUTHORIZATION_HEADER); + if (StringUtils.isNotBlank(bearerToken) && bearerToken.startsWith(TOKEN_PREFIX)) { + return bearerToken.substring(7); + } + bearerToken = request.getParameter(Constants.ACCESS_TOKEN); + if (StringUtils.isBlank(bearerToken)) { + String userName = request.getParameter("username"); + String password = request.getParameter("password"); + bearerToken = resolveTokenFromUser(userName, password); + } + + return bearerToken; + } + + private String resolveTokenFromUser(String userName, String rawPassword) throws AccessException { + + try { + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userName, rawPassword); + authenticationManager.authenticate(authenticationToken); + } catch (AuthenticationException e) { + throw new AccessException("unknown user!"); + } + + return tokenManager.createToken(userName); + } +} diff --git a/console/src/main/java/com/alibaba/nacos/console/security/nacos/roles/NacosRoleServiceImpl.java b/console/src/main/java/com/alibaba/nacos/console/security/nacos/roles/NacosRoleServiceImpl.java new file mode 100644 index 000000000..212500b4b --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/security/nacos/roles/NacosRoleServiceImpl.java @@ -0,0 +1,208 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.nacos.console.security.nacos.roles; + + +import com.alibaba.nacos.config.server.auth.PermissionInfo; +import com.alibaba.nacos.config.server.auth.PermissionPersistService; +import com.alibaba.nacos.config.server.auth.RoleInfo; +import com.alibaba.nacos.config.server.auth.RolePersistService; +import com.alibaba.nacos.config.server.model.Page; +import com.alibaba.nacos.console.security.nacos.NacosAuthConfig; +import com.alibaba.nacos.console.security.nacos.users.NacosUserDetailsServiceImpl; +import com.alibaba.nacos.core.auth.AuthConfigs; +import com.alibaba.nacos.core.auth.Permission; +import com.alibaba.nacos.core.utils.Loggers; +import io.jsonwebtoken.lang.Collections; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; + +/** + * Nacos builtin role service. + * + * @author nkorange + * @since 1.2.0 + */ +@Service +public class NacosRoleServiceImpl { + + public static final String GLOBAL_ADMIN_ROLE = "GLOBAL_ADMIN"; + + @Autowired + private AuthConfigs authConfigs; + + @Autowired + private RolePersistService rolePersistService; + + @Autowired + private NacosUserDetailsServiceImpl userDetailsService; + + @Autowired + private PermissionPersistService permissionPersistService; + + private Map> roleInfoMap = new ConcurrentHashMap<>(); + + private Map> permissionInfoMap = new ConcurrentHashMap<>(); + + @Scheduled(initialDelay = 5000, fixedDelay = 15000) + private void reload() { + try { + Page roleInfoPage = rolePersistService.getRolesByUserName(StringUtils.EMPTY, 1, Integer.MAX_VALUE); + if (roleInfoPage == null) { + return; + } + Set roleSet = new HashSet<>(16); + Map> tmpRoleInfoMap = new ConcurrentHashMap<>(16); + for (RoleInfo roleInfo : roleInfoPage.getPageItems()) { + if (!tmpRoleInfoMap.containsKey(roleInfo.getUsername())) { + tmpRoleInfoMap.put(roleInfo.getUsername(), new ArrayList<>()); + } + tmpRoleInfoMap.get(roleInfo.getUsername()).add(roleInfo); + roleSet.add(roleInfo.getRole()); + } + + Map> tmpPermissionInfoMap = new ConcurrentHashMap<>(16); + for (String role : roleSet) { + Page permissionInfoPage = permissionPersistService.getPermissions(role, 1, Integer.MAX_VALUE); + tmpPermissionInfoMap.put(role, permissionInfoPage.getPageItems()); + } + + roleInfoMap = tmpRoleInfoMap; + permissionInfoMap = tmpPermissionInfoMap; + } catch (Exception e) { + Loggers.AUTH.warn("[LOAD-ROLES] load failed", e); + } + } + + /** + * Determine if the user has permission of the resource. + *

+ * Note if the user has many roles, this method returns true if any one role of the user has the + * desired permission. + * + * @param username user info + * @param permission permission to auth + * @return true if granted, false otherwise + */ + public boolean hasPermission(String username, Permission permission) { + + List roleInfoList = getRoles(username); + if (Collections.isEmpty(roleInfoList)) { + return false; + } + + // Global admin pass: + for (RoleInfo roleInfo : roleInfoList) { + if (GLOBAL_ADMIN_ROLE.equals(roleInfo.getRole())) { + return true; + } + } + + // Old global admin can pass resource 'console/': + if (permission.getResource().startsWith(NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX)) { + return false; + } + + // For other roles, use a pattern match to decide if pass or not. + for (RoleInfo roleInfo : roleInfoList) { + List permissionInfoList = getPermissions(roleInfo.getRole()); + if (Collections.isEmpty(permissionInfoList)) { + continue; + } + for (PermissionInfo permissionInfo : permissionInfoList) { + String permissionResource = permissionInfo.getResource().replaceAll("\\*", ".*"); + String permissionAction = permissionInfo.getAction(); + if (permissionAction.contains(permission.getAction()) && + Pattern.matches(permissionResource, permission.getResource())) { + return true; + } + } + } + return false; + } + + public List getRoles(String username) { + List roleInfoList = roleInfoMap.get(username); + if (!authConfigs.isCachingEnabled()) { + Page roleInfoPage = getRolesFromDatabase(username, 1, Integer.MAX_VALUE); + if (roleInfoPage != null) { + roleInfoList = roleInfoPage.getPageItems(); + } + } + return roleInfoList; + } + + public Page getRolesFromDatabase(String userName, int pageNo, int pageSize) { + Page roles = rolePersistService.getRolesByUserName(userName, pageNo, pageSize); + if (roles == null) { + return new Page<>(); + } + return roles; + } + + public List getPermissions(String role) { + List permissionInfoList = permissionInfoMap.get(role); + if (!authConfigs.isCachingEnabled()) { + Page permissionInfoPage = getPermissionsFromDatabase(role, 1, Integer.MAX_VALUE); + if (permissionInfoPage != null) { + permissionInfoList = permissionInfoPage.getPageItems(); + } + } + return permissionInfoList; + } + + public Page getPermissionsByRoleFromDatabase(String role, int pageNo, int pageSize) { + return permissionPersistService.getPermissions(role, pageNo, pageSize); + } + + public void addRole(String role, String username) { + if (userDetailsService.getUser(username) == null) { + throw new IllegalArgumentException("user '" + username + "' not found!"); + } + rolePersistService.addRole(role, username); + } + + public void deleteRole(String role, String userName) { + rolePersistService.deleteRole(role, userName); + } + + public void deleteRole(String role) { + + rolePersistService.deleteRole(role); + } + + public Page getPermissionsFromDatabase(String role, int pageNo, int pageSize) { + Page pageInfo = permissionPersistService.getPermissions(role, pageNo, pageSize); + if (pageInfo == null) { + return new Page<>(); + } + return pageInfo; + } + + public void addPermission(String role, String resource, String action) { + permissionPersistService.addPermission(role, resource, action); + } + + public void deletePermission(String role, String resource, String action) { + permissionPersistService.deletePermission(role, resource, action); + } +} diff --git a/console/src/main/java/com/alibaba/nacos/console/security/nacos/users/NacosUser.java b/console/src/main/java/com/alibaba/nacos/console/security/nacos/users/NacosUser.java new file mode 100644 index 000000000..f2f519c58 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/security/nacos/users/NacosUser.java @@ -0,0 +1,52 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.nacos.console.security.nacos.users; + +import com.alibaba.fastjson.JSON; +import com.alibaba.nacos.core.auth.User; + +/** + * @author nkorange + * @since 1.2.0 + */ +public class NacosUser extends User { + + private String token; + + private boolean globalAdmin = false; + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public boolean isGlobalAdmin() { + return globalAdmin; + } + + public void setGlobalAdmin(boolean globalAdmin) { + this.globalAdmin = globalAdmin; + } + + @Override + public String toString() { + return JSON.toJSONString(this); + } +} diff --git a/console/src/main/java/com/alibaba/nacos/console/security/CustomUserDetails.java b/console/src/main/java/com/alibaba/nacos/console/security/nacos/users/NacosUserDetails.java similarity index 91% rename from console/src/main/java/com/alibaba/nacos/console/security/CustomUserDetails.java rename to console/src/main/java/com/alibaba/nacos/console/security/nacos/users/NacosUserDetails.java index 6e34e4a51..3390a2ce2 100644 --- a/console/src/main/java/com/alibaba/nacos/console/security/CustomUserDetails.java +++ b/console/src/main/java/com/alibaba/nacos/console/security/nacos/users/NacosUserDetails.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alibaba.nacos.console.security; +package com.alibaba.nacos.console.security.nacos.users; import com.alibaba.nacos.config.server.model.User; import org.springframework.security.core.GrantedAuthority; @@ -27,11 +27,11 @@ import java.util.Collection; * * @author wfnuser */ -public class CustomUserDetails implements UserDetails { +public class NacosUserDetails implements UserDetails { private User user; - public CustomUserDetails(User user) { + public NacosUserDetails(User user) { this.user = user; } diff --git a/console/src/main/java/com/alibaba/nacos/console/security/nacos/users/NacosUserDetailsServiceImpl.java b/console/src/main/java/com/alibaba/nacos/console/security/nacos/users/NacosUserDetailsServiceImpl.java new file mode 100644 index 000000000..7c9b66ceb --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/security/nacos/users/NacosUserDetailsServiceImpl.java @@ -0,0 +1,110 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.nacos.console.security.nacos.users; + + +import com.alibaba.nacos.config.server.auth.UserPersistService; +import com.alibaba.nacos.config.server.model.Page; +import com.alibaba.nacos.config.server.model.User; +import com.alibaba.nacos.core.auth.AuthConfigs; +import com.alibaba.nacos.core.utils.Loggers; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Custem user service + * + * @author wfnuser + * @author nkorange + */ +@Service +public class NacosUserDetailsServiceImpl implements UserDetailsService { + + private Map userMap = new ConcurrentHashMap<>(); + + @Autowired + private UserPersistService userPersistService; + + @Autowired + private AuthConfigs authConfigs; + + @Scheduled(initialDelay = 5000, fixedDelay = 15000) + private void reload() { + try { + Page users = getUsersFromDatabase(1, Integer.MAX_VALUE); + if (users == null) { + return; + } + + Map map = new ConcurrentHashMap<>(16); + for (User user : users.getPageItems()) { + map.put(user.getUsername(), user); + } + userMap = map; + } catch (Exception e) { + Loggers.AUTH.warn("[LOAD-USERS] load failed", e); + } + } + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + + User user = userMap.get(username); + if (!authConfigs.isCachingEnabled()) { + user = userPersistService.findUserByUsername(username); + } + + if (user == null) { + throw new UsernameNotFoundException(username); + } + return new NacosUserDetails(user); + } + + public void updateUserPassword(String username, String password) { + userPersistService.updateUserPassword(username, password); + } + + public Page getUsersFromDatabase(int pageNo, int pageSize) { + return userPersistService.getUsers(pageNo, pageSize); + } + + public User getUser(String username) { + User user = userMap.get(username); + if (!authConfigs.isCachingEnabled()) { + user = getUserFromDatabase(username); + } + return user; + } + + public User getUserFromDatabase(String username) { + return userPersistService.findUserByUsername(username); + } + + public void createUser(String username, String password) { + userPersistService.createUser(username, password); + } + + public void deleteUser(String username) { + userPersistService.deleteUser(username); + } +} diff --git a/console/src/main/resources/META-INF/schema.sql b/console/src/main/resources/META-INF/schema.sql index a5f19bf54..5842a2267 100644 --- a/console/src/main/resources/META-INF/schema.sql +++ b/console/src/main/resources/META-INF/schema.sql @@ -1,4 +1,4 @@ -CREATE SCHEMA diamond AUTHORIZATION diamond; +CREATE SCHEMA nacos AUTHORIZATION nacos; CREATE TABLE config_info ( id bigint NOT NULL generated by default as identity, @@ -172,18 +172,25 @@ CREATE TABLE tenant_info ( constraint uk_tenant_info_kptenantid UNIQUE (kp,tenant_id)); CREATE INDEX tenant_info_tenant_id_idx ON tenant_info(tenant_id); - CREATE TABLE users ( username varchar(50) NOT NULL PRIMARY KEY, password varchar(500) NOT NULL, - enabled boolean NOT NULL + enabled boolean NOT NULL DEFAULT true ); CREATE TABLE roles ( username varchar(50) NOT NULL, - role varchar(50) NOT NULL + role varchar(50) NOT NULL, + constraint uk_username_role UNIQUE (username,role) +); + +CREATE TABLE permissions ( + role varchar(50) NOT NULL, + resource varchar(512) NOT NULL, + action varchar(8) NOT NULL, + constraint uk_role_permission UNIQUE (role,resource,action) ); INSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE); -INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN'); +INSERT INTO roles (username, role) VALUES ('nacos', 'GLOBAL_ADMIN'); diff --git a/console/src/main/resources/application.properties b/console/src/main/resources/application.properties index 601f685a0..585e9e5f8 100644 --- a/console/src/main/resources/application.properties +++ b/console/src/main/resources/application.properties @@ -27,4 +27,11 @@ server.tomcat.basedir= #management.security=false #security.basic.enabled=false #nacos.security.ignore.urls=/** -nacos.security.ignore.urls=/,/error,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/v1/auth/login,/v1/console/health/**,/v1/cs/**,/v1/ns/**,/v1/cmdb/**,/actuator/**,/v1/console/server/** +#nacos.security.ignore.urls=/,/error,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/v1/auth/login,/v1/console/health/**,/v1/cs/**,/v1/ns/**,/v1/cmdb/**,/actuator/**,/v1/console/server/** + +nacos.security.ignore.urls=/,/error,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/v1/auth/**,/v1/console/health/**,/actuator/**,/v1/console/server/** + + +nacos.core.auth.system.type=nacos +nacos.core.auth.enabled=false +nacos.core.auth.default.token.secret.key=SecretKey012345678901234567890123456789012345678901234567890123456789 diff --git a/core/pom.xml b/core/pom.xml index 62c1e3b5e..6538248e9 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -59,6 +59,28 @@ true + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + org.springframework.security + spring-security-test + test + + junit junit @@ -79,5 +101,25 @@ tomcat-embed-core + + io.jsonwebtoken + jjwt-api + + + io.jsonwebtoken + jjwt-impl + runtime + + + io.jsonwebtoken + jjwt-jackson + runtime + + + + org.reflections + reflections + + diff --git a/core/src/main/java/com/alibaba/nacos/core/auth/AccessException.java b/core/src/main/java/com/alibaba/nacos/core/auth/AccessException.java new file mode 100644 index 000000000..a5697f203 --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/auth/AccessException.java @@ -0,0 +1,40 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.nacos.core.auth; + +import com.alibaba.nacos.api.exception.NacosException; + +/** + * Exception to be thrown if authorization is failed. + * + * @author nkorange + * @since 1.2.0 + */ +public class AccessException extends NacosException { + + public AccessException(){ + + } + + public AccessException(int code) { + this.setErrCode(code); + } + + public AccessException(String msg) { + this.setErrMsg(msg); + } + +} diff --git a/core/src/main/java/com/alibaba/nacos/core/auth/ActionTypes.java b/core/src/main/java/com/alibaba/nacos/core/auth/ActionTypes.java new file mode 100644 index 000000000..8455c423d --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/auth/ActionTypes.java @@ -0,0 +1,44 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.nacos.core.auth; + +/** + * Resource action type definitions + * + * @author nkorange + * @since 1.2.0 + */ +public enum ActionTypes { + /** + * Read + */ + READ("r"), + /** + * Write + */ + WRITE("w"); + + private String action; + + ActionTypes(String action) { + this.action = action; + } + + @Override + public String toString() { + return action; + } +} diff --git a/core/src/main/java/com/alibaba/nacos/core/auth/AuthConfigs.java b/core/src/main/java/com/alibaba/nacos/core/auth/AuthConfigs.java new file mode 100644 index 000000000..283aeb61c --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/auth/AuthConfigs.java @@ -0,0 +1,98 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.nacos.core.auth; + +import com.alibaba.nacos.core.env.ReloadableConfigs; +import org.apache.commons.lang3.BooleanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.stereotype.Component; + +/** + * Auth related configurations + * + * @author nkorange + * @since 1.2.0 + */ +@Component +@Configuration +public class AuthConfigs { + + @Autowired + private ReloadableConfigs reloadableConfigs; + + /** + * secret key + */ + @Value("${nacos.core.auth.default.token.secret.key:}") + private String secretKey; + + /** + * Token validity time(seconds) + */ + @Value("${nacos.core.auth.default.token.expire.seconds:1800}") + private long tokenValidityInSeconds; + + /** + * Which auth system is in use + */ + @Value("${nacos.core.auth.system.type:}") + private String nacosAuthSystemType; + + public String getSecretKey() { + return secretKey; + } + + public long getTokenValidityInSeconds() { + return tokenValidityInSeconds; + } + + public String getNacosAuthSystemType() { + return nacosAuthSystemType; + } + + public boolean isAuthEnabled() { + return BooleanUtils.toBoolean(reloadableConfigs.getProperties() + .getProperty("nacos.core.auth.enabled", "false")); + } + + public boolean isCachingEnabled() { + return BooleanUtils.toBoolean(reloadableConfigs.getProperties() + .getProperty("nacos.core.auth.caching.enabled", "true")); + } + + @Bean + public FilterRegistrationBean authFilterRegistration() { + FilterRegistrationBean registration = new FilterRegistrationBean<>(); + registration.setFilter(authFilter()); + registration.addUrlPatterns("/*"); + registration.setName("authFilter"); + registration.setOrder(6); + + return registration; + } + + @Bean + public AuthFilter authFilter() { + return new AuthFilter(); + } + +} diff --git a/core/src/main/java/com/alibaba/nacos/core/auth/AuthFilter.java b/core/src/main/java/com/alibaba/nacos/core/auth/AuthFilter.java new file mode 100644 index 000000000..f4f3c6df2 --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/auth/AuthFilter.java @@ -0,0 +1,119 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.nacos.core.auth; + +import com.alibaba.nacos.core.code.ControllerMethodsCache; +import com.alibaba.nacos.core.utils.Constants; +import com.alibaba.nacos.core.utils.ExceptionUtil; +import com.alibaba.nacos.core.utils.Loggers; +import com.alibaba.nacos.core.utils.WebUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.lang.reflect.Method; +import java.net.URI; + +/** + * Unified filter to handle authentication and authorization + * + * @author nkorange + * @since 1.2.0 + */ +public class AuthFilter implements Filter { + + @Autowired + private AuthConfigs authConfigs; + + @Autowired + private AuthManager authManager; + + @Autowired + private ControllerMethodsCache methodsCache; + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + + if (!authConfigs.isAuthEnabled()) { + chain.doFilter(request, response); + return; + } + + HttpServletRequest req = (HttpServletRequest) request; + HttpServletResponse resp = (HttpServletResponse) response; + + String userAgent = WebUtils.getUserAgent(req); + + if (StringUtils.startsWith(userAgent, Constants.NACOS_SERVER_HEADER)) { + chain.doFilter(req, resp); + return; + } + + if (Loggers.AUTH.isDebugEnabled()) { + Loggers.AUTH.debug("auth filter start, request: {}", req.getRequestURI()); + } + + try { + + String path = new URI(req.getRequestURI()).getPath(); + Method method = methodsCache.getMethod(req.getMethod(), path); + + if (method == null) { + throw new NoSuchMethodException(); + } + + if (method.isAnnotationPresent(Secured.class) && authConfigs.isAuthEnabled()) { + + Secured secured = method.getAnnotation(Secured.class); + String action = secured.action().toString(); + String resource = secured.resource(); + + if (StringUtils.isBlank(resource)) { + ResourceParser parser = secured.parser().newInstance(); + resource = parser.parseName(req); + } + + if (StringUtils.isBlank(resource)) { + // deny if we don't find any resource: + throw new AccessException("resource name invalid!"); + } + + authManager.auth(new Permission(resource, action), authManager.login(req)); + + } + + } catch (AccessException e) { + resp.sendError(HttpServletResponse.SC_FORBIDDEN, e.getErrMsg()); + return; + } catch (NoSuchMethodException e) { + resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, + "no such api:" + req.getMethod() + ":" + req.getRequestURI()); + return; + } catch (IllegalArgumentException e) { + resp.sendError(HttpServletResponse.SC_BAD_REQUEST, ExceptionUtil.getAllExceptionMsg(e)); + return; + } catch (Exception e) { + resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Server failed," + e.getMessage()); + return; + } + + chain.doFilter(request, response); + } +} diff --git a/core/src/main/java/com/alibaba/nacos/core/auth/AuthManager.java b/core/src/main/java/com/alibaba/nacos/core/auth/AuthManager.java new file mode 100644 index 000000000..d3c0012cd --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/auth/AuthManager.java @@ -0,0 +1,44 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.nacos.core.auth; + + +/** + * Access control entry. Can be extended by 3rd party implementations. + * + * @author nkorange + * @since 1.2.0 + */ +public interface AuthManager { + + /** + * Authentication of request, identify the user who request the resource. + * + * @param request where we can find the user information + * @return user related to this request, null if no user info is found. + * @throws AccessException if authentication is failed + */ + User login(Object request) throws AccessException; + + /** + * Authorization of request, constituted with resource and user. + * + * @param permission permission to auth + * @param user user who wants to access the resource. + * @throws AccessException if authorization is failed + */ + void auth(Permission permission, User user) throws AccessException; +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/web/NeedAuth.java b/core/src/main/java/com/alibaba/nacos/core/auth/AuthSystemTypes.java similarity index 74% rename from naming/src/main/java/com/alibaba/nacos/naming/web/NeedAuth.java rename to core/src/main/java/com/alibaba/nacos/core/auth/AuthSystemTypes.java index 30ac156cf..905359a88 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/web/NeedAuth.java +++ b/core/src/main/java/com/alibaba/nacos/core/auth/AuthSystemTypes.java @@ -13,14 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alibaba.nacos.naming.web; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; +package com.alibaba.nacos.core.auth; /** - * Innovated By: Xuanyin.zy + * Types of all auth implementations + * + * @author nkorange + * @since 1.2.0 */ -@Retention(RetentionPolicy.RUNTIME) -public @interface NeedAuth { +public enum AuthSystemTypes { + + /** + * Nacos builtin auth system + */ + NACOS } diff --git a/core/src/main/java/com/alibaba/nacos/core/auth/DefaultResourceParser.java b/core/src/main/java/com/alibaba/nacos/core/auth/DefaultResourceParser.java new file mode 100644 index 000000000..a331a46c8 --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/auth/DefaultResourceParser.java @@ -0,0 +1,32 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.nacos.core.auth; + +import org.apache.commons.lang3.StringUtils; + +/** + * Default resource parser + * + * @author nkorange + * @since 1.2.0 + */ +public class DefaultResourceParser implements ResourceParser { + + @Override + public String parseName(Object request) { + return StringUtils.EMPTY; + } +} diff --git a/core/src/main/java/com/alibaba/nacos/core/auth/Permission.java b/core/src/main/java/com/alibaba/nacos/core/auth/Permission.java new file mode 100644 index 000000000..f13379f30 --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/auth/Permission.java @@ -0,0 +1,60 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.nacos.core.auth; + +/** + * Permission to auth + * + * @author nkorange + * @since 1.2.0 + */ +public class Permission { + + public Permission() { + + } + + public Permission(String resource, String action) { + this.resource = resource; + this.action = action; + } + + /** + * An unique key of resource + */ + private String resource; + + /** + * Action on resource, refer to class ActionTypes + */ + private String action; + + public String getResource() { + return resource; + } + + public void setResource(String resource) { + this.resource = resource; + } + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } +} diff --git a/core/src/main/java/com/alibaba/nacos/core/auth/Resource.java b/core/src/main/java/com/alibaba/nacos/core/auth/Resource.java new file mode 100644 index 000000000..47b2ab423 --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/auth/Resource.java @@ -0,0 +1,53 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.nacos.core.auth; + + +import com.alibaba.fastjson.JSON; + +/** + * Resource used in authorization. + * + * @author nkorange + * @since 1.2.0 + */ +public class Resource { + + public static final String SPLITTER = ":"; + public static final String ANY = "*"; + + /** + * The unique key of resource. + */ + private String key; + + public Resource(String key) { + this.key = key; + } + + public String getKey() { + return key; + } + + public String parseName() { + return key.substring(0, key.lastIndexOf(SPLITTER)); + } + + @Override + public String toString() { + return JSON.toJSONString(this); + } +} diff --git a/core/src/main/java/com/alibaba/nacos/core/auth/ResourceParser.java b/core/src/main/java/com/alibaba/nacos/core/auth/ResourceParser.java new file mode 100644 index 000000000..b51964708 --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/auth/ResourceParser.java @@ -0,0 +1,35 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.nacos.core.auth; + + +/** + * Resource parser + * + * @author nkorange + * @since 1.2.0 + */ +public interface ResourceParser { + + /** + * Parse a unique name of the resource from the request + * + * @param request where we can find the resource info. Given it may vary from Http request to gRPC request, + * we use a Object type for future accommodation. + * @return resource name + */ + String parseName(Object request); +} diff --git a/core/src/main/java/com/alibaba/nacos/core/auth/Secured.java b/core/src/main/java/com/alibaba/nacos/core/auth/Secured.java new file mode 100644 index 000000000..7ed611cc8 --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/auth/Secured.java @@ -0,0 +1,52 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.nacos.core.auth; + +import org.apache.commons.lang3.StringUtils; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Annotation indicating that the annotated request should be authorized. + * + * @author nkorange + * @since 1.2.0 + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface Secured { + + /** + * The action type of the request + * + * @return action type, default READ + */ + ActionTypes action() default ActionTypes.READ; + + /** + * The name of resource related to the request + * + * @return resource name + */ + String resource() default StringUtils.EMPTY; + + /** + * Resource name parser. Should have lower priority than name() + * + * @return class type of resource parser + */ + Class parser() default DefaultResourceParser.class; +} diff --git a/core/src/main/java/com/alibaba/nacos/core/auth/User.java b/core/src/main/java/com/alibaba/nacos/core/auth/User.java new file mode 100644 index 000000000..fe82eb919 --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/auth/User.java @@ -0,0 +1,39 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.nacos.core.auth; + + +/** + * User information in authorization. + * + * @author nkorange + * @since 1.2.0 + */ +public class User { + + /** + * Unique string representing user + */ + private String userName; + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/web/FilterBase.java b/core/src/main/java/com/alibaba/nacos/core/code/ControllerMethodsCache.java similarity index 59% rename from naming/src/main/java/com/alibaba/nacos/naming/web/FilterBase.java rename to core/src/main/java/com/alibaba/nacos/core/code/ControllerMethodsCache.java index ab6cf5653..97dc9bfca 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/web/FilterBase.java +++ b/core/src/main/java/com/alibaba/nacos/core/code/ControllerMethodsCache.java @@ -13,64 +13,72 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alibaba.nacos.naming.web; +package com.alibaba.nacos.core.code; + -import com.alibaba.nacos.naming.controllers.*; import org.apache.commons.lang3.ArrayUtils; +import org.reflections.Reflections; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.*; -import javax.annotation.PostConstruct; import java.lang.reflect.Method; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** - * Basic methods for filter to use + * Method cache * * @author nkorange - * @since 1.0.0 + * @since 1.2.0 */ @Component -public class FilterBase { +public class ControllerMethodsCache { - private ConcurrentMap methodCache = new + private ConcurrentMap methods = new ConcurrentHashMap<>(); - @PostConstruct - public void init() { - initClassMethod(InstanceController.class); - initClassMethod(ServiceController.class); - initClassMethod(ClusterController.class); - initClassMethod(CatalogController.class); - initClassMethod(HealthController.class); - initClassMethod(RaftController.class); - initClassMethod(DistroController.class); - initClassMethod(OperatorController.class); - initClassMethod(ApiController.class); + public ConcurrentMap getMethods() { + return methods; } public Method getMethod(String httpMethod, String path) { String key = httpMethod + "-->" + path.replace("/nacos", ""); - return methodCache.get(key); + return methods.get(key); } - private void initClassMethod(Class clazz) { + public void initClassMethod(String packageName) { + Reflections reflections = new Reflections(packageName); + Set> classesList = reflections.getTypesAnnotatedWith(RequestMapping.class); + + for (Class clazz : classesList) { + initClassMethod(clazz); + } + } + + public void initClassMethod(Set> classesList) { + for (Class clazz : classesList) { + initClassMethod(clazz); + } + } + + public void initClassMethod(Class clazz) { RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class); - String classPath = requestMapping.value()[0]; - for (Method method : clazz.getMethods()) { - if (!method.isAnnotationPresent(RequestMapping.class)) { - parseSubAnnotations(method, classPath); - continue; - } - requestMapping = method.getAnnotation(RequestMapping.class); - RequestMethod[] requestMethods = requestMapping.method(); - if (requestMethods.length == 0) { - requestMethods = new RequestMethod[1]; - requestMethods[0] = RequestMethod.GET; - } - for (String methodPath : requestMapping.value()) { - methodCache.put(requestMethods[0].name() + "-->" + classPath + methodPath, method); + for (String classPath : requestMapping.value()) { + for (Method method : clazz.getMethods()) { + if (!method.isAnnotationPresent(RequestMapping.class)) { + parseSubAnnotations(method, classPath); + continue; + } + requestMapping = method.getAnnotation(RequestMapping.class); + RequestMethod[] requestMethods = requestMapping.method(); + if (requestMethods.length == 0) { + requestMethods = new RequestMethod[1]; + requestMethods[0] = RequestMethod.GET; + } + for (String methodPath : requestMapping.value()) { + methods.put(requestMethods[0].name() + "-->" + classPath + methodPath, method); + } } } } @@ -107,11 +115,11 @@ public class FilterBase { private void put(RequestMethod requestMethod, String classPath, String[] requestPaths, Method method) { if (ArrayUtils.isEmpty(requestPaths)) { - methodCache.put(requestMethod.name() + "-->" + classPath, method); + methods.put(requestMethod.name() + "-->" + classPath, method); return; } for (String requestPath : requestPaths) { - methodCache.put(requestMethod.name() + "-->" + classPath + requestPath, method); + methods.put(requestMethod.name() + "-->" + classPath + requestPath, method); } } } diff --git a/core/src/main/java/com/alibaba/nacos/core/env/ReloadableConfigs.java b/core/src/main/java/com/alibaba/nacos/core/env/ReloadableConfigs.java new file mode 100644 index 000000000..debf22c81 --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/env/ReloadableConfigs.java @@ -0,0 +1,69 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.nacos.core.env; + + +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +/** + * Reload application.properties + * + * @author nkorange + * @since 1.2.0 + */ +@Component +public class ReloadableConfigs { + + private Properties properties; + + @Value("${spring.config.location:}") + private String path; + + private static final String FILE_PREFIX = "file:"; + + @Scheduled(fixedRate = 5000) + public void reload() throws IOException { + Properties properties = new Properties(); + InputStream inputStream = null; + if (StringUtils.isNotBlank(path) && path.contains(FILE_PREFIX)) { + String[] paths = path.split(","); + path = paths[paths.length - 1].substring(FILE_PREFIX.length()); + } + try { + inputStream = new FileInputStream(new File(path + "application.properties")); + } catch (Exception ignore) { + } + if (inputStream == null) { + inputStream = getClass().getResourceAsStream("/application.properties"); + } + properties.load(inputStream); + inputStream.close(); + this.properties = properties; + } + + public final Properties getProperties() { + return properties; + } +} diff --git a/core/src/main/java/com/alibaba/nacos/core/utils/Constants.java b/core/src/main/java/com/alibaba/nacos/core/utils/Constants.java index 079c43d41..da4d72844 100644 --- a/core/src/main/java/com/alibaba/nacos/core/utils/Constants.java +++ b/core/src/main/java/com/alibaba/nacos/core/utils/Constants.java @@ -58,4 +58,6 @@ public interface Constants { String SYSTEM_PREFER_HOSTNAME_OVER_IP = "nacos.preferHostnameOverIp"; String WEB_CONTEXT_PATH = "server.servlet.context-path"; String COMMA_DIVISION = ","; + + String NACOS_SERVER_HEADER = "Nacos-Server"; } diff --git a/core/src/main/java/com/alibaba/nacos/core/utils/ExceptionUtil.java b/core/src/main/java/com/alibaba/nacos/core/utils/ExceptionUtil.java new file mode 100644 index 000000000..3d4fe7bbb --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/utils/ExceptionUtil.java @@ -0,0 +1,40 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.nacos.core.utils; + +import org.apache.commons.lang3.StringUtils; + + +/** + * Common methods for exception + * + * @author nkorange + * @since 1.2.0 + */ +public class ExceptionUtil { + + public static String getAllExceptionMsg(Throwable e) { + Throwable cause = e; + StringBuilder strBuilder = new StringBuilder(); + + while (cause != null && !StringUtils.isEmpty(cause.getMessage())) { + strBuilder.append("caused: ").append(cause.getMessage()).append(";"); + cause = cause.getCause(); + } + + return strBuilder.toString(); + } +} diff --git a/core/src/main/java/com/alibaba/nacos/core/utils/Loggers.java b/core/src/main/java/com/alibaba/nacos/core/utils/Loggers.java new file mode 100644 index 000000000..c285e5f95 --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/utils/Loggers.java @@ -0,0 +1,30 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.nacos.core.utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Loggers for core + * + * @author nkorange + * @since 1.2.0 + */ +public class Loggers { + + public static final Logger AUTH = LoggerFactory.getLogger("com.alibaba.nacos.core.auth"); +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/web/OverrideParameterRequestWrapper.java b/core/src/main/java/com/alibaba/nacos/core/utils/OverrideParameterRequestWrapper.java similarity index 98% rename from naming/src/main/java/com/alibaba/nacos/naming/web/OverrideParameterRequestWrapper.java rename to core/src/main/java/com/alibaba/nacos/core/utils/OverrideParameterRequestWrapper.java index 65b4a1529..e01809870 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/web/OverrideParameterRequestWrapper.java +++ b/core/src/main/java/com/alibaba/nacos/core/utils/OverrideParameterRequestWrapper.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alibaba.nacos.naming.web; +package com.alibaba.nacos.core.utils; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; diff --git a/core/src/main/resources/META-INF/logback/nacos.xml b/core/src/main/resources/META-INF/logback/nacos.xml index 10ba94284..3e96b36b5 100644 --- a/core/src/main/resources/META-INF/logback/nacos.xml +++ b/core/src/main/resources/META-INF/logback/nacos.xml @@ -34,11 +34,33 @@ + + ${LOG_HOME}/core-auth.log + true + + ${LOG_HOME}/core-auth.log.%d{yyyy-MM-dd}.%i + 2GB + 7 + 7GB + true + + + %date %level %msg%n%n + UTF-8 + + + + + + + + diff --git a/distribution/bin/startup.sh b/distribution/bin/startup.sh index 93c288474..55c967f44 100644 --- a/distribution/bin/startup.sh +++ b/distribution/bin/startup.sh @@ -104,7 +104,7 @@ else fi JAVA_OPT="${JAVA_OPT} -Dloader.path=${BASE_DIR}/plugins/health,${BASE_DIR}/plugins/cmdb,${BASE_DIR}/plugins/mysql" - +JAVA_OPT="${JAVA_OPT} -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000" JAVA_OPT="${JAVA_OPT} -Dnacos.home=${BASE_DIR}" JAVA_OPT="${JAVA_OPT} -jar ${BASE_DIR}/target/${SERVER}.jar" JAVA_OPT="${JAVA_OPT} ${JAVA_OPT_EXT}" diff --git a/distribution/conf/application.properties b/distribution/conf/application.properties index 00d21a48e..4d9c5cf1d 100644 --- a/distribution/conf/application.properties +++ b/distribution/conf/application.properties @@ -1,23 +1,71 @@ -# spring - -server.contextPath=/nacos +#*************** Spring Boot Related Configurations ***************# +### Default web context path: server.servlet.contextPath=/nacos +### Default web server port: server.port=8848 +#*************** Network Related Configurations ***************# +### If prefer hostname over ip for Nacos server addresses in cluster.conf: +# nacos.inetutils.prefer-hostname-over-ip=false + +### Specify local server's IP: +# nacos.inetutils.ip-address= + + + +#*************** Config Module Related Configurations ***************# +### If user MySQL as datasource: +# spring.datasource.platform=mysql + +### Count of DB: +# db.num=1 + +### Connect URL of DB: +# db.url.0=jdbc:mysql://1.1.1.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true +# db.user=user +# db.password=password + + +#*************** Naming Module Related Configurations ***************# +### Data dispatch task execution period in milliseconds: +# nacos.naming.distro.taskDispatchPeriod=200 + +### Data count of batch sync task: +# nacos.naming.distro.batchSyncKeyCount=1000 + +### Retry delay in milliseconds if sync task failed: +# nacos.naming.distro.syncRetryDelay=5000 + +### If enable data warmup. If set to false, the server would accept request without local data preparation: +# nacos.naming.data.warmup=true + +### If enable the instance auto expiration, kind like of health check of instance: +# nacos.naming.expireInstance=true + + +#*************** CMDB Module Related Configurations ***************# +### The interval to dump external CMDB in seconds: # nacos.cmdb.dumpTaskInterval=3600 + +### The interval of polling data change event in seconds: # nacos.cmdb.eventTaskInterval=10 + +### The interval of loading labels in seconds: # nacos.cmdb.labelTaskInterval=300 + +### If turn on data loading task: # nacos.cmdb.loadDataAtStart=false -# metrics for prometheus +#*************** Metrics Related Configurations ***************# +### Metrics for prometheus #management.endpoints.web.exposure.include=* -# metrics for elastic search +### Metrics for elastic search management.metrics.export.elastic.enabled=false #management.metrics.export.elastic.host=http://localhost:9200 -# metrics for influx +### Metrics for influx management.metrics.export.influx.enabled=false #management.metrics.export.influx.db=springboot #management.metrics.export.influx.uri=http://localhost:8086 @@ -25,24 +73,41 @@ management.metrics.export.influx.enabled=false #management.metrics.export.influx.consistency=one #management.metrics.export.influx.compressed=true + +#*************** Access Log Related Configurations ***************# +### If turn on the access log: server.tomcat.accesslog.enabled=true + +### The access log pattern: server.tomcat.accesslog.pattern=%h %l %u %t "%r" %s %b %D %{User-Agent}i -# default current work dir + +### The directory of access log: server.tomcat.basedir= -## spring security config -### turn off security + +#*************** Access Control Related Configurations ***************# +### If enable spring security, this option is deprecated in 1.2.0: #spring.security.enabled=false -#management.security=false -#security.basic.enabled=false -#nacos.security.ignore.urls=/** -nacos.security.ignore.urls=/,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/v1/auth/login,/v1/console/health/**,/v1/cs/**,/v1/ns/**,/v1/cmdb/**,/actuator/**,/v1/console/server/** +### The ignore urls of auth, is deprecated in 1.2.0: +nacos.security.ignore.urls=/,/error,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/v1/auth/**,/v1/console/health/**,/actuator/**,/v1/console/server/** -# nacos.naming.distro.taskDispatchPeriod=200 -# nacos.naming.distro.batchSyncKeyCount=1000 -# nacos.naming.distro.syncRetryDelay=5000 -# nacos.naming.data.warmup=true -# nacos.naming.expireInstance=true +### The auth system to use, currently only 'nacos' is supported: +nacos.core.auth.system.type=nacos +### If turn on auth system: +nacos.core.auth.enabled=false + +### The token expiration in seconds: +nacos.core.auth.default.token.expire.seconds=18000 + +### The default token: +nacos.core.auth.default.token.secret.key=SecretKey012345678901234567890123456789012345678901234567890123456789 + +### Turn on/off caching of auth information. By turning on this switch, the update of auth information would have a 15 seconds delay. +nacos.core.auth.caching.enabled=true + + +#*************** Istio Related Configurations ***************# +### If turn on the MCP server: nacos.istio.mcp.server.enabled=false diff --git a/distribution/conf/nacos-logback.xml b/distribution/conf/nacos-logback.xml index e11659633..ce473f39f 100644 --- a/distribution/conf/nacos-logback.xml +++ b/distribution/conf/nacos-logback.xml @@ -376,6 +376,23 @@ + + ${LOG_HOME}/core-auth.log + true + + ${LOG_HOME}/core-auth.log.%d{yyyy-MM-dd}.%i + 2GB + 7 + 7GB + true + + + %date %level %msg%n%n + UTF-8 + + + @@ -461,6 +478,11 @@ + + + + + diff --git a/distribution/conf/nacos-mysql.sql b/distribution/conf/nacos-mysql.sql index af608a832..001928fbf 100644 --- a/distribution/conf/nacos-mysql.sql +++ b/distribution/conf/nacos-mysql.sql @@ -178,17 +178,26 @@ CREATE TABLE `tenant_info` ( KEY `idx_tenant_id` (`tenant_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='tenant_info'; -CREATE TABLE users ( - username varchar(50) NOT NULL PRIMARY KEY, - password varchar(500) NOT NULL, - enabled boolean NOT NULL +CREATE TABLE `users` ( + `username` varchar(50) NOT NULL PRIMARY KEY, + `password` varchar(500) NOT NULL, + `enabled` boolean NOT NULL ); -CREATE TABLE roles ( - username varchar(50) NOT NULL, - role varchar(50) NOT NULL +CREATE TABLE `roles` ( + `username` varchar(50) NOT NULL, + `role` varchar(50) NOT NULL, + UNIQUE INDEX `idx_user_role` (`username` ASC, `role` ASC) USING BTREE +); + +CREATE TABLE `permissions` ( + `role` varchar(50) NOT NULL, + `permission` varchar(512) NOT NULL, + `gmt_create` bigint NULL, + `gmt_modified` bigint NULL, +UNIQUE INDEX `idx_role_resource` (`role` ASC, `permission` ASC) USING BTREE ); INSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE); -INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN'); +INSERT INTO roles (username, role) VALUES ('nacos', 'GLOBAL_ADMIN'); diff --git a/distribution/conf/schema.sql b/distribution/conf/schema.sql index fd95b3e9f..5842a2267 100644 --- a/distribution/conf/schema.sql +++ b/distribution/conf/schema.sql @@ -175,14 +175,22 @@ CREATE INDEX tenant_info_tenant_id_idx ON tenant_info(tenant_id); CREATE TABLE users ( username varchar(50) NOT NULL PRIMARY KEY, password varchar(500) NOT NULL, - enabled boolean NOT NULL + enabled boolean NOT NULL DEFAULT true ); CREATE TABLE roles ( username varchar(50) NOT NULL, - role varchar(50) NOT NULL + role varchar(50) NOT NULL, + constraint uk_username_role UNIQUE (username,role) +); + +CREATE TABLE permissions ( + role varchar(50) NOT NULL, + resource varchar(512) NOT NULL, + action varchar(8) NOT NULL, + constraint uk_role_permission UNIQUE (role,resource,action) ); INSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE); -INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN'); +INSERT INTO roles (username, role) VALUES ('nacos', 'GLOBAL_ADMIN'); diff --git a/naming/src/main/java/com/alibaba/nacos/naming/acl/AuthChecker.java b/naming/src/main/java/com/alibaba/nacos/naming/acl/AuthChecker.java deleted file mode 100644 index aa863732b..000000000 --- a/naming/src/main/java/com/alibaba/nacos/naming/acl/AuthChecker.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 1999-2018 Alibaba Group Holding Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.alibaba.nacos.naming.acl; - -import com.alibaba.nacos.api.common.Constants; -import com.alibaba.nacos.api.naming.CommonParams; -import com.alibaba.nacos.core.utils.WebUtils; -import com.alibaba.nacos.naming.core.Service; -import com.alibaba.nacos.naming.core.ServiceManager; -import com.alibaba.nacos.naming.misc.SwitchDomain; -import com.alibaba.nacos.naming.misc.UtilsAndCommons; -import org.apache.commons.lang3.ArrayUtils; -import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import javax.servlet.http.HttpServletRequest; -import java.security.AccessControlException; -import java.util.Map; - -/** - * @author nkorange - */ -@Component -public class AuthChecker { - - @Autowired - private ServiceManager serviceManager; - - @Autowired - private SwitchDomain switchDomain; - - static private String[] APP_WHITE_LIST = {}; - static private String[] TOKEN_WHITE_LIST = {"traffic-scheduling@midware"}; - - public void doRaftAuth(HttpServletRequest req) throws Exception { - String token = req.getParameter("token"); - if (StringUtils.equals(UtilsAndCommons.SUPER_TOKEN, token)) { - return; - } - - String agent = WebUtils.getUserAgent(req); - if (StringUtils.startsWith(agent, UtilsAndCommons.NACOS_SERVER_HEADER)) { - return; - } - - throw new IllegalAccessException("illegal access,agent= " + agent + ", token=" + token); - } - - public void doAuth(Map params, HttpServletRequest req) throws Exception { - - String namespaceId = WebUtils.optional(req, CommonParams.NAMESPACE_ID, - Constants.DEFAULT_NAMESPACE_ID); - String serviceName = WebUtils.optional(req, "name", ""); - if (StringUtils.isEmpty(serviceName)) { - serviceName = WebUtils.optional(req, "serviceName", ""); - } - - if (StringUtils.isEmpty(serviceName)) { - serviceName = WebUtils.optional(req, "tag", ""); - } - - Service service = serviceManager.getService(namespaceId, serviceName); - - if (service == null) { - if (!req.getRequestURI().equals(UtilsAndCommons.NACOS_NAMING_CONTEXT + UtilsAndCommons.API_SET_ALL_WEIGHTS)) { - throw new IllegalStateException("auth failed, service does not exist: " + serviceName); - } - } - - String token = req.getParameter("token"); - String auth = req.getParameter("auth"); - String userName = req.getParameter("userName"); - if (StringUtils.isEmpty(auth) && StringUtils.isEmpty(token)) { - throw new IllegalArgumentException("provide 'authInfo' or 'token' to access this service"); - } - - // try valid token - if ((service != null && StringUtils.equals(service.getToken(), token))) { - return; - } - - if (ArrayUtils.contains(TOKEN_WHITE_LIST, token)) { - return; - } - - if (ArrayUtils.contains(APP_WHITE_LIST, userName)) { - return; - } - - // if token failed, try AuthInfo - AuthInfo authInfo = AuthInfo.fromString(auth, WebUtils.getAcceptEncoding(req)); - if (authInfo == null) { - throw new IllegalAccessException("invalid token or malformed auth info"); - } - - if (!ArrayUtils.contains(APP_WHITE_LIST, authInfo.getAppKey())) { - throw new AccessControlException("un-registered SDK app"); - } - - if (!service.getOwners().contains(authInfo.getOperator()) - && !switchDomain.getMasters().contains(authInfo.getOperator())) { - throw new AccessControlException("dom already exists and you're not among the owners"); - } - } -} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/acl/AuthInfo.java b/naming/src/main/java/com/alibaba/nacos/naming/acl/AuthInfo.java deleted file mode 100644 index 7847590cc..000000000 --- a/naming/src/main/java/com/alibaba/nacos/naming/acl/AuthInfo.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 1999-2018 Alibaba Group Holding Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.alibaba.nacos.naming.acl; - -import java.nio.charset.StandardCharsets; - -/** - * @author nkorange - */ -public class AuthInfo { - private String operator; - - private String appKey; - - public AuthInfo() { - } - - public AuthInfo(String operator, String appKey) { - this.operator = operator; - this.appKey = appKey; - } - - public static AuthInfo fromString(String auth, String encoding) { - try { - String[] byteStrs = auth.split(","); - byte[] bytes = new byte[byteStrs.length]; - for (int i = 0; i < byteStrs.length; i++) { - bytes[i] = (byte) (~(Short.parseShort(byteStrs[i]))); - } - - String contentStr = new String(bytes, encoding); - String[] params = contentStr.split(":"); - return new AuthInfo(params[0], params[1]); - } catch (Throwable e) { - return null; - } - } - - @Override - public String toString() { - try { - // very simple encryption is enough - byte[] authBytes = (operator + ":" + appKey).getBytes(StandardCharsets.UTF_8); - StringBuilder authBuilder = new StringBuilder(); - for (byte authByte : authBytes) { - authBuilder.append((byte) (~((short) authByte))).append(","); - } - - return authBuilder.substring(0, authBuilder.length() - 1); - } catch (Exception e) { - return "Error while encrypt AuthInfo" + e; - } - } - - public void setOperator(String operator) { - this.operator = operator; - } - - public String getOperator() { - return operator; - } - - public String getAppKey() { - return appKey; - } - - public void setAppKey(String appKey) { - this.appKey = appKey; - } -} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/consistency/persistent/raft/RaftCore.java b/naming/src/main/java/com/alibaba/nacos/naming/consistency/persistent/raft/RaftCore.java index 014dd4ea6..c52709ff5 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/consistency/persistent/raft/RaftCore.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/consistency/persistent/raft/RaftCore.java @@ -293,7 +293,7 @@ public class RaftCore { local.resetLeaderDue(); - // if data should be persistent, usually this is always true: + // if data should be persisted, usually this is true: if (KeyBuilder.matchPersistentKey(datum.key)) { raftStore.write(datum); } diff --git a/naming/src/main/java/com/alibaba/nacos/naming/controllers/ApiController.java b/naming/src/main/java/com/alibaba/nacos/naming/controllers/ApiController.java index 98ea7724f..ef11fcaf1 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/controllers/ApiController.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/controllers/ApiController.java @@ -25,7 +25,7 @@ import com.alibaba.nacos.naming.core.ServiceManager; import com.alibaba.nacos.naming.misc.UtilsAndCommons; import com.alibaba.nacos.naming.push.ClientInfo; import com.alibaba.nacos.naming.web.CanDistro; -import com.alibaba.nacos.naming.web.OverrideParameterRequestWrapper; +import com.alibaba.nacos.core.utils.OverrideParameterRequestWrapper; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.codehaus.jackson.util.VersionUtil; diff --git a/naming/src/main/java/com/alibaba/nacos/naming/controllers/CatalogController.java b/naming/src/main/java/com/alibaba/nacos/naming/controllers/CatalogController.java index ae00ded1a..dda14908b 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/controllers/CatalogController.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/controllers/CatalogController.java @@ -22,6 +22,8 @@ import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.naming.CommonParams; import com.alibaba.nacos.api.naming.pojo.Cluster; import com.alibaba.nacos.api.naming.utils.NamingUtils; +import com.alibaba.nacos.core.auth.ActionTypes; +import com.alibaba.nacos.core.auth.Secured; import com.alibaba.nacos.core.utils.WebUtils; import com.alibaba.nacos.naming.core.Instance; import com.alibaba.nacos.naming.core.Service; @@ -53,6 +55,7 @@ public class CatalogController { @Autowired protected ServiceManager serviceManager; + @Secured(action = ActionTypes.READ) @GetMapping("/service") public JSONObject serviceDetail(@RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId, String serviceName) throws NacosException { @@ -93,6 +96,7 @@ public class CatalogController { return detailView; } + @Secured(action = ActionTypes.READ) @RequestMapping(value = "/instances") public JSONObject instanceList(@RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId, @RequestParam String serviceName, @@ -134,6 +138,7 @@ public class CatalogController { return result; } + @Secured(action = ActionTypes.READ) @GetMapping("/services") public Object listDetail(@RequestParam(required = false) boolean withInstances, @RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId, diff --git a/naming/src/main/java/com/alibaba/nacos/naming/controllers/ClusterController.java b/naming/src/main/java/com/alibaba/nacos/naming/controllers/ClusterController.java index 1c1a393a8..760d3602f 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/controllers/ClusterController.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/controllers/ClusterController.java @@ -21,6 +21,8 @@ import com.alibaba.nacos.api.common.Constants; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.naming.CommonParams; import com.alibaba.nacos.api.naming.pojo.AbstractHealthChecker; +import com.alibaba.nacos.core.auth.ActionTypes; +import com.alibaba.nacos.core.auth.Secured; import com.alibaba.nacos.core.utils.WebUtils; import com.alibaba.nacos.naming.core.Cluster; import com.alibaba.nacos.naming.core.Service; @@ -49,6 +51,7 @@ public class ClusterController { protected ServiceManager serviceManager; @PutMapping + @Secured(action = ActionTypes.WRITE) public String update(HttpServletRequest request) throws Exception { String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, diff --git a/naming/src/main/java/com/alibaba/nacos/naming/controllers/HealthController.java b/naming/src/main/java/com/alibaba/nacos/naming/controllers/HealthController.java index 62419fb59..cbf4249c3 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/controllers/HealthController.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/controllers/HealthController.java @@ -19,6 +19,8 @@ import com.alibaba.fastjson.JSONObject; import com.alibaba.nacos.api.common.Constants; import com.alibaba.nacos.api.naming.CommonParams; import com.alibaba.nacos.api.naming.pojo.AbstractHealthChecker; +import com.alibaba.nacos.core.auth.ActionTypes; +import com.alibaba.nacos.core.auth.Secured; import com.alibaba.nacos.core.utils.WebUtils; import com.alibaba.nacos.naming.boot.RunningConfig; import com.alibaba.nacos.naming.core.Instance; @@ -72,6 +74,7 @@ public class HealthController { @CanDistro @PutMapping(value = {"", "/instance"}) + @Secured(action = ActionTypes.WRITE) public String update(HttpServletRequest request) { String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, diff --git a/naming/src/main/java/com/alibaba/nacos/naming/controllers/InstanceController.java b/naming/src/main/java/com/alibaba/nacos/naming/controllers/InstanceController.java index 3cd1e9fc9..ecf1f9e62 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/controllers/InstanceController.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/controllers/InstanceController.java @@ -23,6 +23,8 @@ import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.naming.CommonParams; import com.alibaba.nacos.api.naming.NamingResponseCode; import com.alibaba.nacos.api.naming.utils.NamingUtils; +import com.alibaba.nacos.core.auth.ActionTypes; +import com.alibaba.nacos.core.auth.Secured; import com.alibaba.nacos.core.utils.WebUtils; import com.alibaba.nacos.naming.core.Instance; import com.alibaba.nacos.naming.core.Service; @@ -36,6 +38,7 @@ import com.alibaba.nacos.naming.push.ClientInfo; import com.alibaba.nacos.naming.push.DataSource; import com.alibaba.nacos.naming.push.PushService; import com.alibaba.nacos.naming.web.CanDistro; +import com.alibaba.nacos.naming.web.NamingResourceParser; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; @@ -86,8 +89,10 @@ public class InstanceController { } }; + @CanDistro @PostMapping + @Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE) public String register(HttpServletRequest request) throws Exception { String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME); @@ -99,6 +104,7 @@ public class InstanceController { @CanDistro @DeleteMapping + @Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE) public String deregister(HttpServletRequest request) throws Exception { Instance instance = getIPAddress(request); String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, @@ -118,6 +124,7 @@ public class InstanceController { @CanDistro @PutMapping + @Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE) public String update(HttpServletRequest request) throws Exception { String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME); String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID); @@ -137,6 +144,7 @@ public class InstanceController { @CanDistro @PatchMapping + @Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE) public String patch(HttpServletRequest request) throws Exception { String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME); String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID); @@ -180,6 +188,7 @@ public class InstanceController { } @GetMapping("/list") + @Secured(parser = NamingResourceParser.class, action = ActionTypes.READ) public JSONObject list(HttpServletRequest request) throws Exception { String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, @@ -204,6 +213,7 @@ public class InstanceController { } @GetMapping + @Secured(parser = NamingResourceParser.class, action = ActionTypes.READ) public JSONObject detail(HttpServletRequest request) throws Exception { String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, @@ -247,6 +257,7 @@ public class InstanceController { @CanDistro @PutMapping("/beat") + @Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE) public JSONObject beat(HttpServletRequest request) throws Exception { JSONObject result = new JSONObject(); diff --git a/naming/src/main/java/com/alibaba/nacos/naming/controllers/OperatorController.java b/naming/src/main/java/com/alibaba/nacos/naming/controllers/OperatorController.java index 170097362..aa63a3d6c 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/controllers/OperatorController.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/controllers/OperatorController.java @@ -19,6 +19,8 @@ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.alibaba.nacos.api.common.Constants; +import com.alibaba.nacos.core.auth.ActionTypes; +import com.alibaba.nacos.core.auth.Secured; import com.alibaba.nacos.core.utils.SystemUtils; import com.alibaba.nacos.naming.cluster.ServerListManager; import com.alibaba.nacos.naming.cluster.ServerStatusManager; @@ -30,7 +32,6 @@ import com.alibaba.nacos.naming.core.ServiceManager; import com.alibaba.nacos.naming.misc.*; import com.alibaba.nacos.naming.pojo.ClusterStateView; import com.alibaba.nacos.naming.push.PushService; -import com.alibaba.nacos.naming.web.NeedAuth; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -117,7 +118,7 @@ public class OperatorController { return switchDomain; } - @NeedAuth + @Secured(resource = "naming/switches", action = ActionTypes.WRITE) @PutMapping("/switches") public String updateSwitch(@RequestParam(required = false) boolean debug, @RequestParam String entry, @RequestParam String value) throws Exception { @@ -127,6 +128,7 @@ public class OperatorController { return "ok"; } + @Secured(resource = "naming/metrics", action = ActionTypes.READ) @GetMapping("/metrics") public JSONObject metrics(HttpServletRequest request) { diff --git a/naming/src/main/java/com/alibaba/nacos/naming/controllers/RaftController.java b/naming/src/main/java/com/alibaba/nacos/naming/controllers/RaftController.java index 3056c2fb8..762cac2ca 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/controllers/RaftController.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/controllers/RaftController.java @@ -34,7 +34,6 @@ import com.alibaba.nacos.naming.core.ServiceManager; import com.alibaba.nacos.naming.misc.NetUtils; import com.alibaba.nacos.naming.misc.SwitchDomain; import com.alibaba.nacos.naming.misc.UtilsAndCommons; -import com.alibaba.nacos.naming.web.NeedAuth; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @@ -67,7 +66,6 @@ public class RaftController { @Autowired private RaftCore raftCore; - @NeedAuth @PostMapping("/vote") public JSONObject vote(HttpServletRequest request, HttpServletResponse response) throws Exception { @@ -77,7 +75,6 @@ public class RaftController { return JSON.parseObject(JSON.toJSONString(peer)); } - @NeedAuth @PostMapping("/beat") public JSONObject beat(HttpServletRequest request, HttpServletResponse response) throws Exception { @@ -93,7 +90,6 @@ public class RaftController { return JSON.parseObject(JSON.toJSONString(peer)); } - @NeedAuth @GetMapping("/peer") public JSONObject getPeer(HttpServletRequest request, HttpServletResponse response) { List peers = raftCore.getPeers(); @@ -113,7 +109,6 @@ public class RaftController { return JSON.parseObject(JSON.toJSONString(peer)); } - @NeedAuth @PutMapping("/datum/reload") public String reloadDatum(HttpServletRequest request, HttpServletResponse response) throws Exception { String key = WebUtils.required(request, "key"); @@ -121,7 +116,6 @@ public class RaftController { return "ok"; } - @NeedAuth @PostMapping("/datum") public String publish(HttpServletRequest request, HttpServletResponse response) throws Exception { @@ -152,7 +146,6 @@ public class RaftController { throw new NacosException(NacosException.INVALID_PARAM, "unknown type publish key: " + key); } - @NeedAuth @DeleteMapping("/datum") public String delete(HttpServletRequest request, HttpServletResponse response) throws Exception { @@ -163,7 +156,6 @@ public class RaftController { return "ok"; } - @NeedAuth @GetMapping("/datum") public String get(HttpServletRequest request, HttpServletResponse response) throws Exception { @@ -197,7 +189,6 @@ public class RaftController { return result; } - @NeedAuth @PostMapping("/datum/commit") public String onPublish(HttpServletRequest request, HttpServletResponse response) throws Exception { @@ -229,7 +220,6 @@ public class RaftController { return "ok"; } - @NeedAuth @DeleteMapping("/datum/commit") public String onDelete(HttpServletRequest request, HttpServletResponse response) throws Exception { diff --git a/naming/src/main/java/com/alibaba/nacos/naming/controllers/ServiceController.java b/naming/src/main/java/com/alibaba/nacos/naming/controllers/ServiceController.java index a86e95561..33e4b12e3 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/controllers/ServiceController.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/controllers/ServiceController.java @@ -24,6 +24,8 @@ import com.alibaba.nacos.api.naming.CommonParams; import com.alibaba.nacos.api.naming.utils.NamingUtils; import com.alibaba.nacos.api.selector.SelectorType; import com.alibaba.nacos.common.utils.IoUtils; +import com.alibaba.nacos.core.auth.ActionTypes; +import com.alibaba.nacos.core.auth.Secured; import com.alibaba.nacos.core.utils.WebUtils; import com.alibaba.nacos.naming.cluster.ServerListManager; import com.alibaba.nacos.naming.core.*; @@ -33,6 +35,7 @@ import com.alibaba.nacos.naming.pojo.Subscriber; import com.alibaba.nacos.naming.selector.LabelSelector; import com.alibaba.nacos.naming.selector.NoneSelector; import com.alibaba.nacos.naming.selector.Selector; +import com.alibaba.nacos.naming.web.NamingResourceParser; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -64,6 +67,7 @@ public class ServiceController { private SubscribeManager subscribeManager; @PostMapping + @Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE) public String create(@RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId, @RequestParam String serviceName, @RequestParam(required = false) float protectThreshold, @@ -98,6 +102,7 @@ public class ServiceController { } @DeleteMapping + @Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE) public String remove(@RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId, @RequestParam String serviceName) throws Exception { @@ -107,6 +112,7 @@ public class ServiceController { } @GetMapping + @Secured(parser = NamingResourceParser.class, action = ActionTypes.READ) public JSONObject detail(@RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId, @RequestParam String serviceName) throws NacosException { @@ -138,6 +144,7 @@ public class ServiceController { } @GetMapping("/list") + @Secured(parser = NamingResourceParser.class, action = ActionTypes.READ) public JSONObject list(HttpServletRequest request) throws Exception { int pageNo = NumberUtils.toInt(WebUtils.required(request, "pageNo")); @@ -209,6 +216,7 @@ public class ServiceController { } @PutMapping + @Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE) public String update(HttpServletRequest request) throws Exception { String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, @@ -238,6 +246,7 @@ public class ServiceController { } @RequestMapping("/names") + @Secured(parser = NamingResourceParser.class, action = ActionTypes.READ) public JSONObject searchService(@RequestParam(defaultValue = StringUtils.EMPTY) String namespaceId, @RequestParam(defaultValue = StringUtils.EMPTY) String expr, @RequestParam(required = false) boolean responsibleOnly) { @@ -350,6 +359,7 @@ public class ServiceController { * @return */ @GetMapping("/subscribers") + @Secured(parser = NamingResourceParser.class, action = ActionTypes.READ) public JSONObject subscribers(HttpServletRequest request) { int pageNo = NumberUtils.toInt(WebUtils.required(request, "pageNo")); diff --git a/naming/src/main/java/com/alibaba/nacos/naming/exception/ResponseExceptionHandler.java b/naming/src/main/java/com/alibaba/nacos/naming/exception/ResponseExceptionHandler.java index 54bc43158..688af9bf7 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/exception/ResponseExceptionHandler.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/exception/ResponseExceptionHandler.java @@ -16,6 +16,7 @@ package com.alibaba.nacos.naming.exception; import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.core.auth.AccessException; import com.alibaba.nacos.naming.misc.Loggers; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; diff --git a/naming/src/main/java/com/alibaba/nacos/naming/misc/GlobalExecutor.java b/naming/src/main/java/com/alibaba/nacos/naming/misc/GlobalExecutor.java index 0f2722428..35e954397 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/misc/GlobalExecutor.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/misc/GlobalExecutor.java @@ -62,7 +62,6 @@ public class GlobalExecutor { } }); - private static ScheduledExecutorService dataSyncExecutor = new ScheduledThreadPoolExecutor(Runtime.getRuntime().availableProcessors(), new ThreadFactory() { @Override diff --git a/naming/src/main/java/com/alibaba/nacos/naming/misc/HttpClient.java b/naming/src/main/java/com/alibaba/nacos/naming/misc/HttpClient.java index 82dc093c2..de3f2c2a1 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/misc/HttpClient.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/misc/HttpClient.java @@ -419,10 +419,7 @@ public class HttpClient { inputStream = new GZIPInputStream(inputStream); } - HttpResult result = new HttpResult(respCode, IoUtils.toString(inputStream, getCharset(conn)), respHeaders); - inputStream.close(); - - return result; + return new HttpResult(respCode, IoUtils.toString(inputStream, getCharset(conn)), respHeaders); } private static String getCharset(HttpURLConnection conn) { diff --git a/naming/src/main/java/com/alibaba/nacos/naming/misc/UtilsAndCommons.java b/naming/src/main/java/com/alibaba/nacos/naming/misc/UtilsAndCommons.java index e6b58519c..f4f07539f 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/misc/UtilsAndCommons.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/misc/UtilsAndCommons.java @@ -195,18 +195,6 @@ public class UtilsAndCommons { } - public static String getAllExceptionMsg(Throwable e) { - Throwable cause = e; - StringBuilder strBuilder = new StringBuilder(); - - while (cause != null && !StringUtils.isEmpty(cause.getMessage())) { - strBuilder.append("caused: ").append(cause.getMessage()).append(";"); - cause = cause.getCause(); - } - - return strBuilder.toString(); - } - public static String getSwitchDomainKey() { return UtilsAndCommons.DOMAINS_DATA_ID_PRE + UtilsAndCommons.SWITCH_DOMAIN_NAME; } diff --git a/naming/src/main/java/com/alibaba/nacos/naming/web/AuthFilter.java b/naming/src/main/java/com/alibaba/nacos/naming/web/AuthFilter.java deleted file mode 100644 index bfe8c4720..000000000 --- a/naming/src/main/java/com/alibaba/nacos/naming/web/AuthFilter.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 1999-2018 Alibaba Group Holding Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.alibaba.nacos.naming.web; - -import com.alibaba.nacos.api.naming.CommonParams; -import com.alibaba.nacos.naming.acl.AuthChecker; -import com.alibaba.nacos.naming.misc.SwitchDomain; -import com.alibaba.nacos.naming.misc.UtilsAndCommons; -import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; - -import javax.servlet.*; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.lang.reflect.Method; -import java.net.URI; -import java.security.AccessControlException; - -/** - * @author nkorange - */ -public class AuthFilter implements Filter { - - private static final String[] NAMESPACE_FORBIDDEN_STRINGS = new String[]{"..", "/"}; - - @Autowired - private AuthChecker authChecker; - - @Autowired - private SwitchDomain switchDomain; - - @Autowired - private FilterBase filterBase; - - @Override - public void init(FilterConfig filterConfig) throws ServletException { - - } - - @Override - public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, - FilterChain filterChain) throws IOException, ServletException { - - HttpServletRequest req = (HttpServletRequest) servletRequest; - HttpServletResponse resp = (HttpServletResponse) servletResponse; - - try { - String path = new URI(req.getRequestURI()).getPath(); - Method method = filterBase.getMethod(req.getMethod(), path); - - if (method == null) { - throw new NoSuchMethodException(); - } - - if (method.isAnnotationPresent(NeedAuth.class) && !switchDomain.isEnableAuthentication()) { - // leave it empty. - } - - // Check namespace: - String namespaceId = req.getParameter(CommonParams.NAMESPACE_ID); - - if (StringUtils.isNotBlank(namespaceId)) { - - if (namespaceId.contains(NAMESPACE_FORBIDDEN_STRINGS[0]) || namespaceId.contains(NAMESPACE_FORBIDDEN_STRINGS[1])) { - throw new IllegalArgumentException("forbidden namespace: " + namespaceId); - } - } - - } catch (AccessControlException e) { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "access denied: " + UtilsAndCommons.getAllExceptionMsg(e)); - return; - } catch (NoSuchMethodException e) { - resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, "no such api"); - return; - } catch (IllegalArgumentException e) { - resp.sendError(HttpServletResponse.SC_BAD_REQUEST, UtilsAndCommons.getAllExceptionMsg(e)); - return; - } catch (Exception e) { - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, - "Server failed," + UtilsAndCommons.getAllExceptionMsg(e)); - return; - } - filterChain.doFilter(req, resp); - } - - @Override - public void destroy() { - - } -} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/web/DistroFilter.java b/naming/src/main/java/com/alibaba/nacos/naming/web/DistroFilter.java index 15baec29d..5eddc5373 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/web/DistroFilter.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/web/DistroFilter.java @@ -19,10 +19,12 @@ import com.alibaba.nacos.api.common.Constants; import com.alibaba.nacos.api.naming.CommonParams; import com.alibaba.nacos.common.constant.HttpHeaderConsts; import com.alibaba.nacos.common.utils.IoUtils; +import com.alibaba.nacos.core.code.ControllerMethodsCache; +import com.alibaba.nacos.core.utils.ExceptionUtil; +import com.alibaba.nacos.core.utils.OverrideParameterRequestWrapper; import com.alibaba.nacos.naming.core.DistroMapper; import com.alibaba.nacos.naming.misc.HttpClient; import com.alibaba.nacos.naming.misc.Loggers; -import com.alibaba.nacos.naming.misc.SwitchDomain; import com.alibaba.nacos.naming.misc.UtilsAndCommons; import org.apache.commons.codec.Charsets; import org.apache.commons.lang3.StringUtils; @@ -52,10 +54,7 @@ public class DistroFilter implements Filter { private DistroMapper distroMapper; @Autowired - private SwitchDomain switchDomain; - - @Autowired - private FilterBase filterBase; + private ControllerMethodsCache controllerMethodsCache; @Override public void init(FilterConfig filterConfig) throws ServletException { @@ -80,7 +79,11 @@ public class DistroFilter implements Filter { if (StringUtils.isBlank(serviceName)) { serviceName = req.getParameter("dom"); } - Method method = filterBase.getMethod(req.getMethod(), path); + + if (StringUtils.isNotBlank(serviceName)) { + serviceName = serviceName.trim(); + } + Method method = controllerMethodsCache.getMethod(req.getMethod(), path); if (method == null) { throw new NoSuchMethodException(req.getMethod() + " " + path); @@ -139,14 +142,15 @@ public class DistroFilter implements Filter { requestWrapper.addParameter(CommonParams.SERVICE_NAME, groupedServiceName); filterChain.doFilter(requestWrapper, resp); } catch (AccessControlException e) { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "access denied: " + UtilsAndCommons.getAllExceptionMsg(e)); + resp.sendError(HttpServletResponse.SC_FORBIDDEN, "access denied: " + ExceptionUtil.getAllExceptionMsg(e)); return; } catch (NoSuchMethodException e) { - resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, "no such api: " + e.getMessage()); + resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, + "no such api:" + req.getMethod() + ":" + req.getRequestURI()); return; } catch (Exception e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, - "Server failed," + UtilsAndCommons.getAllExceptionMsg(e)); + "Server failed," + ExceptionUtil.getAllExceptionMsg(e)); return; } diff --git a/naming/src/main/java/com/alibaba/nacos/naming/web/NamingConfig.java b/naming/src/main/java/com/alibaba/nacos/naming/web/NamingConfig.java index d18b10a7f..1be93abc1 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/web/NamingConfig.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/web/NamingConfig.java @@ -47,18 +47,6 @@ public class NamingConfig { return registration; } - @Bean - public FilterRegistrationBean authFilterRegistration() { - FilterRegistrationBean registration = new FilterRegistrationBean<>(); - - registration.setFilter(authFilter()); - registration.addUrlPatterns("/v1/ns/*"); - registration.setName("authFilter"); - registration.setOrder(5); - - return registration; - } - @Bean public DistroFilter distroFilter() { return new DistroFilter(); @@ -69,9 +57,4 @@ public class NamingConfig { return new TrafficReviseFilter(); } - @Bean - public AuthFilter authFilter() { - return new AuthFilter(); - } - } diff --git a/naming/src/main/java/com/alibaba/nacos/naming/web/NamingResourceParser.java b/naming/src/main/java/com/alibaba/nacos/naming/web/NamingResourceParser.java new file mode 100644 index 000000000..0632b9104 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/web/NamingResourceParser.java @@ -0,0 +1,72 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.nacos.naming.web; + +import com.alibaba.nacos.api.common.Constants; +import com.alibaba.nacos.api.naming.CommonParams; +import com.alibaba.nacos.api.naming.utils.NamingUtils; +import com.alibaba.nacos.core.auth.Resource; +import com.alibaba.nacos.core.auth.ResourceParser; +import org.apache.commons.lang3.StringUtils; + +import javax.servlet.http.HttpServletRequest; + +/** + * Naming resource parser + * + * @author nkorange + * @since 1.2.0 + */ +public class NamingResourceParser implements ResourceParser { + + private static final String AUTH_NAMING_PREFIX = "naming/"; + + @Override + public String parseName(Object request) { + + HttpServletRequest req = (HttpServletRequest) request; + + String namespaceId = req.getParameter(CommonParams.NAMESPACE_ID); + String serviceName = req.getParameter(CommonParams.SERVICE_NAME); + String groupName = req.getParameter(CommonParams.GROUP_NAME); + if (StringUtils.isBlank(groupName)) { + groupName = NamingUtils.getGroupName(serviceName); + } + serviceName = NamingUtils.getServiceName(serviceName); + + if (StringUtils.isBlank(namespaceId)) { + namespaceId = Constants.DEFAULT_NAMESPACE_ID; + } + + StringBuilder sb = new StringBuilder(); + + sb.append(namespaceId).append(Resource.SPLITTER); + + if (StringUtils.isBlank(serviceName)) { + sb.append("*") + .append(Resource.SPLITTER) + .append(AUTH_NAMING_PREFIX) + .append("*"); + } else { + sb.append(groupName) + .append(Resource.SPLITTER) + .append(AUTH_NAMING_PREFIX) + .append(serviceName); + } + + return sb.toString(); + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/web/TrafficReviseFilter.java b/naming/src/main/java/com/alibaba/nacos/naming/web/TrafficReviseFilter.java index caeecc47b..f3e609274 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/web/TrafficReviseFilter.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/web/TrafficReviseFilter.java @@ -16,11 +16,11 @@ package com.alibaba.nacos.naming.web; import com.alibaba.nacos.common.utils.HttpMethod; +import com.alibaba.nacos.core.utils.Constants; import com.alibaba.nacos.core.utils.WebUtils; import com.alibaba.nacos.naming.cluster.ServerStatus; import com.alibaba.nacos.naming.cluster.ServerStatusManager; import com.alibaba.nacos.naming.misc.SwitchDomain; -import com.alibaba.nacos.naming.misc.UtilsAndCommons; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -73,7 +73,7 @@ public class TrafficReviseFilter implements Filter { // requests from peer server should be let pass: String agent = WebUtils.getUserAgent(req); - if (StringUtils.startsWith(agent, UtilsAndCommons.NACOS_SERVER_HEADER)) { + if (StringUtils.startsWith(agent, Constants.NACOS_SERVER_HEADER)) { filterChain.doFilter(req, resp); return; } diff --git a/pom.xml b/pom.xml index f81f9e9b0..1801d6673 100644 --- a/pom.xml +++ b/pom.xml @@ -525,6 +525,11 @@ nacos-example ${project.version} + + ${project.groupId} + nacos-address + ${project.version} + ${project.groupId} @@ -539,12 +544,12 @@ 1.2.58 - - - javax.ws.rs - javax.ws.rs - 2.1 - + + + + + + javax.servlet @@ -783,6 +788,12 @@ pom + + org.reflections + reflections + 0.9.11 + + diff --git a/test/pom.xml b/test/pom.xml index bf3d2c301..865693c33 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -75,6 +75,16 @@ nacos-core + + ${project.groupId} + nacos-console + + + + ${project.groupId} + nacos-address + + junit junit diff --git a/test/src/test/java/com/alibaba/nacos/test/base/HttpClient4Test.java b/test/src/test/java/com/alibaba/nacos/test/base/HttpClient4Test.java new file mode 100644 index 000000000..e4ef176e7 --- /dev/null +++ b/test/src/test/java/com/alibaba/nacos/test/base/HttpClient4Test.java @@ -0,0 +1,52 @@ +package com.alibaba.nacos.test.base; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.util.MultiValueMap; +import org.springframework.web.util.UriComponentsBuilder; + +import java.net.URL; + +/** + * Http client for test module + * + * @author nkorange + * @since 1.2.0 + */ +public class HttpClient4Test { + + protected URL base; + + @Autowired + protected TestRestTemplate restTemplate; + + protected ResponseEntity request(String path, MultiValueMap params, Class clazz) { + + HttpHeaders headers = new HttpHeaders(); + + HttpEntity entity = new HttpEntity(headers); + + UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(this.base.toString() + path) + .queryParams(params); + + return this.restTemplate.exchange( + builder.toUriString(), HttpMethod.GET, entity, clazz); + } + + protected ResponseEntity request(String path, MultiValueMap params, Class clazz, HttpMethod httpMethod) { + + HttpHeaders headers = new HttpHeaders(); + + HttpEntity entity = new HttpEntity(headers); + + UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(this.base.toString() + path) + .queryParams(params); + + return this.restTemplate.exchange( + builder.toUriString(), httpMethod, entity, clazz); + } +} diff --git a/test/src/test/java/com/alibaba/nacos/test/naming/Params.java b/test/src/test/java/com/alibaba/nacos/test/base/Params.java similarity index 96% rename from test/src/test/java/com/alibaba/nacos/test/naming/Params.java rename to test/src/test/java/com/alibaba/nacos/test/base/Params.java index e11e57587..aee7d0e75 100644 --- a/test/src/test/java/com/alibaba/nacos/test/naming/Params.java +++ b/test/src/test/java/com/alibaba/nacos/test/base/Params.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alibaba.nacos.test.naming; +package com.alibaba.nacos.test.base; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; diff --git a/test/src/test/java/com/alibaba/nacos/test/config/ConfigBeta_ITCase.java b/test/src/test/java/com/alibaba/nacos/test/config/ConfigBeta_ITCase.java index d6a7ef2c3..3c0c4df85 100644 --- a/test/src/test/java/com/alibaba/nacos/test/config/ConfigBeta_ITCase.java +++ b/test/src/test/java/com/alibaba/nacos/test/config/ConfigBeta_ITCase.java @@ -20,7 +20,7 @@ import com.alibaba.fastjson.JSONObject; import com.alibaba.nacos.api.config.ConfigService; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.config.server.Config; -import com.alibaba.nacos.test.naming.Params; +import com.alibaba.nacos.test.base.Params; import org.junit.Assert; import org.junit.Before; import org.junit.Test; diff --git a/test/src/test/java/com/alibaba/nacos/test/core/auth/Permission_ITCase.java b/test/src/test/java/com/alibaba/nacos/test/core/auth/Permission_ITCase.java new file mode 100644 index 000000000..25a18558a --- /dev/null +++ b/test/src/test/java/com/alibaba/nacos/test/core/auth/Permission_ITCase.java @@ -0,0 +1,313 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.nacos.test.core.auth; + + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.TypeReference; +import com.alibaba.nacos.Nacos; +import com.alibaba.nacos.config.server.model.Page; +import com.alibaba.nacos.core.auth.Permission; +import com.alibaba.nacos.test.base.HttpClient4Test; +import com.alibaba.nacos.test.base.Params; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringRunner; + +import java.net.URL; +import java.util.concurrent.TimeUnit; + +/** + * @author nkorange + * @since 1.2.0 + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = Nacos.class, properties = {"server.servlet.context-path=/nacos", "server.port=7001"}, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class Permission_ITCase extends HttpClient4Test { + + @LocalServerPort + private int port; + + private String accessToken; + + @Before + public void init() throws Exception { + TimeUnit.SECONDS.sleep(5L); + String url = String.format("http://localhost:%d/", port); + this.base = new URL(url); + } + + @After + public void destroy() { + + // Delete permission: + ResponseEntity response = request("/nacos/v1/auth/permissions", + Params.newParams() + .appendParam("role", "role1") + .appendParam("resource", "public:*:*") + .appendParam("action", "rw") + .appendParam("accessToken", accessToken) + .done(), + String.class, + HttpMethod.DELETE); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + // Delete permission: + response = request("/nacos/v1/auth/permissions", + Params.newParams() + .appendParam("role", "role1") + .appendParam("resource", "test1:*:*") + .appendParam("action", "r") + .appendParam("accessToken", accessToken) + .done(), + String.class, + HttpMethod.DELETE); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + // Delete role: + response = request("/nacos/v1/auth/roles", + Params.newParams() + .appendParam("role", "role1") + .appendParam("username", "username2") + .appendParam("accessToken", accessToken) + .done(), + String.class, + HttpMethod.DELETE); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + // Delete a user: + response = request("/nacos/v1/auth/users", + Params.newParams() + .appendParam("username", "username3") + .appendParam("accessToken", accessToken) + .done(), + String.class, + HttpMethod.DELETE); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + } + + @Test + public void login() { + + ResponseEntity response = request("/nacos/v1/auth/users/login", + Params.newParams() + .appendParam("username", "nacos") + .appendParam("password", "nacos") + .done(), + String.class, + HttpMethod.POST); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + JSONObject json = JSON.parseObject(response.getBody()); + Assert.assertTrue(json.containsKey("accessToken")); + accessToken = json.getString("accessToken"); + } + + @Test + public void createDeleteQueryPermission() { + + login(); + + // Create a user: + ResponseEntity response = request("/nacos/v1/auth/users", + Params.newParams() + .appendParam("username", "username3") + .appendParam("password", "password1") + .appendParam("accessToken", accessToken) + .done(), + String.class, + HttpMethod.POST); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + // Create role: + response = request("/nacos/v1/auth/roles", + Params.newParams() + .appendParam("role", "role1") + .appendParam("username", "username3") + .appendParam("accessToken", accessToken) + .done(), + String.class, + HttpMethod.POST); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + // Create permission: + response = request("/nacos/v1/auth/permissions", + Params.newParams() + .appendParam("role", "role1") + .appendParam("resource", "public:*:*") + .appendParam("action", "rw") + .appendParam("accessToken", accessToken) + .done(), + String.class, + HttpMethod.POST); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + // Create another permission: + response = request("/nacos/v1/auth/permissions", + Params.newParams() + .appendParam("role", "role1") + .appendParam("resource", "test1:*:*") + .appendParam("action", "r") + .appendParam("accessToken", accessToken) + .done(), + String.class, + HttpMethod.POST); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + // Query permission: + response = request("/nacos/v1/auth/permissions", + Params.newParams() + .appendParam("role", "role1") + .appendParam("pageNo", "1") + .appendParam("pageSize", "10") + .appendParam("accessToken", accessToken) + .done(), + String.class, + HttpMethod.GET); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + Page permissionPage = JSON.parseObject(response.getBody(), new TypeReference>() { + }); + + Assert.assertNotNull(permissionPage); + Assert.assertNotNull(permissionPage.getPageItems()); + + boolean found1=false,found2=false; + for (Permission permission : permissionPage.getPageItems()) { + if (permission.getResource().equals("public:*:*") && permission.getAction().equals("rw")) { + found1 = true; + } + if (permission.getResource().equals("test1:*:*") && permission.getAction().equals("r")) { + found2 = true; + } + if (found1 && found2) { + break; + } + } + Assert.assertTrue(found1); + Assert.assertTrue(found2); + + // Delete permission: + response = request("/nacos/v1/auth/permissions", + Params.newParams() + .appendParam("role", "role1") + .appendParam("resource", "public:*:*") + .appendParam("action", "rw") + .appendParam("accessToken", accessToken) + .done(), + String.class, + HttpMethod.DELETE); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + // Query permission: + response = request("/nacos/v1/auth/permissions", + Params.newParams() + .appendParam("role", "role1") + .appendParam("pageNo", "1") + .appendParam("pageSize", "10") + .appendParam("accessToken", accessToken) + .done(), + String.class, + HttpMethod.GET); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + permissionPage = JSON.parseObject(response.getBody(), new TypeReference>() { + }); + + Assert.assertNotNull(permissionPage); + Assert.assertNotNull(permissionPage.getPageItems()); + + found1=false; + found2=false; + + for (Permission permission : permissionPage.getPageItems()) { + if (permission.getResource().equals("public:*:*") && permission.getAction().equals("rw")) { + found1 = true; + } + if (permission.getResource().equals("test1:*:*") && permission.getAction().equals("r")) { + found2 = true; + } + } + Assert.assertFalse(found1); + Assert.assertTrue(found2); + + // Delete permission: + response = request("/nacos/v1/auth/permissions", + Params.newParams() + .appendParam("role", "role1") + .appendParam("resource", "test1:*:*") + .appendParam("action", "r") + .appendParam("accessToken", accessToken) + .done(), + String.class, + HttpMethod.DELETE); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + // Query permission: + response = request("/nacos/v1/auth/permissions", + Params.newParams() + .appendParam("role", "role1") + .appendParam("pageNo", "1") + .appendParam("pageSize", "10") + .appendParam("accessToken", accessToken) + .done(), + String.class, + HttpMethod.GET); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + permissionPage = JSON.parseObject(response.getBody(), new TypeReference>() { + }); + + Assert.assertNotNull(permissionPage); + Assert.assertNotNull(permissionPage.getPageItems()); + + found1=false; + found2=false; + + for (Permission permission : permissionPage.getPageItems()) { + if (permission.getResource().equals("public:*:*") && permission.getAction().equals("rw")) { + found1 = true; + } + if (permission.getResource().equals("test1:*:*") && permission.getAction().equals("r")) { + found2 = true; + } + } + Assert.assertFalse(found1); + Assert.assertFalse(found2); + } +} diff --git a/test/src/test/java/com/alibaba/nacos/test/core/auth/Role_ITCase.java b/test/src/test/java/com/alibaba/nacos/test/core/auth/Role_ITCase.java new file mode 100644 index 000000000..625364452 --- /dev/null +++ b/test/src/test/java/com/alibaba/nacos/test/core/auth/Role_ITCase.java @@ -0,0 +1,306 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.nacos.test.core.auth; + + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.TypeReference; +import com.alibaba.nacos.Nacos; +import com.alibaba.nacos.config.server.auth.RoleInfo; +import com.alibaba.nacos.config.server.model.Page; +import com.alibaba.nacos.test.base.HttpClient4Test; +import com.alibaba.nacos.test.base.Params; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringRunner; + +import java.net.URL; +import java.util.concurrent.TimeUnit; + +/** + * @author nkorange + * @since 1.2.0 + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = Nacos.class, properties = {"server.servlet.context-path=/nacos", "server.port=7001"}, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class Role_ITCase extends HttpClient4Test { + + @LocalServerPort + private int port; + + private String accessToken; + + @Before + public void init() throws Exception { + TimeUnit.SECONDS.sleep(5L); + String url = String.format("http://localhost:%d/", port); + this.base = new URL(url); + } + + @After + public void destroy() { + + // Delete role: + ResponseEntity response = request("/nacos/v1/auth/roles", + Params.newParams() + .appendParam("role", "role1") + .appendParam("username", "username2") + .appendParam("accessToken", accessToken) + .done(), + String.class, + HttpMethod.DELETE); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + // Delete role: + response = request("/nacos/v1/auth/roles", + Params.newParams() + .appendParam("role", "role2") + .appendParam("username", "username2") + .appendParam("accessToken", accessToken) + .done(), + String.class, + HttpMethod.DELETE); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + // Delete a user: + response = request("/nacos/v1/auth/users", + Params.newParams() + .appendParam("username", "username2") + .appendParam("accessToken", accessToken) + .done(), + String.class, + HttpMethod.DELETE); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + } + + @Test + public void login() { + + ResponseEntity response = request("/nacos/v1/auth/users/login", + Params.newParams() + .appendParam("username", "nacos") + .appendParam("password", "nacos") + .done(), + String.class, + HttpMethod.POST); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + JSONObject json = JSON.parseObject(response.getBody()); + Assert.assertTrue(json.containsKey("accessToken")); + accessToken = json.getString("accessToken"); + } + + @Test + public void createDeleteQueryRole() { + + login(); + + // Create a user: + ResponseEntity response = request("/nacos/v1/auth/users", + Params.newParams() + .appendParam("username", "username2") + .appendParam("password", "password1") + .appendParam("accessToken", accessToken) + .done(), + String.class, + HttpMethod.POST); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + // Create a role: + response = request("/nacos/v1/auth/roles", + Params.newParams() + .appendParam("role", "role1") + .appendParam("username", "username2") + .appendParam("accessToken", accessToken) + .done(), + String.class, + HttpMethod.POST); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + // Query role of user: + response = request("/nacos/v1/auth/roles", + Params.newParams() + .appendParam("username", "username2") + .appendParam("pageNo", "1") + .appendParam("pageSize", "10") + .appendParam("accessToken", accessToken) + .done(), + String.class, + HttpMethod.GET); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + Page roleInfoPage = JSON.parseObject(response.getBody(), new TypeReference>() { + }); + + Assert.assertNotNull(roleInfoPage); + Assert.assertNotNull(roleInfoPage.getPageItems()); + boolean found = false; + for (RoleInfo roleInfo : roleInfoPage.getPageItems()) { + if (roleInfo.getRole().equals("role1")) { + found = true; + break; + } + } + Assert.assertTrue(found); + + // Add second role to user: + response = request("/nacos/v1/auth/roles", + Params.newParams() + .appendParam("role", "role2") + .appendParam("username", "username2") + .appendParam("accessToken", accessToken) + .done(), + String.class, + HttpMethod.POST); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + // Query roles of user: + response = request("/nacos/v1/auth/roles", + Params.newParams() + .appendParam("username", "username2") + .appendParam("pageNo", "1") + .appendParam("pageSize", "10") + .appendParam("accessToken", accessToken) + .done(), + String.class, + HttpMethod.GET); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + roleInfoPage = JSON.parseObject(response.getBody(), new TypeReference>() { + }); + + Assert.assertNotNull(roleInfoPage); + Assert.assertNotNull(roleInfoPage.getPageItems()); + found = false; + boolean found2 = false; + for (RoleInfo roleInfo : roleInfoPage.getPageItems()) { + if (roleInfo.getRole().equals("role1")) { + found = true; + } + if (roleInfo.getRole().equals("role2")) { + found2 = true; + } + if (found && found2) { + break; + } + } + Assert.assertTrue(found); + Assert.assertTrue(found2); + + // Delete role: + response = request("/nacos/v1/auth/roles", + Params.newParams() + .appendParam("role", "role2") + .appendParam("username", "username2") + .appendParam("accessToken", accessToken) + .done(), + String.class, + HttpMethod.DELETE); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + // Query roles of user: + response = request("/nacos/v1/auth/roles", + Params.newParams() + .appendParam("username", "username2") + .appendParam("pageNo", "1") + .appendParam("pageSize", "10") + .appendParam("accessToken", accessToken) + .done(), + String.class, + HttpMethod.GET); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + roleInfoPage = JSON.parseObject(response.getBody(), new TypeReference>() { + }); + + Assert.assertNotNull(roleInfoPage); + Assert.assertNotNull(roleInfoPage.getPageItems()); + found = false; + found2 = false; + for (RoleInfo roleInfo : roleInfoPage.getPageItems()) { + if (roleInfo.getRole().equals("role1")) { + found = true; + } + if (roleInfo.getRole().equals("role2")) { + found2 = true; + } + } + Assert.assertFalse(found2); + Assert.assertTrue(found); + + // Delete role: + response = request("/nacos/v1/auth/roles", + Params.newParams() + .appendParam("role", "role1") + .appendParam("username", "username2") + .appendParam("accessToken", accessToken) + .done(), + String.class, + HttpMethod.DELETE); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + // Query roles of user: + response = request("/nacos/v1/auth/roles", + Params.newParams() + .appendParam("username", "username2") + .appendParam("pageNo", "1") + .appendParam("pageSize", "10") + .appendParam("accessToken", accessToken) + .done(), + String.class, + HttpMethod.GET); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + roleInfoPage = JSON.parseObject(response.getBody(), new TypeReference>() { + }); + + Assert.assertNotNull(roleInfoPage); + Assert.assertNotNull(roleInfoPage.getPageItems()); + found = false; + found2 = false; + for (RoleInfo roleInfo : roleInfoPage.getPageItems()) { + if (roleInfo.getRole().equals("role1")) { + found = true; + } + if (roleInfo.getRole().equals("role2")) { + found2 = true; + } + } + Assert.assertFalse(found2); + Assert.assertFalse(found); + } +} diff --git a/test/src/test/java/com/alibaba/nacos/test/core/auth/User_ITCase.java b/test/src/test/java/com/alibaba/nacos/test/core/auth/User_ITCase.java new file mode 100644 index 000000000..f0188e85d --- /dev/null +++ b/test/src/test/java/com/alibaba/nacos/test/core/auth/User_ITCase.java @@ -0,0 +1,216 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.nacos.test.core.auth; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.TypeReference; +import com.alibaba.nacos.Nacos; +import com.alibaba.nacos.config.server.model.Page; +import com.alibaba.nacos.config.server.model.User; +import com.alibaba.nacos.console.utils.PasswordEncoderUtil; +import com.alibaba.nacos.test.base.HttpClient4Test; +import com.alibaba.nacos.test.base.Params; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringRunner; + +import java.net.URL; +import java.util.concurrent.TimeUnit; + +/** + * @author nkorange + * @since 1.2.0 + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = Nacos.class, properties = {"server.servlet.context-path=/nacos", "server.port=7001"}, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class User_ITCase extends HttpClient4Test { + + @LocalServerPort + private int port; + + private String accessToken; + + @Before + public void init() throws Exception { + TimeUnit.SECONDS.sleep(5L); + String url = String.format("http://localhost:%d/", port); + this.base = new URL(url); + } + + @After + public void destroy() { + + // Delete a user: + ResponseEntity response = request("/nacos/v1/auth/users", + Params.newParams() + .appendParam("username", "username1") + .appendParam("accessToken", accessToken) + .done(), + String.class, + HttpMethod.DELETE); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + } + + + @Test + public void login() { + + ResponseEntity response = request("/nacos/v1/auth/users/login", + Params.newParams() + .appendParam("username", "nacos") + .appendParam("password", "nacos") + .done(), + String.class, + HttpMethod.POST); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + JSONObject json = JSON.parseObject(response.getBody()); + Assert.assertTrue(json.containsKey("accessToken")); + accessToken = json.getString("accessToken"); + } + + @Test + public void createUpdateDeleteUser() { + + login(); + + // Create a user: + ResponseEntity response = request("/nacos/v1/auth/users", + Params.newParams() + .appendParam("username", "username1") + .appendParam("password", "password1") + .appendParam("accessToken", accessToken) + .done(), + String.class, + HttpMethod.POST); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + // Query a user: + response = request("/nacos/v1/auth/users", + Params.newParams() + .appendParam("pageNo", "1") + .appendParam("pageSize", String.valueOf(Integer.MAX_VALUE)) + .appendParam("accessToken", accessToken) + .done(), + String.class); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + Page userPage = JSON.parseObject(response.getBody(), new TypeReference>() { + }); + + Assert.assertNotNull(userPage); + Assert.assertNotNull(userPage.getPageItems()); + Assert.assertTrue(userPage.getPageItems().size() > 0); + + boolean found = false; + for (User user : userPage.getPageItems()) { + if ("username1".equals(user.getUsername()) && + PasswordEncoderUtil.matches("password1", user.getPassword())) { + found = true; + break; + } + } + Assert.assertTrue(found); + + // Update a user: + response = request("/nacos/v1/auth/users", + Params.newParams() + .appendParam("username", "username1") + .appendParam("newPassword", "password2") + .appendParam("accessToken", accessToken) + .done(), + String.class, + HttpMethod.PUT); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + // Query a user: + response = request("/nacos/v1/auth/users", + Params.newParams() + .appendParam("pageNo", "1") + .appendParam("pageSize", String.valueOf(Integer.MAX_VALUE)) + .appendParam("accessToken", accessToken) + .done(), + String.class); + + userPage = JSON.parseObject(response.getBody(), new TypeReference>() { + }); + + Assert.assertNotNull(userPage); + Assert.assertNotNull(userPage.getPageItems()); + Assert.assertTrue(userPage.getPageItems().size() > 0); + + found = false; + for (User user : userPage.getPageItems()) { + if ("username1".equals(user.getUsername()) && + PasswordEncoderUtil.matches("password2", user.getPassword())) { + found = true; + break; + } + } + Assert.assertTrue(found); + + // Delete a user: + response = request("/nacos/v1/auth/users", + Params.newParams() + .appendParam("username", "username1") + .appendParam("accessToken", accessToken) + .done(), + String.class, + HttpMethod.DELETE); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + // Query a user: + response = request("/nacos/v1/auth/users", + Params.newParams() + .appendParam("pageNo", "1") + .appendParam("pageSize", String.valueOf(Integer.MAX_VALUE)) + .appendParam("accessToken", accessToken) + .done(), + String.class); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + userPage = JSON.parseObject(response.getBody(), new TypeReference>() { + }); + + Assert.assertNotNull(userPage); + Assert.assertNotNull(userPage.getPageItems()); + Assert.assertTrue(userPage.getPageItems().size() > 0); + + found = false; + for (User user : userPage.getPageItems()) { + if ("username1".equals(user.getUsername())) { + found = true; + break; + } + } + Assert.assertFalse(found); + } +} diff --git a/test/src/test/java/com/alibaba/nacos/test/naming/Auth_ITCase.java b/test/src/test/java/com/alibaba/nacos/test/naming/Auth_ITCase.java new file mode 100644 index 000000000..4710f9a42 --- /dev/null +++ b/test/src/test/java/com/alibaba/nacos/test/naming/Auth_ITCase.java @@ -0,0 +1,117 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.nacos.test.naming; + +import com.alibaba.nacos.api.naming.NamingFactory; +import com.alibaba.nacos.api.naming.NamingService; +import com.alibaba.nacos.naming.NamingApp; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.test.context.junit4.SpringRunner; + +import java.net.URL; +import java.util.Arrays; + +/** + * Auth related cases + * + * @author nkorange + * @since 1.2.0 + */ +//@RunWith(SpringRunner.class) +//@SpringBootTest(classes = NamingApp.class, properties = {"server.servlet.context-path=/nacos"}, +// webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class Auth_ITCase extends NamingBase { + +// private NamingService naming; +// +// @LocalServerPort +// private int port; +// +// @Before +// public void init() throws Exception { +// NamingBase.prepareServer(port); +// +// if (naming == null) { +// naming = NamingFactory.createNamingService("127.0.0.1" + ":" + port); +// } +// while (true) { +// if (!"UP".equals(naming.getServerStatus())) { +// Thread.sleep(1000L); +// continue; +// } +// break; +// } +// String url = String.format("http://localhost:%d/", port); +// this.base = new URL(url); +// } + + @Test + public void testAuth() { + + + } + + public int numSimilarGroups(String[] A) { + char[][] css = new char[A.length][]; + int i = 0; + for (String a : A) { + css[i++] = a.toCharArray(); + } + boolean[] used = new boolean[A.length]; + int res=0; + for (i = 0; i < A.length; i++) { + if (!used[i]) { + res++; + used[i] = true; + bfs(used, css, i); + } + } + return res; + } + + public void bfs(boolean[] used, char[][] css, int start) { + int[] nexts = new int[]{start}; + int size = 1; + while (size > 0) { + int[] tmp = new int[used.length]; + int size2 = 0; + for (int j = 0; j < size; j++) { + for (int i = 0; i < used.length; i++) { + if (!used[i] && similar(css[nexts[j]], css[i])) { + tmp[size2++] = i; + used[i] = true; + } + } + } + nexts = tmp; + size = size2; + } + } + + public boolean similar(char[] cs1, char[] cs2) { + int dif = 0; + for (int i = 0; i < cs1.length && dif <= 2; i++) { + if (cs1[i] != cs2[i]) { + dif++; + } + } + return dif <= 2; + } +} diff --git a/test/src/test/java/com/alibaba/nacos/test/naming/CPInstancesAPI_ITCase.java b/test/src/test/java/com/alibaba/nacos/test/naming/CPInstancesAPI_ITCase.java index b728018a1..d97c0dac2 100644 --- a/test/src/test/java/com/alibaba/nacos/test/naming/CPInstancesAPI_ITCase.java +++ b/test/src/test/java/com/alibaba/nacos/test/naming/CPInstancesAPI_ITCase.java @@ -30,6 +30,7 @@ import com.alibaba.nacos.api.naming.pojo.Instance; import com.alibaba.nacos.api.naming.pojo.ListView; import com.alibaba.nacos.naming.NamingApp; +import com.alibaba.nacos.test.base.Params; import org.junit.After; import org.junit.Assert; import org.junit.Before; diff --git a/test/src/test/java/com/alibaba/nacos/test/naming/ClientBeat_ITCase.java b/test/src/test/java/com/alibaba/nacos/test/naming/ClientBeat_ITCase.java index c2fd56bfe..9fbdd4aa1 100644 --- a/test/src/test/java/com/alibaba/nacos/test/naming/ClientBeat_ITCase.java +++ b/test/src/test/java/com/alibaba/nacos/test/naming/ClientBeat_ITCase.java @@ -21,6 +21,7 @@ import com.alibaba.nacos.api.naming.NamingFactory; import com.alibaba.nacos.api.naming.NamingService; import com.alibaba.nacos.api.naming.pojo.Instance; import com.alibaba.nacos.naming.NamingApp; +import com.alibaba.nacos.test.base.Params; import org.apache.commons.lang3.StringUtils; import org.junit.Assert; import org.junit.Before; diff --git a/test/src/test/java/com/alibaba/nacos/test/naming/Cmdb_ITCase.java b/test/src/test/java/com/alibaba/nacos/test/naming/Cmdb_ITCase.java index ec1889b00..2d5770893 100644 --- a/test/src/test/java/com/alibaba/nacos/test/naming/Cmdb_ITCase.java +++ b/test/src/test/java/com/alibaba/nacos/test/naming/Cmdb_ITCase.java @@ -27,6 +27,7 @@ import com.alibaba.nacos.api.common.Constants; import com.alibaba.nacos.api.naming.NamingFactory; import com.alibaba.nacos.api.naming.NamingService; +import com.alibaba.nacos.test.base.Params; import org.junit.Ignore; import org.springframework.http.HttpMethod; import com.alibaba.nacos.naming.NamingApp; diff --git a/test/src/test/java/com/alibaba/nacos/test/naming/MultiTenant_InstanceAPI_ITCase.java b/test/src/test/java/com/alibaba/nacos/test/naming/MultiTenant_InstanceAPI_ITCase.java index 36a4da15b..546ed783e 100644 --- a/test/src/test/java/com/alibaba/nacos/test/naming/MultiTenant_InstanceAPI_ITCase.java +++ b/test/src/test/java/com/alibaba/nacos/test/naming/MultiTenant_InstanceAPI_ITCase.java @@ -16,6 +16,7 @@ import com.alibaba.nacos.api.naming.pojo.Instance; import com.alibaba.nacos.client.naming.NacosNamingService; import com.alibaba.nacos.naming.NamingApp; +import com.alibaba.nacos.test.base.Params; import org.junit.Assert; import org.junit.Before; import org.junit.Test; diff --git a/test/src/test/java/com/alibaba/nacos/test/naming/NamingBase.java b/test/src/test/java/com/alibaba/nacos/test/naming/NamingBase.java index 810942514..ca69fd526 100644 --- a/test/src/test/java/com/alibaba/nacos/test/naming/NamingBase.java +++ b/test/src/test/java/com/alibaba/nacos/test/naming/NamingBase.java @@ -18,25 +18,17 @@ package com.alibaba.nacos.test.naming; import com.alibaba.nacos.api.naming.pojo.Instance; import com.alibaba.nacos.client.naming.net.HttpClient; import com.alibaba.nacos.common.constant.HttpHeaderConsts; +import com.alibaba.nacos.test.base.HttpClient4Test; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpStatus; import org.junit.Assert; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.ResponseEntity; -import org.springframework.util.MultiValueMap; -import org.springframework.web.util.UriComponentsBuilder; -import java.net.URL; import java.util.*; /** * @author nkorange */ -public class NamingBase { +public class NamingBase extends HttpClient4Test { public static final String TEST_DOM_1 = "nacos.test.1"; @@ -66,13 +58,6 @@ public class NamingBase { public static final int TIME_OUT = 3000; - - @Autowired - protected TestRestTemplate restTemplate; - - - protected URL base; - public static String randomDomainName() { StringBuilder sb = new StringBuilder(); sb.append("jinhan"); @@ -216,30 +201,4 @@ public class NamingBase { Assert.assertEquals(HttpStatus.SC_OK, result.code); } - - protected ResponseEntity request(String path, MultiValueMap params, Class clazz) { - - HttpHeaders headers = new HttpHeaders(); - - HttpEntity entity = new HttpEntity(headers); - - UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(this.base.toString() + path) - .queryParams(params); - - return this.restTemplate.exchange( - builder.toUriString(), HttpMethod.GET, entity, clazz); - } - - protected ResponseEntity request(String path, MultiValueMap params, Class clazz, HttpMethod httpMethod) { - - HttpHeaders headers = new HttpHeaders(); - - HttpEntity entity = new HttpEntity(headers); - - UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(this.base.toString() + path) - .queryParams(params); - - return this.restTemplate.exchange( - builder.toUriString(), httpMethod, entity, clazz); - } } diff --git a/test/src/test/java/com/alibaba/nacos/test/naming/RestAPI_ITCase.java b/test/src/test/java/com/alibaba/nacos/test/naming/RestAPI_ITCase.java index 91d26ed4d..2a6af22ff 100644 --- a/test/src/test/java/com/alibaba/nacos/test/naming/RestAPI_ITCase.java +++ b/test/src/test/java/com/alibaba/nacos/test/naming/RestAPI_ITCase.java @@ -19,24 +19,19 @@ import java.net.URL; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; -import com.alibaba.nacos.naming.NamingApp; +import com.alibaba.nacos.naming.NamingApp; +import com.alibaba.nacos.test.base.Params; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.web.server.LocalServerPort; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.util.MultiValueMap; -import org.springframework.web.util.UriComponentsBuilder; /** * @author nkorange diff --git a/test/src/test/java/com/alibaba/nacos/test/naming/Subscribe_ITCase.java b/test/src/test/java/com/alibaba/nacos/test/naming/Subscribe_ITCase.java index 1adc7486f..c9a26ba85 100644 --- a/test/src/test/java/com/alibaba/nacos/test/naming/Subscribe_ITCase.java +++ b/test/src/test/java/com/alibaba/nacos/test/naming/Subscribe_ITCase.java @@ -24,6 +24,7 @@ import com.alibaba.nacos.api.naming.listener.EventListener; import com.alibaba.nacos.api.naming.listener.NamingEvent; import com.alibaba.nacos.api.naming.pojo.Instance; import com.alibaba.nacos.naming.NamingApp; +import com.alibaba.nacos.test.base.Params; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -38,8 +39,6 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; -import static com.alibaba.nacos.test.naming.NamingBase.*; - /** * Created by wangtong.wt on 2018/6/20. * diff --git a/test/src/test/java/com/alibaba/nacos/test/smoke/nacosSmoke_ITCase.java b/test/src/test/java/com/alibaba/nacos/test/smoke/nacosSmoke_ITCase.java index f0a0ac22f..650b4bb4b 100644 --- a/test/src/test/java/com/alibaba/nacos/test/smoke/nacosSmoke_ITCase.java +++ b/test/src/test/java/com/alibaba/nacos/test/smoke/nacosSmoke_ITCase.java @@ -37,6 +37,5 @@ public class nacosSmoke_ITCase { @Test public void testSmoke() { - logger.info("nacosSmoke_ITCase :testSmoke"); } } diff --git a/test/src/test/resources/application.properties b/test/src/test/resources/application.properties index b611435ea..2dca8441e 100644 --- a/test/src/test/resources/application.properties +++ b/test/src/test/resources/application.properties @@ -18,3 +18,22 @@ server.tomcat.accesslog.enabled=false server.tomcat.accesslog.pattern=%h %l %u %t "%r" %s %b %D # default current work dir server.tomcat.basedir= + +### The ignore urls of auth, is deprecated in 1.2.0: +nacos.security.ignore.urls=/,/error,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/v1/auth/**,/v1/console/health/**,/actuator/**,/v1/console/server/** + +### The auth system to use, currently only 'nacos' is supported: +nacos.core.auth.system.type=nacos + +### If turn on auth system: +nacos.core.auth.enabled=true + +nacos.core.auth.caching.enabled=false + +### The token expiration in seconds: +nacos.core.auth.default.token.expire.seconds=18000 + +### The default token: +nacos.core.auth.default.token.secret.key=SecretKey012345678901234567890123456789012345678901234567890123456789 + +tldSkipPatterns=derbyLocale_*.jar,jaxb-api.jar,jsr173_1.0_api.jar,jaxb1-impl.jar,activation.jar diff --git a/test/src/test/resources/schema.sql b/test/src/test/resources/schema.sql index b8419a380..5842a2267 100644 --- a/test/src/test/resources/schema.sql +++ b/test/src/test/resources/schema.sql @@ -1,4 +1,4 @@ -CREATE SCHEMA diamond AUTHORIZATION diamond; +CREATE SCHEMA nacos AUTHORIZATION nacos; CREATE TABLE config_info ( id bigint NOT NULL generated by default as identity, @@ -159,17 +159,38 @@ CREATE TABLE tenant_capacity ( constraint tenant_capacity_id_key PRIMARY KEY (id), constraint uk_tenant_id UNIQUE (tenant_id)); +CREATE TABLE tenant_info ( + id bigint NOT NULL generated by default as identity, + kp varchar(128) NOT NULL, + tenant_id varchar(128) DEFAULT '', + tenant_name varchar(128) DEFAULT '', + tenant_desc varchar(256) DEFAULT NULL, + create_source varchar(32) DEFAULT NULL, + gmt_create bigint NOT NULL, + gmt_modified bigint NOT NULL, + constraint tenant_info_id_key PRIMARY KEY (id), + constraint uk_tenant_info_kptenantid UNIQUE (kp,tenant_id)); +CREATE INDEX tenant_info_tenant_id_idx ON tenant_info(tenant_id); + CREATE TABLE users ( username varchar(50) NOT NULL PRIMARY KEY, password varchar(500) NOT NULL, - enabled boolean NOT NULL + enabled boolean NOT NULL DEFAULT true ); CREATE TABLE roles ( username varchar(50) NOT NULL, - role varchar(50) NOT NULL + role varchar(50) NOT NULL, + constraint uk_username_role UNIQUE (username,role) +); + +CREATE TABLE permissions ( + role varchar(50) NOT NULL, + resource varchar(512) NOT NULL, + action varchar(8) NOT NULL, + constraint uk_role_permission UNIQUE (role,resource,action) ); INSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE); -INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN'); +INSERT INTO roles (username, role) VALUES ('nacos', 'GLOBAL_ADMIN');