This is an automated email from the ASF dual-hosted git repository.

liubao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/servicecomb-java-chassis.git


The following commit(s) were added to refs/heads/master by this push:
     new be1ff21  [SCB-2089] 支持服务中心rbac认证 (#2000)
be1ff21 is described below

commit be1ff217423dbba58c6c453dcb856f7a8c92d3ce
Author: 兴 <[email protected]>
AuthorDate: Thu Oct 15 09:13:00 2020 +0800

    [SCB-2089] 支持服务中心rbac认证 (#2000)
    
    Co-authored-by: yhs0092 <[email protected]>
---
 .../config/client/ConfigCenterClient.java          |   6 +-
 .../apache/servicecomb/foundation/auth/Cipher.java |  24 +++
 .../servicecomb/foundation/auth/DefaultCipher.java |  41 +++++
 .../servicecomb/serviceregistry/RegistryUtils.java |   8 +
 .../servicecomb/serviceregistry/api/Const.java     |   3 +-
 .../api/request/RbacTokenRequest.java              |  52 ++++++
 .../api/response/RbacTokenResponse.java            |  49 ++++++
 .../auth/TokenAuthHeaderProvider.java              |  90 ++++++++++
 .../serviceregistry/auth/TokenCacheManager.java    | 194 +++++++++++++++++++++
 .../client/ServiceRegistryClient.java              |   8 +-
 .../client/http/ServiceRegistryClientImpl.java     |  45 ++++-
 ....servicecomb.foundation.auth.AuthHeaderProvider |   1 +
 .../client/LocalServiceRegistryClientImpl.java     |   7 +
 .../client/http/TestServiceRegistryClientImpl.java | 103 +++++++++++
 14 files changed, 621 insertions(+), 10 deletions(-)

diff --git 
a/dynamic-config/config-cc/src/main/java/org/apache/servicecomb/config/client/ConfigCenterClient.java
 
b/dynamic-config/config-cc/src/main/java/org/apache/servicecomb/config/client/ConfigCenterClient.java
index 397a3b1..622c6c2 100644
--- 
a/dynamic-config/config-cc/src/main/java/org/apache/servicecomb/config/client/ConfigCenterClient.java
+++ 
b/dynamic-config/config-cc/src/main/java/org/apache/servicecomb/config/client/ConfigCenterClient.java
@@ -31,7 +31,6 @@ import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.ServiceLoader;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
@@ -45,6 +44,7 @@ import 
org.apache.servicecomb.foundation.common.event.EventManager;
 import org.apache.servicecomb.foundation.common.net.IpPort;
 import org.apache.servicecomb.foundation.common.net.NetUtils;
 import org.apache.servicecomb.foundation.common.utils.JsonUtils;
+import org.apache.servicecomb.foundation.common.utils.SPIServiceUtils;
 import 
org.apache.servicecomb.foundation.vertx.client.http.HttpClientWithContext;
 import org.apache.servicecomb.foundation.vertx.client.http.HttpClients;
 import org.slf4j.Logger;
@@ -95,8 +95,8 @@ public class ConfigCenterClient {
 
   private boolean isWatching = false;
 
-  private final ServiceLoader<AuthHeaderProvider> authHeaderProviders =
-      ServiceLoader.load(AuthHeaderProvider.class);
+  private final List<AuthHeaderProvider> authHeaderProviders =
+      SPIServiceUtils.getSortedService(AuthHeaderProvider.class);
 
   private URIConst uriConst = new URIConst();
 
diff --git 
a/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/auth/Cipher.java
 
b/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/auth/Cipher.java
new file mode 100644
index 0000000..3912ce3
--- /dev/null
+++ 
b/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/auth/Cipher.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.servicecomb.foundation.auth;
+
+public interface Cipher {
+  String name();
+
+  char[] decrypt(char[] encrypted);
+}
diff --git 
a/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/auth/DefaultCipher.java
 
b/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/auth/DefaultCipher.java
new file mode 100644
index 0000000..7138cca
--- /dev/null
+++ 
b/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/auth/DefaultCipher.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.servicecomb.foundation.auth;
+
+public final class DefaultCipher implements Cipher {
+  public static final String DEFAULT_CYPHER = "default";
+
+  private static final DefaultCipher INSTANCE = new DefaultCipher();
+
+  public static DefaultCipher getInstance() {
+    return INSTANCE;
+  }
+
+  private DefaultCipher() {
+  }
+
+  @Override
+  public String name() {
+    return DEFAULT_CYPHER;
+  }
+
+  @Override
+  public char[] decrypt(char[] encrypted) {
+    return encrypted;
+  }
+}
diff --git 
a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/RegistryUtils.java
 
b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/RegistryUtils.java
index b581de8..c2eb750 100644
--- 
a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/RegistryUtils.java
+++ 
b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/RegistryUtils.java
@@ -271,6 +271,14 @@ public final class RegistryUtils {
     }
   }
 
+  public static ServiceRegistry getServiceRegistry(String registryName) {
+    if (ServiceRegistry.DEFAULT_REGISTRY_NAME.equals(registryName)) {
+      return getServiceRegistry();
+    }
+
+    return EXTRA_SERVICE_REGISTRIES.get(registryName);
+  }
+
   public static class AfterServiceInstanceRegistryHandler {
     private static AtomicInteger instanceRegisterCounter = new 
AtomicInteger(EXTRA_SERVICE_REGISTRIES.size() + 1);
 
diff --git 
a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/api/Const.java
 
b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/api/Const.java
index 55c68cc..d2ff215 100644
--- 
a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/api/Const.java
+++ 
b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/api/Const.java
@@ -177,13 +177,14 @@ public final class Const {
         MICROSERVICE_INSTANCE_STATUS = V4_PREFIX + 
"/microservices/%s/instances/%s/status";
       }
     }
+
+    public static final String RBAC_TOKEN = "/v4/token";
   }
 
   public static final String REGISTRY_APP_ID = "default";
 
   public static final String REGISTRY_SERVICE_NAME = "SERVICECENTER";
 
-
   public static final String PATH_CHECKSESSION = "checksession";
 
   public static final int SERVICE_CENTER_ORDER = 100;
diff --git 
a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/api/request/RbacTokenRequest.java
 
b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/api/request/RbacTokenRequest.java
new file mode 100644
index 0000000..fa6ee71
--- /dev/null
+++ 
b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/api/request/RbacTokenRequest.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.servicecomb.serviceregistry.api.request;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class RbacTokenRequest {
+  @JsonProperty("name")
+  private String accountName;
+
+  private String password;
+
+  public String getAccountName() {
+    return accountName;
+  }
+
+  public void setAccountName(String accountName) {
+    this.accountName = accountName;
+  }
+
+  public String getPassword() {
+    return password;
+  }
+
+  public void setPassword(String password) {
+    this.password = password;
+  }
+
+  @Override
+  public String toString() {
+    final StringBuilder sb = new StringBuilder("RbacTokenRequest{");
+    sb.append("accountName='").append(accountName).append('\'');
+    sb.append(", password='").append(password).append('\'');
+    sb.append('}');
+    return sb.toString();
+  }
+}
diff --git 
a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/api/response/RbacTokenResponse.java
 
b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/api/response/RbacTokenResponse.java
new file mode 100644
index 0000000..40ecadc
--- /dev/null
+++ 
b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/api/response/RbacTokenResponse.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.servicecomb.serviceregistry.api.response;
+
+public class RbacTokenResponse {
+  private transient int statusCode;
+
+  private String token;
+
+  public int getStatusCode() {
+    return statusCode;
+  }
+
+  public void setStatusCode(int statusCode) {
+    this.statusCode = statusCode;
+  }
+
+  public String getToken() {
+    return token;
+  }
+
+  public void setToken(String token) {
+    this.token = token;
+  }
+
+  @Override
+  public String toString() {
+    final StringBuilder sb = new StringBuilder("RbacTokenResponse{");
+    sb.append("statusCode=").append(statusCode);
+    sb.append(", token='").append(token).append('\'');
+    sb.append('}');
+    return sb.toString();
+  }
+}
diff --git 
a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/auth/TokenAuthHeaderProvider.java
 
b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/auth/TokenAuthHeaderProvider.java
new file mode 100644
index 0000000..7b82a0e
--- /dev/null
+++ 
b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/auth/TokenAuthHeaderProvider.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.servicecomb.serviceregistry.auth;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.servicecomb.foundation.auth.AuthHeaderProvider;
+import org.apache.servicecomb.foundation.auth.Cipher;
+import org.apache.servicecomb.foundation.auth.DefaultCipher;
+import org.apache.servicecomb.foundation.common.utils.BeanUtils;
+import org.apache.servicecomb.serviceregistry.ServiceRegistry;
+
+import com.netflix.config.DynamicPropertyFactory;
+
+public class TokenAuthHeaderProvider implements AuthHeaderProvider {
+  public static final String ACCOUNT_NAME_KEY = 
"servicecomb.credentials.account.name";
+
+  public static final String PASSWORD_KEY = 
"servicecomb.credentials.account.password";
+
+  public static final String CIPHER_KEY = "servicecomb.credentials.cipher";
+
+  private String registryName;
+
+  private String accountName;
+
+  private String password;
+
+  private String cipherName;
+
+  public TokenAuthHeaderProvider() {
+    this.registryName = ServiceRegistry.DEFAULT_REGISTRY_NAME;
+    this.accountName = DynamicPropertyFactory.getInstance()
+        .getStringProperty(ACCOUNT_NAME_KEY, null).get();
+    this.password = DynamicPropertyFactory.getInstance()
+        .getStringProperty(PASSWORD_KEY, null).get();
+    this.cipherName = DynamicPropertyFactory.getInstance()
+        .getStringProperty(CIPHER_KEY, DefaultCipher.DEFAULT_CYPHER).get();
+    if (StringUtils.isNotEmpty(accountName)) {
+      TokenCacheManager.getInstance().addTokenCache(registryName, accountName, 
password, getCipher());
+    }
+  }
+
+  public TokenAuthHeaderProvider(String registryName, String accountName, 
String password, String cipherName) {
+    this.registryName = registryName;
+    this.accountName = accountName;
+    this.password = password;
+    this.cipherName = cipherName;
+    TokenCacheManager.getInstance().addTokenCache(this.registryName, 
this.accountName, this.password, getCipher());
+  }
+
+  @Override
+  public Map<String, String> authHeaders() {
+    String token = TokenCacheManager.getInstance().getToken(registryName);
+    if (StringUtils.isEmpty(token)) {
+      return new HashMap<>();
+    }
+
+    HashMap<String, String> header = new HashMap<>();
+    header.put("Authorization", "Bearer " + token);
+    return Collections.unmodifiableMap(header);
+  }
+
+  private Cipher getCipher() {
+    if (DefaultCipher.DEFAULT_CYPHER.equals(cipherName)) {
+      return DefaultCipher.getInstance();
+    }
+
+    Map<String, Cipher> cipherBeans = BeanUtils.getBeansOfType(Cipher.class);
+    return cipherBeans.values().stream().filter(c -> 
c.name().equals(cipherName)).findFirst()
+        .orElseThrow(() -> new IllegalArgumentException("failed to find cipher 
named " + cipherName));
+  }
+}
diff --git 
a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/auth/TokenCacheManager.java
 
b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/auth/TokenCacheManager.java
new file mode 100644
index 0000000..248c25a
--- /dev/null
+++ 
b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/auth/TokenCacheManager.java
@@ -0,0 +1,194 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.servicecomb.serviceregistry.auth;
+
+import java.time.Clock;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.annotation.Nonnull;
+import javax.ws.rs.core.Response.Status;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.servicecomb.foundation.auth.Cipher;
+import 
org.apache.servicecomb.foundation.common.concurrency.SuppressedRunnableWrapper;
+import org.apache.servicecomb.foundation.common.concurrent.ConcurrentHashMapEx;
+import org.apache.servicecomb.foundation.common.utils.TimeUtils;
+import org.apache.servicecomb.foundation.vertx.client.http.HttpClients;
+import org.apache.servicecomb.serviceregistry.RegistryUtils;
+import org.apache.servicecomb.serviceregistry.ServiceRegistry;
+import org.apache.servicecomb.serviceregistry.api.request.RbacTokenRequest;
+import org.apache.servicecomb.serviceregistry.api.response.RbacTokenResponse;
+import org.apache.servicecomb.serviceregistry.client.ServiceRegistryClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class TokenCacheManager {
+  private static final Logger LOGGER = 
LoggerFactory.getLogger(TokenCacheManager.class);
+
+  private static final TokenCacheManager INSTANCE = new TokenCacheManager();
+
+  private Clock clock = TimeUtils.getSystemDefaultZoneClock();
+
+  private ScheduledExecutorService tokenCacheWorker;
+
+  private Map<String, TokenCache> tokenCacheMap;
+
+  public static TokenCacheManager getInstance() {
+    return INSTANCE;
+  }
+
+  private TokenCacheManager() {
+    tokenCacheWorker = Executors.newScheduledThreadPool(2, new ThreadFactory() 
{
+      private final AtomicInteger threadIndexer = new AtomicInteger();
+
+      @Override
+      public Thread newThread(@Nonnull Runnable r) {
+        Thread thread = new Thread(r, "auth-token-cache-" + 
threadIndexer.getAndIncrement());
+        thread.setDaemon(true);
+        return thread;
+      }
+    });
+    tokenCacheMap = new ConcurrentHashMapEx<>();
+  }
+
+  public void addTokenCache(String registryName, String accountName, String 
password, Cipher cipher) {
+    Objects.requireNonNull(registryName, "registryName should not be null!");
+    if (tokenCacheMap.containsKey(registryName)) {
+      LOGGER.warn("duplicate token cache registration for 
serviceRegistry[{}]", registryName);
+      return;
+    }
+
+    TokenCache tokenCache = new TokenCache(registryName, accountName, 
password, cipher, this.clock);
+    tokenCache.setTokenCacheWorker(this.tokenCacheWorker);
+    tokenCacheMap.put(registryName, tokenCache);
+    HttpClients.load();
+    RegistryUtils.init();
+    tokenCache.refreshToken();
+  }
+
+  public String getToken(String registryName) {
+    return Optional.ofNullable(tokenCacheMap.get(registryName))
+        .map(TokenCache::getToken)
+        .orElse("");
+  }
+
+  public static class TokenCache {
+    private final String registryName;
+
+    private final String accountName;
+
+    private final String password;
+
+    private final Clock clock;
+
+    private String token;
+
+    private long nextRefreshTime;
+
+    /**
+     * The life cycle period of a token, in millisecond.
+     * After the {@code tokenLife} time since the token created, it should be 
refreshed.
+     * <p>
+     * Default life time in sc is 30min, give 2min buffer
+     * </p>
+     */
+    private long tokenLife = TimeUnit.MINUTES.toMillis(30 - 2);
+
+    private ScheduledExecutorService tokenCacheWorker;
+
+    private Cipher cipher;
+
+    public TokenCache(String registryName, String accountName, String password,
+        Cipher cipher, Clock clock) {
+      this.registryName = registryName;
+      this.accountName = accountName;
+      this.password = password;
+      this.cipher = cipher;
+      this.clock = clock;
+    }
+
+    public String getToken() {
+      return token == null ? "" : token;
+    }
+
+    public void setTokenCacheWorker(ScheduledExecutorService tokenCacheWorker) 
{
+      Objects.requireNonNull(tokenCacheWorker, "input tokenCacheWorker is 
null");
+      if (this.tokenCacheWorker != null) {
+        throw new IllegalStateException("tokenCacheWorker already set!");
+      }
+
+      this.tokenCacheWorker = tokenCacheWorker;
+      startTokenRefreshTask();
+    }
+
+    private void startTokenRefreshTask() {
+      this.tokenCacheWorker.scheduleAtFixedRate(
+          new SuppressedRunnableWrapper(() -> {
+            if (isTokenOutdated()) {
+              refreshToken();
+            }
+          }),
+          1,
+          5,
+          TimeUnit.SECONDS);
+    }
+
+    private boolean isTokenOutdated() {
+      return clock.millis() > nextRefreshTime;
+    }
+
+    private void refreshToken() {
+      ServiceRegistry serviceRegistry = 
RegistryUtils.getServiceRegistry(registryName);
+      ServiceRegistryClient serviceRegistryClient =
+          serviceRegistry == null ? null : 
serviceRegistry.getServiceRegistryClient();
+      if ((serviceRegistry == null || serviceRegistryClient == null)
+          && ServiceRegistry.DEFAULT_REGISTRY_NAME.equals(registryName)) {
+        LOGGER.error("failed to get default serviceRegistry");
+        tokenCacheWorker.schedule( // retry after 1 second
+            this::refreshToken, 1, TimeUnit.SECONDS);
+        return;
+      }
+      RbacTokenRequest request = new RbacTokenRequest();
+      request.setAccountName(new 
String(cipher.decrypt(accountName.toCharArray())));
+      request.setPassword(new String(cipher.decrypt(password.toCharArray())));
+      RbacTokenResponse rbacTokenResponse = 
serviceRegistryClient.getRbacToken(request);
+      LOGGER.info("refresh token successfully {}", 
rbacTokenResponse.getStatusCode());
+      if (StringUtils.isEmpty(this.token)) {
+        if (Status.UNAUTHORIZED.getStatusCode() == 
rbacTokenResponse.getStatusCode()) {
+          // password wrong, do not try anymore
+          LOGGER.warn("username or password may be wrong!");
+          this.tokenCacheWorker.shutdown();
+        } else if (Status.NOT_FOUND.getStatusCode() == 
rbacTokenResponse.getStatusCode()) {
+          // service center not support, do not try
+          LOGGER.warn("service center do not support rbac token, you should 
not config account info");
+          this.tokenCacheWorker.shutdown();
+        }
+      }
+      this.token = rbacTokenResponse.getToken();
+      this.nextRefreshTime = clock.millis() + tokenLife;
+
+    }
+  }
+}
diff --git 
a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/client/ServiceRegistryClient.java
 
b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/client/ServiceRegistryClient.java
index 7b6cab7..ab028b1 100644
--- 
a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/client/ServiceRegistryClient.java
+++ 
b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/client/ServiceRegistryClient.java
@@ -21,15 +21,17 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.servicecomb.foundation.vertx.AsyncResultCallback;
+import 
org.apache.servicecomb.registry.api.event.MicroserviceInstanceChangedEvent;
 import org.apache.servicecomb.registry.api.registry.Microservice;
 import org.apache.servicecomb.registry.api.registry.MicroserviceInstance;
 import org.apache.servicecomb.registry.api.registry.MicroserviceInstanceStatus;
+import org.apache.servicecomb.registry.api.registry.MicroserviceInstances;
 import org.apache.servicecomb.serviceregistry.api.registry.ServiceCenterInfo;
+import org.apache.servicecomb.serviceregistry.api.request.RbacTokenRequest;
 import org.apache.servicecomb.serviceregistry.api.response.GetSchemaResponse;
 import org.apache.servicecomb.serviceregistry.api.response.HeartbeatResponse;
-import 
org.apache.servicecomb.registry.api.event.MicroserviceInstanceChangedEvent;
+import org.apache.servicecomb.serviceregistry.api.response.RbacTokenResponse;
 import org.apache.servicecomb.serviceregistry.client.http.Holder;
-import org.apache.servicecomb.registry.api.registry.MicroserviceInstances;
 
 public interface ServiceRegistryClient {
   void init();
@@ -202,4 +204,6 @@ public interface ServiceRegistryClient {
    * @return whether this operation success
    */
   boolean updateMicroserviceInstanceStatus(String microserviceId, String 
instanceId, MicroserviceInstanceStatus status);
+
+  RbacTokenResponse getRbacToken(RbacTokenRequest request);
 }
diff --git 
a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/client/http/ServiceRegistryClientImpl.java
 
b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/client/http/ServiceRegistryClientImpl.java
index d4921bd..a96908d 100644
--- 
a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/client/http/ServiceRegistryClientImpl.java
+++ 
b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/client/http/ServiceRegistryClientImpl.java
@@ -35,19 +35,22 @@ import javax.ws.rs.core.Response.Status;
 import org.apache.servicecomb.foundation.common.net.IpPort;
 import org.apache.servicecomb.foundation.common.utils.JsonUtils;
 import org.apache.servicecomb.foundation.vertx.AsyncResultCallback;
-import org.apache.servicecomb.serviceregistry.RegistryUtils;
-import org.apache.servicecomb.serviceregistry.api.Const;
+import 
org.apache.servicecomb.registry.api.event.MicroserviceInstanceChangedEvent;
+import org.apache.servicecomb.registry.api.registry.FindInstancesResponse;
 import org.apache.servicecomb.registry.api.registry.Microservice;
 import org.apache.servicecomb.registry.api.registry.MicroserviceInstance;
 import org.apache.servicecomb.registry.api.registry.MicroserviceInstanceStatus;
 import org.apache.servicecomb.registry.api.registry.MicroserviceInstances;
+import org.apache.servicecomb.serviceregistry.RegistryUtils;
+import org.apache.servicecomb.serviceregistry.api.Const;
+import org.apache.servicecomb.serviceregistry.api.Const.REGISTRY_API;
 import org.apache.servicecomb.serviceregistry.api.registry.ServiceCenterInfo;
 import org.apache.servicecomb.serviceregistry.api.request.CreateSchemaRequest;
 import org.apache.servicecomb.serviceregistry.api.request.CreateServiceRequest;
+import org.apache.servicecomb.serviceregistry.api.request.RbacTokenRequest;
 import 
org.apache.servicecomb.serviceregistry.api.request.RegisterInstanceRequest;
 import 
org.apache.servicecomb.serviceregistry.api.request.UpdatePropertiesRequest;
 import 
org.apache.servicecomb.serviceregistry.api.response.CreateServiceResponse;
-import org.apache.servicecomb.registry.api.registry.FindInstancesResponse;
 import 
org.apache.servicecomb.serviceregistry.api.response.GetAllServicesResponse;
 import 
org.apache.servicecomb.serviceregistry.api.response.GetExistenceResponse;
 import 
org.apache.servicecomb.serviceregistry.api.response.GetInstancesResponse;
@@ -55,8 +58,8 @@ import 
org.apache.servicecomb.serviceregistry.api.response.GetSchemaResponse;
 import org.apache.servicecomb.serviceregistry.api.response.GetSchemasResponse;
 import org.apache.servicecomb.serviceregistry.api.response.GetServiceResponse;
 import org.apache.servicecomb.serviceregistry.api.response.HeartbeatResponse;
-import 
org.apache.servicecomb.registry.api.event.MicroserviceInstanceChangedEvent;
 import 
org.apache.servicecomb.serviceregistry.api.response.MicroserviceInstanceResponse;
+import org.apache.servicecomb.serviceregistry.api.response.RbacTokenResponse;
 import 
org.apache.servicecomb.serviceregistry.api.response.RegisterInstanceResponse;
 import org.apache.servicecomb.serviceregistry.client.ClientException;
 import org.apache.servicecomb.serviceregistry.client.IpPortManager;
@@ -67,6 +70,7 @@ import 
org.apache.servicecomb.serviceregistry.task.MicroserviceInstanceHeartbeat
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.fasterxml.jackson.core.JsonProcessingException;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.cache.CacheBuilder;
 import com.google.common.cache.CacheLoader;
@@ -955,4 +959,37 @@ public final class ServiceRegistryClientImpl implements 
ServiceRegistryClient {
       ipPortManager.initAutoDiscovery();
     }
   }
+
+  @Override
+  public RbacTokenResponse getRbacToken(RbacTokenRequest request) {
+    Holder<RbacTokenResponse> holder = new Holder<>();
+    IpPort ipPort = ipPortManager.getAvailableAddress();
+
+    byte[] requestBody;
+    try {
+      requestBody = JsonUtils.writeValueAsBytes(request);
+    } catch (JsonProcessingException e) {
+      LOGGER.error("failed to write request as byte array");
+      return new RbacTokenResponse();
+    }
+
+    CountDownLatch countDownLatch = new CountDownLatch(1);
+    restClientUtil.post(ipPort, REGISTRY_API.RBAC_TOKEN,
+        new RequestParam().setBody(requestBody),
+        syncHandler(countDownLatch, RbacTokenResponse.class, holder));
+    try {
+      countDownLatch.await();
+    } catch (InterruptedException e) {
+      LOGGER.error("failed to wait for rbac token response", e);
+    }
+
+    if (holder.value != null) {
+      holder.value.setStatusCode(holder.getStatusCode());
+      return holder.value;
+    }
+
+    RbacTokenResponse response = new RbacTokenResponse();
+    response.setStatusCode(holder.getStatusCode());
+    return response;
+  }
 }
diff --git 
a/service-registry/registry-service-center/src/main/resources/META-INF/services/org.apache.servicecomb.foundation.auth.AuthHeaderProvider
 
b/service-registry/registry-service-center/src/main/resources/META-INF/services/org.apache.servicecomb.foundation.auth.AuthHeaderProvider
index ee17cf9..0992eee 100644
--- 
a/service-registry/registry-service-center/src/main/resources/META-INF/services/org.apache.servicecomb.foundation.auth.AuthHeaderProvider
+++ 
b/service-registry/registry-service-center/src/main/resources/META-INF/services/org.apache.servicecomb.foundation.auth.AuthHeaderProvider
@@ -16,3 +16,4 @@
 #
 
 org.apache.servicecomb.serviceregistry.client.http.EmptyAuthHeaderProvider
+org.apache.servicecomb.serviceregistry.auth.TokenAuthHeaderProvider
diff --git 
a/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/client/LocalServiceRegistryClientImpl.java
 
b/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/client/LocalServiceRegistryClientImpl.java
index a63e9b2..4c7183e 100644
--- 
a/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/client/LocalServiceRegistryClientImpl.java
+++ 
b/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/client/LocalServiceRegistryClientImpl.java
@@ -43,8 +43,10 @@ import 
org.apache.servicecomb.registry.version.VersionRuleUtils;
 import org.apache.servicecomb.registry.version.VersionUtils;
 import org.apache.servicecomb.serviceregistry.api.registry.ServiceCenterConfig;
 import org.apache.servicecomb.serviceregistry.api.registry.ServiceCenterInfo;
+import org.apache.servicecomb.serviceregistry.api.request.RbacTokenRequest;
 import org.apache.servicecomb.serviceregistry.api.response.GetSchemaResponse;
 import org.apache.servicecomb.serviceregistry.api.response.HeartbeatResponse;
+import org.apache.servicecomb.serviceregistry.api.response.RbacTokenResponse;
 import org.apache.servicecomb.serviceregistry.client.http.Holder;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -455,4 +457,9 @@ public class LocalServiceRegistryClientImpl implements 
ServiceRegistryClient {
     microserviceInstance.setStatus(status);
     return true;
   }
+
+  @Override
+  public RbacTokenResponse getRbacToken(RbacTokenRequest request) {
+    return new RbacTokenResponse();
+  }
 }
diff --git 
a/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/client/http/TestServiceRegistryClientImpl.java
 
b/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/client/http/TestServiceRegistryClientImpl.java
index fffee24..87d022f 100644
--- 
a/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/client/http/TestServiceRegistryClientImpl.java
+++ 
b/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/client/http/TestServiceRegistryClientImpl.java
@@ -20,6 +20,7 @@ package org.apache.servicecomb.serviceregistry.client.http;
 import static org.hamcrest.core.Is.is;
 import static org.junit.Assert.fail;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -35,6 +36,7 @@ import org.apache.log4j.Logger;
 import org.apache.log4j.spi.LoggingEvent;
 import org.apache.servicecomb.config.BootStrapProperties;
 import org.apache.servicecomb.foundation.common.net.IpPort;
+import org.apache.servicecomb.foundation.common.utils.JsonUtils;
 import org.apache.servicecomb.foundation.test.scaffolding.config.ArchaiusUtils;
 import org.apache.servicecomb.foundation.vertx.client.http.HttpClients;
 import org.apache.servicecomb.registry.api.registry.Microservice;
@@ -44,10 +46,12 @@ import 
org.apache.servicecomb.registry.definition.DefinitionConst;
 import org.apache.servicecomb.serviceregistry.RegistryUtils;
 import org.apache.servicecomb.serviceregistry.api.registry.ServiceCenterConfig;
 import org.apache.servicecomb.serviceregistry.api.registry.ServiceCenterInfo;
+import org.apache.servicecomb.serviceregistry.api.request.RbacTokenRequest;
 import 
org.apache.servicecomb.serviceregistry.api.response.GetExistenceResponse;
 import org.apache.servicecomb.serviceregistry.api.response.GetSchemaResponse;
 import org.apache.servicecomb.serviceregistry.api.response.GetSchemasResponse;
 import org.apache.servicecomb.serviceregistry.api.response.GetServiceResponse;
+import org.apache.servicecomb.serviceregistry.api.response.RbacTokenResponse;
 import org.apache.servicecomb.serviceregistry.client.ClientException;
 import 
org.apache.servicecomb.serviceregistry.client.http.ServiceRegistryClientImpl.ResponseWrapper;
 import org.apache.servicecomb.serviceregistry.config.ServiceRegistryConfig;
@@ -624,4 +628,103 @@ public class TestServiceRegistryClientImpl {
   private void shouldThrowException() {
     fail("an exception is expected");
   }
+
+  @Test
+  public void getRbacToken() {
+    mockForGetRbacToken(200, "{\"token\":\"test_token_content\"}");
+    RbacTokenRequest request = new RbacTokenRequest();
+    request.setAccountName("test_account_name");
+    request.setPassword("test_password");
+    RbacTokenResponse response = oClient.getRbacToken(request);
+
+    Assert.assertEquals(200, response.getStatusCode());
+    Assert.assertEquals("test_token_content", response.getToken());
+  }
+
+  @Test
+  public void getRbacToken_waiting_response_interrupted() {
+    InterruptedException e = new InterruptedException();
+    new MockUp<CountDownLatch>() {
+      @Mock
+      public void await() throws InterruptedException {
+        throw e;
+      }
+    };
+    new MockUp<RestClientUtil>() {
+      @Mock
+      void post(IpPort ipPort, String uri, RequestParam requestParam,
+          Handler<RestResponse> responseHandler) {
+      }
+    };
+    RbacTokenRequest request = new RbacTokenRequest();
+    request.setAccountName("test_account_name");
+    request.setPassword("test_password");
+
+    RbacTokenResponse response = oClient.getRbacToken(request);
+
+    Assert.assertEquals(0, response.getStatusCode());
+    Assert.assertNull(response.getToken());
+  }
+
+  @Test
+  public void getRbacToken_serialize_request_exception() {
+    mockForGetRbacToken(200, "{\"token\":\"test_token_content\"}");
+    RbacTokenRequest request = new RbacTokenRequest() {
+      @Override
+      public String getAccountName() {
+        throw new IllegalStateException("mock serialization error");
+      }
+    };
+    request.setAccountName("test_account_name");
+    request.setPassword("test_password");
+
+    RbacTokenResponse response = oClient.getRbacToken(request);
+
+    Assert.assertEquals(0, response.getStatusCode());
+    Assert.assertNull(response.getToken());
+  }
+
+  @Test
+  public void getRbacToken_auth_failed() {
+    mockForGetRbacToken(401, "{\"detail\":\"wrong user name or password\","
+        + "\"errorCode\":\"401002\","
+        + "\"errorMessage\":\"Request unauthorized\"}");
+    RbacTokenRequest request = new RbacTokenRequest();
+    request.setAccountName("test_account_name");
+    request.setPassword("test_password");
+    RbacTokenResponse response = oClient.getRbacToken(request);
+
+    Assert.assertEquals(401, response.getStatusCode());
+    Assert.assertNull(response.getToken());
+  }
+
+  private void mockForGetRbacToken(final int responseStatusCode, final String 
responseBody) {
+    new MockUp<RestClientUtil>() {
+      @Mock
+      void post(IpPort ipPort, String uri, RequestParam requestParam, 
Handler<RestResponse> responseHandler) {
+        Assert.assertEquals("/v4/token", uri);
+        try {
+          RbacTokenRequest rbacTokenRequest = 
JsonUtils.readValue(requestParam.getBody(), RbacTokenRequest.class);
+          Assert.assertEquals("test_account_name", 
rbacTokenRequest.getAccountName());
+          Assert.assertEquals("test_password", rbacTokenRequest.getPassword());
+        } catch (IOException e) {
+          fail("malformed request body");
+          e.printStackTrace();
+        }
+        HttpClientResponse httpClientResponse = new 
MockUp<HttpClientResponse>() {
+          @Mock
+          int statusCode() {
+            return responseStatusCode;
+          }
+
+          @Mock
+          HttpClientResponse bodyHandler(Handler<Buffer> bodyHandler) {
+            bodyHandler.handle(Buffer.buffer(responseBody));
+            return null;
+          }
+        }.getMockInstance();
+        responseHandler.handle(new RestResponse(null, httpClientResponse));
+      }
+    };
+  }
 }

Reply via email to