Merge pull request #12 from alibaba/develop

拉代码
This commit is contained in:
邪影oO 2020-01-14 12:39:21 +08:00 committed by GitHub
commit 89760cd7bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
150 changed files with 5559 additions and 978 deletions

View File

@ -1,8 +1,6 @@
notifications: notifications:
email: email:
recipients: recipients:
- xchaos8@126.com
- nacos_dev@linux.alibaba.com
- dev-nacos@googlegroups.com - dev-nacos@googlegroups.com
- mw_configcenter@list.alibaba-inc.com - mw_configcenter@list.alibaba-inc.com
on_success: change on_success: change
@ -30,7 +28,7 @@ before_install:
script: script:
- mvn -B clean package apache-rat:check findbugs:findbugs -Dmaven.test.skip=true - 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 - mvn clean package -Pit-test
after_success: after_success:
- mvn clean package -Pit-test - mvn clean package -Pit-test

View File

@ -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: Execute the following command in order to build the tar.gz packages and install JAR into local repository:
#build nacos #build nacos
$ mvn -Prelease-nacos -DskipTests clean install -U $ mvn -Prelease-nacos -Dmaven.test.skip=true clean install -U

View File

@ -163,7 +163,6 @@ These are only part of the companies using Nacos, for reference only. If you are
![时代光华](https://img.alicdn.com/tfs/TB1UzuyNhTpK1RjSZR0XXbEwXXa-201-86.jpg) ![时代光华](https://img.alicdn.com/tfs/TB1UzuyNhTpK1RjSZR0XXbEwXXa-201-86.jpg)
![康美](https://img.alicdn.com/tfs/TB19RCANgHqK1RjSZFPXXcwapXa-180-180.jpg) ![康美](https://img.alicdn.com/tfs/TB19RCANgHqK1RjSZFPXXcwapXa-180-180.jpg)
![环球易购](https://img.alicdn.com/tfs/TB1iCGyNb2pK1RjSZFsXXaNlXXa-143-143.jpg) ![环球易购](https://img.alicdn.com/tfs/TB1iCGyNb2pK1RjSZFsXXaNlXXa-143-143.jpg)
![海格管家](https://img.alicdn.com/tfs/TB1FNq4EwHqK1RjSZFgXXa7JXXa-232-232.jpg)
![Nepxion](https://avatars0.githubusercontent.com/u/16344119?s=200&v=4) ![Nepxion](https://avatars0.githubusercontent.com/u/16344119?s=200&v=4)
![东莞最佳拍档](https://img.alicdn.com/tfs/TB11ugsDzTpK1RjSZKPXXa3UpXa-300-300.png) ![东莞最佳拍档](https://img.alicdn.com/tfs/TB11ugsDzTpK1RjSZKPXXa3UpXa-300-300.png)
![chigua](https://img.alicdn.com/tfs/TB1aUe5EpzqK1RjSZSgXXcpAVXa-248-124.png) ![chigua](https://img.alicdn.com/tfs/TB1aUe5EpzqK1RjSZSgXXcpAVXa-248-124.png)

View File

@ -32,6 +32,10 @@ public class PropertyKeyConst {
public final static String NAMESPACE = "namespace"; 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 ACCESS_KEY = "accessKey";
public final static String SECRET_KEY = "secretKey"; public final static String SECRET_KEY = "secretKey";

View File

@ -62,6 +62,8 @@ public class Constants {
public static final String CONFIG_VERSION = "Config-Version"; public static final String CONFIG_VERSION = "Config-Version";
public static final String CONFIG_TYPE = "Config-Type";
public static final String IF_MODIFIED_SINCE = "If-Modified-Since"; public static final String IF_MODIFIED_SINCE = "If-Modified-Since";
public static final String SPACING_INTERVAL = "client-spacing-interval"; public static final String SPACING_INTERVAL = "client-spacing-interval";
@ -70,6 +72,16 @@ public class Constants {
public static final String CONFIG_CONTROLLER_PATH = BASE_PATH + "/configs"; 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 * second
*/ */
@ -169,4 +181,6 @@ public class Constants {
public static final String SNOWFLAKE_INSTANCE_ID_GENERATOR = "snowflake"; public static final String SNOWFLAKE_INSTANCE_ID_GENERATOR = "snowflake";
public static final String HTTP_PREFIX = "http";
} }

View File

@ -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.api.config;
import com.alibaba.nacos.api.config.ConfigChangeItem;
import java.util.Collection;
import java.util.Map;
/**
* ConfigChangeEvent
*
* @author rushsky518
*/
public class ConfigChangeEvent {
private Map<String, ConfigChangeItem> data;
public ConfigChangeEvent(Map<String, ConfigChangeItem> data) {
this.data = data;
}
public ConfigChangeItem getChangeItem(String key) {
return data.get(key);
}
public Collection<ConfigChangeItem> getChangeItems() {
return data.values();
}
}

View File

@ -0,0 +1,77 @@
/*
* 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.api.config;
/**
* ConfigChangeItem
*
* @author rushsky518
*/
public class ConfigChangeItem {
private String key;
private String oldValue;
private String newValue;
private PropertyChangeType type;
public ConfigChangeItem(String key, String oldValue, String newValue) {
this.key = key;
this.oldValue = oldValue;
this.newValue = newValue;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getOldValue() {
return oldValue;
}
public void setOldValue(String oldValue) {
this.oldValue = oldValue;
}
public String getNewValue() {
return newValue;
}
public void setNewValue(String newValue) {
this.newValue = newValue;
}
public PropertyChangeType getType() {
return type;
}
public void setType(PropertyChangeType type) {
this.type = type;
}
@Override
public String toString() {
return "ConfigChangeItem{" +
"key='" + key + '\'' +
", oldValue='" + oldValue + '\'' +
", newValue='" + newValue + '\'' +
", type=" + type +
'}';
}
}

View File

@ -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.api.config;
/**
* Property Change Type
*
* @author rushsky518
*/
public enum PropertyChangeType {
/** add */
ADDED,
/** modified */
MODIFIED,
/** deleted */
DELETED
}

View File

@ -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.api.config.listener;
import com.alibaba.nacos.api.config.ConfigChangeItem;
import java.io.IOException;
import java.util.Map;
/**
* ConfigChangeParser
*
* @author rushsky518
*/
public interface ConfigChangeParser {
/**
* judge type
* @param type
* @return
*/
boolean isResponsibleFor(String type);
/**
* compare old and new data
* @param oldContent
* @param newContent
* @param type
* @return
* @throws IOException
*/
Map<String, ConfigChangeItem> doParse(String oldContent, String newContent, String type) throws IOException;
}

View File

@ -16,6 +16,7 @@
package com.alibaba.nacos.api.naming.utils; package com.alibaba.nacos.api.naming.utils;
import com.alibaba.nacos.api.common.Constants; import com.alibaba.nacos.api.common.Constants;
import org.apache.commons.lang3.StringUtils;
/** /**
* @author nkorange * @author nkorange
@ -32,6 +33,9 @@ public class NamingUtils {
} }
public static String getServiceName(String serviceNameWithGroup) { public static String getServiceName(String serviceNameWithGroup) {
if (StringUtils.isBlank(serviceNameWithGroup)) {
return StringUtils.EMPTY;
}
if (!serviceNameWithGroup.contains(Constants.SERVICE_INFO_SPLITER)) { if (!serviceNameWithGroup.contains(Constants.SERVICE_INFO_SPLITER)) {
return serviceNameWithGroup; return serviceNameWithGroup;
} }
@ -39,6 +43,9 @@ public class NamingUtils {
} }
public static String getGroupName(String serviceNameWithGroup) { public static String getGroupName(String serviceNameWithGroup) {
if (StringUtils.isBlank(serviceNameWithGroup)) {
return StringUtils.EMPTY;
}
if (!serviceNameWithGroup.contains(Constants.SERVICE_INFO_SPLITER)) { if (!serviceNameWithGroup.contains(Constants.SERVICE_INFO_SPLITER)) {
return Constants.DEFAULT_GROUP; return Constants.DEFAULT_GROUP;
} }

View File

@ -116,6 +116,10 @@
<artifactId>mockito-core</artifactId> <artifactId>mockito-core</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</dependency>
</dependencies> </dependencies>

View File

@ -16,7 +16,6 @@
package com.alibaba.nacos.client.config; package com.alibaba.nacos.client.config;
import com.alibaba.nacos.api.PropertyKeyConst; import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.SystemPropertyKeyConst;
import com.alibaba.nacos.api.common.Constants; import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.api.config.ConfigService; import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener; 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.ContentUtils;
import com.alibaba.nacos.client.config.utils.ParamUtils; import com.alibaba.nacos.client.config.utils.ParamUtils;
import com.alibaba.nacos.client.utils.LogUtils; import com.alibaba.nacos.client.utils.LogUtils;
import com.alibaba.nacos.client.utils.TemplateUtils; import com.alibaba.nacos.client.utils.ParamUtil;
import com.alibaba.nacos.client.utils.TenantUtil;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -44,7 +42,6 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.Callable;
/** /**
* Config Impl * Config Impl
@ -86,34 +83,7 @@ public class NacosConfigService implements ConfigService {
} }
private void initNamespace(Properties properties) { private void initNamespace(Properties properties) {
String namespaceTmp = null; namespace = ParamUtil.parseNamespace(properties);
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<String>() {
@Override
public String call() {
return TenantUtil.getUserTenantForAcm();
}
});
namespaceTmp = TemplateUtils.stringBlankAndThenExecute(namespaceTmp, new Callable<String>() {
@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;
properties.put(PropertyKeyConst.NAMESPACE, namespace); properties.put(PropertyKeyConst.NAMESPACE, namespace);
} }
@ -170,9 +140,8 @@ public class NacosConfigService implements ConfigService {
} }
try { try {
content = worker.getServerConfig(dataId, group, tenant, timeoutMs); String[] ct = worker.getServerConfig(dataId, group, tenant, timeoutMs);
cr.setContent(ct[0]);
cr.setContent(content);
configFilterChainManager.doFilter(null, cr); configFilterChainManager.doFilter(null, cr);
content = cr.getContent(); content = cr.getContent();

View File

@ -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.ServerListManager;
import com.alibaba.nacos.client.config.impl.SpasAdapter; import com.alibaba.nacos.client.config.impl.SpasAdapter;
import com.alibaba.nacos.client.identify.STSConfig; 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.JSONUtils;
import com.alibaba.nacos.client.utils.LogUtils; import com.alibaba.nacos.client.utils.LogUtils;
import com.alibaba.nacos.client.utils.ParamUtil; import com.alibaba.nacos.client.utils.ParamUtil;
@ -43,7 +44,7 @@ import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.Callable; import java.util.concurrent.*;
/** /**
* Server Agent * Server Agent
@ -54,6 +55,12 @@ public class ServerHttpAgent implements HttpAgent {
private static final Logger LOGGER = LogUtils.logger(ServerHttpAgent.class); 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 path 相对于web应用根/开头
* @param headers * @param headers
@ -68,7 +75,7 @@ public class ServerHttpAgent implements HttpAgent {
long readTimeoutMs) throws IOException { long readTimeoutMs) throws IOException {
final long endTime = System.currentTimeMillis() + readTimeoutMs; final long endTime = System.currentTimeMillis() + readTimeoutMs;
final boolean isSSL = false; final boolean isSSL = false;
injectSecurityInfo(paramValues);
String currentServerAddr = serverListMgr.getCurrentServerAddr(); String currentServerAddr = serverListMgr.getCurrentServerAddr();
int maxRetry = this.maxRetry; int maxRetry = this.maxRetry;
@ -121,7 +128,7 @@ public class ServerHttpAgent implements HttpAgent {
long readTimeoutMs) throws IOException { long readTimeoutMs) throws IOException {
final long endTime = System.currentTimeMillis() + readTimeoutMs; final long endTime = System.currentTimeMillis() + readTimeoutMs;
boolean isSSL = false; boolean isSSL = false;
injectSecurityInfo(paramValues);
String currentServerAddr = serverListMgr.getCurrentServerAddr(); String currentServerAddr = serverListMgr.getCurrentServerAddr();
int maxRetry = this.maxRetry; int maxRetry = this.maxRetry;
@ -176,7 +183,7 @@ public class ServerHttpAgent implements HttpAgent {
long readTimeoutMs) throws IOException { long readTimeoutMs) throws IOException {
final long endTime = System.currentTimeMillis() + readTimeoutMs; final long endTime = System.currentTimeMillis() + readTimeoutMs;
boolean isSSL = false; boolean isSSL = false;
injectSecurityInfo(paramValues);
String currentServerAddr = serverListMgr.getCurrentServerAddr(); String currentServerAddr = serverListMgr.getCurrentServerAddr();
int maxRetry = this.maxRetry; int maxRetry = this.maxRetry;
@ -243,7 +250,39 @@ public class ServerHttpAgent implements HttpAgent {
public ServerHttpAgent(Properties properties) throws NacosException { public ServerHttpAgent(Properties properties) throws NacosException {
serverListMgr = new ServerListManager(properties); serverListMgr = new ServerListManager(properties);
securityProxy = new SecurityProxy(properties);
namespaceId = properties.getProperty(PropertyKeyConst.NAMESPACE);
init(properties); 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<String> params) {
ArrayList<String> 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) { private void init(Properties properties) {

View File

@ -0,0 +1,74 @@
/*
* 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.config.impl;
import com.alibaba.nacos.api.config.ConfigChangeItem;
import com.alibaba.nacos.api.config.PropertyChangeType;
import com.alibaba.nacos.api.config.listener.ConfigChangeParser;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* AbstractConfigChangeParser
*
* @author rushsky518
*/
public abstract class AbstractConfigChangeParser implements ConfigChangeParser {
private String configType;
public AbstractConfigChangeParser(String configType) {
this.configType = configType;
}
@Override
public boolean isResponsibleFor(String type) {
return this.configType.equalsIgnoreCase(type);
}
protected Map<String, ConfigChangeItem> filterChangeData(Map oldMap, Map newMap) {
Map<String, ConfigChangeItem> result = new HashMap<String, ConfigChangeItem>(16);
for (Iterator<Map.Entry<String, Object>> entryItr = oldMap.entrySet().iterator(); entryItr.hasNext();) {
Map.Entry<String, Object> e = entryItr.next();
ConfigChangeItem cci = null;
if (newMap.containsKey(e.getKey())) {
if (e.getValue().equals(newMap.get(e.getKey()))) {
continue;
}
cci = new ConfigChangeItem(e.getKey(), e.getValue().toString(), newMap.get(e.getKey()).toString());
cci.setType(PropertyChangeType.MODIFIED);
} else {
cci = new ConfigChangeItem(e.getKey(), e.getValue().toString(), null);
cci.setType(PropertyChangeType.DELETED);
}
result.put(e.getKey(), cci);
}
for (Iterator<Map.Entry<String, Object>> entryItr = newMap.entrySet().iterator(); entryItr.hasNext();) {
Map.Entry<String, Object> e = entryItr.next();
if (!oldMap.containsKey(e.getKey())) {
ConfigChangeItem cci = new ConfigChangeItem(e.getKey(), null, e.getValue().toString());
cci.setType(PropertyChangeType.ADDED);
result.put(e.getKey(), cci);
}
}
return result;
}
}

View File

@ -16,11 +16,13 @@
package com.alibaba.nacos.client.config.impl; package com.alibaba.nacos.client.config.impl;
import com.alibaba.nacos.api.common.Constants; import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.api.config.ConfigChangeEvent;
import com.alibaba.nacos.api.config.listener.AbstractSharedListener; import com.alibaba.nacos.api.config.listener.AbstractSharedListener;
import com.alibaba.nacos.api.config.listener.Listener; import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.client.config.filter.impl.ConfigFilterChainManager; import com.alibaba.nacos.client.config.filter.impl.ConfigFilterChainManager;
import com.alibaba.nacos.client.config.filter.impl.ConfigResponse; import com.alibaba.nacos.client.config.filter.impl.ConfigResponse;
import com.alibaba.nacos.client.config.listener.impl.AbstractConfigChangeListener;
import com.alibaba.nacos.client.config.utils.MD5; import com.alibaba.nacos.client.config.utils.MD5;
import com.alibaba.nacos.client.utils.LogUtils; import com.alibaba.nacos.client.utils.LogUtils;
import com.alibaba.nacos.client.utils.TenantUtil; import com.alibaba.nacos.client.utils.TenantUtil;
@ -28,6 +30,7 @@ import org.slf4j.Logger;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
/** /**
@ -59,9 +62,17 @@ public class CacheData {
return content; return content;
} }
public void setContent(String newContent) { public void setContent(String content) {
this.content = newContent; this.content = content;
this.md5 = getMd5String(content); this.md5 = getMd5String(this.content);
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
} }
/** /**
@ -74,7 +85,9 @@ public class CacheData {
if (null == listener) { if (null == listener) {
throw new IllegalArgumentException("listener is null"); throw new IllegalArgumentException("listener is null");
} }
ManagerListenerWrap wrap = new ManagerListenerWrap(listener, md5); ManagerListenerWrap wrap = (listener instanceof AbstractConfigChangeListener) ?
new ManagerListenerWrap(listener, md5, content) : new ManagerListenerWrap(listener, md5);
if (listeners.addIfAbsent(wrap)) { if (listeners.addIfAbsent(wrap)) {
LOGGER.info("[{}] [add-listener] ok, tenant={}, dataId={}, group={}, cnt={}", name, tenant, dataId, group, LOGGER.info("[{}] [add-listener] ok, tenant={}, dataId={}, group={}, cnt={}", name, tenant, dataId, group,
listeners.size()); listeners.size());
@ -158,12 +171,12 @@ public class CacheData {
void checkListenerMd5() { void checkListenerMd5() {
for (ManagerListenerWrap wrap : listeners) { for (ManagerListenerWrap wrap : listeners) {
if (!md5.equals(wrap.lastCallMd5)) { if (!md5.equals(wrap.lastCallMd5)) {
safeNotifyListener(dataId, group, content, md5, wrap); safeNotifyListener(dataId, group, content, type, md5, wrap);
} }
} }
} }
private void safeNotifyListener(final String dataId, final String group, final String content, private void safeNotifyListener(final String dataId, final String group, final String content, final String type,
final String md5, final ManagerListenerWrap listenerWrap) { final String md5, final ManagerListenerWrap listenerWrap) {
final Listener listener = listenerWrap.listener; final Listener listener = listenerWrap.listener;
@ -188,6 +201,15 @@ public class CacheData {
configFilterChainManager.doFilter(null, cr); configFilterChainManager.doFilter(null, cr);
String contentTmp = cr.getContent(); String contentTmp = cr.getContent();
listener.receiveConfigInfo(contentTmp); listener.receiveConfigInfo(contentTmp);
// compare lastContent and content
if (listener instanceof AbstractConfigChangeListener) {
Map data = ConfigChangeHandler.getInstance().parseChangeData(listenerWrap.lastContent, content, type);
ConfigChangeEvent event = new ConfigChangeEvent(data);
((AbstractConfigChangeListener)listener).receiveConfigChange(event);
listenerWrap.lastContent = content;
}
listenerWrap.lastCallMd5 = md5; listenerWrap.lastCallMd5 = md5;
LOGGER.info("[{}] [notify-ok] dataId={}, group={}, md5={}, listener={} ", name, dataId, group, md5, LOGGER.info("[{}] [notify-ok] dataId={}, group={}, md5={}, listener={} ", name, dataId, group, md5,
listener); listener);
@ -282,11 +304,13 @@ public class CacheData {
private volatile String content; private volatile String content;
private int taskId; private int taskId;
private volatile boolean isInitializing = true; private volatile boolean isInitializing = true;
private String type;
} }
class ManagerListenerWrap { class ManagerListenerWrap {
final Listener listener; final Listener listener;
String lastCallMd5 = CacheData.getMd5String(null); String lastCallMd5 = CacheData.getMd5String(null);
String lastContent = null;
ManagerListenerWrap(Listener listener) { ManagerListenerWrap(Listener listener) {
this.listener = listener; this.listener = listener;
@ -297,6 +321,12 @@ class ManagerListenerWrap {
this.lastCallMd5 = md5; this.lastCallMd5 = md5;
} }
ManagerListenerWrap(Listener listener, String md5, String lastContent) {
this.listener = listener;
this.lastCallMd5 = md5;
this.lastContent = lastContent;
}
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (null == obj || obj.getClass() != getClass()) { if (null == obj || obj.getClass() != getClass()) {

View File

@ -17,6 +17,7 @@ package com.alibaba.nacos.client.config.impl;
import com.alibaba.nacos.api.PropertyKeyConst; import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.common.Constants; import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.api.config.ConfigType;
import com.alibaba.nacos.api.config.listener.Listener; import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.client.config.common.GroupKey; import com.alibaba.nacos.client.config.common.GroupKey;
@ -46,6 +47,7 @@ import java.util.concurrent.atomic.AtomicReference;
import static com.alibaba.nacos.api.common.Constants.LINE_SEPARATOR; import static com.alibaba.nacos.api.common.Constants.LINE_SEPARATOR;
import static com.alibaba.nacos.api.common.Constants.WORD_SEPARATOR; import static com.alibaba.nacos.api.common.Constants.WORD_SEPARATOR;
import static com.alibaba.nacos.api.common.Constants.CONFIG_TYPE;
/** /**
* Longpolling * Longpolling
@ -183,8 +185,8 @@ public class ClientWorker {
cache = new CacheData(configFilterChainManager, agent.getName(), dataId, group, tenant); cache = new CacheData(configFilterChainManager, agent.getName(), dataId, group, tenant);
// fix issue # 1317 // fix issue # 1317
if (enableRemoteSyncConfig) { if (enableRemoteSyncConfig) {
String content = getServerConfig(dataId, group, tenant, 3000L); String[] ct = getServerConfig(dataId, group, tenant, 3000L);
cache.setContent(content); cache.setContent(ct[0]);
} }
} }
@ -210,8 +212,9 @@ public class ClientWorker {
return cacheMap.get().get(GroupKey.getKeyTenant(dataId, group, tenant)); return cacheMap.get().get(GroupKey.getKeyTenant(dataId, group, tenant));
} }
public String getServerConfig(String dataId, String group, String tenant, long readTimeout) public String[] getServerConfig(String dataId, String group, String tenant, long readTimeout)
throws NacosException { throws NacosException {
String[] ct = new String[2];
if (StringUtils.isBlank(group)) { if (StringUtils.isBlank(group)) {
group = Constants.DEFAULT_GROUP; group = Constants.DEFAULT_GROUP;
} }
@ -220,9 +223,9 @@ public class ClientWorker {
try { try {
List<String> params = null; List<String> params = null;
if (StringUtils.isBlank(tenant)) { if (StringUtils.isBlank(tenant)) {
params = Arrays.asList("dataId", dataId, "group", group); params = new ArrayList<String>(Arrays.asList("dataId", dataId, "group", group));
} else { } else {
params = Arrays.asList("dataId", dataId, "group", group, "tenant", tenant); params = new ArrayList<String>(Arrays.asList("dataId", dataId, "group", group, "tenant", tenant));
} }
result = agent.httpGet(Constants.CONFIG_CONTROLLER_PATH, null, params, agent.getEncode(), readTimeout); result = agent.httpGet(Constants.CONFIG_CONTROLLER_PATH, null, params, agent.getEncode(), readTimeout);
} catch (IOException e) { } catch (IOException e) {
@ -236,10 +239,16 @@ public class ClientWorker {
switch (result.code) { switch (result.code) {
case HttpURLConnection.HTTP_OK: case HttpURLConnection.HTTP_OK:
LocalConfigInfoProcessor.saveSnapshot(agent.getName(), dataId, group, tenant, result.content); LocalConfigInfoProcessor.saveSnapshot(agent.getName(), dataId, group, tenant, result.content);
return result.content; ct[0] = result.content;
if (result.headers.containsKey(CONFIG_TYPE)) {
ct[1] = result.headers.get(CONFIG_TYPE).get(0);
} else {
ct[1] = ConfigType.TEXT.getType();
}
return ct;
case HttpURLConnection.HTTP_NOT_FOUND: case HttpURLConnection.HTTP_NOT_FOUND:
LocalConfigInfoProcessor.saveSnapshot(agent.getName(), dataId, group, tenant, null); LocalConfigInfoProcessor.saveSnapshot(agent.getName(), dataId, group, tenant, null);
return null; return ct;
case HttpURLConnection.HTTP_CONFLICT: { case HttpURLConnection.HTTP_CONFLICT: {
LOGGER.error( LOGGER.error(
"[{}] [sub-server-error] get server config being modified concurrently, dataId={}, group={}, " "[{}] [sub-server-error] get server config being modified concurrently, dataId={}, group={}, "
@ -517,12 +526,15 @@ public class ClientWorker {
tenant = key[2]; tenant = key[2];
} }
try { try {
String content = getServerConfig(dataId, group, tenant, 3000L); String[] ct = getServerConfig(dataId, group, tenant, 3000L);
CacheData cache = cacheMap.get().get(GroupKey.getKeyTenant(dataId, group, tenant)); CacheData cache = cacheMap.get().get(GroupKey.getKeyTenant(dataId, group, tenant));
cache.setContent(content); cache.setContent(ct[0]);
LOGGER.info("[{}] [data-received] dataId={}, group={}, tenant={}, md5={}, content={}", if (null != ct[1]) {
cache.setType(ct[1]);
}
LOGGER.info("[{}] [data-received] dataId={}, group={}, tenant={}, md5={}, content={}, type={}",
agent.getName(), dataId, group, tenant, cache.getMd5(), agent.getName(), dataId, group, tenant, cache.getMd5(),
ContentUtils.truncateContent(content)); ContentUtils.truncateContent(ct[0]), ct[1]);
} catch (NacosException ioe) { } catch (NacosException ioe) {
String message = String.format( String message = String.format(
"[%s] [get-update] get changed config exception. dataId=%s, group=%s, tenant=%s", "[%s] [get-update] get changed config exception. dataId=%s, group=%s, tenant=%s",

View File

@ -0,0 +1,67 @@
/*
* 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.config.impl;
import com.alibaba.nacos.api.config.listener.ConfigChangeParser;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.ServiceLoader;
import java.util.Map;
import java.util.Iterator;
/**
* ConfigChangeHandler
*
* @author rushsky518
*/
public class ConfigChangeHandler {
private static class ConfigChangeHandlerHolder {
private final static ConfigChangeHandler INSTANCE = new ConfigChangeHandler();
}
private ConfigChangeHandler() {
this.parserList = new LinkedList<ConfigChangeParser>();
ServiceLoader<ConfigChangeParser> loader = ServiceLoader.load(ConfigChangeParser.class);
Iterator<ConfigChangeParser> itr = loader.iterator();
while (itr.hasNext()) {
this.parserList.add(itr.next());
}
this.parserList.add(new PropertiesChangeParser());
this.parserList.add(new YmlChangeParser());
}
public static ConfigChangeHandler getInstance() {
return ConfigChangeHandlerHolder.INSTANCE;
}
public Map parseChangeData(String oldContent, String newContent, String type) throws IOException {
for (ConfigChangeParser changeParser: this.parserList) {
if (changeParser.isResponsibleFor(type)) {
return changeParser.doParse(oldContent, newContent, type);
}
}
return Collections.emptyMap();
}
private List<ConfigChangeParser> parserList;
}

View File

@ -29,10 +29,7 @@ import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.util.ArrayList; import java.util.*;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/** /**
* Http tool * Http tool

View File

@ -0,0 +1,50 @@
/*
* 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.config.impl;
import com.alibaba.nacos.api.config.ConfigChangeItem;
import com.alibaba.nacos.client.utils.StringUtils;
import java.io.IOException;
import java.io.StringReader;
import java.util.Map;
import java.util.Properties;
/**
* PropertiesChangeParser
*
* @author rushsky518
*/
public class PropertiesChangeParser extends AbstractConfigChangeParser {
public PropertiesChangeParser() {
super("properties");
}
@Override
public Map<String, ConfigChangeItem> doParse(String oldContent, String newContent, String type) throws IOException {
Properties oldProps = new Properties();
Properties newProps = new Properties();
if (StringUtils.isNotBlank(oldContent)) {
oldProps.load(new StringReader(oldContent));
}
if (StringUtils.isNotBlank(newContent)) {
newProps.load(new StringReader(newContent));
}
return filterChangeData(oldProps, newProps);
}
}

View File

@ -234,6 +234,10 @@ public class ServerListManager {
isStarted = true; isStarted = true;
} }
public List<String> getServerUrls() {
return serverUrls;
}
Iterator<String> iterator() { Iterator<String> iterator() {
if (serverUrls.isEmpty()) { if (serverUrls.isEmpty()) {
LOGGER.error("[{}] [iterator-serverlist] No server address defined!", name); LOGGER.error("[{}] [iterator-serverlist] No server address defined!", name);

View File

@ -0,0 +1,90 @@
/*
* 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.config.impl;
import com.alibaba.nacos.api.config.ConfigChangeItem;
import com.alibaba.nacos.client.utils.StringUtils;
import org.yaml.snakeyaml.Yaml;
import java.util.*;
/**
* YmlChangeParser
*
* @author rushsky518
*/
public class YmlChangeParser extends AbstractConfigChangeParser {
public YmlChangeParser() {
super("yaml");
}
@Override
public Map<String, ConfigChangeItem> doParse(String oldContent, String newContent, String type) {
Map<String, Object> oldMap = Collections.emptyMap();
Map<String, Object> newMap = Collections.emptyMap();
if (StringUtils.isNotBlank(oldContent)) {
oldMap = (new Yaml()).load(oldContent);
oldMap = getFlattenedMap(oldMap);
}
if (StringUtils.isNotBlank(newContent)) {
newMap = (new Yaml()).load(newContent);
newMap = getFlattenedMap(newMap);
}
return filterChangeData(oldMap, newMap);
}
private final Map<String, Object> getFlattenedMap(Map<String, Object> source) {
Map<String, Object> result = new LinkedHashMap<String, Object>(128);
buildFlattenedMap(result, source, null);
return result;
}
private void buildFlattenedMap(Map<String, Object> result, Map<String, Object> source, String path) {
for (Iterator<Map.Entry<String, Object>> itr = source.entrySet().iterator(); itr.hasNext(); ) {
Map.Entry<String, Object> e = itr.next();
String key = e.getKey();
if (StringUtils.isNotBlank(path)) {
if (e.getKey().startsWith("[")) {
key = path + key;
} else {
key = path + '.' + key;
}
}
if (e.getValue() instanceof String) {
result.put(key, e.getValue());
} else if (e.getValue() instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) e.getValue();
buildFlattenedMap(result, map, key);
} else if (e.getValue() instanceof Collection) {
@SuppressWarnings("unchecked")
Collection<Object> collection = (Collection<Object>) e.getValue();
if (collection.isEmpty()) {
result.put(key, "");
} else {
int count = 0;
for (Object object : collection) {
buildFlattenedMap(result, Collections.singletonMap("[" + (count++) + "]", object), key);
}
}
} else {
result.put(key, (e.getValue() != null ? e.getValue() : ""));
}
}
}
}

View File

@ -0,0 +1,36 @@
/*
* 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.config.listener.impl;
import com.alibaba.nacos.api.config.listener.AbstractListener;
import com.alibaba.nacos.api.config.ConfigChangeEvent;
/**
* AbstractConfigChangeListener
*
* @author rushsky518
*/
public abstract class AbstractConfigChangeListener extends AbstractListener {
/**
* handle config change
* @param event
*/
public abstract void receiveConfigChange(final ConfigChangeEvent event);
@Override
public void receiveConfigInfo(final String configInfo) {}
}

View File

@ -53,7 +53,7 @@ public final class CredentialService implements SpasCredentialLoader {
} }
public static CredentialService getInstance(String appName) { 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); CredentialService instance = instances.get(key);
if (instance == null) { if (instance == null) {
instance = new CredentialService(appName); instance = new CredentialService(appName);
@ -70,7 +70,7 @@ public final class CredentialService implements SpasCredentialLoader {
} }
public static CredentialService freeInstance(String appName) { 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); CredentialService instance = instances.remove(key);
if (instance != null) { if (instance != null) {
instance.free(); instance.free();

View File

@ -94,7 +94,7 @@ public class CredentialWatcher {
private void loadCredential(boolean init) { private void loadCredential(boolean init) {
boolean logWarn = init; boolean logWarn = init;
if (propertyPath == null) { if (propertyPath == null) {
URL url = ClassLoader.getSystemResource(Constants.PROPERTIES_FILENAME); URL url = ClassLoader.getSystemResource(IdentifyConstants.PROPERTIES_FILENAME);
if (url != null) { if (url != null) {
propertyPath = url.getPath(); propertyPath = url.getPath();
} }
@ -105,7 +105,7 @@ public class CredentialWatcher {
propertyPath = value; propertyPath = value;
} }
if (propertyPath == null || propertyPath.isEmpty()) { if (propertyPath == null || propertyPath.isEmpty()) {
propertyPath = Constants.CREDENTIAL_PATH + (appName == null ? Constants.CREDENTIAL_DEFAULT propertyPath = IdentifyConstants.CREDENTIAL_PATH + (appName == null ? IdentifyConstants.CREDENTIAL_DEFAULT
: appName); : appName);
} else { } else {
if (logWarn) { if (logWarn) {
@ -115,7 +115,7 @@ public class CredentialWatcher {
} else { } else {
if (logWarn) { if (logWarn) {
SpasLogger.info("[{}] Load credential file from classpath: {}", appName, SpasLogger.info("[{}] Load credential file from classpath: {}", appName,
Constants.PROPERTIES_FILENAME); IdentifyConstants.PROPERTIES_FILENAME);
} }
} }
} }
@ -125,13 +125,13 @@ public class CredentialWatcher {
try { try {
propertiesIS = new FileInputStream(propertyPath); propertiesIS = new FileInputStream(propertyPath);
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
if (appName != null && !appName.equals(Constants.CREDENTIAL_DEFAULT) && propertyPath.equals( if (appName != null && !appName.equals(IdentifyConstants.CREDENTIAL_DEFAULT) && propertyPath.equals(
Constants.CREDENTIAL_PATH + appName)) { IdentifyConstants.CREDENTIAL_PATH + appName)) {
propertyPath = Constants.CREDENTIAL_PATH + Constants.CREDENTIAL_DEFAULT; propertyPath = IdentifyConstants.CREDENTIAL_PATH + IdentifyConstants.CREDENTIAL_DEFAULT;
continue; continue;
} }
if (!Constants.DOCKER_CREDENTIAL_PATH.equals(propertyPath)) { if (!IdentifyConstants.DOCKER_CREDENTIAL_PATH.equals(propertyPath)) {
propertyPath = Constants.DOCKER_CREDENTIAL_PATH; propertyPath = IdentifyConstants.DOCKER_CREDENTIAL_PATH;
continue; continue;
} }
} }
@ -143,8 +143,8 @@ public class CredentialWatcher {
String tenantId = null; String tenantId = null;
if (propertiesIS == null) { if (propertiesIS == null) {
propertyPath = null; propertyPath = null;
accessKey = System.getenv(Constants.ENV_ACCESS_KEY); accessKey = System.getenv(IdentifyConstants.ENV_ACCESS_KEY);
secretKey = System.getenv(Constants.ENV_SECRET_KEY); secretKey = System.getenv(IdentifyConstants.ENV_SECRET_KEY);
if (accessKey == null && secretKey == null) { if (accessKey == null && secretKey == null) {
if (logWarn) { if (logWarn) {
SpasLogger.info("{} No credential found", appName); SpasLogger.info("{} No credential found", appName);
@ -173,26 +173,26 @@ public class CredentialWatcher {
SpasLogger.info("[{}] Load credential file {}", appName, propertyPath); SpasLogger.info("[{}] Load credential file {}", appName, propertyPath);
} }
if (!Constants.DOCKER_CREDENTIAL_PATH.equals(propertyPath)) { if (!IdentifyConstants.DOCKER_CREDENTIAL_PATH.equals(propertyPath)) {
if (properties.containsKey(Constants.ACCESS_KEY)) { if (properties.containsKey(IdentifyConstants.ACCESS_KEY)) {
accessKey = properties.getProperty(Constants.ACCESS_KEY); accessKey = properties.getProperty(IdentifyConstants.ACCESS_KEY);
} }
if (properties.containsKey(Constants.SECRET_KEY)) { if (properties.containsKey(IdentifyConstants.SECRET_KEY)) {
secretKey = properties.getProperty(Constants.SECRET_KEY); secretKey = properties.getProperty(IdentifyConstants.SECRET_KEY);
} }
if (properties.containsKey(Constants.TENANT_ID)) { if (properties.containsKey(IdentifyConstants.TENANT_ID)) {
tenantId = properties.getProperty(Constants.TENANT_ID); tenantId = properties.getProperty(IdentifyConstants.TENANT_ID);
} }
} else { } else {
if (properties.containsKey(Constants.DOCKER_ACCESS_KEY)) { if (properties.containsKey(IdentifyConstants.DOCKER_ACCESS_KEY)) {
accessKey = properties.getProperty(Constants.DOCKER_ACCESS_KEY); accessKey = properties.getProperty(IdentifyConstants.DOCKER_ACCESS_KEY);
} }
if (properties.containsKey(Constants.DOCKER_SECRET_KEY)) { if (properties.containsKey(IdentifyConstants.DOCKER_SECRET_KEY)) {
secretKey = properties.getProperty(Constants.DOCKER_SECRET_KEY); secretKey = properties.getProperty(IdentifyConstants.DOCKER_SECRET_KEY);
} }
if (properties.containsKey(Constants.DOCKER_TENANT_ID)) { if (properties.containsKey(IdentifyConstants.DOCKER_TENANT_ID)) {
tenantId = properties.getProperty(Constants.DOCKER_TENANT_ID); tenantId = properties.getProperty(IdentifyConstants.DOCKER_TENANT_ID);
} }
} }
} }
@ -211,7 +211,7 @@ public class CredentialWatcher {
Credentials credential = new Credentials(accessKey, secretKey, tenantId); Credentials credential = new Credentials(accessKey, secretKey, tenantId);
if (!credential.valid()) { if (!credential.valid()) {
SpasLogger.warn("[1] Credential file missing required property {} Credential file missing {} or {}", 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; propertyPath = null;
// return; // return;
} }

View File

@ -20,7 +20,7 @@ package com.alibaba.nacos.client.identify;
* *
* @author Nacos * @author Nacos
*/ */
public class Constants { public class IdentifyConstants {
public static final String ACCESS_KEY = "accessKey"; public static final String ACCESS_KEY = "accessKey";
public static final String SECRET_KEY = "secretKey"; public static final String SECRET_KEY = "secretKey";

View File

@ -63,9 +63,7 @@ public class NacosNamingMaintainService implements NamingMaintainService {
namespace = InitUtils.initNamespaceForNaming(properties); namespace = InitUtils.initNamespaceForNaming(properties);
initServerAddr(properties); initServerAddr(properties);
InitUtils.initWebRootContext(); InitUtils.initWebRootContext();
serverProxy = new NamingProxy(namespace, endpoint, serverList, properties);
serverProxy = new NamingProxy(namespace, endpoint, serverList);
serverProxy.setProperties(properties);
} }
private void initServerAddr(Properties properties) { private void initServerAddr(Properties properties) {

View File

@ -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.CollectionUtils;
import com.alibaba.nacos.client.naming.utils.InitUtils; import com.alibaba.nacos.client.naming.utils.InitUtils;
import com.alibaba.nacos.client.naming.utils.UtilAndComs; 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.BooleanUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils; import org.apache.commons.lang3.math.NumberUtils;
@ -45,15 +46,15 @@ import java.util.Properties;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
* Nacos Naming Service
*
* @author nkorange * @author nkorange
*/ */
@SuppressWarnings("PMD.ServiceOrDaoClassShouldEndWithImplRule") @SuppressWarnings("PMD.ServiceOrDaoClassShouldEndWithImplRule")
public class NacosNamingService implements NamingService { 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; private String namespace;
@ -92,8 +93,7 @@ public class NacosNamingService implements NamingService {
initLogName(properties); initLogName(properties);
eventDispatcher = new EventDispatcher(); eventDispatcher = new EventDispatcher();
serverProxy = new NamingProxy(namespace, endpoint, serverList); serverProxy = new NamingProxy(namespace, endpoint, serverList, properties);
serverProxy.setProperties(properties);
beatReactor = new BeatReactor(serverProxy, initClientBeatThreadCount(properties)); beatReactor = new BeatReactor(serverProxy, initClientBeatThreadCount(properties));
hostReactor = new HostReactor(eventDispatcher, serverProxy, cacheDir, isLoadCacheAtStart(properties), hostReactor = new HostReactor(eventDispatcher, serverProxy, cacheDir, isLoadCacheAtStart(properties),
initPollingThreadCount(properties)); initPollingThreadCount(properties));

View File

@ -45,9 +45,6 @@ public class HttpClient {
private static final boolean ENABLE_HTTPS = Boolean private static final boolean ENABLE_HTTPS = Boolean
.getBoolean("com.alibaba.nacos.client.naming.tls.enable"); .getBoolean("com.alibaba.nacos.client.naming.tls.enable");
private static final String POST = "POST";
private static final String PUT = "PUT";
static { static {
// limit max redirection // limit max redirection
System.setProperty("http.maxRedirects", "5"); System.setProperty("http.maxRedirects", "5");
@ -80,7 +77,6 @@ public class HttpClient {
conn.setRequestMethod(method); conn.setRequestMethod(method);
conn.setDoOutput(true); conn.setDoOutput(true);
if (StringUtils.isNotBlank(body)) { if (StringUtils.isNotBlank(body)) {
// fix: apache http nio framework must set some content to request body
byte[] b = body.getBytes(); byte[] b = body.getBytes();
conn.setRequestProperty("Content-Length", String.valueOf(b.length)); conn.setRequestProperty("Content-Length", String.valueOf(b.length));
conn.getOutputStream().write(b, 0, b.length); conn.getOutputStream().write(b, 0, b.length);
@ -88,7 +84,9 @@ public class HttpClient {
conn.getOutputStream().close(); conn.getOutputStream().close();
} }
conn.connect(); conn.connect();
NAMING_LOGGER.debug("Request from server: " + url); if (NAMING_LOGGER.isDebugEnabled()) {
NAMING_LOGGER.debug("Request from server: " + url);
}
return getResult(conn); return getResult(conn);
} catch (Exception e) { } catch (Exception e) {
try { try {

View File

@ -20,6 +20,7 @@ import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference; import com.alibaba.fastjson.TypeReference;
import com.alibaba.nacos.api.PropertyKeyConst; import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.SystemPropertyKeyConst; 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.exception.NacosException;
import com.alibaba.nacos.api.naming.CommonParams; import com.alibaba.nacos.api.naming.CommonParams;
import com.alibaba.nacos.api.naming.pojo.Instance; 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.NetUtils;
import com.alibaba.nacos.client.naming.utils.SignUtil; import com.alibaba.nacos.client.naming.utils.SignUtil;
import com.alibaba.nacos.client.naming.utils.UtilAndComs; 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.AppNameUtils;
import com.alibaba.nacos.client.utils.TemplateUtils; import com.alibaba.nacos.client.utils.TemplateUtils;
import com.alibaba.nacos.common.constant.HttpHeaderConsts; import com.alibaba.nacos.common.constant.HttpHeaderConsts;
@ -71,14 +73,21 @@ public class NamingProxy {
private List<String> serversFromEndpoint = new ArrayList<String>(); private List<String> serversFromEndpoint = new ArrayList<String>();
private SecurityProxy securityProxy;
private long lastSrvRefTime = 0L; private long lastSrvRefTime = 0L;
private long vipSrvRefInterMillis = TimeUnit.SECONDS.toMillis(30); private long vipSrvRefInterMillis = TimeUnit.SECONDS.toMillis(30);
private long securityInfoRefreshIntervalMills = TimeUnit.SECONDS.toMillis(5);
private Properties properties; 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.namespaceId = namespaceId;
this.endpoint = endpoint; this.endpoint = endpoint;
if (StringUtils.isNotEmpty(serverList)) { if (StringUtils.isNotEmpty(serverList)) {
@ -88,19 +97,16 @@ public class NamingProxy {
} }
} }
initRefreshSrvIfNeed(); initRefreshTask();
} }
private void initRefreshSrvIfNeed() { private void initRefreshTask() {
if (StringUtils.isEmpty(endpoint)) {
return;
}
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1, new ThreadFactory() { ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(2, new ThreadFactory() {
@Override @Override
public Thread newThread(Runnable r) { public Thread newThread(Runnable r) {
Thread t = new Thread(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); t.setDaemon(true);
return t; return t;
} }
@ -113,6 +119,14 @@ public class NamingProxy {
} }
}, 0, vipSrvRefInterMillis, TimeUnit.MILLISECONDS); }, 0, vipSrvRefInterMillis, TimeUnit.MILLISECONDS);
executorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
securityProxy.login(getServerList());
}
}, 0, securityInfoRefreshIntervalMills, TimeUnit.MILLISECONDS);
refreshSrvIfNeed(); refreshSrvIfNeed();
} }
@ -377,13 +391,15 @@ public class NamingProxy {
} }
public String reqAPI(String api, Map<String, String> params, String body, String method) throws NacosException { public String reqAPI(String api, Map<String, String> params, String body, String method) throws NacosException {
return reqAPI(api, params, body, getServerList(), method);
}
private List<String> getServerList() {
List<String> snapshot = serversFromEndpoint; List<String> snapshot = serversFromEndpoint;
if (!CollectionUtils.isEmpty(serverList)) { if (!CollectionUtils.isEmpty(serverList)) {
snapshot = serverList; snapshot = serverList;
} }
return snapshot;
return reqAPI(api, params, body, snapshot, method);
} }
public String callServer(String api, Map<String, String> params, String body, String curServer) throws NacosException { public String callServer(String api, Map<String, String> params, String body, String curServer) throws NacosException {
@ -394,7 +410,7 @@ public class NamingProxy {
throws NacosException { throws NacosException {
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
long end = 0; long end = 0;
checkSignature(params); injectSecurityInfo(params);
List<String> headers = builderHeaders(); List<String> headers = builderHeaders();
String url; String url;
@ -456,7 +472,7 @@ public class NamingProxy {
if (StringUtils.isNotBlank(nacosDomain)) { if (StringUtils.isNotBlank(nacosDomain)) {
for (int i = 0; i < UtilAndComs.REQUEST_DOMAIN_RETRY_COUNT; i++) { for (int i = 0; i < UtilAndComs.REQUEST_DOMAIN_RETRY_COUNT; i++) {
try { try {
return callServer(api, params, body, nacosDomain); return callServer(api, params, body, nacosDomain, method);
} catch (NacosException e) { } catch (NacosException e) {
exception = e; exception = e;
if (NAMING_LOGGER.isDebugEnabled()) { if (NAMING_LOGGER.isDebugEnabled()) {
@ -474,22 +490,27 @@ public class NamingProxy {
} }
private void checkSignature(Map<String, String> params) { private void injectSecurityInfo(Map<String, String> 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 ak = getAccessKey();
String sk = getSecretKey(); String sk = getSecretKey();
params.put("app", AppNameUtils.getAppName()); params.put("app", AppNameUtils.getAppName());
if (StringUtils.isEmpty(ak) && StringUtils.isEmpty(sk)) { if (StringUtils.isNotBlank(ak) && StringUtils.isNotBlank(sk)) {
return; try {
} String signData = getSignData(params.get("serviceName"));
String signature = SignUtil.sign(signData, sk);
try { params.put("signature", signature);
String signData = getSignData(params.get("serviceName")); params.put("data", signData);
String signature = SignUtil.sign(signData, sk); params.put("ak", ak);
params.put("signature", signature); } catch (Exception e) {
params.put("data", signData); NAMING_LOGGER.error("inject ak/sk failed.", e);
params.put("ak", ak); }
} catch (Exception e) {
e.printStackTrace();
} }
} }

View File

@ -20,7 +20,7 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
/** /**
* @author nkorange * @author alibaba
*/ */
public class Chooser<K, T> { public class Chooser<K, T> {

View File

@ -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<String> 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<String>(2),
new HashMap<String, String>(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;
}
}

View File

@ -16,6 +16,8 @@
package com.alibaba.nacos.client.utils; package com.alibaba.nacos.client.utils;
import com.alibaba.nacos.api.PropertyKeyConst; 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 com.alibaba.nacos.client.config.impl.HttpSimpleClient;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -152,6 +154,36 @@ public class ParamUtil {
ParamUtil.defaultNodesPath = defaultNodesPath; 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<String>() {
@Override
public String call() {
return TenantUtil.getUserTenantForAcm();
}
});
namespaceTmp = TemplateUtils.stringBlankAndThenExecute(namespaceTmp, new Callable<String>() {
@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) { public static String parsingEndpointRule(String endpointUrl) {
// 配置文件中输入的话 ENV 中的优先 // 配置文件中输入的话 ENV 中的优先

View File

@ -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.client.config.listener.impl;
import com.alibaba.nacos.api.config.ConfigChangeItem;
import com.alibaba.nacos.client.config.impl.ConfigChangeHandler;
import org.junit.Assert;
import org.junit.Test;
import java.io.IOException;
import java.util.Map;
public class ConfigChangeHandlerTest {
@Test
public void testParseProperties() throws IOException {
Map properties = ConfigChangeHandler.getInstance().parseChangeData("", "app.name = nacos", "properties");
Assert.assertEquals("nacos", ((ConfigChangeItem)properties.get("app.name")).getNewValue());
}
@Test
public void testParseYaml() throws IOException {
Map properties = ConfigChangeHandler.getInstance().parseChangeData("", "app:\n name: nacos", "yaml");
Assert.assertEquals("nacos", ((ConfigChangeItem)properties.get("app.name")).getNewValue());
}
}

View File

@ -0,0 +1,58 @@
/*
* 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.config.listener.impl;
import com.alibaba.nacos.api.config.ConfigChangeItem;
import com.alibaba.nacos.client.config.impl.PropertiesChangeParser;
import org.junit.Assert;
import org.junit.Test;
import java.io.IOException;
import java.util.Map;
public class PropertiesChangeParserTest {
private PropertiesChangeParser parser = new PropertiesChangeParser();
private final String type = "properties";
@Test
public void testType() {
Assert.assertEquals(true, parser.isResponsibleFor(type));
}
@Test
public void testAddKey() throws IOException {
Map<String, ConfigChangeItem> map = parser.doParse("", "app.name = nacos", type);
Assert.assertEquals(null, map.get("app.name").getOldValue());
Assert.assertEquals("nacos", map.get("app.name").getNewValue());
}
@Test
public void testRemoveKey() throws IOException {
Map<String, ConfigChangeItem> map = parser.doParse("app.name = nacos", "", type);
Assert.assertEquals("nacos", map.get("app.name").getOldValue());
Assert.assertEquals(null, map.get("app.name").getNewValue());
}
@Test
public void testModifyKey() throws IOException {
Map<String, ConfigChangeItem> map = parser.doParse("app.name = rocketMQ", "app.name = nacos", type);
Assert.assertEquals("rocketMQ", map.get("app.name").getOldValue());
Assert.assertEquals("nacos", map.get("app.name").getNewValue());
}
}

View File

@ -0,0 +1,57 @@
/*
* 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.config.listener.impl;
import com.alibaba.nacos.api.config.ConfigChangeItem;
import com.alibaba.nacos.client.config.impl.YmlChangeParser;
import org.junit.Assert;
import org.junit.Test;
import java.io.IOException;
import java.util.Map;
public class YmlChangeParserTest {
private YmlChangeParser parser = new YmlChangeParser();
private final String type = "yaml";
@Test
public void testType() {
Assert.assertEquals(true, parser.isResponsibleFor(type));
}
@Test
public void testAddKey() throws IOException {
Map<String, ConfigChangeItem> map = parser.doParse("", "app:\n name: nacos", type);
Assert.assertEquals(null, map.get("app.name").getOldValue());
Assert.assertEquals("nacos", map.get("app.name").getNewValue());
}
@Test
public void testRemoveKey() throws IOException {
Map<String, ConfigChangeItem> map = parser.doParse("app:\n name: nacos", "", type);
Assert.assertEquals("nacos", map.get("app.name").getOldValue());
Assert.assertEquals(null, map.get("app.name").getNewValue());
}
@Test
public void testModifyKey() throws IOException {
Map<String, ConfigChangeItem> map = parser.doParse("app:\n name: rocketMQ", "app:\n name: nacos", type);
Assert.assertEquals("rocketMQ", map.get("app.name").getOldValue());
Assert.assertEquals("nacos", map.get("app.name").getNewValue());
}
}

View File

@ -19,8 +19,6 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.EnableScheduling;
import java.net.UnknownHostException;
/** /**
* Config main * Config main
* *
@ -30,7 +28,7 @@ import java.net.UnknownHostException;
@SpringBootApplication @SpringBootApplication
public class Config { public class Config {
public static void main(String[] args) throws UnknownHostException { public static void main(String[] args) {
SpringApplication.run(Config.class, args); SpringApplication.run(Config.class, args);
} }
} }

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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<PermissionInfo> getPermissions(String role, int pageNo, int pageSize) {
PaginationHelper<PermissionInfo> 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<PermissionInfo> pageInfo = helper.fetchPage(jt, sqlCountRows
+ where, sqlFetchRows + where, new ArrayList<String>().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<PermissionInfo> {
@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();
}

View File

@ -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;
}
}

View File

@ -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<RoleInfo> getRoles(int pageNo, int pageSize) {
PaginationHelper<RoleInfo> 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<RoleInfo> pageInfo = helper.fetchPage(jt, sqlCountRows
+ where, sqlFetchRows + where, new ArrayList<String>().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<RoleInfo> getRolesByUserName(String username, int pageNo, int pageSize) {
PaginationHelper<RoleInfo> 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<String>().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<RoleInfo> {
@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();
}

View File

@ -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<User> getUsers(int pageNo, int pageSize) {
PaginationHelper<User> helper = new PaginationHelper<>();
String sqlCountRows = "select count(*) from users where ";
String sqlFetchRows
= "select username,password from users where ";
String where = " 1=1 ";
try {
Page<User> pageInfo = helper.fetchPage(jt, sqlCountRows
+ where, sqlFetchRows + where, new ArrayList<String>().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<User> {
@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();
}

View File

@ -15,8 +15,10 @@
*/ */
package com.alibaba.nacos.config.server.controller; 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.config.server.constant.Constants;
import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.config.server.controller.parameters.SameNamespaceCloneConfigBean;
import com.alibaba.nacos.config.server.model.*; import com.alibaba.nacos.config.server.model.*;
import com.alibaba.nacos.config.server.result.ResultBuilder; import com.alibaba.nacos.config.server.result.ResultBuilder;
import com.alibaba.nacos.config.server.result.code.ResultCodeEnum; import com.alibaba.nacos.config.server.result.code.ResultCodeEnum;
@ -27,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.service.trace.ConfigTraceService;
import com.alibaba.nacos.config.server.utils.*; import com.alibaba.nacos.config.server.utils.*;
import com.alibaba.nacos.config.server.utils.event.EventDispatcher; 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.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils; import org.apache.commons.lang3.time.DateFormatUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -46,6 +50,7 @@ import java.io.IOException;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.util.*; import java.util.*;
import java.util.stream.Collectors;
import static com.alibaba.nacos.core.utils.SystemUtils.LOCAL_IP; import static com.alibaba.nacos.core.utils.SystemUtils.LOCAL_IP;
@ -66,7 +71,7 @@ public class ConfigController {
private static final String EXPORT_CONFIG_FILE_NAME_EXT = ".zip"; private static final String EXPORT_CONFIG_FILE_NAME_EXT = ".zip";
private static final String EXPORT_CONFIG_FILE_NAME_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; private static final String EXPORT_CONFIG_FILE_NAME_DATE_FORMAT = "yyyyMMddHHmmss";
private final ConfigServletInner inner; private final ConfigServletInner inner;
@ -88,6 +93,7 @@ public class ConfigController {
* @throws NacosException * @throws NacosException
*/ */
@PostMapping @PostMapping
@Secured(action = ActionTypes.WRITE, parser = ConfigResourceParser.class)
public Boolean publishConfig(HttpServletRequest request, HttpServletResponse response, public Boolean publishConfig(HttpServletRequest request, HttpServletResponse response,
@RequestParam("dataId") String dataId, @RequestParam("group") String group, @RequestParam("dataId") String dataId, @RequestParam("group") String group,
@RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY) @RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY)
@ -164,6 +170,7 @@ public class ConfigController {
* @throws NacosException * @throws NacosException
*/ */
@GetMapping @GetMapping
@Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class)
public void getConfig(HttpServletRequest request, HttpServletResponse response, public void getConfig(HttpServletRequest request, HttpServletResponse response,
@RequestParam("dataId") String dataId, @RequestParam("group") String group, @RequestParam("dataId") String dataId, @RequestParam("group") String group,
@RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY) @RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY)
@ -184,6 +191,7 @@ public class ConfigController {
* @throws NacosException * @throws NacosException
*/ */
@GetMapping(params = "show=all") @GetMapping(params = "show=all")
@Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class)
public ConfigAllInfo detailConfigInfo(HttpServletRequest request, HttpServletResponse response, public ConfigAllInfo detailConfigInfo(HttpServletRequest request, HttpServletResponse response,
@RequestParam("dataId") String dataId, @RequestParam("group") String group, @RequestParam("dataId") String dataId, @RequestParam("group") String group,
@RequestParam(value = "tenant", required = false, @RequestParam(value = "tenant", required = false,
@ -200,6 +208,7 @@ public class ConfigController {
* @throws NacosException * @throws NacosException
*/ */
@DeleteMapping @DeleteMapping
@Secured(action = ActionTypes.WRITE, parser = ConfigResourceParser.class)
public Boolean deleteConfig(HttpServletRequest request, HttpServletResponse response, public Boolean deleteConfig(HttpServletRequest request, HttpServletResponse response,
@RequestParam("dataId") String dataId, // @RequestParam("dataId") String dataId, //
@RequestParam("group") String group, // @RequestParam("group") String group, //
@ -229,6 +238,7 @@ public class ConfigController {
* @Param [request, response, dataId, group, tenant, tag] * @Param [request, response, dataId, group, tenant, tag]
*/ */
@DeleteMapping(params = "delType=ids") @DeleteMapping(params = "delType=ids")
@Secured(action = ActionTypes.WRITE, parser = ConfigResourceParser.class)
public RestResult<Boolean> deleteConfigs(HttpServletRequest request, HttpServletResponse response, public RestResult<Boolean> deleteConfigs(HttpServletRequest request, HttpServletResponse response,
@RequestParam(value = "ids") List<Long> ids) { @RequestParam(value = "ids") List<Long> ids) {
String clientIp = RequestUtil.getRemoteIp(request); String clientIp = RequestUtil.getRemoteIp(request);
@ -247,6 +257,7 @@ public class ConfigController {
} }
@GetMapping("/catalog") @GetMapping("/catalog")
@Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class)
public RestResult<ConfigAdvanceInfo> getConfigAdvanceInfo(@RequestParam("dataId") String dataId, public RestResult<ConfigAdvanceInfo> getConfigAdvanceInfo(@RequestParam("dataId") String dataId,
@RequestParam("group") String group, @RequestParam("group") String group,
@RequestParam(value = "tenant", required = false, @RequestParam(value = "tenant", required = false,
@ -262,6 +273,7 @@ public class ConfigController {
* 比较MD5 * 比较MD5
*/ */
@PostMapping("/listener") @PostMapping("/listener")
@Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class)
public void listener(HttpServletRequest request, HttpServletResponse response) public void listener(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { throws ServletException, IOException {
request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true); request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true);
@ -287,6 +299,7 @@ public class ConfigController {
* 订阅改配置的客户端信息 * 订阅改配置的客户端信息
*/ */
@GetMapping("/listener") @GetMapping("/listener")
@Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class)
public GroupkeyListenserStatus getListeners(@RequestParam("dataId") String dataId, public GroupkeyListenserStatus getListeners(@RequestParam("dataId") String dataId,
@RequestParam("group") String group, @RequestParam("group") String group,
@RequestParam(value = "tenant", required = false) String tenant, @RequestParam(value = "tenant", required = false) String tenant,
@ -306,6 +319,7 @@ public class ConfigController {
* 查询配置信息返回JSON格式 * 查询配置信息返回JSON格式
*/ */
@GetMapping(params = "search=accurate") @GetMapping(params = "search=accurate")
@Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class)
public Page<ConfigInfo> searchConfig(@RequestParam("dataId") String dataId, public Page<ConfigInfo> searchConfig(@RequestParam("dataId") String dataId,
@RequestParam("group") String group, @RequestParam("group") String group,
@RequestParam(value = "appName", required = false) String appName, @RequestParam(value = "appName", required = false) String appName,
@ -335,6 +349,7 @@ public class ConfigController {
* 模糊查询配置信息不允许只根据内容模糊查询即dataId和group都为NULL但content不是NULL这种情况下返回所有配置 * 模糊查询配置信息不允许只根据内容模糊查询即dataId和group都为NULL但content不是NULL这种情况下返回所有配置
*/ */
@GetMapping(params = "search=blur") @GetMapping(params = "search=blur")
@Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class)
public Page<ConfigInfo> fuzzySearchConfig(@RequestParam("dataId") String dataId, public Page<ConfigInfo> fuzzySearchConfig(@RequestParam("dataId") String dataId,
@RequestParam("group") String group, @RequestParam("group") String group,
@RequestParam(value = "appName", required = false) String appName, @RequestParam(value = "appName", required = false) String appName,
@ -361,6 +376,7 @@ public class ConfigController {
} }
@DeleteMapping(params = "beta=true") @DeleteMapping(params = "beta=true")
@Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class)
public RestResult<Boolean> stopBeta(@RequestParam(value = "dataId") String dataId, public RestResult<Boolean> stopBeta(@RequestParam(value = "dataId") String dataId,
@RequestParam(value = "group") String group, @RequestParam(value = "group") String group,
@RequestParam(value = "tenant", required = false, @RequestParam(value = "tenant", required = false,
@ -383,6 +399,7 @@ public class ConfigController {
} }
@GetMapping(params = "beta=true") @GetMapping(params = "beta=true")
@Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class)
public RestResult<ConfigInfo4Beta> queryBeta(@RequestParam(value = "dataId") String dataId, public RestResult<ConfigInfo4Beta> queryBeta(@RequestParam(value = "dataId") String dataId,
@RequestParam(value = "group") String group, @RequestParam(value = "group") String group,
@RequestParam(value = "tenant", required = false, @RequestParam(value = "tenant", required = false,
@ -403,6 +420,7 @@ public class ConfigController {
} }
@GetMapping(params = "export=true") @GetMapping(params = "export=true")
@Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class)
public ResponseEntity<byte[]> exportConfig(@RequestParam(value = "dataId", required = false) String dataId, public ResponseEntity<byte[]> exportConfig(@RequestParam(value = "dataId", required = false) String dataId,
@RequestParam(value = "group", required = false) String group, @RequestParam(value = "group", required = false) String group,
@RequestParam(value = "appName", required = false) String appName, @RequestParam(value = "appName", required = false) String appName,
@ -442,6 +460,7 @@ public class ConfigController {
} }
@PostMapping(params = "import=true") @PostMapping(params = "import=true")
@Secured(action = ActionTypes.WRITE, parser = ConfigResourceParser.class)
public RestResult<Map<String, Object>> importAndPublishConfig(HttpServletRequest request, public RestResult<Map<String, Object>> importAndPublishConfig(HttpServletRequest request,
@RequestParam(value = "src_user", required = false) String srcUser, @RequestParam(value = "src_user", required = false) String srcUser,
@RequestParam(value = "namespace", required = false) String namespace, @RequestParam(value = "namespace", required = false) String namespace,
@ -525,14 +544,20 @@ public class ConfigController {
return ResultBuilder.buildSuccessResult("导入成功", saveResult); return ResultBuilder.buildSuccessResult("导入成功", saveResult);
} }
@GetMapping(params = "clone=true") @PostMapping(params = "clone=true")
public RestResult<Map<String, Object>> cloneConfig(HttpServletRequest request, public RestResult<Map<String, Object>> cloneConfig(HttpServletRequest request,
@RequestParam(value = "src_user", required = false) String srcUser, @RequestParam(value = "src_user", required = false) String srcUser,
@RequestParam(value = "tenant", required = true) String namespace, @RequestParam(value = "tenant", required = true) String namespace,
@RequestParam(value = "ids", required = true) List<Long> ids, @RequestBody(required = true)
List<SameNamespaceCloneConfigBean> configBeansList,
@RequestParam(value = "policy", defaultValue = "ABORT") @RequestParam(value = "policy", defaultValue = "ABORT")
SameConfigPolicy policy) throws NacosException { SameConfigPolicy policy) throws NacosException {
Map<String, Object> failedData = new HashMap<>(4); Map<String, Object> failedData = new HashMap<>(4);
if(CollectionUtils.isEmpty(configBeansList)){
failedData.put("succCount", 0);
return ResultBuilder.buildResult(ResultCodeEnum.NO_SELECTED_CONFIG, failedData);
}
configBeansList.removeAll(Collections.singleton(null));
if (NAMESPACE_PUBLIC_KEY.equalsIgnoreCase(namespace)) { if (NAMESPACE_PUBLIC_KEY.equalsIgnoreCase(namespace)) {
namespace = ""; namespace = "";
@ -541,8 +566,14 @@ public class ConfigController {
return ResultBuilder.buildResult(ResultCodeEnum.NAMESPACE_NOT_EXIST, failedData); return ResultBuilder.buildResult(ResultCodeEnum.NAMESPACE_NOT_EXIST, failedData);
} }
ids.removeAll(Collections.singleton(null)); List<Long> idList = new ArrayList<>(configBeansList.size());
List<ConfigAllInfo> queryedDataList = persistService.findAllConfigInfo4Export(null, null, null, null, ids); Map<Long, SameNamespaceCloneConfigBean> configBeansMap = configBeansList.stream()
.collect(Collectors.toMap(SameNamespaceCloneConfigBean::getCfgId, cfg -> {
idList.add(cfg.getCfgId());
return cfg;
},(k1, k2) -> k1));
List<ConfigAllInfo> queryedDataList = persistService.findAllConfigInfo4Export(null, null, null, null, idList);
if (queryedDataList == null || queryedDataList.isEmpty()) { if (queryedDataList == null || queryedDataList.isEmpty()) {
failedData.put("succCount", 0); failedData.put("succCount", 0);
@ -552,11 +583,12 @@ public class ConfigController {
List<ConfigAllInfo> configInfoList4Clone = new ArrayList<>(queryedDataList.size()); List<ConfigAllInfo> configInfoList4Clone = new ArrayList<>(queryedDataList.size());
for (ConfigAllInfo ci : queryedDataList) { for (ConfigAllInfo ci : queryedDataList) {
SameNamespaceCloneConfigBean prarmBean = configBeansMap.get(ci.getId());
ConfigAllInfo ci4save = new ConfigAllInfo(); ConfigAllInfo ci4save = new ConfigAllInfo();
ci4save.setTenant(namespace); ci4save.setTenant(namespace);
ci4save.setType(ci.getType()); ci4save.setType(ci.getType());
ci4save.setGroup(ci.getGroup()); ci4save.setGroup((prarmBean != null && StringUtils.isNotBlank(prarmBean.getGroup())) ? prarmBean.getGroup() : ci.getGroup());
ci4save.setDataId(ci.getDataId()); ci4save.setDataId((prarmBean != null && StringUtils.isNotBlank(prarmBean.getDataId())) ? prarmBean.getDataId() : ci.getDataId());
ci4save.setContent(ci.getContent()); ci4save.setContent(ci.getContent());
if (StringUtils.isNotBlank(ci.getAppName())) { if (StringUtils.isNotBlank(ci.getAppName())) {
ci4save.setAppName(ci.getAppName()); ci4save.setAppName(ci.getAppName());
@ -580,7 +612,7 @@ public class ConfigController {
configInfo.getTenant(), requestIpApp, time.getTime(), configInfo.getTenant(), requestIpApp, time.getTime(),
LOCAL_IP, ConfigTraceService.PERSISTENCE_EVENT_PUB, configInfo.getContent()); LOCAL_IP, ConfigTraceService.PERSISTENCE_EVENT_PUB, configInfo.getContent());
} }
return ResultBuilder.buildSuccessResult("导入成功", saveResult); return ResultBuilder.buildSuccessResult("克隆成功", saveResult);
} }
} }

View File

@ -130,6 +130,8 @@ public class ConfigServletInner {
isBeta = true; isBeta = true;
} }
} }
String configType = cacheItem.getType();
response.setHeader("Config-Type", (null != configType) ? configType : "text");
} }
File file = null; File file = null;
ConfigInfoBase configInfoBase = null; ConfigInfoBase configInfoBase = null;

View File

@ -0,0 +1,55 @@
/*
* 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.controller.parameters;
/**
* @author klw(213539 @ qq.com)
* @ClassName: SameNamespaceCloneConfigBean
* @Description: 同namespace克隆接口的配制bean
* @date 2019/12/13 16:10
*/
public class SameNamespaceCloneConfigBean {
private Long cfgId;
private String dataId;
private String group;
public Long getCfgId() {
return cfgId;
}
public void setCfgId(Long cfgId) {
this.cfgId = cfgId;
}
public String getDataId() {
return dataId;
}
public void setDataId(String dataId) {
this.dataId = dataId;
}
public String getGroup() {
return group;
}
public void setGroup(String group) {
this.group = group;
}
}

View File

@ -109,6 +109,14 @@ public class CacheItem {
this.tagLastModifiedTs = tagLastModifiedTs; this.tagLastModifiedTs = tagLastModifiedTs;
} }
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
final String groupKey; final String groupKey;
public volatile String md5 = Constants.NULL; public volatile String md5 = Constants.NULL;
public volatile long lastModifiedTs; public volatile long lastModifiedTs;
@ -123,5 +131,6 @@ public class CacheItem {
public volatile Map<String, String> tagMd5; public volatile Map<String, String> tagMd5;
public volatile Map<String, Long> tagLastModifiedTs; public volatile Map<String, Long> tagLastModifiedTs;
public SimpleReadWriteLock rwLock = new SimpleReadWriteLock(); public SimpleReadWriteLock rwLock = new SimpleReadWriteLock();
public String type;
} }

View File

@ -28,6 +28,8 @@ public class ConfigInfo extends ConfigInfoBase {
private String appName; private String appName;
private String type;
public ConfigInfo() { public ConfigInfo() {
} }
@ -63,6 +65,14 @@ public class ConfigInfo extends ConfigInfoBase {
this.appName = appName; this.appName = appName;
} }
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
@Override @Override
public int hashCode() { public int hashCode() {
return super.hashCode(); return super.hashCode();

View File

@ -44,6 +44,8 @@ public enum ResultCodeEnum implements IResultCode {
DATA_EMPTY(100005, "导入的文件数据为空"), DATA_EMPTY(100005, "导入的文件数据为空"),
NO_SELECTED_CONFIG(100006, "没有选择任何配制"),
; ;

View File

@ -57,9 +57,10 @@ public class ConfigService {
/** /**
* 保存配置文件并缓存md5. * 保存配置文件并缓存md5.
*/ */
static public boolean dump(String dataId, String group, String tenant, String content, long lastModifiedTs) { static public boolean dump(String dataId, String group, String tenant, String content, long lastModifiedTs, String type) {
String groupKey = GroupKey2.getKey(dataId, group, tenant); String groupKey = GroupKey2.getKey(dataId, group, tenant);
makeSure(groupKey); CacheItem ci = makeSure(groupKey);
ci.setType(type);
final int lockResult = tryWriteLock(groupKey); final int lockResult = tryWriteLock(groupKey);
assert (lockResult != 0); assert (lockResult != 0);

View File

@ -176,7 +176,7 @@ public class DiskUtil {
} }
static public File heartBeatFile() { static public File heartBeatFile() {
return new File(NACOS_HOME, "status/heartBeat.txt"); return new File(NACOS_HOME, "status" + File.separator + "heartBeat.txt");
} }
static public String relativePath(String dataId, String group) { static public String relativePath(String dataId, String group) {

View File

@ -113,6 +113,7 @@ public class PersistService {
info.setGroup(rs.getString("group_id")); info.setGroup(rs.getString("group_id"));
info.setTenant(rs.getString("tenant_id")); info.setTenant(rs.getString("tenant_id"));
info.setAppName(rs.getString("app_name")); info.setAppName(rs.getString("app_name"));
info.setType(rs.getString("type"));
try { try {
info.setContent(rs.getString("content")); info.setContent(rs.getString("content"));
@ -235,6 +236,11 @@ public class PersistService {
} catch (SQLException e) { } catch (SQLException e) {
// ignore // ignore
} }
try {
info.setType(rs.getString("type"));
} catch (SQLException e) {
// ignore
}
return info; return info;
} }
} }
@ -1325,7 +1331,7 @@ public class PersistService {
final String appName = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("appName"); final String appName = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("appName");
final String configTags = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("config_tags"); final String configTags = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("config_tags");
String sqlCount = "select count(*) from config_info"; String sqlCount = "select count(*) from config_info";
String sql = "select ID,data_id,group_id,tenant_id,app_name,content from config_info"; String sql = "select ID,data_id,group_id,tenant_id,app_name,content,type from config_info";
StringBuilder where = new StringBuilder(" where "); StringBuilder where = new StringBuilder(" where ");
List<String> paramList = new ArrayList<String>(); List<String> paramList = new ArrayList<String>();
paramList.add(tenantTmp); paramList.add(tenantTmp);
@ -1944,7 +1950,7 @@ public class PersistService {
public Page<ConfigInfoWrapper> findAllConfigInfoFragment(final long lastMaxId, final int pageSize) { public Page<ConfigInfoWrapper> findAllConfigInfoFragment(final long lastMaxId, final int pageSize) {
String select String select
= "SELECT id,data_id,group_id,tenant_id,app_name,content,md5,gmt_modified from config_info where id > ? " = "SELECT id,data_id,group_id,tenant_id,app_name,content,md5,gmt_modified,type from config_info where id > ? "
+ "order by id asc limit ?,?"; + "order by id asc limit ?,?";
PaginationHelper<ConfigInfoWrapper> helper = new PaginationHelper<ConfigInfoWrapper>(); PaginationHelper<ConfigInfoWrapper> helper = new PaginationHelper<ConfigInfoWrapper>();
try { try {
@ -2927,8 +2933,8 @@ public class PersistService {
final String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; final String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;
try { try {
return this.jt.queryForObject( return this.jt.queryForObject(
"SELECT ID,data_id,group_id,tenant_id,app_name,content,md5 FROM config_info WHERE data_id=? AND group_id=? AND tenant_id=?", "SELECT ID,data_id,group_id,tenant_id,app_name,content,md5,type FROM config_info WHERE data_id=? AND group_id=? AND tenant_id=?",
new Object[]{dataId, group, tenantTmp}, CONFIG_INFO_ROW_MAPPER); new Object[] {dataId, group, tenantTmp}, CONFIG_INFO_ROW_MAPPER);
} catch (EmptyResultDataAccessException e) { // 表明数据不存在, 返回null } catch (EmptyResultDataAccessException e) { // 表明数据不存在, 返回null
return null; return null;
} catch (CannotGetJdbcConnectionException e) { } catch (CannotGetJdbcConnectionException e) {
@ -3231,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<ConfigInfo> convertDeletedConfig(List<Map<String, Object>> list) { private List<ConfigInfo> convertDeletedConfig(List<Map<String, Object>> list) {
List<ConfigInfo> configs = new ArrayList<ConfigInfo>(); List<ConfigInfo> configs = new ArrayList<ConfigInfo>();
for (Map<String, Object> map : list) { for (Map<String, Object> map : list) {
@ -3566,7 +3542,7 @@ public class PersistService {
static final TenantInfoRowMapper TENANT_INFO_ROW_MAPPER = new TenantInfoRowMapper(); 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(); static final ConfigInfoWrapperRowMapper CONFIG_INFO_WRAPPER_ROW_MAPPER = new ConfigInfoWrapperRowMapper();
@ -3599,7 +3575,7 @@ public class PersistService {
private static String PATTERN_STR = "*"; private static String PATTERN_STR = "*";
private final static int QUERY_LIMIT_SIZE = 50; private final static int QUERY_LIMIT_SIZE = 50;
private JdbcTemplate jt; protected JdbcTemplate jt;
private TransactionTemplate tjt; protected TransactionTemplate tjt;
} }

View File

@ -187,7 +187,7 @@ class DumpProcessor implements TaskProcessor {
boolean result; boolean result;
if (null != cf) { if (null != cf) {
result = ConfigService.dump(dataId, group, tenant, cf.getContent(), lastModified); result = ConfigService.dump(dataId, group, tenant, cf.getContent(), lastModified, cf.getType());
if (result) { if (result) {
ConfigTraceService.logDumpEvent(dataId, group, tenant, null, lastModified, handleIp, ConfigTraceService.logDumpEvent(dataId, group, tenant, null, lastModified, handleIp,
@ -261,7 +261,7 @@ class DumpAllProcessor implements TaskProcessor {
} }
boolean result = ConfigService.dump(cf.getDataId(), cf.getGroup(), cf.getTenant(), cf.getContent(), boolean result = ConfigService.dump(cf.getDataId(), cf.getGroup(), cf.getTenant(), cf.getContent(),
cf.getLastModified()); cf.getLastModified(), cf.getType());
final String content = cf.getContent(); final String content = cf.getContent();
final String md5 = MD5.getInstance().getMD5String(content); final String md5 = MD5.getInstance().getMD5String(content);

View File

@ -76,7 +76,7 @@ public class PaginationHelper<E> {
page.setTotalCount(rowCountInt); page.setTotalCount(rowCountInt);
if (pageNo > pageCount) { if (pageNo > pageCount) {
return null; return page;
} }
final int startRow = (pageNo - 1) * pageSize; final int startRow = (pageNo - 1) * pageSize;
@ -121,7 +121,7 @@ public class PaginationHelper<E> {
page.setTotalCount(rowCountInt); page.setTotalCount(rowCountInt);
if (pageNo > pageCount) { if (pageNo > pageCount) {
return null; return page;
} }
String selectSQL = sqlFetchRows; String selectSQL = sqlFetchRows;
@ -162,7 +162,7 @@ public class PaginationHelper<E> {
page.setTotalCount(rowCountInt); page.setTotalCount(rowCountInt);
if (pageNo > pageCount) { if (pageNo > pageCount) {
return null; return page;
} }
String selectSQL = sqlFetchRows; String selectSQL = sqlFetchRows;

View File

@ -19,7 +19,6 @@ import org.apache.commons.lang3.time.FastDateFormat;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date;
/** /**
* Time util * Time util
@ -29,13 +28,12 @@ import java.util.Date;
public class TimeUtils { public class TimeUtils {
public static Timestamp getCurrentTime() { public static Timestamp getCurrentTime() {
Date date = new Date(); return new Timestamp(System.currentTimeMillis());
return new Timestamp(date.getTime());
} }
public static String getCurrentTimeStr() { public static String getCurrentTimeStr() {
Calendar c = Calendar.getInstance(); Calendar c = Calendar.getInstance();
c.setTime(new Date()); c.setTimeInMillis(System.currentTimeMillis());
FastDateFormat format = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss"); FastDateFormat format = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss");
return format.format(c.getTime()); return format.format(c.getTime());
} }

View File

@ -189,6 +189,13 @@ CREATE TABLE roles (
role varchar(50) NOT NULL 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 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');

View File

@ -184,6 +184,13 @@ CREATE TABLE roles (
role varchar(50) NOT NULL 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 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');

View File

@ -13,24 +13,40 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.alibaba.nacos.console.config; 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.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.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter; import org.springframework.web.filter.CorsFilter;
/** import javax.annotation.PostConstruct;
* Spring cors config
*
* @author yshen
*/
@Configuration
public class CorsConfig {
/**
* @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 @Bean
public CorsFilter corsFilter() { public CorsFilter corsFilter() {
@ -44,5 +60,4 @@ public class CorsConfig {
source.registerCorsConfiguration("/**", config); source.registerCorsConfiguration("/**", config);
return new CorsFilter(source); return new CorsFilter(source);
} }
} }

View File

@ -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<String> login(@RequestParam String username, @RequestParam String password, HttpServletResponse response) {
// 通过用户名和密码创建一个 Authentication 认证对象实现类为 UsernamePasswordAuthenticationToken
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
RestResult<String> rr = new RestResult<String>();
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<String> updatePassword(@RequestParam(value = "oldPassword") String oldPassword,
@RequestParam(value = "newPassword") String newPassword) {
RestResult<String> rr = new RestResult<String>();
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;
}
}

View File

@ -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!");
}
}

View File

@ -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
* <p>
* 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!");
}
}

View File

@ -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
* <p>
* 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<String> rr = new RestResult<String>();
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<String> updatePassword(@RequestParam(value = "oldPassword") String oldPassword,
@RequestParam(value = "newPassword") String newPassword) {
RestResult<String> rr = new RestResult<String>();
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;
}
}

View File

@ -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<String> handleAccessException(AccessException e) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(e.getErrMsg());
}
@ExceptionHandler(IllegalArgumentException.class)
private ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.toString());
}
@ExceptionHandler(Exception.class)
private ResponseEntity<String> handleException(Exception e) {
logger.error("CONSOLE", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.toString());
}
}

View File

@ -15,11 +15,12 @@
*/ */
package com.alibaba.nacos.console.filter; package com.alibaba.nacos.console.filter;
import com.alibaba.nacos.console.config.WebSecurityConfig; import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.console.utils.JwtTokenUtils; 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.Authentication;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain; import javax.servlet.FilterChain;
@ -37,29 +38,23 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
private static final String TOKEN_PREFIX = "Bearer "; private static final String TOKEN_PREFIX = "Bearer ";
private JwtTokenUtils tokenProvider; private JwtTokenManager tokenManager;
public JwtAuthenticationTokenFilter(JwtTokenUtils tokenProvider) { public JwtAuthenticationTokenFilter(JwtTokenManager tokenManager) {
this.tokenProvider = tokenProvider; this.tokenManager = tokenManager;
} }
@Override @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); String jwt = resolveToken(request);
if (jwt != null && !"".equals(jwt.trim()) && SecurityContextHolder.getContext().getAuthentication() == null) { if (StringUtils.isNotBlank(jwt) && SecurityContextHolder.getContext().getAuthentication() == null) {
if (this.tokenProvider.validateToken(jwt)) { this.tokenManager.validateToken(jwt);
/** Authentication authentication = this.tokenManager.getAuthentication(jwt);
* get auth info SecurityContextHolder.getContext().setAuthentication(authentication);
*/
Authentication authentication = this.tokenProvider.getAuthentication(jwt);
/**
* save user info to securityContext
*/
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} }
chain.doFilter(request, response); chain.doFilter(request, response);
} }
@ -67,12 +62,12 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
* Get token from header * Get token from header
*/ */
private String resolveToken(HttpServletRequest request) { private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader(WebSecurityConfig.AUTHORIZATION_HEADER); String bearerToken = request.getHeader(NacosAuthConfig.AUTHORIZATION_HEADER);
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(TOKEN_PREFIX)) { if (StringUtils.isNotBlank(bearerToken) && bearerToken.startsWith(TOKEN_PREFIX)) {
return bearerToken.substring(7, bearerToken.length()); return bearerToken.substring(7);
} }
String jwt = request.getParameter(WebSecurityConfig.AUTHORIZATION_TOKEN); String jwt = request.getParameter(Constants.ACCESS_TOKEN);
if (StringUtils.hasText(jwt)) { if (StringUtils.isNotBlank(jwt)) {
return jwt; return jwt;
} }
return null; return null;

View File

@ -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);
}
}

View File

@ -13,8 +13,9 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
@ -32,7 +33,7 @@ import org.springframework.stereotype.Component;
public class CustomAuthenticationProvider implements AuthenticationProvider { public class CustomAuthenticationProvider implements AuthenticationProvider {
@Autowired @Autowired
private CustomUserDetailsServiceImpl userDetailsService; private NacosUserDetailsServiceImpl userDetailsService;
@Override @Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException { public Authentication authenticate(Authentication authentication) throws AuthenticationException {

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.alibaba.nacos.console.security; package com.alibaba.nacos.console.security.nacos;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -37,10 +37,10 @@ public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationEntryPoint.class); private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationEntryPoint.class);
@Override @Override
public void commence(HttpServletRequest httpServletRequest, public void commence(HttpServletRequest request,
HttpServletResponse httpServletResponse, HttpServletResponse response,
AuthenticationException e) throws IOException, ServletException { AuthenticationException e) throws IOException, ServletException {
logger.error("Responding with unauthorized error. Message - {}", e.getMessage()); logger.error("Responding with unauthorized error. Message:{}, url:{}", e.getMessage(), request.getRequestURI());
httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
} }
} }

View File

@ -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<GrantedAuthority> 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);
}
}

View File

@ -13,12 +13,14 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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.filter.JwtAuthenticationTokenFilter;
import com.alibaba.nacos.console.security.CustomUserDetailsServiceImpl; import com.alibaba.nacos.console.security.nacos.users.NacosUserDetailsServiceImpl;
import com.alibaba.nacos.console.security.JwtAuthenticationEntryPoint; import com.alibaba.nacos.core.auth.AuthConfigs;
import com.alibaba.nacos.console.utils.JwtTokenUtils; import com.alibaba.nacos.core.auth.AuthSystemTypes;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -43,64 +45,95 @@ import org.springframework.web.cors.CorsUtils;
*/ */
@Configuration @Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true) @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_HEADER = "Authorization";
public static final String AUTHORIZATION_TOKEN = "access_token";
public static final String SECURITY_IGNORE_URLS_SPILT_CHAR = ","; public static final String SECURITY_IGNORE_URLS_SPILT_CHAR = ",";
@Autowired public static final String LOGIN_ENTRY_POINT = "/v1/auth/login";
private CustomUserDetailsServiceImpl userDetailsService;
@Autowired public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/v1/auth/**";
private JwtAuthenticationEntryPoint unauthorizedHandler;
@Autowired public static final String TOKEN_PREFIX = "Bearer ";
private JwtTokenUtils tokenProvider;
public static final String CONSOLE_RESOURCE_NAME_PREFIX = "console/";
@Autowired @Autowired
private Environment env; private Environment env;
@Autowired
private JwtTokenManager tokenProvider;
@Autowired
private AuthConfigs authConfigs;
@Autowired
private NacosUserDetailsServiceImpl userDetailsService;
@Bean(name = BeanIds.AUTHENTICATION_MANAGER) @Bean(name = BeanIds.AUTHENTICATION_MANAGER)
@Override @Override
public AuthenticationManager authenticationManagerBean() throws Exception { public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean(); 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 @Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception { protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); 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 @Override
protected void configure(HttpSecurity http) throws Exception { 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 if (StringUtils.isBlank(authConfigs.getNacosAuthSystemType())) {
http.headers().cacheControl(); 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 @Bean

View File

@ -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<RoleInfo> 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);
}
}

View File

@ -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<String, List<RoleInfo>> roleInfoMap = new ConcurrentHashMap<>();
private Map<String, List<PermissionInfo>> permissionInfoMap = new ConcurrentHashMap<>();
@Scheduled(initialDelay = 5000, fixedDelay = 15000)
private void reload() {
try {
Page<RoleInfo> roleInfoPage = rolePersistService.getRolesByUserName(StringUtils.EMPTY, 1, Integer.MAX_VALUE);
if (roleInfoPage == null) {
return;
}
Set<String> roleSet = new HashSet<>(16);
Map<String, List<RoleInfo>> 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<String, List<PermissionInfo>> tmpPermissionInfoMap = new ConcurrentHashMap<>(16);
for (String role : roleSet) {
Page<PermissionInfo> 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.
* <p>
* 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<RoleInfo> 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<PermissionInfo> 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<RoleInfo> getRoles(String username) {
List<RoleInfo> roleInfoList = roleInfoMap.get(username);
if (!authConfigs.isCachingEnabled()) {
Page<RoleInfo> roleInfoPage = getRolesFromDatabase(username, 1, Integer.MAX_VALUE);
if (roleInfoPage != null) {
roleInfoList = roleInfoPage.getPageItems();
}
}
return roleInfoList;
}
public Page<RoleInfo> getRolesFromDatabase(String userName, int pageNo, int pageSize) {
Page<RoleInfo> roles = rolePersistService.getRolesByUserName(userName, pageNo, pageSize);
if (roles == null) {
return new Page<>();
}
return roles;
}
public List<PermissionInfo> getPermissions(String role) {
List<PermissionInfo> permissionInfoList = permissionInfoMap.get(role);
if (!authConfigs.isCachingEnabled()) {
Page<PermissionInfo> permissionInfoPage = getPermissionsFromDatabase(role, 1, Integer.MAX_VALUE);
if (permissionInfoPage != null) {
permissionInfoList = permissionInfoPage.getPageItems();
}
}
return permissionInfoList;
}
public Page<PermissionInfo> 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<PermissionInfo> getPermissionsFromDatabase(String role, int pageNo, int pageSize) {
Page<PermissionInfo> 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);
}
}

View File

@ -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);
}
}

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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 com.alibaba.nacos.config.server.model.User;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
@ -27,11 +27,11 @@ import java.util.Collection;
* *
* @author wfnuser * @author wfnuser
*/ */
public class CustomUserDetails implements UserDetails { public class NacosUserDetails implements UserDetails {
private User user; private User user;
public CustomUserDetails(User user) { public NacosUserDetails(User user) {
this.user = user; this.user = user;
} }

View File

@ -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<String, User> userMap = new ConcurrentHashMap<>();
@Autowired
private UserPersistService userPersistService;
@Autowired
private AuthConfigs authConfigs;
@Scheduled(initialDelay = 5000, fixedDelay = 15000)
private void reload() {
try {
Page<User> users = getUsersFromDatabase(1, Integer.MAX_VALUE);
if (users == null) {
return;
}
Map<String, User> 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<User> 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);
}
}

View File

@ -1,4 +1,4 @@
CREATE SCHEMA diamond AUTHORIZATION diamond; CREATE SCHEMA nacos AUTHORIZATION nacos;
CREATE TABLE config_info ( CREATE TABLE config_info (
id bigint NOT NULL generated by default as identity, 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)); constraint uk_tenant_info_kptenantid UNIQUE (kp,tenant_id));
CREATE INDEX tenant_info_tenant_id_idx ON tenant_info(tenant_id); CREATE INDEX tenant_info_tenant_id_idx ON tenant_info(tenant_id);
CREATE TABLE users ( CREATE TABLE users (
username varchar(50) NOT NULL PRIMARY KEY, username varchar(50) NOT NULL PRIMARY KEY,
password varchar(500) NOT NULL, password varchar(500) NOT NULL,
enabled boolean NOT NULL enabled boolean NOT NULL DEFAULT true
); );
CREATE TABLE roles ( CREATE TABLE roles (
username varchar(50) NOT NULL, 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 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');

View File

@ -27,4 +27,11 @@ server.tomcat.basedir=
#management.security=false #management.security=false
#security.basic.enabled=false #security.basic.enabled=false
#nacos.security.ignore.urls=/** #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

View File

@ -313,6 +313,7 @@ const I18N_CONF = {
delSelectedAlertTitle: 'Delete config', delSelectedAlertTitle: 'Delete config',
delSelectedAlertContent: 'please select the configuration to delete', delSelectedAlertContent: 'please select the configuration to delete',
delSuccessMsg: 'delete successful', delSuccessMsg: 'delete successful',
cloneEditableTitle: 'Modify Data Id and Group (optional)',
}, },
NewConfig: { NewConfig: {
newListingMain: 'Create Configuration', newListingMain: 'Create Configuration',

View File

@ -311,6 +311,7 @@ const I18N_CONF = {
delSelectedAlertTitle: '配置删除', delSelectedAlertTitle: '配置删除',
delSelectedAlertContent: '请选择要删除的配置', delSelectedAlertContent: '请选择要删除的配置',
delSuccessMsg: '删除成功', delSuccessMsg: '删除成功',
cloneEditableTitle: '修改 Data Id Group (可选操作)',
}, },
NewConfig: { NewConfig: {
newListingMain: '新建配置', newListingMain: '新建配置',

View File

@ -39,6 +39,7 @@ import ShowCodeing from 'components/ShowCodeing';
import DeleteDialog from 'components/DeleteDialog'; import DeleteDialog from 'components/DeleteDialog';
import DashboardCard from './DashboardCard'; import DashboardCard from './DashboardCard';
import { getParams, setParams, request, aliwareIntl } from '@/globalLib'; import { getParams, setParams, request, aliwareIntl } from '@/globalLib';
import axios from 'axios';
import './index.scss'; import './index.scss';
import { LANGUAGE_KEY } from '../../../constants'; import { LANGUAGE_KEY } from '../../../constants';
@ -689,10 +690,9 @@ class ConfigurationManagement extends React.Component {
} }
exportData() { exportData() {
let url = let url = `v1/cs/configs?export=true&group=${this.group}&tenant=${getParams(
`v1/cs/configs?export=true&group=${this.group}&tenant=${getParams('namespace')}&appName=${ 'namespace'
this.appName )}&appName=${this.appName}&ids=&dataId=${this.dataId}`;
}&ids=&dataId=` + this.dataId;
window.location.href = url; window.location.href = url;
} }
@ -783,19 +783,54 @@ class ConfigurationManagement extends React.Component {
} }
let namespaces = data.data; let namespaces = data.data;
let namespaceSelectData = []; let namespaceSelectData = [];
namespaces.forEach(item => { let namespaceSelecItemRender = item => {
if (self.state.nownamespace_id !== item.namespace) { if (item.isCurrent) {
let dataItem = {}; return <span style={{ color: '#00AA00', 'font-weight': 'bold' }}>{item.label}</span>;
if (item.namespaceShowName === 'public') { } else {
dataItem.label = 'public | public'; return <span>{item.label}</span>;
dataItem.value = 'public';
} else {
dataItem.label = `${item.namespaceShowName} | ${item.namespace}`;
dataItem.value = item.namespace;
}
namespaceSelectData.push(dataItem);
} }
};
namespaces.forEach(item => {
let dataItem = {};
dataItem.isCurrent = false;
if (self.state.nownamespace_id === item.namespace) {
dataItem.isCurrent = true;
}
if (item.namespaceShowName === 'public') {
dataItem.label = 'public | public';
dataItem.value = 'public';
} else {
dataItem.label = `${item.namespaceShowName} | ${item.namespace}`;
dataItem.value = item.namespace;
}
namespaceSelectData.push(dataItem);
}); });
let editableTableData = [];
let configsTableSelectedDeepCopyed = new Map();
configsTableSelected.forEach((value, key, map) => {
let dataItem = {};
dataItem.id = key;
dataItem.dataId = value.dataId;
dataItem.group = value.group;
editableTableData.push(dataItem);
configsTableSelectedDeepCopyed.set(key, JSON.parse(JSON.stringify(value)));
});
let editableTableOnBlur = (record, type, e) => {
if (type === 1) {
configsTableSelectedDeepCopyed.get(record.id).dataId = e.target.value;
} else {
configsTableSelectedDeepCopyed.get(record.id).group = e.target.value;
}
};
let renderEditableTableCellDataId = (value, index, record) => (
<Input defaultValue={value} onBlur={editableTableOnBlur.bind(this, record, 1)} />
);
let renderEditableTableCellGroup = (value, index, record) => (
<Input defaultValue={value} onBlur={editableTableOnBlur.bind(this, record, 2)} />
);
const cloneConfirm = Dialog.confirm({ const cloneConfirm = Dialog.confirm({
title: locale.cloningConfiguration, title: locale.cloningConfiguration,
footer: false, footer: false,
@ -822,6 +857,7 @@ class ConfigurationManagement extends React.Component {
showSearch showSearch
hasClear={false} hasClear={false}
mode="single" mode="single"
itemRender={namespaceSelecItemRender}
dataSource={namespaceSelectData} dataSource={namespaceSelectData}
onChange={(value, actionType, item) => { onChange={(value, actionType, item) => {
if (value) { if (value) {
@ -866,7 +902,7 @@ class ConfigurationManagement extends React.Component {
}} }}
/> />
</div> </div>
<div> <div style={{ marginBottom: 10 }}>
<Button <Button
type={'primary'} type={'primary'}
style={{ marginRight: 10 }} style={{ marginRight: 10 }}
@ -878,13 +914,21 @@ class ConfigurationManagement extends React.Component {
document.getElementById('cloneTargetSpaceSelectErr').style.display = 'none'; document.getElementById('cloneTargetSpaceSelectErr').style.display = 'none';
} }
let idsStr = ''; let idsStr = '';
configsTableSelected.forEach((value, key, map) => { let clonePostData = [];
idsStr = `${idsStr + key},`; configsTableSelectedDeepCopyed.forEach((value, key, map) => {
let postDataItem = {};
postDataItem.cfgId = key;
postDataItem.dataId = value.dataId;
postDataItem.group = value.group;
clonePostData.push(postDataItem);
}); });
let cloneTargetSpace = self.field.getValue('cloneTargetSpace'); let cloneTargetSpace = self.field.getValue('cloneTargetSpace');
let sameConfigPolicy = self.field.getValue('sameConfigPolicy'); let sameConfigPolicy = self.field.getValue('sameConfigPolicy');
request({ request({
url: `v1/cs/configs?clone=true&tenant=${cloneTargetSpace}&policy=${sameConfigPolicy}&ids=${idsStr}`, url: `v1/cs/configs?clone=true&tenant=${cloneTargetSpace}&policy=${sameConfigPolicy}&namespaceId=`,
method: 'post',
data: JSON.stringify(clonePostData),
contentType: 'application/json',
beforeSend() { beforeSend() {
self.openLoading(); self.openLoading();
}, },
@ -908,6 +952,25 @@ class ConfigurationManagement extends React.Component {
{locale.startCloning} {locale.startCloning}
</Button> </Button>
</div> </div>
<div style={{ marginBottom: 10 }}>
<span style={{ color: '#00AA00', 'font-weight': 'bold' }}>
{locale.cloneEditableTitle}
</span>
</div>
<div>
<Table dataSource={editableTableData}>
<Table.Column
title="Data Id"
dataIndex="dataId"
cell={renderEditableTableCellDataId}
/>
<Table.Column
title="Group"
dataIndex="group"
cell={renderEditableTableCellGroup}
/>
</Table>
</div>
</div> </div>
), ),
}); });
@ -1089,7 +1152,9 @@ class ConfigurationManagement extends React.Component {
rowSelection.selectedRowKeys = ids; rowSelection.selectedRowKeys = ids;
this.setState({ rowSelection }); this.setState({ rowSelection });
configsTableSelected.clear(); configsTableSelected.clear();
records.forEach(item => configsTableSelected.set(item.id, item)); records.forEach((record, i) => {
configsTableSelected.set(record.id, record);
});
} }
render() { render() {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -59,6 +59,28 @@
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
@ -79,5 +101,25 @@
<artifactId>tomcat-embed-core</artifactId> <artifactId>tomcat-embed-core</artifactId>
</dependency> </dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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<AuthFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(authFilter());
registration.addUrlPatterns("/*");
registration.setName("authFilter");
registration.setOrder(6);
return registration;
}
@Bean
public AuthFilter authFilter() {
return new AuthFilter();
}
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -13,14 +13,19 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.alibaba.nacos.naming.web;
import java.lang.annotation.Retention; package com.alibaba.nacos.core.auth;
import java.lang.annotation.RetentionPolicy;
/** /**
* Innovated By: Xuanyin.zy * Types of all auth implementations
*
* @author nkorange
* @since 1.2.0
*/ */
@Retention(RetentionPolicy.RUNTIME) public enum AuthSystemTypes {
public @interface NeedAuth {
/**
* Nacos builtin auth system
*/
NACOS
} }

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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<? extends ResourceParser> parser() default DefaultResourceParser.class;
}

View File

@ -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;
}
}

View File

@ -13,64 +13,72 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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.apache.commons.lang3.ArrayUtils;
import org.reflections.Reflections;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import javax.annotation.PostConstruct;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
/** /**
* Basic methods for filter to use * Method cache
* *
* @author nkorange * @author nkorange
* @since 1.0.0 * @since 1.2.0
*/ */
@Component @Component
public class FilterBase { public class ControllerMethodsCache {
private ConcurrentMap<String, Method> methodCache = new private ConcurrentMap<String, Method> methods = new
ConcurrentHashMap<>(); ConcurrentHashMap<>();
@PostConstruct public ConcurrentMap<String, Method> getMethods() {
public void init() { return methods;
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 Method getMethod(String httpMethod, String path) { public Method getMethod(String httpMethod, String path) {
String key = httpMethod + "-->" + path.replace("/nacos", ""); 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<Class<?>> classesList = reflections.getTypesAnnotatedWith(RequestMapping.class);
for (Class clazz : classesList) {
initClassMethod(clazz);
}
}
public void initClassMethod(Set<Class<?>> classesList) {
for (Class clazz : classesList) {
initClassMethod(clazz);
}
}
public void initClassMethod(Class<?> clazz) {
RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class); RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class);
String classPath = requestMapping.value()[0]; for (String classPath : requestMapping.value()) {
for (Method method : clazz.getMethods()) { for (Method method : clazz.getMethods()) {
if (!method.isAnnotationPresent(RequestMapping.class)) { if (!method.isAnnotationPresent(RequestMapping.class)) {
parseSubAnnotations(method, classPath); parseSubAnnotations(method, classPath);
continue; continue;
} }
requestMapping = method.getAnnotation(RequestMapping.class); requestMapping = method.getAnnotation(RequestMapping.class);
RequestMethod[] requestMethods = requestMapping.method(); RequestMethod[] requestMethods = requestMapping.method();
if (requestMethods.length == 0) { if (requestMethods.length == 0) {
requestMethods = new RequestMethod[1]; requestMethods = new RequestMethod[1];
requestMethods[0] = RequestMethod.GET; requestMethods[0] = RequestMethod.GET;
} }
for (String methodPath : requestMapping.value()) { for (String methodPath : requestMapping.value()) {
methodCache.put(requestMethods[0].name() + "-->" + classPath + methodPath, method); 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) { private void put(RequestMethod requestMethod, String classPath, String[] requestPaths, Method method) {
if (ArrayUtils.isEmpty(requestPaths)) { if (ArrayUtils.isEmpty(requestPaths)) {
methodCache.put(requestMethod.name() + "-->" + classPath, method); methods.put(requestMethod.name() + "-->" + classPath, method);
return; return;
} }
for (String requestPath : requestPaths) { for (String requestPath : requestPaths) {
methodCache.put(requestMethod.name() + "-->" + classPath + requestPath, method); methods.put(requestMethod.name() + "-->" + classPath + requestPath, method);
} }
} }
} }

View File

@ -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;
}
}

View File

@ -25,6 +25,7 @@ import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
@ -147,9 +148,10 @@ public class StartingSpringApplicationRunListener implements SpringApplicationRu
} }
private void logFilePath() { private void logFilePath() {
LOGGER.info("Nacos Log files: {}/logs/", NACOS_HOME); String[] dirNames = new String[]{"logs", "conf", "data"};
LOGGER.info("Nacos Conf files: {}/conf/", NACOS_HOME); for (String dirName: dirNames) {
LOGGER.info("Nacos Data files: {}/data/", NACOS_HOME); LOGGER.info("Nacos Log files: {}{}{}{}", NACOS_HOME, File.separatorChar, dirName, File.separatorChar);
}
} }
private void logStarting() { private void logStarting() {

View File

@ -58,4 +58,6 @@ public interface Constants {
String SYSTEM_PREFER_HOSTNAME_OVER_IP = "nacos.preferHostnameOverIp"; String SYSTEM_PREFER_HOSTNAME_OVER_IP = "nacos.preferHostnameOverIp";
String WEB_CONTEXT_PATH = "server.servlet.context-path"; String WEB_CONTEXT_PATH = "server.servlet.context-path";
String COMMA_DIVISION = ","; String COMMA_DIVISION = ",";
String NACOS_SERVER_HEADER = "Nacos-Server";
} }

View File

@ -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();
}
}

View File

@ -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");
}

Some files were not shown because too many files have changed in this diff Show More