[ISSUE # 5696] Replace original auth client (#6885)

* Replace auth client

* Replace auth client original implement

* Replace original auth client

* Revise client: combine function and modify name
This commit is contained in:
Wuyunfan-BUPT 2021-09-17 20:43:30 -05:00 committed by GitHub
parent af8d04f59b
commit c771c5d2b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 81 additions and 253 deletions

View File

@ -1045,7 +1045,7 @@ public class ClientWorker implements Closeable {
private Response requestProxy(RpcClient rpcClientInner, Request request, long timeoutMills)
throws NacosException {
try {
request.putAllHeader(super.getSecurityHeaders());
request.putAllHeader(super.getAccessToken());
request.putAllHeader(super.getSpasHeaders());
request.putAllHeader(super.getCommonHeader());
} catch (Exception e) {

View File

@ -69,6 +69,8 @@ public abstract class ConfigTransportClient {
final ServerListManager serverListManager;
final Properties properties;
private int maxRetry = 3;
private final long securityInfoRefreshIntervalMills = TimeUnit.SECONDS.toMillis(5);
@ -96,7 +98,8 @@ public abstract class ConfigTransportClient {
this.tenant = properties.getProperty(PropertyKeyConst.NAMESPACE);
this.serverListManager = serverListManager;
this.securityProxy = new SecurityProxy(properties,
this.properties = properties;
this.securityProxy = new SecurityProxy(serverListManager.getServerUrls(),
ConfigHttpClientManager.getInstance().getNacosRestTemplate());
initAkSk(properties);
}
@ -125,20 +128,6 @@ public abstract class ConfigTransportClient {
return spasHeaders;
}
/**
* get accessToken from server of using username/password.
*
* @return map that contains accessToken , null if acessToken is empty.
*/
protected Map<String, String> getSecurityHeaders() {
if (StringUtils.isBlank(securityProxy.getAccessToken())) {
return null;
}
Map<String, String> spasHeaders = new HashMap<String, String>(2);
spasHeaders.put(Constants.ACCESS_TOKEN, securityProxy.getAccessToken());
return spasHeaders;
}
/**
* get common header.
*
@ -158,8 +147,8 @@ public abstract class ConfigTransportClient {
return headers;
}
public String getAccessToken() {
return securityProxy.getAccessToken();
public Map<String, String> getAccessToken() {
return this.securityProxy.getIdentityContext();
}
private StsCredential getStsCredential() throws Exception {
@ -240,13 +229,13 @@ public abstract class ConfigTransportClient {
*/
public void start() throws NacosException {
if (securityProxy.isEnabled()) {
securityProxy.login(serverListManager.getServerUrls());
if (StringUtils.isNotBlank(this.properties.getProperty(PropertyKeyConst.USERNAME))) {
securityProxy.login(this.properties);
this.executor.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
securityProxy.login(serverListManager.getServerUrls());
securityProxy.login(properties);
}
}, 0, this.securityInfoRefreshIntervalMills, TimeUnit.MILLISECONDS);

View File

@ -64,7 +64,7 @@ public class NacosNamingMaintainService implements NamingMaintainService {
InitUtils.initSerialization();
InitUtils.initWebRootContext(properties);
ServerListManager serverListManager = new ServerListManager(properties, namespace);
SecurityProxy securityProxy = new SecurityProxy(properties,
SecurityProxy securityProxy = new SecurityProxy(serverListManager.getServerList(),
NamingHttpClientManager.getInstance().getNacosRestTemplate());
serverProxy = new NamingHttpClientProxy(namespace, securityProxy, serverListManager, properties, null);
}

View File

@ -17,7 +17,6 @@
package com.alibaba.nacos.client.naming.remote;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.client.config.impl.SpasAdapter;
import com.alibaba.nacos.client.naming.event.ServerListChangedEvent;
import com.alibaba.nacos.client.naming.utils.SignUtil;
@ -66,11 +65,7 @@ public abstract class AbstractNamingClientProxy extends Subscriber<ServerListCha
* @return nacos security access token
*/
protected Map<String, String> getSecurityHeaders() {
Map<String, String> result = new HashMap<>(1);
if (StringUtils.isNotBlank(securityProxy.getAccessToken())) {
result.put(Constants.ACCESS_TOKEN, securityProxy.getAccessToken());
}
return result;
return this.securityProxy.getIdentityContext();
}
/**

View File

@ -70,23 +70,23 @@ public class NamingClientProxyDelegate implements NamingClientProxy {
changeNotifier);
this.serverListManager = new ServerListManager(properties, namespace);
this.serviceInfoHolder = serviceInfoHolder;
this.securityProxy = new SecurityProxy(properties, NamingHttpClientManager.getInstance().getNacosRestTemplate());
initSecurityProxy();
this.securityProxy = new SecurityProxy(this.serverListManager.getServerList(), NamingHttpClientManager.getInstance().getNacosRestTemplate());
initSecurityProxy(properties);
this.httpClientProxy = new NamingHttpClientProxy(namespace, securityProxy, serverListManager, properties,
serviceInfoHolder);
this.grpcClientProxy = new NamingGrpcClientProxy(namespace, securityProxy, serverListManager, properties,
serviceInfoHolder);
}
private void initSecurityProxy() {
private void initSecurityProxy(Properties properties) {
this.executorService = new ScheduledThreadPoolExecutor(1, r -> {
Thread t = new Thread(r);
t.setName("com.alibaba.nacos.client.naming.security");
t.setDaemon(true);
return t;
});
this.securityProxy.login(serverListManager.getServerList());
this.executorService.scheduleWithFixedDelay(() -> securityProxy.login(serverListManager.getServerList()), 0,
this.securityProxy.login(properties);
this.executorService.scheduleWithFixedDelay(() -> securityProxy.login(properties), 0,
securityInfoRefreshIntervalMills, TimeUnit.MILLISECONDS);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
* Copyright 1999-2021 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.
@ -16,27 +16,16 @@
package com.alibaba.nacos.client.security;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.client.utils.ContextPathUtil;
import com.alibaba.nacos.common.http.HttpRestResult;
import com.alibaba.nacos.client.auth.ClientAuthPluginManager;
import com.alibaba.nacos.client.auth.ClientAuthService;
import com.alibaba.nacos.client.auth.LoginIdentityContext;
import com.alibaba.nacos.common.http.client.NacosRestTemplate;
import com.alibaba.nacos.common.http.param.Header;
import com.alibaba.nacos.common.http.param.Query;
import com.alibaba.nacos.common.utils.JacksonUtils;
import com.alibaba.nacos.common.utils.StringUtils;
import com.fasterxml.jackson.databind.JsonNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import static com.alibaba.nacos.client.naming.utils.UtilAndComs.HTTP;
import static com.alibaba.nacos.client.naming.utils.UtilAndComs.webContext;
import java.util.Set;
/**
* Security proxy to update security information.
@ -46,129 +35,51 @@ import static com.alibaba.nacos.client.naming.utils.UtilAndComs.webContext;
*/
public class SecurityProxy {
private static final Logger SECURITY_LOGGER = LoggerFactory.getLogger(SecurityProxy.class);
private static final String LOGIN_URL = "/v1/auth/users/login";
private final NacosRestTemplate nacosRestTemplate;
private final String contextPath;
/**
* User's name.
* ClientAuthPlugin instance set.
*/
private final String username;
private final Set<ClientAuthService> clientAuthServiceHashSet;
/**
* User's password.
*/
private final String password;
/**
* A token to take with when sending request to Nacos server.
*/
private volatile 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.
* Construct from serverList, nacosRestTemplate, init client auth plugin.
*
* @param properties a bunch of properties to read
* @param serverList a server list that client request to.
* @Param nacosRestTemplate http request template.
*/
public SecurityProxy(Properties properties, NacosRestTemplate nacosRestTemplate) {
username = properties.getProperty(PropertyKeyConst.USERNAME, StringUtils.EMPTY);
password = properties.getProperty(PropertyKeyConst.PASSWORD, StringUtils.EMPTY);
contextPath = ContextPathUtil
.normalizeContextPath(properties.getProperty(PropertyKeyConst.CONTEXT_PATH, webContext));
this.nacosRestTemplate = nacosRestTemplate;
public SecurityProxy(List<String> serverList, NacosRestTemplate nacosRestTemplate) {
ClientAuthPluginManager clientAuthPluginManager = new ClientAuthPluginManager();
clientAuthPluginManager.init(serverList, nacosRestTemplate);
clientAuthServiceHashSet = clientAuthPluginManager.getAuthServiceSpiImplSet();
}
/**
* Login to servers.
*
* @param servers server list
* @return true if login successfully
* Login all available ClientAuthService instance.
* @param properties login identity information.
* @return if there are any available clientAuthService instances.
*/
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 throwable) {
SECURITY_LOGGER.warn("[SecurityProxy] login failed, error: ", throwable);
}
return false;
}
/**
* Login to server.
*
* @param server server address
* @return true if login successfully
*/
public boolean login(String server) {
if (StringUtils.isNotBlank(username)) {
Map<String, String> params = new HashMap<String, String>(2);
Map<String, String> bodyMap = new HashMap<String, String>(2);
params.put(PropertyKeyConst.USERNAME, username);
bodyMap.put(PropertyKeyConst.PASSWORD, password);
String url = HTTP + server + contextPath + LOGIN_URL;
if (server.contains(Constants.HTTP_PREFIX)) {
url = server + contextPath + LOGIN_URL;
}
try {
HttpRestResult<String> restResult = nacosRestTemplate
.postForm(url, Header.EMPTY, Query.newInstance().initParams(params), bodyMap, String.class);
if (!restResult.ok()) {
SECURITY_LOGGER.error("login failed: {}", JacksonUtils.toJson(restResult));
return false;
}
JsonNode obj = JacksonUtils.toObj(restResult.getData());
if (obj.has(Constants.ACCESS_TOKEN)) {
accessToken = obj.get(Constants.ACCESS_TOKEN).asText();
tokenTtl = obj.get(Constants.TOKEN_TTL).asInt();
tokenRefreshWindow = tokenTtl / 10;
}
} catch (Exception e) {
SECURITY_LOGGER.error("[SecurityProxy] login http request failed"
+ " url: {}, params: {}, bodyMap: {}, errorMsg: {}", url, params, bodyMap, e.getMessage());
public boolean login(Properties properties) {
if (clientAuthServiceHashSet.isEmpty()) {
return false;
}
for (ClientAuthService clientAuthService : clientAuthServiceHashSet) {
clientAuthService.login(properties);
}
return true;
}
public String getAccessToken() {
return accessToken;
/**
* get the context of all nacosRestTemplate instance.
* @return a combination of all context.
*/
public Map<String, String> getIdentityContext() {
Map<String, String> header = new HashMap<>();
for (ClientAuthService clientAuthService : this.clientAuthServiceHashSet) {
LoginIdentityContext loginIdentityContext = clientAuthService.getLoginIdentityContext();
for (String key : loginIdentityContext.getAllKey()) {
header.put(key, (String) loginIdentityContext.getParameter(key));
}
}
return header;
}
public boolean isEnabled() {
return StringUtils.isNotBlank(this.username);
}
}

View File

@ -35,6 +35,7 @@ import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
@ -129,7 +130,10 @@ public class AbstractNamingClientProxyTest {
}
};
String token = "aa";
Mockito.when(sc.getAccessToken()).thenReturn(token);
Map<String, String> keyMap = new HashMap<>();
keyMap.put(Constants.ACCESS_TOKEN, token);
Mockito.when(sc.getIdentityContext()).thenReturn(keyMap);
Map<String, String> securityHeaders = proxy.getSecurityHeaders();
Assert.assertEquals(1, securityHeaders.size());
Assert.assertEquals(token, securityHeaders.get(Constants.ACCESS_TOKEN));

View File

@ -17,13 +17,17 @@
package com.alibaba.nacos.client.security;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.client.auth.LoginAuthConstant;
import com.alibaba.nacos.common.http.HttpRestResult;
import com.alibaba.nacos.common.http.client.NacosRestTemplate;
import com.alibaba.nacos.common.http.param.Header;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import java.util.Collections;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import static org.mockito.ArgumentMatchers.any;
@ -32,118 +36,43 @@ import static org.mockito.Mockito.when;
public class SecurityProxyTest {
@Test
public void testLoginSuccess() throws Exception {
private SecurityProxy securityProxy;
@Before
public void setUp() throws Exception {
//given
NacosRestTemplate nacosRestTemplate = mock(NacosRestTemplate.class);
HttpRestResult<Object> result = new HttpRestResult<>();
result.setData("{\"accessToken\":\"ttttttttttttttttt\",\"tokenTtl\":1000}");
result.setCode(200);
when(nacosRestTemplate.postForm(any(), (Header) any(), any(), any(), any())).thenReturn(result);
List<String> serverList = new ArrayList<>();
serverList.add("localhost");
securityProxy = new SecurityProxy(serverList, nacosRestTemplate);
}
@Test
public void testLoginClientAuthService() {
Properties properties = new Properties();
properties.setProperty(PropertyKeyConst.USERNAME, "aaa");
properties.setProperty(PropertyKeyConst.PASSWORD, "123456");
SecurityProxy securityProxy = new SecurityProxy(properties, nacosRestTemplate);
//when
boolean ret = securityProxy.login("localhost");
//then
Assert.assertTrue(ret);
}
@Test
public void testTestLoginFailCode() throws Exception {
NacosRestTemplate nacosRestTemplate = mock(NacosRestTemplate.class);
HttpRestResult<Object> result = new HttpRestResult<>();
result.setCode(400);
when(nacosRestTemplate.postForm(any(), (Header) any(), any(), any(), any())).thenReturn(result);
Properties properties = new Properties();
properties.setProperty(PropertyKeyConst.USERNAME, "aaa");
properties.setProperty(PropertyKeyConst.PASSWORD, "123456");
SecurityProxy securityProxy = new SecurityProxy(properties, nacosRestTemplate);
boolean ret = securityProxy.login("localhost");
Assert.assertFalse(ret);
}
@Test
public void testTestLoginFailHttp() throws Exception {
NacosRestTemplate nacosRestTemplate = mock(NacosRestTemplate.class);
when(nacosRestTemplate.postForm(any(), (Header) any(), any(), any(), any())).thenThrow(new Exception());
Properties properties = new Properties();
properties.setProperty(PropertyKeyConst.USERNAME, "aaa");
properties.setProperty(PropertyKeyConst.PASSWORD, "123456");
SecurityProxy securityProxy = new SecurityProxy(properties, nacosRestTemplate);
boolean ret = securityProxy.login("localhost");
Assert.assertFalse(ret);
}
@Test
public void testTestLoginServerListSuccess() throws Exception {
//given
NacosRestTemplate nacosRestTemplate = mock(NacosRestTemplate.class);
HttpRestResult<Object> result = new HttpRestResult<>();
result.setData("{\"accessToken\":\"ttttttttttttttttt\",\"tokenTtl\":1000}");
result.setCode(200);
when(nacosRestTemplate.postForm(any(), (Header) any(), any(), any(), any())).thenReturn(result);
Properties properties = new Properties();
properties.setProperty(PropertyKeyConst.USERNAME, "aaa");
properties.setProperty(PropertyKeyConst.PASSWORD, "123456");
SecurityProxy securityProxy = new SecurityProxy(properties, nacosRestTemplate);
//when
boolean ret = securityProxy.login(Collections.singletonList("localhost"));
boolean ret = securityProxy.login(properties);
//then
Assert.assertTrue(ret);
}
@Test
public void testTestLoginServerListLoginInWindow() throws Exception {
//given
NacosRestTemplate nacosRestTemplate = mock(NacosRestTemplate.class);
HttpRestResult<Object> result = new HttpRestResult<>();
result.setData("{\"accessToken\":\"ttttttttttttttttt\",\"tokenTtl\":1000}");
result.setCode(200);
when(nacosRestTemplate.postForm(any(), (Header) any(), any(), any(), any())).thenReturn(result);
public void testGetIdentityContext() {
Properties properties = new Properties();
properties.setProperty(PropertyKeyConst.USERNAME, "aaa");
properties.setProperty(PropertyKeyConst.PASSWORD, "123456");
SecurityProxy securityProxy = new SecurityProxy(properties, nacosRestTemplate);
securityProxy.login(properties);
//when
securityProxy.login(Collections.singletonList("localhost"));
Map<String, String> keyMap = securityProxy.getIdentityContext();
//then
boolean ret = securityProxy.login(Collections.singletonList("localhost"));
//then
Assert.assertTrue(ret);
}
@Test
public void testGetAccessToken() throws Exception {
NacosRestTemplate nacosRestTemplate = mock(NacosRestTemplate.class);
HttpRestResult<Object> result = new HttpRestResult<>();
result.setData("{\"accessToken\":\"abc\",\"tokenTtl\":1000}");
result.setCode(200);
when(nacosRestTemplate.postForm(any(), (Header) any(), any(), any(), any())).thenReturn(result);
Properties properties = new Properties();
properties.setProperty(PropertyKeyConst.USERNAME, "aaa");
properties.setProperty(PropertyKeyConst.PASSWORD, "123456");
SecurityProxy securityProxy = new SecurityProxy(properties, nacosRestTemplate);
securityProxy.login("localhost");
String accessToken = securityProxy.getAccessToken();
Assert.assertEquals("abc", accessToken);
}
@Test
public void testIsEnabled() throws Exception {
NacosRestTemplate nacosRestTemplate = mock(NacosRestTemplate.class);
Properties properties = new Properties();
properties.setProperty(PropertyKeyConst.USERNAME, "aaa");
properties.setProperty(PropertyKeyConst.PASSWORD, "123456");
SecurityProxy securityProxy = new SecurityProxy(properties, nacosRestTemplate);
Assert.assertTrue(securityProxy.isEnabled());
Assert.assertEquals("ttttttttttttttttt", keyMap.get(LoginAuthConstant.ACCESSTOKEN));
}
}