Skip to content

Commit

Permalink
[ISSUE #12719] Interval refresh the client's access token.
Browse files Browse the repository at this point in the history
  • Loading branch information
lucky8987 committed Nov 8, 2024
1 parent 3f97cf7 commit eadc546
Show file tree
Hide file tree
Showing 11 changed files with 112 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public class ConfigQueryResponse extends Response {

public static final int CONFIG_QUERY_CONFLICT = 400;

public static final int NO_RIGHT = 403;

String content;

String encryptedDataKey;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,5 @@ public class NacosAuthLoginConstant {

public static final String SERVER = "server";

public static final int RELOGIN_CODE = 403;

public static final String NEXTREFRESHTIME = "nextRefreshTime";
public static final String RELOGINCYCLE = "reLoginCycle";
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

import java.util.Properties;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
* a ClientAuthService implement.
Expand Down Expand Up @@ -60,6 +61,11 @@ public class NacosClientAuthServiceImpl extends AbstractClientAuthService {
*/
private volatile LoginIdentityContext loginIdentityContext = new LoginIdentityContext();

/**
* reLoginCycle is used as a time window to calculate token refreshes.
*/
private final AtomicInteger reLoginCycle = new AtomicInteger(0);


/**
* Login to servers.
Expand All @@ -70,15 +76,14 @@ public class NacosClientAuthServiceImpl extends AbstractClientAuthService {
@Override
public Boolean login(Properties properties) {
try {
String nextRefreshTimeStr = loginIdentityContext.getParameter(NacosAuthLoginConstant.NEXTREFRESHTIME);
long nextRefreshTime = NumberUtils.toLong(nextRefreshTimeStr, 0);

if (System.currentTimeMillis() < nextRefreshTime) {
tokenRefreshWindow = NumberUtils.toLong(loginIdentityContext.getParameter(NacosAuthLoginConstant.TOKENREFRESHWINDOW), 0);
if ((System.currentTimeMillis() - lastRefreshTime) < TimeUnit.SECONDS
.toMillis(tokenTtl - tokenRefreshWindow)) {
return true;
}

if (StringUtils.isBlank(properties.getProperty(PropertyKeyConst.USERNAME))) {
loginIdentityContext.setParameter(NacosAuthLoginConstant.NEXTREFRESHTIME, "0");
lastRefreshTime = System.currentTimeMillis();
return true;
}

Expand All @@ -90,11 +95,13 @@ public Boolean login(Properties properties) {
if (identityContext.getAllKey().contains(NacosAuthLoginConstant.ACCESSTOKEN)) {
tokenTtl = Long.parseLong(identityContext.getParameter(NacosAuthLoginConstant.TOKENTTL));
tokenRefreshWindow = tokenTtl / 10;
nextRefreshTime = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(tokenTtl - tokenRefreshWindow);
lastRefreshTime = System.currentTimeMillis();

LoginIdentityContext newCtx = new LoginIdentityContext();
newCtx.setParameter(NacosAuthLoginConstant.ACCESSTOKEN,
identityContext.getParameter(NacosAuthLoginConstant.ACCESSTOKEN));
newCtx.setParameter(NacosAuthLoginConstant.NEXTREFRESHTIME, String.valueOf(nextRefreshTime));
newCtx.setParameter(NacosAuthLoginConstant.TOKENREFRESHWINDOW, String.valueOf(tokenRefreshWindow));
newCtx.setParameter(NacosAuthLoginConstant.RELOGINCYCLE, String.valueOf(incrReLoginCycle()));
this.loginIdentityContext = newCtx;
}
return true;
Expand All @@ -116,4 +123,16 @@ public LoginIdentityContext getLoginIdentityContext(RequestResource resource) {
public void shutdown() throws NacosException {

}

/**
* Increase the number of reLoginCycle.
* @return current cycle
*/
private int incrReLoginCycle() {
int limit = 10;
if (reLoginCycle.incrementAndGet() > limit) {
reLoginCycle.set(1);
}
return reLoginCycle.get();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1201,10 +1201,6 @@ ConfigResponse queryConfigInner(RpcClient rpcClient, String dataId, String group
}
}

private Response requestProxy(RpcClient rpcClientInner, Request request) throws NacosException {
return requestProxy(rpcClientInner, request, requestTimeout);
}

private Response requestProxy(RpcClient rpcClientInner, Request request, long timeoutMills)
throws NacosException {
try {
Expand All @@ -1221,10 +1217,21 @@ private Response requestProxy(RpcClient rpcClientInner, Request request, long ti
throw new NacosException(NacosException.CLIENT_OVER_THRESHOLD,
"More than client-side current limit threshold");
}
Response response;
if (timeoutMills < 0) {
return rpcClientInner.request(request);
response = rpcClientInner.request(request);
} else {
response = rpcClientInner.request(request, timeoutMills);
}
// If the 403 login operation is triggered, refresh the accessToken of the client
if (response.getErrorCode() == ConfigQueryResponse.NO_RIGHT) {
reLogin();
}
return rpcClientInner.request(request, timeoutMills);
return response;
}

private Response requestProxy(RpcClient rpcClientInner, Request request) throws NacosException {
return requestProxy(rpcClientInner, request, requestTimeout);
}

private RequestResource resourceBuild(Request request) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ public void start() throws NacosException {
startInternal();
}

public void reLogin() {
securityProxy.reLogin();
}

/**
* start client inner.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
import com.alibaba.nacos.api.remote.response.ResponseCode;
import com.alibaba.nacos.api.selector.AbstractSelector;
import com.alibaba.nacos.api.selector.SelectorType;
import com.alibaba.nacos.client.auth.impl.NacosAuthLoginConstant;
import com.alibaba.nacos.client.env.NacosClientProperties;
import com.alibaba.nacos.client.monitor.MetricsMonitor;
import com.alibaba.nacos.client.naming.cache.ServiceInfoHolder;
Expand Down Expand Up @@ -448,7 +447,7 @@ private <T extends Response> T requestToServer(AbstractNamingRequest request, Cl
response = requestTimeout < 0 ? rpcClient.request(request) : rpcClient.request(request, requestTimeout);
if (ResponseCode.SUCCESS.getCode() != response.getResultCode()) {
// If the 403 login operation is triggered, refresh the accessToken of the client
if (NacosAuthLoginConstant.RELOGIN_CODE == response.getErrorCode()) {
if (NacosException.NO_RIGHT == response.getErrorCode()) {
reLogin();
}
throw new NacosException(response.getErrorCode(), response.getMessage());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
import com.alibaba.nacos.api.selector.ExpressionSelector;
import com.alibaba.nacos.api.selector.SelectorType;
import com.alibaba.nacos.client.address.ServerListChangeEvent;
import com.alibaba.nacos.client.auth.impl.NacosAuthLoginConstant;
import com.alibaba.nacos.client.env.NacosClientProperties;
import com.alibaba.nacos.client.monitor.MetricsMonitor;
import com.alibaba.nacos.client.naming.core.NamingServerListManager;
Expand Down Expand Up @@ -443,7 +442,7 @@ public String callServer(String api, Map<String, String> params, Map<String, Str
}

// If the 403 login operation is triggered, refresh the accessToken of the client
if (NacosAuthLoginConstant.RELOGIN_CODE == restResult.getCode()) {
if (HttpStatus.SC_FORBIDDEN == restResult.getCode()) {
reLogin();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,15 @@

import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.client.auth.impl.NacosAuthLoginConstant;
import com.alibaba.nacos.common.utils.NumberUtils;
import com.alibaba.nacos.plugin.auth.spi.client.ClientAuthPluginManager;
import com.alibaba.nacos.plugin.auth.api.LoginIdentityContext;
import com.alibaba.nacos.plugin.auth.spi.client.ClientAuthService;
import com.alibaba.nacos.plugin.auth.api.RequestResource;
import com.alibaba.nacos.common.http.client.NacosRestTemplate;
import com.alibaba.nacos.common.lifecycle.Closeable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.List;
Expand All @@ -38,6 +41,8 @@
*/
public class SecurityProxy implements Closeable {

private static final Logger LOGGER = LoggerFactory.getLogger(SecurityProxy.class);

private ClientAuthPluginManager clientAuthPluginManager;

/**
Expand Down Expand Up @@ -95,10 +100,19 @@ public void reLogin() {
return;
}
for (ClientAuthService clientAuthService : clientAuthPluginManager.getAuthServiceSpiImplSet()) {
LoginIdentityContext loginIdentityContext = clientAuthService.getLoginIdentityContext(null);
if (loginIdentityContext != null) {
loginIdentityContext.setParameter(NacosAuthLoginConstant.NEXTREFRESHTIME, "0");
try {
LoginIdentityContext loginIdentityContext = clientAuthService.getLoginIdentityContext(null);
if (loginIdentityContext != null && loginIdentityContext.getAllKey().contains(NacosAuthLoginConstant.TOKENTTL)
&& loginIdentityContext.getAllKey().contains(NacosAuthLoginConstant.RELOGINCYCLE)) {
long tokenTtl = NumberUtils.toLong(loginIdentityContext.getParameter(NacosAuthLoginConstant.TOKENTTL), 1);
int reLoginCycle = NumberUtils.toInt(loginIdentityContext.getParameter(NacosAuthLoginConstant.RELOGINCYCLE));
long tokenRefreshWindow = tokenTtl / reLoginCycle;
loginIdentityContext.setParameter(NacosAuthLoginConstant.TOKENREFRESHWINDOW, String.valueOf(tokenRefreshWindow));
}
} catch (Exception e) {
LOGGER.error("[SecurityProxy] set tokenRefreshWindow failed.", e);
}

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@
import com.alibaba.nacos.common.http.HttpRestResult;
import com.alibaba.nacos.common.http.client.NacosRestTemplate;
import com.alibaba.nacos.common.http.param.Header;
import com.alibaba.nacos.common.utils.NumberUtils;
import com.alibaba.nacos.plugin.auth.api.LoginIdentityContext;
import org.junit.jupiter.api.Test;

import java.util.ArrayList;
Expand Down Expand Up @@ -237,28 +235,4 @@ void testGetAccessTokenWithInvalidTtl() throws Exception {
//when
assertFalse(nacosClientAuthService.login(properties));
}

@Test
void testNextRefreshTime() throws Exception {
NacosRestTemplate nacosRestTemplate = mock(NacosRestTemplate.class);
HttpRestResult<Object> result = new HttpRestResult<>();
result.setData("{\"accessToken\":\"abc\",\"tokenTtl\":\"60\"}");
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");

List<String> serverList = new ArrayList<>();
serverList.add("localhost");

NacosClientAuthServiceImpl nacosClientAuthService = new NacosClientAuthServiceImpl();
nacosClientAuthService.setServerList(serverList);
nacosClientAuthService.setNacosRestTemplate(nacosRestTemplate);
//when
nacosClientAuthService.login(properties);
LoginIdentityContext loginIdentityContext = nacosClientAuthService.getLoginIdentityContext(null);
String nextRefreshTime = loginIdentityContext.getParameter(NacosAuthLoginConstant.NEXTREFRESHTIME);
assertTrue(System.currentTimeMillis() < NumberUtils.toLong(nextRefreshTime, 0));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import com.alibaba.nacos.api.config.remote.response.ConfigChangeBatchListenResponse;
import com.alibaba.nacos.api.config.remote.response.ConfigPublishResponse;
import com.alibaba.nacos.api.config.remote.response.ConfigQueryResponse;
import com.alibaba.nacos.api.config.remote.response.ConfigRemoveResponse;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.client.config.common.GroupKey;
import com.alibaba.nacos.client.config.filter.impl.ConfigFilterChainManager;
Expand Down Expand Up @@ -761,4 +762,21 @@ void testAddTenantListenersWithContentEnsureCacheDataSafe()
assertFalse(cacheDataFromCache2.isDiscard());
assertFalse(cacheDataFromCache2.isConsistentWithServer());
}

@Test
void testResponse403() throws NacosException {
Properties prop = new Properties();
ConfigFilterChainManager filter = new ConfigFilterChainManager(new Properties());
ConfigServerListManager agent = Mockito.mock(ConfigServerListManager.class);

final NacosClientProperties nacosClientProperties = NacosClientProperties.PROTOTYPE.derive(prop);
final ClientWorker clientWorker = new ClientWorker(filter, agent, nacosClientProperties);

ConfigRemoveResponse response = ConfigRemoveResponse.buildFailResponse("accessToken invalid");
response.setErrorCode(ConfigQueryResponse.NO_RIGHT);
Mockito.when(rpcClient.request(any(ConfigRemoveRequest.class)))
.thenReturn(response);
boolean result = clientWorker.removeConfig("a", "b", "c", "tag");
assertFalse(result);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@
package com.alibaba.nacos.client.security;

import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.client.auth.impl.NacosAuthLoginConstant;
import com.alibaba.nacos.client.auth.impl.NacosClientAuthServiceImpl;
import com.alibaba.nacos.common.http.HttpRestResult;
import com.alibaba.nacos.common.http.client.NacosRestTemplate;
import com.alibaba.nacos.common.http.param.Header;
import com.alibaba.nacos.plugin.auth.api.LoginIdentityContext;
import com.alibaba.nacos.plugin.auth.api.RequestResource;
import com.alibaba.nacos.plugin.auth.spi.client.AbstractClientAuthService;
import com.alibaba.nacos.plugin.auth.spi.client.ClientAuthPluginManager;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -105,15 +106,36 @@ void testLoginWithoutAnyPlugin() throws NoSuchFieldException, IllegalAccessExcep

@Test
void testReLogin() throws NoSuchFieldException, IllegalAccessException {
NacosClientAuthServiceImpl authService = new NacosClientAuthServiceImpl();
ClientAuthPluginManager clientAuthPluginManager = mock(ClientAuthPluginManager.class);
Field clientAuthPluginManagerField = SecurityProxy.class.getDeclaredField("clientAuthPluginManager");
clientAuthPluginManagerField.setAccessible(true);
ClientAuthPluginManager clientAuthPluginManager = mock(ClientAuthPluginManager.class);
clientAuthPluginManagerField.set(securityProxy, clientAuthPluginManager);
when(clientAuthPluginManager.getAuthServiceSpiImplSet()).thenReturn(Collections.singleton(authService));
when(clientAuthPluginManager.getAuthServiceSpiImplSet()).thenReturn(Collections.singleton(new AbstractClientAuthService() {

private LoginIdentityContext loginIdentityContext;

@Override
public Boolean login(Properties properties) {
return null;
}

@Override
public LoginIdentityContext getLoginIdentityContext(RequestResource resource) {
if (loginIdentityContext == null) {
loginIdentityContext = new LoginIdentityContext();
loginIdentityContext.setParameter(NacosAuthLoginConstant.RELOGINCYCLE, "2");
loginIdentityContext.setParameter(NacosAuthLoginConstant.TOKENTTL, "100");
}
return loginIdentityContext;
}

@Override
public void shutdown() throws NacosException {

}
}));
securityProxy.reLogin();
LoginIdentityContext loginIdentityContext = authService.getLoginIdentityContext(null);
String nextRefreshTime = loginIdentityContext.getParameter(NacosAuthLoginConstant.NEXTREFRESHTIME);
assertEquals(nextRefreshTime, "0");
Map<String, String> identityContext = securityProxy.getIdentityContext(new RequestResource());
assertEquals(identityContext.get(NacosAuthLoginConstant.TOKENREFRESHWINDOW), "50");
}
}

0 comments on commit eadc546

Please sign in to comment.