Merge remote-tracking branch 'upstream/develop' into feature/191226

This commit is contained in:
LoadChange 2020-01-13 11:40:06 +08:00
commit f29079a411
113 changed files with 4347 additions and 896 deletions

View File

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

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:
#build nacos
$ mvn -Prelease-nacos -DskipTests clean install -U
$ mvn -Prelease-nacos -Dmaven.test.skip=true clean install -U

View File

@ -32,6 +32,10 @@ public class PropertyKeyConst {
public final static String NAMESPACE = "namespace";
public final static String USERNAME = "username";
public final static String PASSWORD = "password";
public final static String ACCESS_KEY = "accessKey";
public final static String SECRET_KEY = "secretKey";

View File

@ -72,6 +72,16 @@ public class Constants {
public static final String CONFIG_CONTROLLER_PATH = BASE_PATH + "/configs";
public static final String TOKEN = "token";
public static final String ACCESS_TOKEN = "accessToken";
public static final String TOKEN_TTL = "tokenTtl";
public static final String GLOBAL_ADMIN = "globalAdmin";
public static final String TOKEN_REFRESH_WINDOW = "tokenRefreshWindow";
/**
* second
*/
@ -171,4 +181,6 @@ public class Constants {
public static final String SNOWFLAKE_INSTANCE_ID_GENERATOR = "snowflake";
public static final String HTTP_PREFIX = "http";
}

View File

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

View File

@ -16,7 +16,6 @@
package com.alibaba.nacos.client.config;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.SystemPropertyKeyConst;
import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
@ -33,8 +32,7 @@ import com.alibaba.nacos.client.config.impl.LocalConfigInfoProcessor;
import com.alibaba.nacos.client.config.utils.ContentUtils;
import com.alibaba.nacos.client.config.utils.ParamUtils;
import com.alibaba.nacos.client.utils.LogUtils;
import com.alibaba.nacos.client.utils.TemplateUtils;
import com.alibaba.nacos.client.utils.TenantUtil;
import com.alibaba.nacos.client.utils.ParamUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
@ -44,7 +42,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Callable;
/**
* Config Impl
@ -86,34 +83,7 @@ public class NacosConfigService implements ConfigService {
}
private void initNamespace(Properties properties) {
String namespaceTmp = null;
String isUseCloudNamespaceParsing =
properties.getProperty(PropertyKeyConst.IS_USE_CLOUD_NAMESPACE_PARSING,
System.getProperty(SystemPropertyKeyConst.IS_USE_CLOUD_NAMESPACE_PARSING,
String.valueOf(Constants.DEFAULT_USE_CLOUD_NAMESPACE_PARSING)));
if (Boolean.parseBoolean(isUseCloudNamespaceParsing)) {
namespaceTmp = TemplateUtils.stringBlankAndThenExecute(namespaceTmp, new Callable<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;
namespace = ParamUtil.parseNamespace(properties);
properties.put(PropertyKeyConst.NAMESPACE, namespace);
}

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.SpasAdapter;
import com.alibaba.nacos.client.identify.STSConfig;
import com.alibaba.nacos.client.security.SecurityProxy;
import com.alibaba.nacos.client.utils.JSONUtils;
import com.alibaba.nacos.client.utils.LogUtils;
import com.alibaba.nacos.client.utils.ParamUtil;
@ -43,7 +44,7 @@ import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Callable;
import java.util.concurrent.*;
/**
* Server Agent
@ -54,6 +55,12 @@ public class ServerHttpAgent implements HttpAgent {
private static final Logger LOGGER = LogUtils.logger(ServerHttpAgent.class);
private SecurityProxy securityProxy;
private String namespaceId;
private long securityInfoRefreshIntervalMills = TimeUnit.SECONDS.toMillis(5);
/**
* @param path 相对于web应用根/开头
* @param headers
@ -68,7 +75,7 @@ public class ServerHttpAgent implements HttpAgent {
long readTimeoutMs) throws IOException {
final long endTime = System.currentTimeMillis() + readTimeoutMs;
final boolean isSSL = false;
injectSecurityInfo(paramValues);
String currentServerAddr = serverListMgr.getCurrentServerAddr();
int maxRetry = this.maxRetry;
@ -121,7 +128,7 @@ public class ServerHttpAgent implements HttpAgent {
long readTimeoutMs) throws IOException {
final long endTime = System.currentTimeMillis() + readTimeoutMs;
boolean isSSL = false;
injectSecurityInfo(paramValues);
String currentServerAddr = serverListMgr.getCurrentServerAddr();
int maxRetry = this.maxRetry;
@ -176,7 +183,7 @@ public class ServerHttpAgent implements HttpAgent {
long readTimeoutMs) throws IOException {
final long endTime = System.currentTimeMillis() + readTimeoutMs;
boolean isSSL = false;
injectSecurityInfo(paramValues);
String currentServerAddr = serverListMgr.getCurrentServerAddr();
int maxRetry = this.maxRetry;
@ -243,7 +250,39 @@ public class ServerHttpAgent implements HttpAgent {
public ServerHttpAgent(Properties properties) throws NacosException {
serverListMgr = new ServerListManager(properties);
securityProxy = new SecurityProxy(properties);
namespaceId = properties.getProperty(PropertyKeyConst.NAMESPACE);
init(properties);
securityProxy.login(serverListMgr.getServerUrls());
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("com.alibaba.nacos.client.config.security.updater");
t.setDaemon(true);
return t;
}
});
executorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
securityProxy.login(serverListMgr.getServerUrls());
}
}, 0, securityInfoRefreshIntervalMills, TimeUnit.MILLISECONDS);
}
private void injectSecurityInfo(List<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) {

View File

@ -223,9 +223,9 @@ public class ClientWorker {
try {
List<String> params = null;
if (StringUtils.isBlank(tenant)) {
params = Arrays.asList("dataId", dataId, "group", group);
params = new ArrayList<String>(Arrays.asList("dataId", dataId, "group", group));
} 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);
} catch (IOException e) {

View File

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

View File

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

View File

@ -53,7 +53,7 @@ public final class CredentialService implements SpasCredentialLoader {
}
public static CredentialService getInstance(String appName) {
String key = appName != null ? appName : Constants.NO_APP_NAME;
String key = appName != null ? appName : IdentifyConstants.NO_APP_NAME;
CredentialService instance = instances.get(key);
if (instance == null) {
instance = new CredentialService(appName);
@ -70,7 +70,7 @@ public final class CredentialService implements SpasCredentialLoader {
}
public static CredentialService freeInstance(String appName) {
String key = appName != null ? appName : Constants.NO_APP_NAME;
String key = appName != null ? appName : IdentifyConstants.NO_APP_NAME;
CredentialService instance = instances.remove(key);
if (instance != null) {
instance.free();

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

@ -20,7 +20,7 @@ import java.util.Arrays;
import java.util.List;
/**
* @author nkorange
* @author alibaba
*/
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;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.SystemPropertyKeyConst;
import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.client.config.impl.HttpSimpleClient;
import org.slf4j.Logger;
@ -152,6 +154,36 @@ public class ParamUtil {
ParamUtil.defaultNodesPath = defaultNodesPath;
}
public static String parseNamespace(Properties properties) {
String namespaceTmp = null;
String isUseCloudNamespaceParsing =
properties.getProperty(PropertyKeyConst.IS_USE_CLOUD_NAMESPACE_PARSING,
System.getProperty(SystemPropertyKeyConst.IS_USE_CLOUD_NAMESPACE_PARSING,
String.valueOf(Constants.DEFAULT_USE_CLOUD_NAMESPACE_PARSING)));
if (Boolean.parseBoolean(isUseCloudNamespaceParsing)) {
namespaceTmp = TemplateUtils.stringBlankAndThenExecute(namespaceTmp, new Callable<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) {
// 配置文件中输入的话 ENV 中的优先

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,6 +15,7 @@
*/
package com.alibaba.nacos.config.server.controller;
import com.alibaba.nacos.config.server.auth.ConfigResourceParser;
import com.alibaba.nacos.config.server.constant.Constants;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.config.server.controller.parameters.SameNamespaceCloneConfigBean;
@ -28,6 +29,8 @@ import com.alibaba.nacos.config.server.service.PersistService;
import com.alibaba.nacos.config.server.service.trace.ConfigTraceService;
import com.alibaba.nacos.config.server.utils.*;
import com.alibaba.nacos.config.server.utils.event.EventDispatcher;
import com.alibaba.nacos.core.auth.ActionTypes;
import com.alibaba.nacos.core.auth.Secured;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.slf4j.Logger;
@ -90,6 +93,7 @@ public class ConfigController {
* @throws NacosException
*/
@PostMapping
@Secured(action = ActionTypes.WRITE, parser = ConfigResourceParser.class)
public Boolean publishConfig(HttpServletRequest request, HttpServletResponse response,
@RequestParam("dataId") String dataId, @RequestParam("group") String group,
@RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY)
@ -166,6 +170,7 @@ public class ConfigController {
* @throws NacosException
*/
@GetMapping
@Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class)
public void getConfig(HttpServletRequest request, HttpServletResponse response,
@RequestParam("dataId") String dataId, @RequestParam("group") String group,
@RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY)
@ -186,6 +191,7 @@ public class ConfigController {
* @throws NacosException
*/
@GetMapping(params = "show=all")
@Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class)
public ConfigAllInfo detailConfigInfo(HttpServletRequest request, HttpServletResponse response,
@RequestParam("dataId") String dataId, @RequestParam("group") String group,
@RequestParam(value = "tenant", required = false,
@ -202,6 +208,7 @@ public class ConfigController {
* @throws NacosException
*/
@DeleteMapping
@Secured(action = ActionTypes.WRITE, parser = ConfigResourceParser.class)
public Boolean deleteConfig(HttpServletRequest request, HttpServletResponse response,
@RequestParam("dataId") String dataId, //
@RequestParam("group") String group, //
@ -231,6 +238,7 @@ public class ConfigController {
* @Param [request, response, dataId, group, tenant, tag]
*/
@DeleteMapping(params = "delType=ids")
@Secured(action = ActionTypes.WRITE, parser = ConfigResourceParser.class)
public RestResult<Boolean> deleteConfigs(HttpServletRequest request, HttpServletResponse response,
@RequestParam(value = "ids") List<Long> ids) {
String clientIp = RequestUtil.getRemoteIp(request);
@ -249,6 +257,7 @@ public class ConfigController {
}
@GetMapping("/catalog")
@Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class)
public RestResult<ConfigAdvanceInfo> getConfigAdvanceInfo(@RequestParam("dataId") String dataId,
@RequestParam("group") String group,
@RequestParam(value = "tenant", required = false,
@ -264,6 +273,7 @@ public class ConfigController {
* 比较MD5
*/
@PostMapping("/listener")
@Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class)
public void listener(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true);
@ -289,6 +299,7 @@ public class ConfigController {
* 订阅改配置的客户端信息
*/
@GetMapping("/listener")
@Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class)
public GroupkeyListenserStatus getListeners(@RequestParam("dataId") String dataId,
@RequestParam("group") String group,
@RequestParam(value = "tenant", required = false) String tenant,
@ -308,6 +319,7 @@ public class ConfigController {
* 查询配置信息返回JSON格式
*/
@GetMapping(params = "search=accurate")
@Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class)
public Page<ConfigInfo> searchConfig(@RequestParam("dataId") String dataId,
@RequestParam("group") String group,
@RequestParam(value = "appName", required = false) String appName,
@ -337,6 +349,7 @@ public class ConfigController {
* 模糊查询配置信息不允许只根据内容模糊查询即dataId和group都为NULL但content不是NULL这种情况下返回所有配置
*/
@GetMapping(params = "search=blur")
@Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class)
public Page<ConfigInfo> fuzzySearchConfig(@RequestParam("dataId") String dataId,
@RequestParam("group") String group,
@RequestParam(value = "appName", required = false) String appName,
@ -363,6 +376,7 @@ public class ConfigController {
}
@DeleteMapping(params = "beta=true")
@Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class)
public RestResult<Boolean> stopBeta(@RequestParam(value = "dataId") String dataId,
@RequestParam(value = "group") String group,
@RequestParam(value = "tenant", required = false,
@ -385,6 +399,7 @@ public class ConfigController {
}
@GetMapping(params = "beta=true")
@Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class)
public RestResult<ConfigInfo4Beta> queryBeta(@RequestParam(value = "dataId") String dataId,
@RequestParam(value = "group") String group,
@RequestParam(value = "tenant", required = false,
@ -405,6 +420,7 @@ public class ConfigController {
}
@GetMapping(params = "export=true")
@Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class)
public ResponseEntity<byte[]> exportConfig(@RequestParam(value = "dataId", required = false) String dataId,
@RequestParam(value = "group", required = false) String group,
@RequestParam(value = "appName", required = false) String appName,
@ -444,6 +460,7 @@ public class ConfigController {
}
@PostMapping(params = "import=true")
@Secured(action = ActionTypes.WRITE, parser = ConfigResourceParser.class)
public RestResult<Map<String, Object>> importAndPublishConfig(HttpServletRequest request,
@RequestParam(value = "src_user", required = false) String srcUser,
@RequestParam(value = "namespace", required = false) String namespace,

View File

@ -3237,36 +3237,6 @@ public class PersistService {
}
}
public User findUserByUsername(String username) {
String sql = "SELECT username,password FROM users WHERE username=? ";
try {
return this.jt.queryForObject(sql, new Object[]{username}, USER_ROW_MAPPER);
} catch (CannotGetJdbcConnectionException e) {
fatalLog.error("[db-error] " + e.toString(), e);
throw e;
} catch (EmptyResultDataAccessException e) {
return null;
} catch (Exception e) {
fatalLog.error("[db-other-error]" + e.getMessage(), e);
throw new RuntimeException(e);
}
}
/**
* 更新用户密码
*/
public void updateUserPassword(String username, String password) {
try {
jt.update(
"UPDATE users SET password = ? WHERE username=?",
password, username);
} catch (CannotGetJdbcConnectionException e) {
fatalLog.error("[db-error] " + e.toString(), e);
throw e;
}
}
private List<ConfigInfo> convertDeletedConfig(List<Map<String, Object>> list) {
List<ConfigInfo> configs = new ArrayList<ConfigInfo>();
for (Map<String, Object> map : list) {
@ -3572,7 +3542,7 @@ public class PersistService {
static final TenantInfoRowMapper TENANT_INFO_ROW_MAPPER = new TenantInfoRowMapper();
static final UserRowMapper USER_ROW_MAPPER = new UserRowMapper();
protected static final UserRowMapper USER_ROW_MAPPER = new UserRowMapper();
static final ConfigInfoWrapperRowMapper CONFIG_INFO_WRAPPER_ROW_MAPPER = new ConfigInfoWrapperRowMapper();
@ -3605,7 +3575,7 @@ public class PersistService {
private static String PATTERN_STR = "*";
private final static int QUERY_LIMIT_SIZE = 50;
private JdbcTemplate jt;
private TransactionTemplate tjt;
protected JdbcTemplate jt;
protected TransactionTemplate tjt;
}

View File

@ -189,6 +189,13 @@ CREATE TABLE roles (
role varchar(50) NOT NULL
);
CREATE TABLE permissions (
role varchar(50) NOT NULL,
resource varchar(512) NOT NULL,
action varchar(8) NOT NULL,
constraint uk_role_permission UNIQUE (role,resource,action)
);
INSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE);
INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN');
INSERT INTO roles (username, role) VALUES ('nacos', 'GLOBAL_ADMIN');

View File

@ -184,6 +184,13 @@ CREATE TABLE roles (
role varchar(50) NOT NULL
);
CREATE TABLE permissions (
role varchar(50) NOT NULL,
resource varchar(512) NOT NULL,
action varchar(8) NOT NULL,
constraint uk_role_permission UNIQUE (role,resource,action)
);
INSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE);
INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN');
INSERT INTO roles (username, role) VALUES ('nacos', 'GLOBAL_ADMIN');

View File

@ -13,24 +13,40 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.console.config;
import com.alibaba.nacos.core.code.ControllerMethodsCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.stereotype.Component;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
/**
* Spring cors config
*
* @author yshen
*/
@Configuration
public class CorsConfig {
import javax.annotation.PostConstruct;
/**
* @author yshen
* @author nkorange
* @since 1.2.0
*/
@Component
@EnableScheduling
@PropertySource("/application.properties")
public class ConsoleConfig {
@Autowired
private ControllerMethodsCache methodsCache;
@PostConstruct
public void init() {
methodsCache.initClassMethod("com.alibaba.nacos.naming.controllers");
methodsCache.initClassMethod("com.alibaba.nacos.console.controller");
methodsCache.initClassMethod("com.alibaba.nacos.config.server.controller");
}
@Bean
public CorsFilter corsFilter() {
@ -44,5 +60,4 @@ public class CorsConfig {
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}

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

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
* limitations under the License.
*/
package com.alibaba.nacos.console.security;
package com.alibaba.nacos.console.security.nacos;
import com.alibaba.nacos.console.security.nacos.users.NacosUserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
@ -32,7 +33,7 @@ import org.springframework.stereotype.Component;
public class CustomAuthenticationProvider implements AuthenticationProvider {
@Autowired
private CustomUserDetailsServiceImpl userDetailsService;
private NacosUserDetailsServiceImpl userDetailsService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {

View File

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

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
* limitations under the License.
*/
package com.alibaba.nacos.console.config;
package com.alibaba.nacos.console.security.nacos;
import com.alibaba.nacos.console.filter.JwtAuthenticationTokenFilter;
import com.alibaba.nacos.console.security.CustomUserDetailsServiceImpl;
import com.alibaba.nacos.console.security.JwtAuthenticationEntryPoint;
import com.alibaba.nacos.console.utils.JwtTokenUtils;
import com.alibaba.nacos.console.security.nacos.users.NacosUserDetailsServiceImpl;
import com.alibaba.nacos.core.auth.AuthConfigs;
import com.alibaba.nacos.core.auth.AuthSystemTypes;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -43,64 +45,95 @@ import org.springframework.web.cors.CorsUtils;
*/
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
public class NacosAuthConfig extends WebSecurityConfigurerAdapter {
public static final String AUTHORIZATION_HEADER = "Authorization";
public static final String AUTHORIZATION_TOKEN = "access_token";
public static final String SECURITY_IGNORE_URLS_SPILT_CHAR = ",";
@Autowired
private CustomUserDetailsServiceImpl userDetailsService;
public static final String LOGIN_ENTRY_POINT = "/v1/auth/login";
@Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/v1/auth/**";
@Autowired
private JwtTokenUtils tokenProvider;
public static final String TOKEN_PREFIX = "Bearer ";
public static final String CONSOLE_RESOURCE_NAME_PREFIX = "console/";
@Autowired
private Environment env;
@Autowired
private JwtTokenManager tokenProvider;
@Autowired
private AuthConfigs authConfigs;
@Autowired
private NacosUserDetailsServiceImpl userDetailsService;
@Bean(name = BeanIds.AUTHENTICATION_MANAGER)
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
public void configure(WebSecurity web) {
String ignoreURLs = null;
//
if (AuthSystemTypes.NACOS.name().equalsIgnoreCase(authConfigs.getNacosAuthSystemType())) {
ignoreURLs = "/**";
}
//
if (StringUtils.isBlank(authConfigs.getNacosAuthSystemType())) {
ignoreURLs = env.getProperty("nacos.security.ignore.urls", "/**");
}
if (StringUtils.isNotBlank(ignoreURLs)) {
for (String ignoreURL : ignoreURLs.trim().split(SECURITY_IGNORE_URLS_SPILT_CHAR)) {
web.ignoring().antMatchers(ignoreURL.trim());
}
}
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Override
public void configure(WebSecurity web) {
String ignoreURLs = env.getProperty("nacos.security.ignore.urls", "/**");
for (String ignoreURL : ignoreURLs.trim().split(SECURITY_IGNORE_URLS_SPILT_CHAR)) {
web.ignoring().antMatchers(ignoreURL.trim());
}
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//open cors
.cors().and()
.authorizeRequests()
.requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
.anyRequest().authenticated().and()
// custom token authorize exception handler
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler).and()
// since we use jwt, session is not necessary
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
// since we use jwt, csrf is not necessary
.csrf().disable();
http.addFilterBefore(new JwtAuthenticationTokenFilter(tokenProvider), UsernamePasswordAuthenticationFilter.class);
// disable cache
http.headers().cacheControl();
if (StringUtils.isBlank(authConfigs.getNacosAuthSystemType())) {
http
.csrf().disable()
.cors() // We don't need CSRF for JWT based authentication
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
.antMatchers(LOGIN_ENTRY_POINT).permitAll()
.and()
.authorizeRequests()
.antMatchers(TOKEN_BASED_AUTH_ENTRY_POINT).authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(new JwtAuthenticationEntryPoint());
// disable cache
http.headers().cacheControl();
http.addFilterBefore(new JwtAuthenticationTokenFilter(tokenProvider), UsernamePasswordAuthenticationFilter.class);
}
}
@Bean

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

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 (
id bigint NOT NULL generated by default as identity,
@ -172,18 +172,25 @@ CREATE TABLE tenant_info (
constraint uk_tenant_info_kptenantid UNIQUE (kp,tenant_id));
CREATE INDEX tenant_info_tenant_id_idx ON tenant_info(tenant_id);
CREATE TABLE users (
username varchar(50) NOT NULL PRIMARY KEY,
password varchar(500) NOT NULL,
enabled boolean NOT NULL
enabled boolean NOT NULL DEFAULT true
);
CREATE TABLE roles (
username varchar(50) NOT NULL,
role varchar(50) NOT NULL
role varchar(50) NOT NULL,
constraint uk_username_role UNIQUE (username,role)
);
CREATE TABLE permissions (
role varchar(50) NOT NULL,
resource varchar(512) NOT NULL,
action varchar(8) NOT NULL,
constraint uk_role_permission UNIQUE (role,resource,action)
);
INSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE);
INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN');
INSERT INTO roles (username, role) VALUES ('nacos', 'GLOBAL_ADMIN');

View File

@ -27,4 +27,11 @@ server.tomcat.basedir=
#management.security=false
#security.basic.enabled=false
#nacos.security.ignore.urls=/**
nacos.security.ignore.urls=/,/error,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/v1/auth/login,/v1/console/health/**,/v1/cs/**,/v1/ns/**,/v1/cmdb/**,/actuator/**,/v1/console/server/**
#nacos.security.ignore.urls=/,/error,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/v1/auth/login,/v1/console/health/**,/v1/cs/**,/v1/ns/**,/v1/cmdb/**,/actuator/**,/v1/console/server/**
nacos.security.ignore.urls=/,/error,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/v1/auth/**,/v1/console/health/**,/actuator/**,/v1/console/server/**
nacos.core.auth.system.type=nacos
nacos.core.auth.enabled=false
nacos.core.auth.default.token.secret.key=SecretKey012345678901234567890123456789012345678901234567890123456789

View File

@ -59,6 +59,28 @@
<optional>true</optional>
</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>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
@ -79,5 +101,25 @@
<artifactId>tomcat-embed-core</artifactId>
</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>
</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
* limitations under the License.
*/
package com.alibaba.nacos.naming.web;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
package com.alibaba.nacos.core.auth;
/**
* Innovated By: Xuanyin.zy
* Types of all auth implementations
*
* @author nkorange
* @since 1.2.0
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedAuth {
public enum AuthSystemTypes {
/**
* Nacos builtin auth system
*/
NACOS
}

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
* limitations under the License.
*/
package com.alibaba.nacos.naming.web;
package com.alibaba.nacos.core.code;
import com.alibaba.nacos.naming.controllers.*;
import org.apache.commons.lang3.ArrayUtils;
import org.reflections.Reflections;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*;
import javax.annotation.PostConstruct;
import java.lang.reflect.Method;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Basic methods for filter to use
* Method cache
*
* @author nkorange
* @since 1.0.0
* @since 1.2.0
*/
@Component
public class FilterBase {
public class ControllerMethodsCache {
private ConcurrentMap<String, Method> methodCache = new
private ConcurrentMap<String, Method> methods = new
ConcurrentHashMap<>();
@PostConstruct
public void init() {
initClassMethod(InstanceController.class);
initClassMethod(ServiceController.class);
initClassMethod(ClusterController.class);
initClassMethod(CatalogController.class);
initClassMethod(HealthController.class);
initClassMethod(RaftController.class);
initClassMethod(DistroController.class);
initClassMethod(OperatorController.class);
initClassMethod(ApiController.class);
public ConcurrentMap<String, Method> getMethods() {
return methods;
}
public Method getMethod(String httpMethod, String path) {
String key = httpMethod + "-->" + path.replace("/nacos", "");
return methodCache.get(key);
return methods.get(key);
}
private void initClassMethod(Class<?> clazz) {
public void initClassMethod(String packageName) {
Reflections reflections = new Reflections(packageName);
Set<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);
String classPath = requestMapping.value()[0];
for (Method method : clazz.getMethods()) {
if (!method.isAnnotationPresent(RequestMapping.class)) {
parseSubAnnotations(method, classPath);
continue;
}
requestMapping = method.getAnnotation(RequestMapping.class);
RequestMethod[] requestMethods = requestMapping.method();
if (requestMethods.length == 0) {
requestMethods = new RequestMethod[1];
requestMethods[0] = RequestMethod.GET;
}
for (String methodPath : requestMapping.value()) {
methodCache.put(requestMethods[0].name() + "-->" + classPath + methodPath, method);
for (String classPath : requestMapping.value()) {
for (Method method : clazz.getMethods()) {
if (!method.isAnnotationPresent(RequestMapping.class)) {
parseSubAnnotations(method, classPath);
continue;
}
requestMapping = method.getAnnotation(RequestMapping.class);
RequestMethod[] requestMethods = requestMapping.method();
if (requestMethods.length == 0) {
requestMethods = new RequestMethod[1];
requestMethods[0] = RequestMethod.GET;
}
for (String methodPath : requestMapping.value()) {
methods.put(requestMethods[0].name() + "-->" + classPath + methodPath, method);
}
}
}
}
@ -107,11 +115,11 @@ public class FilterBase {
private void put(RequestMethod requestMethod, String classPath, String[] requestPaths, Method method) {
if (ArrayUtils.isEmpty(requestPaths)) {
methodCache.put(requestMethod.name() + "-->" + classPath, method);
methods.put(requestMethod.name() + "-->" + classPath, method);
return;
}
for (String requestPath : requestPaths) {
methodCache.put(requestMethod.name() + "-->" + classPath + requestPath, method);
methods.put(requestMethod.name() + "-->" + classPath + requestPath, method);
}
}
}

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

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

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

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.naming.web;
package com.alibaba.nacos.core.utils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

View File

@ -34,11 +34,33 @@
</encoder>
</appender>
<appender name="core-auth"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/core-auth.log</file>
<append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/core-auth.log.%d{yyyy-MM-dd}.%i</fileNamePattern>
<maxFileSize>2GB</maxFileSize>
<MaxHistory>7</MaxHistory>
<totalSizeCap>7GB</totalSizeCap>
<cleanHistoryOnStart>true</cleanHistoryOnStart>
</rollingPolicy>
<encoder>
<Pattern>%date %level %msg%n%n</Pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<root>
<level value="INFO"/>
<appender-ref ref="rootFile"/>
</root>
<logger name="com.alibaba.nacos.core.auth" additivity="false">
<level value="DEBUG"/>
<appender-ref ref="core-auth"/>
</logger>
<springProfile name="standalone">
<logger name="org.springframework">

View File

@ -104,7 +104,7 @@ else
fi
JAVA_OPT="${JAVA_OPT} -Dloader.path=${BASE_DIR}/plugins/health,${BASE_DIR}/plugins/cmdb,${BASE_DIR}/plugins/mysql"
JAVA_OPT="${JAVA_OPT} -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000"
JAVA_OPT="${JAVA_OPT} -Dnacos.home=${BASE_DIR}"
JAVA_OPT="${JAVA_OPT} -jar ${BASE_DIR}/target/${SERVER}.jar"
JAVA_OPT="${JAVA_OPT} ${JAVA_OPT_EXT}"

View File

@ -1,23 +1,71 @@
# spring
server.contextPath=/nacos
#*************** Spring Boot Related Configurations ***************#
### Default web context path:
server.servlet.contextPath=/nacos
### Default web server port:
server.port=8848
#*************** Network Related Configurations ***************#
### If prefer hostname over ip for Nacos server addresses in cluster.conf:
# nacos.inetutils.prefer-hostname-over-ip=false
### Specify local server's IP:
# nacos.inetutils.ip-address=
#*************** Config Module Related Configurations ***************#
### If user MySQL as datasource:
# spring.datasource.platform=mysql
### Count of DB:
# db.num=1
### Connect URL of DB:
# db.url.0=jdbc:mysql://1.1.1.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
# db.user=user
# db.password=password
#*************** Naming Module Related Configurations ***************#
### Data dispatch task execution period in milliseconds:
# nacos.naming.distro.taskDispatchPeriod=200
### Data count of batch sync task:
# nacos.naming.distro.batchSyncKeyCount=1000
### Retry delay in milliseconds if sync task failed:
# nacos.naming.distro.syncRetryDelay=5000
### If enable data warmup. If set to false, the server would accept request without local data preparation:
# nacos.naming.data.warmup=true
### If enable the instance auto expiration, kind like of health check of instance:
# nacos.naming.expireInstance=true
#*************** CMDB Module Related Configurations ***************#
### The interval to dump external CMDB in seconds:
# nacos.cmdb.dumpTaskInterval=3600
### The interval of polling data change event in seconds:
# nacos.cmdb.eventTaskInterval=10
### The interval of loading labels in seconds:
# nacos.cmdb.labelTaskInterval=300
### If turn on data loading task:
# nacos.cmdb.loadDataAtStart=false
# metrics for prometheus
#*************** Metrics Related Configurations ***************#
### Metrics for prometheus
#management.endpoints.web.exposure.include=*
# metrics for elastic search
### Metrics for elastic search
management.metrics.export.elastic.enabled=false
#management.metrics.export.elastic.host=http://localhost:9200
# metrics for influx
### Metrics for influx
management.metrics.export.influx.enabled=false
#management.metrics.export.influx.db=springboot
#management.metrics.export.influx.uri=http://localhost:8086
@ -25,24 +73,41 @@ management.metrics.export.influx.enabled=false
#management.metrics.export.influx.consistency=one
#management.metrics.export.influx.compressed=true
#*************** Access Log Related Configurations ***************#
### If turn on the access log:
server.tomcat.accesslog.enabled=true
### The access log pattern:
server.tomcat.accesslog.pattern=%h %l %u %t "%r" %s %b %D %{User-Agent}i
# default current work dir
### The directory of access log:
server.tomcat.basedir=
## spring security config
### turn off security
#*************** Access Control Related Configurations ***************#
### If enable spring security, this option is deprecated in 1.2.0:
#spring.security.enabled=false
#management.security=false
#security.basic.enabled=false
#nacos.security.ignore.urls=/**
nacos.security.ignore.urls=/,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/v1/auth/login,/v1/console/health/**,/v1/cs/**,/v1/ns/**,/v1/cmdb/**,/actuator/**,/v1/console/server/**
### The ignore urls of auth, is deprecated in 1.2.0:
nacos.security.ignore.urls=/,/error,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/v1/auth/**,/v1/console/health/**,/actuator/**,/v1/console/server/**
# nacos.naming.distro.taskDispatchPeriod=200
# nacos.naming.distro.batchSyncKeyCount=1000
# nacos.naming.distro.syncRetryDelay=5000
# nacos.naming.data.warmup=true
# nacos.naming.expireInstance=true
### The auth system to use, currently only 'nacos' is supported:
nacos.core.auth.system.type=nacos
### If turn on auth system:
nacos.core.auth.enabled=false
### The token expiration in seconds:
nacos.core.auth.default.token.expire.seconds=18000
### The default token:
nacos.core.auth.default.token.secret.key=SecretKey012345678901234567890123456789012345678901234567890123456789
### Turn on/off caching of auth information. By turning on this switch, the update of auth information would have a 15 seconds delay.
nacos.core.auth.caching.enabled=true
#*************** Istio Related Configurations ***************#
### If turn on the MCP server:
nacos.istio.mcp.server.enabled=false

View File

@ -376,6 +376,23 @@
</encoder>
</appender>
<appender name="core-auth"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/core-auth.log</file>
<append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/core-auth.log.%d{yyyy-MM-dd}.%i</fileNamePattern>
<maxFileSize>2GB</maxFileSize>
<MaxHistory>7</MaxHistory>
<totalSizeCap>7GB</totalSizeCap>
<cleanHistoryOnStart>true</cleanHistoryOnStart>
</rollingPolicy>
<encoder>
<Pattern>%date %level %msg%n%n</Pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<logger name="com.alibaba.nacos.address.main" additivity="false">
<level value="INFO"/>
<appender-ref ref="nacos-address"/>
@ -461,6 +478,11 @@
<appender-ref ref="istio-main"/>
</logger>
<logger name="com.alibaba.nacos.core.auth" additivity="false">
<level value="DEBUG"/>
<appender-ref ref="core-auth"/>
</logger>
<springProfile name="standalone">
<logger name="org.springframework">
<appender-ref ref="CONSOLE"/>

View File

@ -178,17 +178,26 @@ CREATE TABLE `tenant_info` (
KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='tenant_info';
CREATE TABLE users (
username varchar(50) NOT NULL PRIMARY KEY,
password varchar(500) NOT NULL,
enabled boolean NOT NULL
CREATE TABLE `users` (
`username` varchar(50) NOT NULL PRIMARY KEY,
`password` varchar(500) NOT NULL,
`enabled` boolean NOT NULL
);
CREATE TABLE roles (
username varchar(50) NOT NULL,
role varchar(50) NOT NULL
CREATE TABLE `roles` (
`username` varchar(50) NOT NULL,
`role` varchar(50) NOT NULL,
UNIQUE INDEX `idx_user_role` (`username` ASC, `role` ASC) USING BTREE
);
CREATE TABLE `permissions` (
`role` varchar(50) NOT NULL,
`permission` varchar(512) NOT NULL,
`gmt_create` bigint NULL,
`gmt_modified` bigint NULL,
UNIQUE INDEX `idx_role_resource` (`role` ASC, `permission` ASC) USING BTREE
);
INSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE);
INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN');
INSERT INTO roles (username, role) VALUES ('nacos', 'GLOBAL_ADMIN');

View File

@ -175,14 +175,22 @@ CREATE INDEX tenant_info_tenant_id_idx ON tenant_info(tenant_id);
CREATE TABLE users (
username varchar(50) NOT NULL PRIMARY KEY,
password varchar(500) NOT NULL,
enabled boolean NOT NULL
enabled boolean NOT NULL DEFAULT true
);
CREATE TABLE roles (
username varchar(50) NOT NULL,
role varchar(50) NOT NULL
role varchar(50) NOT NULL,
constraint uk_username_role UNIQUE (username,role)
);
CREATE TABLE permissions (
role varchar(50) NOT NULL,
resource varchar(512) NOT NULL,
action varchar(8) NOT NULL,
constraint uk_role_permission UNIQUE (role,resource,action)
);
INSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE);
INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN');
INSERT INTO roles (username, role) VALUES ('nacos', 'GLOBAL_ADMIN');

View File

@ -1,119 +0,0 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.naming.acl;
import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.api.naming.CommonParams;
import com.alibaba.nacos.core.utils.WebUtils;
import com.alibaba.nacos.naming.core.Service;
import com.alibaba.nacos.naming.core.ServiceManager;
import com.alibaba.nacos.naming.misc.SwitchDomain;
import com.alibaba.nacos.naming.misc.UtilsAndCommons;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.security.AccessControlException;
import java.util.Map;
/**
* @author nkorange
*/
@Component
public class AuthChecker {
@Autowired
private ServiceManager serviceManager;
@Autowired
private SwitchDomain switchDomain;
static private String[] APP_WHITE_LIST = {};
static private String[] TOKEN_WHITE_LIST = {"traffic-scheduling@midware"};
public void doRaftAuth(HttpServletRequest req) throws Exception {
String token = req.getParameter("token");
if (StringUtils.equals(UtilsAndCommons.SUPER_TOKEN, token)) {
return;
}
String agent = WebUtils.getUserAgent(req);
if (StringUtils.startsWith(agent, UtilsAndCommons.NACOS_SERVER_HEADER)) {
return;
}
throw new IllegalAccessException("illegal access,agent= " + agent + ", token=" + token);
}
public void doAuth(Map<String, String[]> params, HttpServletRequest req) throws Exception {
String namespaceId = WebUtils.optional(req, CommonParams.NAMESPACE_ID,
Constants.DEFAULT_NAMESPACE_ID);
String serviceName = WebUtils.optional(req, "name", "");
if (StringUtils.isEmpty(serviceName)) {
serviceName = WebUtils.optional(req, "serviceName", "");
}
if (StringUtils.isEmpty(serviceName)) {
serviceName = WebUtils.optional(req, "tag", "");
}
Service service = serviceManager.getService(namespaceId, serviceName);
if (service == null) {
if (!req.getRequestURI().equals(UtilsAndCommons.NACOS_NAMING_CONTEXT + UtilsAndCommons.API_SET_ALL_WEIGHTS)) {
throw new IllegalStateException("auth failed, service does not exist: " + serviceName);
}
}
String token = req.getParameter("token");
String auth = req.getParameter("auth");
String userName = req.getParameter("userName");
if (StringUtils.isEmpty(auth) && StringUtils.isEmpty(token)) {
throw new IllegalArgumentException("provide 'authInfo' or 'token' to access this service");
}
// try valid token
if ((service != null && StringUtils.equals(service.getToken(), token))) {
return;
}
if (ArrayUtils.contains(TOKEN_WHITE_LIST, token)) {
return;
}
if (ArrayUtils.contains(APP_WHITE_LIST, userName)) {
return;
}
// if token failed, try AuthInfo
AuthInfo authInfo = AuthInfo.fromString(auth, WebUtils.getAcceptEncoding(req));
if (authInfo == null) {
throw new IllegalAccessException("invalid token or malformed auth info");
}
if (!ArrayUtils.contains(APP_WHITE_LIST, authInfo.getAppKey())) {
throw new AccessControlException("un-registered SDK app");
}
if (!service.getOwners().contains(authInfo.getOperator())
&& !switchDomain.getMasters().contains(authInfo.getOperator())) {
throw new AccessControlException("dom already exists and you're not among the owners");
}
}
}

View File

@ -1,83 +0,0 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.naming.acl;
import java.nio.charset.StandardCharsets;
/**
* @author nkorange
*/
public class AuthInfo {
private String operator;
private String appKey;
public AuthInfo() {
}
public AuthInfo(String operator, String appKey) {
this.operator = operator;
this.appKey = appKey;
}
public static AuthInfo fromString(String auth, String encoding) {
try {
String[] byteStrs = auth.split(",");
byte[] bytes = new byte[byteStrs.length];
for (int i = 0; i < byteStrs.length; i++) {
bytes[i] = (byte) (~(Short.parseShort(byteStrs[i])));
}
String contentStr = new String(bytes, encoding);
String[] params = contentStr.split(":");
return new AuthInfo(params[0], params[1]);
} catch (Throwable e) {
return null;
}
}
@Override
public String toString() {
try {
// very simple encryption is enough
byte[] authBytes = (operator + ":" + appKey).getBytes(StandardCharsets.UTF_8);
StringBuilder authBuilder = new StringBuilder();
for (byte authByte : authBytes) {
authBuilder.append((byte) (~((short) authByte))).append(",");
}
return authBuilder.substring(0, authBuilder.length() - 1);
} catch (Exception e) {
return "Error while encrypt AuthInfo" + e;
}
}
public void setOperator(String operator) {
this.operator = operator;
}
public String getOperator() {
return operator;
}
public String getAppKey() {
return appKey;
}
public void setAppKey(String appKey) {
this.appKey = appKey;
}
}

View File

@ -293,7 +293,7 @@ public class RaftCore {
local.resetLeaderDue();
// if data should be persistent, usually this is always true:
// if data should be persisted, usually this is true:
if (KeyBuilder.matchPersistentKey(datum.key)) {
raftStore.write(datum);
}

View File

@ -25,7 +25,7 @@ import com.alibaba.nacos.naming.core.ServiceManager;
import com.alibaba.nacos.naming.misc.UtilsAndCommons;
import com.alibaba.nacos.naming.push.ClientInfo;
import com.alibaba.nacos.naming.web.CanDistro;
import com.alibaba.nacos.naming.web.OverrideParameterRequestWrapper;
import com.alibaba.nacos.core.utils.OverrideParameterRequestWrapper;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.codehaus.jackson.util.VersionUtil;

View File

@ -22,6 +22,8 @@ import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.CommonParams;
import com.alibaba.nacos.api.naming.pojo.Cluster;
import com.alibaba.nacos.api.naming.utils.NamingUtils;
import com.alibaba.nacos.core.auth.ActionTypes;
import com.alibaba.nacos.core.auth.Secured;
import com.alibaba.nacos.core.utils.WebUtils;
import com.alibaba.nacos.naming.core.Instance;
import com.alibaba.nacos.naming.core.Service;
@ -53,6 +55,7 @@ public class CatalogController {
@Autowired
protected ServiceManager serviceManager;
@Secured(action = ActionTypes.READ)
@GetMapping("/service")
public JSONObject serviceDetail(@RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId,
String serviceName) throws NacosException {
@ -93,6 +96,7 @@ public class CatalogController {
return detailView;
}
@Secured(action = ActionTypes.READ)
@RequestMapping(value = "/instances")
public JSONObject instanceList(@RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId,
@RequestParam String serviceName,
@ -134,6 +138,7 @@ public class CatalogController {
return result;
}
@Secured(action = ActionTypes.READ)
@GetMapping("/services")
public Object listDetail(@RequestParam(required = false) boolean withInstances,
@RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId,

View File

@ -21,6 +21,8 @@ import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.CommonParams;
import com.alibaba.nacos.api.naming.pojo.AbstractHealthChecker;
import com.alibaba.nacos.core.auth.ActionTypes;
import com.alibaba.nacos.core.auth.Secured;
import com.alibaba.nacos.core.utils.WebUtils;
import com.alibaba.nacos.naming.core.Cluster;
import com.alibaba.nacos.naming.core.Service;
@ -49,6 +51,7 @@ public class ClusterController {
protected ServiceManager serviceManager;
@PutMapping
@Secured(action = ActionTypes.WRITE)
public String update(HttpServletRequest request) throws Exception {
String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID,

View File

@ -19,6 +19,8 @@ import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.api.naming.CommonParams;
import com.alibaba.nacos.api.naming.pojo.AbstractHealthChecker;
import com.alibaba.nacos.core.auth.ActionTypes;
import com.alibaba.nacos.core.auth.Secured;
import com.alibaba.nacos.core.utils.WebUtils;
import com.alibaba.nacos.naming.boot.RunningConfig;
import com.alibaba.nacos.naming.core.Instance;
@ -72,6 +74,7 @@ public class HealthController {
@CanDistro
@PutMapping(value = {"", "/instance"})
@Secured(action = ActionTypes.WRITE)
public String update(HttpServletRequest request) {
String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID,

View File

@ -23,6 +23,8 @@ import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.CommonParams;
import com.alibaba.nacos.api.naming.NamingResponseCode;
import com.alibaba.nacos.api.naming.utils.NamingUtils;
import com.alibaba.nacos.core.auth.ActionTypes;
import com.alibaba.nacos.core.auth.Secured;
import com.alibaba.nacos.core.utils.WebUtils;
import com.alibaba.nacos.naming.core.Instance;
import com.alibaba.nacos.naming.core.Service;
@ -36,6 +38,7 @@ import com.alibaba.nacos.naming.push.ClientInfo;
import com.alibaba.nacos.naming.push.DataSource;
import com.alibaba.nacos.naming.push.PushService;
import com.alibaba.nacos.naming.web.CanDistro;
import com.alibaba.nacos.naming.web.NamingResourceParser;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
@ -86,8 +89,10 @@ public class InstanceController {
}
};
@CanDistro
@PostMapping
@Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
public String register(HttpServletRequest request) throws Exception {
String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
@ -99,6 +104,7 @@ public class InstanceController {
@CanDistro
@DeleteMapping
@Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
public String deregister(HttpServletRequest request) throws Exception {
Instance instance = getIPAddress(request);
String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID,
@ -118,6 +124,7 @@ public class InstanceController {
@CanDistro
@PutMapping
@Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
public String update(HttpServletRequest request) throws Exception {
String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
@ -137,6 +144,7 @@ public class InstanceController {
@CanDistro
@PatchMapping
@Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
public String patch(HttpServletRequest request) throws Exception {
String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
@ -180,6 +188,7 @@ public class InstanceController {
}
@GetMapping("/list")
@Secured(parser = NamingResourceParser.class, action = ActionTypes.READ)
public JSONObject list(HttpServletRequest request) throws Exception {
String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID,
@ -204,6 +213,7 @@ public class InstanceController {
}
@GetMapping
@Secured(parser = NamingResourceParser.class, action = ActionTypes.READ)
public JSONObject detail(HttpServletRequest request) throws Exception {
String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID,
@ -247,6 +257,7 @@ public class InstanceController {
@CanDistro
@PutMapping("/beat")
@Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
public JSONObject beat(HttpServletRequest request) throws Exception {
JSONObject result = new JSONObject();

View File

@ -19,6 +19,8 @@ import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.core.auth.ActionTypes;
import com.alibaba.nacos.core.auth.Secured;
import com.alibaba.nacos.core.utils.SystemUtils;
import com.alibaba.nacos.naming.cluster.ServerListManager;
import com.alibaba.nacos.naming.cluster.ServerStatusManager;
@ -30,7 +32,6 @@ import com.alibaba.nacos.naming.core.ServiceManager;
import com.alibaba.nacos.naming.misc.*;
import com.alibaba.nacos.naming.pojo.ClusterStateView;
import com.alibaba.nacos.naming.push.PushService;
import com.alibaba.nacos.naming.web.NeedAuth;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
@ -117,7 +118,7 @@ public class OperatorController {
return switchDomain;
}
@NeedAuth
@Secured(resource = "naming/switches", action = ActionTypes.WRITE)
@PutMapping("/switches")
public String updateSwitch(@RequestParam(required = false) boolean debug,
@RequestParam String entry, @RequestParam String value) throws Exception {
@ -127,6 +128,7 @@ public class OperatorController {
return "ok";
}
@Secured(resource = "naming/metrics", action = ActionTypes.READ)
@GetMapping("/metrics")
public JSONObject metrics(HttpServletRequest request) {

View File

@ -34,7 +34,6 @@ import com.alibaba.nacos.naming.core.ServiceManager;
import com.alibaba.nacos.naming.misc.NetUtils;
import com.alibaba.nacos.naming.misc.SwitchDomain;
import com.alibaba.nacos.naming.misc.UtilsAndCommons;
import com.alibaba.nacos.naming.web.NeedAuth;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@ -67,7 +66,6 @@ public class RaftController {
@Autowired
private RaftCore raftCore;
@NeedAuth
@PostMapping("/vote")
public JSONObject vote(HttpServletRequest request, HttpServletResponse response) throws Exception {
@ -77,7 +75,6 @@ public class RaftController {
return JSON.parseObject(JSON.toJSONString(peer));
}
@NeedAuth
@PostMapping("/beat")
public JSONObject beat(HttpServletRequest request, HttpServletResponse response) throws Exception {
@ -93,7 +90,6 @@ public class RaftController {
return JSON.parseObject(JSON.toJSONString(peer));
}
@NeedAuth
@GetMapping("/peer")
public JSONObject getPeer(HttpServletRequest request, HttpServletResponse response) {
List<RaftPeer> peers = raftCore.getPeers();
@ -113,7 +109,6 @@ public class RaftController {
return JSON.parseObject(JSON.toJSONString(peer));
}
@NeedAuth
@PutMapping("/datum/reload")
public String reloadDatum(HttpServletRequest request, HttpServletResponse response) throws Exception {
String key = WebUtils.required(request, "key");
@ -121,7 +116,6 @@ public class RaftController {
return "ok";
}
@NeedAuth
@PostMapping("/datum")
public String publish(HttpServletRequest request, HttpServletResponse response) throws Exception {
@ -152,7 +146,6 @@ public class RaftController {
throw new NacosException(NacosException.INVALID_PARAM, "unknown type publish key: " + key);
}
@NeedAuth
@DeleteMapping("/datum")
public String delete(HttpServletRequest request, HttpServletResponse response) throws Exception {
@ -163,7 +156,6 @@ public class RaftController {
return "ok";
}
@NeedAuth
@GetMapping("/datum")
public String get(HttpServletRequest request, HttpServletResponse response) throws Exception {
@ -197,7 +189,6 @@ public class RaftController {
return result;
}
@NeedAuth
@PostMapping("/datum/commit")
public String onPublish(HttpServletRequest request, HttpServletResponse response) throws Exception {
@ -229,7 +220,6 @@ public class RaftController {
return "ok";
}
@NeedAuth
@DeleteMapping("/datum/commit")
public String onDelete(HttpServletRequest request, HttpServletResponse response) throws Exception {

View File

@ -24,6 +24,8 @@ import com.alibaba.nacos.api.naming.CommonParams;
import com.alibaba.nacos.api.naming.utils.NamingUtils;
import com.alibaba.nacos.api.selector.SelectorType;
import com.alibaba.nacos.common.utils.IoUtils;
import com.alibaba.nacos.core.auth.ActionTypes;
import com.alibaba.nacos.core.auth.Secured;
import com.alibaba.nacos.core.utils.WebUtils;
import com.alibaba.nacos.naming.cluster.ServerListManager;
import com.alibaba.nacos.naming.core.*;
@ -33,6 +35,7 @@ import com.alibaba.nacos.naming.pojo.Subscriber;
import com.alibaba.nacos.naming.selector.LabelSelector;
import com.alibaba.nacos.naming.selector.NoneSelector;
import com.alibaba.nacos.naming.selector.Selector;
import com.alibaba.nacos.naming.web.NamingResourceParser;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.beans.factory.annotation.Autowired;
@ -64,6 +67,7 @@ public class ServiceController {
private SubscribeManager subscribeManager;
@PostMapping
@Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
public String create(@RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId,
@RequestParam String serviceName,
@RequestParam(required = false) float protectThreshold,
@ -98,6 +102,7 @@ public class ServiceController {
}
@DeleteMapping
@Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
public String remove(@RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId,
@RequestParam String serviceName) throws Exception {
@ -107,6 +112,7 @@ public class ServiceController {
}
@GetMapping
@Secured(parser = NamingResourceParser.class, action = ActionTypes.READ)
public JSONObject detail(@RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId,
@RequestParam String serviceName) throws NacosException {
@ -138,6 +144,7 @@ public class ServiceController {
}
@GetMapping("/list")
@Secured(parser = NamingResourceParser.class, action = ActionTypes.READ)
public JSONObject list(HttpServletRequest request) throws Exception {
int pageNo = NumberUtils.toInt(WebUtils.required(request, "pageNo"));
@ -209,6 +216,7 @@ public class ServiceController {
}
@PutMapping
@Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
public String update(HttpServletRequest request) throws Exception {
String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID,
@ -238,6 +246,7 @@ public class ServiceController {
}
@RequestMapping("/names")
@Secured(parser = NamingResourceParser.class, action = ActionTypes.READ)
public JSONObject searchService(@RequestParam(defaultValue = StringUtils.EMPTY) String namespaceId,
@RequestParam(defaultValue = StringUtils.EMPTY) String expr,
@RequestParam(required = false) boolean responsibleOnly) {
@ -350,6 +359,7 @@ public class ServiceController {
* @return
*/
@GetMapping("/subscribers")
@Secured(parser = NamingResourceParser.class, action = ActionTypes.READ)
public JSONObject subscribers(HttpServletRequest request) {
int pageNo = NumberUtils.toInt(WebUtils.required(request, "pageNo"));

View File

@ -16,6 +16,7 @@
package com.alibaba.nacos.naming.exception;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.core.auth.AccessException;
import com.alibaba.nacos.naming.misc.Loggers;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

View File

@ -62,7 +62,6 @@ public class GlobalExecutor {
}
});
private static ScheduledExecutorService dataSyncExecutor =
new ScheduledThreadPoolExecutor(Runtime.getRuntime().availableProcessors(), new ThreadFactory() {
@Override

View File

@ -419,10 +419,7 @@ public class HttpClient {
inputStream = new GZIPInputStream(inputStream);
}
HttpResult result = new HttpResult(respCode, IoUtils.toString(inputStream, getCharset(conn)), respHeaders);
inputStream.close();
return result;
return new HttpResult(respCode, IoUtils.toString(inputStream, getCharset(conn)), respHeaders);
}
private static String getCharset(HttpURLConnection conn) {

View File

@ -195,18 +195,6 @@ public class UtilsAndCommons {
}
public static String getAllExceptionMsg(Throwable e) {
Throwable cause = e;
StringBuilder strBuilder = new StringBuilder();
while (cause != null && !StringUtils.isEmpty(cause.getMessage())) {
strBuilder.append("caused: ").append(cause.getMessage()).append(";");
cause = cause.getCause();
}
return strBuilder.toString();
}
public static String getSwitchDomainKey() {
return UtilsAndCommons.DOMAINS_DATA_ID_PRE + UtilsAndCommons.SWITCH_DOMAIN_NAME;
}

View File

@ -1,104 +0,0 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.naming.web;
import com.alibaba.nacos.api.naming.CommonParams;
import com.alibaba.nacos.naming.acl.AuthChecker;
import com.alibaba.nacos.naming.misc.SwitchDomain;
import com.alibaba.nacos.naming.misc.UtilsAndCommons;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URI;
import java.security.AccessControlException;
/**
* @author nkorange
*/
public class AuthFilter implements Filter {
private static final String[] NAMESPACE_FORBIDDEN_STRINGS = new String[]{"..", "/"};
@Autowired
private AuthChecker authChecker;
@Autowired
private SwitchDomain switchDomain;
@Autowired
private FilterBase filterBase;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
HttpServletResponse resp = (HttpServletResponse) servletResponse;
try {
String path = new URI(req.getRequestURI()).getPath();
Method method = filterBase.getMethod(req.getMethod(), path);
if (method == null) {
throw new NoSuchMethodException();
}
if (method.isAnnotationPresent(NeedAuth.class) && !switchDomain.isEnableAuthentication()) {
// leave it empty.
}
// Check namespace:
String namespaceId = req.getParameter(CommonParams.NAMESPACE_ID);
if (StringUtils.isNotBlank(namespaceId)) {
if (namespaceId.contains(NAMESPACE_FORBIDDEN_STRINGS[0]) || namespaceId.contains(NAMESPACE_FORBIDDEN_STRINGS[1])) {
throw new IllegalArgumentException("forbidden namespace: " + namespaceId);
}
}
} catch (AccessControlException e) {
resp.sendError(HttpServletResponse.SC_FORBIDDEN, "access denied: " + UtilsAndCommons.getAllExceptionMsg(e));
return;
} catch (NoSuchMethodException e) {
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, "no such api");
return;
} catch (IllegalArgumentException e) {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, UtilsAndCommons.getAllExceptionMsg(e));
return;
} catch (Exception e) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Server failed," + UtilsAndCommons.getAllExceptionMsg(e));
return;
}
filterChain.doFilter(req, resp);
}
@Override
public void destroy() {
}
}

View File

@ -19,10 +19,12 @@ import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.api.naming.CommonParams;
import com.alibaba.nacos.common.constant.HttpHeaderConsts;
import com.alibaba.nacos.common.utils.IoUtils;
import com.alibaba.nacos.core.code.ControllerMethodsCache;
import com.alibaba.nacos.core.utils.ExceptionUtil;
import com.alibaba.nacos.core.utils.OverrideParameterRequestWrapper;
import com.alibaba.nacos.naming.core.DistroMapper;
import com.alibaba.nacos.naming.misc.HttpClient;
import com.alibaba.nacos.naming.misc.Loggers;
import com.alibaba.nacos.naming.misc.SwitchDomain;
import com.alibaba.nacos.naming.misc.UtilsAndCommons;
import org.apache.commons.codec.Charsets;
import org.apache.commons.lang3.StringUtils;
@ -52,10 +54,7 @@ public class DistroFilter implements Filter {
private DistroMapper distroMapper;
@Autowired
private SwitchDomain switchDomain;
@Autowired
private FilterBase filterBase;
private ControllerMethodsCache controllerMethodsCache;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
@ -80,7 +79,11 @@ public class DistroFilter implements Filter {
if (StringUtils.isBlank(serviceName)) {
serviceName = req.getParameter("dom");
}
Method method = filterBase.getMethod(req.getMethod(), path);
if (StringUtils.isNotBlank(serviceName)) {
serviceName = serviceName.trim();
}
Method method = controllerMethodsCache.getMethod(req.getMethod(), path);
if (method == null) {
throw new NoSuchMethodException(req.getMethod() + " " + path);
@ -139,14 +142,15 @@ public class DistroFilter implements Filter {
requestWrapper.addParameter(CommonParams.SERVICE_NAME, groupedServiceName);
filterChain.doFilter(requestWrapper, resp);
} catch (AccessControlException e) {
resp.sendError(HttpServletResponse.SC_FORBIDDEN, "access denied: " + UtilsAndCommons.getAllExceptionMsg(e));
resp.sendError(HttpServletResponse.SC_FORBIDDEN, "access denied: " + ExceptionUtil.getAllExceptionMsg(e));
return;
} catch (NoSuchMethodException e) {
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, "no such api: " + e.getMessage());
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED,
"no such api:" + req.getMethod() + ":" + req.getRequestURI());
return;
} catch (Exception e) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Server failed," + UtilsAndCommons.getAllExceptionMsg(e));
"Server failed," + ExceptionUtil.getAllExceptionMsg(e));
return;
}

View File

@ -47,18 +47,6 @@ public class NamingConfig {
return registration;
}
@Bean
public FilterRegistrationBean authFilterRegistration() {
FilterRegistrationBean<AuthFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(authFilter());
registration.addUrlPatterns("/v1/ns/*");
registration.setName("authFilter");
registration.setOrder(5);
return registration;
}
@Bean
public DistroFilter distroFilter() {
return new DistroFilter();
@ -69,9 +57,4 @@ public class NamingConfig {
return new TrafficReviseFilter();
}
@Bean
public AuthFilter authFilter() {
return new AuthFilter();
}
}

View File

@ -0,0 +1,72 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.naming.web;
import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.api.naming.CommonParams;
import com.alibaba.nacos.api.naming.utils.NamingUtils;
import com.alibaba.nacos.core.auth.Resource;
import com.alibaba.nacos.core.auth.ResourceParser;
import org.apache.commons.lang3.StringUtils;
import javax.servlet.http.HttpServletRequest;
/**
* Naming resource parser
*
* @author nkorange
* @since 1.2.0
*/
public class NamingResourceParser implements ResourceParser {
private static final String AUTH_NAMING_PREFIX = "naming/";
@Override
public String parseName(Object request) {
HttpServletRequest req = (HttpServletRequest) request;
String namespaceId = req.getParameter(CommonParams.NAMESPACE_ID);
String serviceName = req.getParameter(CommonParams.SERVICE_NAME);
String groupName = req.getParameter(CommonParams.GROUP_NAME);
if (StringUtils.isBlank(groupName)) {
groupName = NamingUtils.getGroupName(serviceName);
}
serviceName = NamingUtils.getServiceName(serviceName);
if (StringUtils.isBlank(namespaceId)) {
namespaceId = Constants.DEFAULT_NAMESPACE_ID;
}
StringBuilder sb = new StringBuilder();
sb.append(namespaceId).append(Resource.SPLITTER);
if (StringUtils.isBlank(serviceName)) {
sb.append("*")
.append(Resource.SPLITTER)
.append(AUTH_NAMING_PREFIX)
.append("*");
} else {
sb.append(groupName)
.append(Resource.SPLITTER)
.append(AUTH_NAMING_PREFIX)
.append(serviceName);
}
return sb.toString();
}
}

View File

@ -16,11 +16,11 @@
package com.alibaba.nacos.naming.web;
import com.alibaba.nacos.common.utils.HttpMethod;
import com.alibaba.nacos.core.utils.Constants;
import com.alibaba.nacos.core.utils.WebUtils;
import com.alibaba.nacos.naming.cluster.ServerStatus;
import com.alibaba.nacos.naming.cluster.ServerStatusManager;
import com.alibaba.nacos.naming.misc.SwitchDomain;
import com.alibaba.nacos.naming.misc.UtilsAndCommons;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
@ -73,7 +73,7 @@ public class TrafficReviseFilter implements Filter {
// requests from peer server should be let pass:
String agent = WebUtils.getUserAgent(req);
if (StringUtils.startsWith(agent, UtilsAndCommons.NACOS_SERVER_HEADER)) {
if (StringUtils.startsWith(agent, Constants.NACOS_SERVER_HEADER)) {
filterChain.doFilter(req, resp);
return;
}

23
pom.xml
View File

@ -525,6 +525,11 @@
<artifactId>nacos-example</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>nacos-address</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
@ -539,12 +544,12 @@
<version>1.2.58</version>
</dependency>
<!-- javax libs-->
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs</artifactId>
<version>2.1</version>
</dependency>
<!-- &lt;!&ndash; javax libs&ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>javax.ws.rs</groupId>-->
<!-- <artifactId>javax.ws.rs</artifactId>-->
<!-- <version>1.0</version>-->
<!-- </dependency>-->
<dependency>
<groupId>javax.servlet</groupId>
@ -783,6 +788,12 @@
<type>pom</type>
</dependency>
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.9.11</version>
</dependency>
</dependencies>
</dependencyManagement>

View File

@ -75,6 +75,16 @@
<artifactId>nacos-core</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>nacos-console</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>nacos-address</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>

View File

@ -0,0 +1,52 @@
package com.alibaba.nacos.test.base;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.util.MultiValueMap;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URL;
/**
* Http client for test module
*
* @author nkorange
* @since 1.2.0
*/
public class HttpClient4Test {
protected URL base;
@Autowired
protected TestRestTemplate restTemplate;
protected <T> ResponseEntity<T> request(String path, MultiValueMap<String, String> params, Class<T> clazz) {
HttpHeaders headers = new HttpHeaders();
HttpEntity<?> entity = new HttpEntity<T>(headers);
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(this.base.toString() + path)
.queryParams(params);
return this.restTemplate.exchange(
builder.toUriString(), HttpMethod.GET, entity, clazz);
}
protected <T> ResponseEntity<T> request(String path, MultiValueMap<String, String> params, Class<T> clazz, HttpMethod httpMethod) {
HttpHeaders headers = new HttpHeaders();
HttpEntity<?> entity = new HttpEntity<T>(headers);
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(this.base.toString() + path)
.queryParams(params);
return this.restTemplate.exchange(
builder.toUriString(), httpMethod, entity, clazz);
}
}

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.test.naming;
package com.alibaba.nacos.test.base;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

View File

@ -20,7 +20,7 @@ import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.config.server.Config;
import com.alibaba.nacos.test.naming.Params;
import com.alibaba.nacos.test.base.Params;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

View File

@ -0,0 +1,313 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.test.core.auth;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.alibaba.nacos.Nacos;
import com.alibaba.nacos.config.server.model.Page;
import com.alibaba.nacos.core.auth.Permission;
import com.alibaba.nacos.test.base.HttpClient4Test;
import com.alibaba.nacos.test.base.Params;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringRunner;
import java.net.URL;
import java.util.concurrent.TimeUnit;
/**
* @author nkorange
* @since 1.2.0
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Nacos.class, properties = {"server.servlet.context-path=/nacos", "server.port=7001"},
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class Permission_ITCase extends HttpClient4Test {
@LocalServerPort
private int port;
private String accessToken;
@Before
public void init() throws Exception {
TimeUnit.SECONDS.sleep(5L);
String url = String.format("http://localhost:%d/", port);
this.base = new URL(url);
}
@After
public void destroy() {
// Delete permission:
ResponseEntity<String> response = request("/nacos/v1/auth/permissions",
Params.newParams()
.appendParam("role", "role1")
.appendParam("resource", "public:*:*")
.appendParam("action", "rw")
.appendParam("accessToken", accessToken)
.done(),
String.class,
HttpMethod.DELETE);
Assert.assertTrue(response.getStatusCode().is2xxSuccessful());
// Delete permission:
response = request("/nacos/v1/auth/permissions",
Params.newParams()
.appendParam("role", "role1")
.appendParam("resource", "test1:*:*")
.appendParam("action", "r")
.appendParam("accessToken", accessToken)
.done(),
String.class,
HttpMethod.DELETE);
Assert.assertTrue(response.getStatusCode().is2xxSuccessful());
// Delete role:
response = request("/nacos/v1/auth/roles",
Params.newParams()
.appendParam("role", "role1")
.appendParam("username", "username2")
.appendParam("accessToken", accessToken)
.done(),
String.class,
HttpMethod.DELETE);
Assert.assertTrue(response.getStatusCode().is2xxSuccessful());
// Delete a user:
response = request("/nacos/v1/auth/users",
Params.newParams()
.appendParam("username", "username3")
.appendParam("accessToken", accessToken)
.done(),
String.class,
HttpMethod.DELETE);
Assert.assertTrue(response.getStatusCode().is2xxSuccessful());
}
@Test
public void login() {
ResponseEntity<String> response = request("/nacos/v1/auth/users/login",
Params.newParams()
.appendParam("username", "nacos")
.appendParam("password", "nacos")
.done(),
String.class,
HttpMethod.POST);
Assert.assertTrue(response.getStatusCode().is2xxSuccessful());
JSONObject json = JSON.parseObject(response.getBody());
Assert.assertTrue(json.containsKey("accessToken"));
accessToken = json.getString("accessToken");
}
@Test
public void createDeleteQueryPermission() {
login();
// Create a user:
ResponseEntity<String> response = request("/nacos/v1/auth/users",
Params.newParams()
.appendParam("username", "username3")
.appendParam("password", "password1")
.appendParam("accessToken", accessToken)
.done(),
String.class,
HttpMethod.POST);
Assert.assertTrue(response.getStatusCode().is2xxSuccessful());
// Create role:
response = request("/nacos/v1/auth/roles",
Params.newParams()
.appendParam("role", "role1")
.appendParam("username", "username3")
.appendParam("accessToken", accessToken)
.done(),
String.class,
HttpMethod.POST);
Assert.assertTrue(response.getStatusCode().is2xxSuccessful());
// Create permission:
response = request("/nacos/v1/auth/permissions",
Params.newParams()
.appendParam("role", "role1")
.appendParam("resource", "public:*:*")
.appendParam("action", "rw")
.appendParam("accessToken", accessToken)
.done(),
String.class,
HttpMethod.POST);
Assert.assertTrue(response.getStatusCode().is2xxSuccessful());
// Create another permission:
response = request("/nacos/v1/auth/permissions",
Params.newParams()
.appendParam("role", "role1")
.appendParam("resource", "test1:*:*")
.appendParam("action", "r")
.appendParam("accessToken", accessToken)
.done(),
String.class,
HttpMethod.POST);
Assert.assertTrue(response.getStatusCode().is2xxSuccessful());
// Query permission:
response = request("/nacos/v1/auth/permissions",
Params.newParams()
.appendParam("role", "role1")
.appendParam("pageNo", "1")
.appendParam("pageSize", "10")
.appendParam("accessToken", accessToken)
.done(),
String.class,
HttpMethod.GET);
Assert.assertTrue(response.getStatusCode().is2xxSuccessful());
Page<Permission> permissionPage = JSON.parseObject(response.getBody(), new TypeReference<Page<Permission>>() {
});
Assert.assertNotNull(permissionPage);
Assert.assertNotNull(permissionPage.getPageItems());
boolean found1=false,found2=false;
for (Permission permission : permissionPage.getPageItems()) {
if (permission.getResource().equals("public:*:*") && permission.getAction().equals("rw")) {
found1 = true;
}
if (permission.getResource().equals("test1:*:*") && permission.getAction().equals("r")) {
found2 = true;
}
if (found1 && found2) {
break;
}
}
Assert.assertTrue(found1);
Assert.assertTrue(found2);
// Delete permission:
response = request("/nacos/v1/auth/permissions",
Params.newParams()
.appendParam("role", "role1")
.appendParam("resource", "public:*:*")
.appendParam("action", "rw")
.appendParam("accessToken", accessToken)
.done(),
String.class,
HttpMethod.DELETE);
Assert.assertTrue(response.getStatusCode().is2xxSuccessful());
// Query permission:
response = request("/nacos/v1/auth/permissions",
Params.newParams()
.appendParam("role", "role1")
.appendParam("pageNo", "1")
.appendParam("pageSize", "10")
.appendParam("accessToken", accessToken)
.done(),
String.class,
HttpMethod.GET);
Assert.assertTrue(response.getStatusCode().is2xxSuccessful());
permissionPage = JSON.parseObject(response.getBody(), new TypeReference<Page<Permission>>() {
});
Assert.assertNotNull(permissionPage);
Assert.assertNotNull(permissionPage.getPageItems());
found1=false;
found2=false;
for (Permission permission : permissionPage.getPageItems()) {
if (permission.getResource().equals("public:*:*") && permission.getAction().equals("rw")) {
found1 = true;
}
if (permission.getResource().equals("test1:*:*") && permission.getAction().equals("r")) {
found2 = true;
}
}
Assert.assertFalse(found1);
Assert.assertTrue(found2);
// Delete permission:
response = request("/nacos/v1/auth/permissions",
Params.newParams()
.appendParam("role", "role1")
.appendParam("resource", "test1:*:*")
.appendParam("action", "r")
.appendParam("accessToken", accessToken)
.done(),
String.class,
HttpMethod.DELETE);
Assert.assertTrue(response.getStatusCode().is2xxSuccessful());
// Query permission:
response = request("/nacos/v1/auth/permissions",
Params.newParams()
.appendParam("role", "role1")
.appendParam("pageNo", "1")
.appendParam("pageSize", "10")
.appendParam("accessToken", accessToken)
.done(),
String.class,
HttpMethod.GET);
Assert.assertTrue(response.getStatusCode().is2xxSuccessful());
permissionPage = JSON.parseObject(response.getBody(), new TypeReference<Page<Permission>>() {
});
Assert.assertNotNull(permissionPage);
Assert.assertNotNull(permissionPage.getPageItems());
found1=false;
found2=false;
for (Permission permission : permissionPage.getPageItems()) {
if (permission.getResource().equals("public:*:*") && permission.getAction().equals("rw")) {
found1 = true;
}
if (permission.getResource().equals("test1:*:*") && permission.getAction().equals("r")) {
found2 = true;
}
}
Assert.assertFalse(found1);
Assert.assertFalse(found2);
}
}

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