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

xuba pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/amoro.git


The following commit(s) were added to refs/heads/master by this push:
     new c8b998c1e [AMORO-3873] Support Bearer/JWT authentication (#3905)
c8b998c1e is described below

commit c8b998c1e3288cf275a72f0c2c79501bcc2724f8
Author: Fei Wang <[email protected]>
AuthorDate: Sun Nov 16 21:35:49 2025 -0800

    [AMORO-3873] Support Bearer/JWT authentication (#3905)
    
    * save
    
    * save
    
    * save
    
    * save
    
    * Save
    
    * asve
    
    * rename package
    
    * comments
    
    * basic
    
    * remove token from log
---
 .../apache/amoro/server/AmoroManagementConf.java   | 24 ++++++++-
 .../DefaultPasswdAuthenticationProvider.java       | 12 +++--
 .../authentication/DefaultPasswordCredential.java  | 46 ++++++++--------
 .../authentication/DefaultTokenCredential.java     | 42 +++++----------
 .../authentication/HttpAuthenticationFactory.java  | 62 +++++++++++++++++++++-
 .../amoro/server/dashboard/DashboardServer.java    | 30 ++++++++---
 .../HttpAuthenticationFactoryTest.java             | 47 ++++++++++++++--
 ...serDefinedTokenAuthenticationProviderImpl.java} | 32 +++++------
 .../{spi => }/authentication/BasicPrincipal.java   |  2 +-
 .../PasswdAuthenticationProvider.java              | 11 ++--
 .../PasswordCredential.java}                       | 41 +++-----------
 .../TokenAuthenticationProvider.java}              | 20 +++----
 .../TokenCredential.java}                          | 41 +++-----------
 docs/admin-guides/deployment.md                    |  6 ++-
 14 files changed, 242 insertions(+), 174 deletions(-)

diff --git 
a/amoro-ams/src/main/java/org/apache/amoro/server/AmoroManagementConf.java 
b/amoro-ams/src/main/java/org/apache/amoro/server/AmoroManagementConf.java
index 58bb886d7..750907e21 100644
--- a/amoro-ams/src/main/java/org/apache/amoro/server/AmoroManagementConf.java
+++ b/amoro-ams/src/main/java/org/apache/amoro/server/AmoroManagementConf.java
@@ -275,7 +275,8 @@ public class AmoroManagementConf {
       ConfigOptions.key("http-server.rest-auth-type")
           .stringType()
           .defaultValue("token")
-          .withDescription("The authentication used by REST APIs, token 
(default) or basic.");
+          .withDescription(
+              "The authentication used by REST APIs, token (default), basic or 
bearer.");
 
   public static final ConfigOption<Duration> HTTP_SERVER_SESSION_TIMEOUT =
       ConfigOptions.key("http-server.session-timeout")
@@ -289,7 +290,26 @@ public class AmoroManagementConf {
           .defaultValue(DefaultPasswdAuthenticationProvider.class.getName())
           .withDescription(
               "User-defined password authentication implementation of"
-                  + " 
org.apache.amoro.spi.authentication.PasswdAuthenticationProvider");
+                  + " 
org.apache.amoro.authentication.PasswdAuthenticationProvider");
+
+  public static final ConfigOption<String> HTTP_SERVER_AUTH_BEARER_PROVIDER =
+      ConfigOptions.key("http-server.auth-bearer-provider")
+          .stringType()
+          .noDefaultValue()
+          .withDescription(
+              "User-defined Bearer token such as JWT (JSON Web Token) 
authentication implementation"
+                  + " of 
org.apache.amoro.authentication.TokenAuthenticationProvider");
+
+  public static final ConfigOption<String> HTTP_SERVER_PROXY_CLIENT_IP_HEADER =
+      ConfigOptions.key("http-server.proxy-client-ip-header")
+          .stringType()
+          .defaultValue("X-Real-IP")
+          .withDescription(
+              "The HTTP header to record the real client IP address. If your 
server is behind a load"
+                  + " balancer or other proxy, the server will see this load 
balancer or proxy IP address as"
+                  + " the client IP address, to get around this common issue, 
most load balancers or proxies"
+                  + " offer the ability to record the real remote IP address 
in an HTTP header that will be"
+                  + " added to the request for other devices to use.");
 
   public static final ConfigOption<Integer> OPTIMIZING_COMMIT_THREAD_COUNT =
       ConfigOptions.key("self-optimizing.commit-thread-count")
diff --git 
a/amoro-ams/src/main/java/org/apache/amoro/server/authentication/DefaultPasswdAuthenticationProvider.java
 
b/amoro-ams/src/main/java/org/apache/amoro/server/authentication/DefaultPasswdAuthenticationProvider.java
index a4b07ab60..2a74a4ea7 100644
--- 
a/amoro-ams/src/main/java/org/apache/amoro/server/authentication/DefaultPasswdAuthenticationProvider.java
+++ 
b/amoro-ams/src/main/java/org/apache/amoro/server/authentication/DefaultPasswdAuthenticationProvider.java
@@ -18,11 +18,12 @@
 
 package org.apache.amoro.server.authentication;
 
+import org.apache.amoro.authentication.BasicPrincipal;
+import org.apache.amoro.authentication.PasswdAuthenticationProvider;
+import org.apache.amoro.authentication.PasswordCredential;
 import org.apache.amoro.config.Configurations;
 import org.apache.amoro.exception.SignatureCheckException;
 import org.apache.amoro.server.AmoroManagementConf;
-import org.apache.amoro.spi.authentication.BasicPrincipal;
-import org.apache.amoro.spi.authentication.PasswdAuthenticationProvider;
 
 public class DefaultPasswdAuthenticationProvider implements 
PasswdAuthenticationProvider {
   private String basicAuthUser;
@@ -34,10 +35,11 @@ public class DefaultPasswdAuthenticationProvider implements 
PasswdAuthentication
   }
 
   @Override
-  public BasicPrincipal authenticate(String user, String password) throws 
SignatureCheckException {
-    if (!(basicAuthUser.equals(user) && basicAuthPassword.equals(password))) {
+  public BasicPrincipal authenticate(PasswordCredential credential) throws 
SignatureCheckException {
+    if (!(basicAuthUser.equals(credential.username())
+        && basicAuthPassword.equals(credential.password()))) {
       throw new SignatureCheckException("Failed to authenticate via basic 
authentication");
     }
-    return new BasicPrincipal(user);
+    return new BasicPrincipal(credential.username());
   }
 }
diff --git 
a/amoro-common/src/main/java/org/apache/amoro/spi/authentication/BasicPrincipal.java
 
b/amoro-ams/src/main/java/org/apache/amoro/server/authentication/DefaultPasswordCredential.java
similarity index 50%
copy from 
amoro-common/src/main/java/org/apache/amoro/spi/authentication/BasicPrincipal.java
copy to 
amoro-ams/src/main/java/org/apache/amoro/server/authentication/DefaultPasswordCredential.java
index 794682020..4304e51b6 100644
--- 
a/amoro-common/src/main/java/org/apache/amoro/spi/authentication/BasicPrincipal.java
+++ 
b/amoro-ams/src/main/java/org/apache/amoro/server/authentication/DefaultPasswordCredential.java
@@ -16,43 +16,41 @@
  * limitations under the License.
  */
 
-package org.apache.amoro.spi.authentication;
+package org.apache.amoro.server.authentication;
 
-import java.security.Principal;
-import java.util.Objects;
+import org.apache.amoro.authentication.PasswordCredential;
 
-public class BasicPrincipal implements Principal {
-  private final String name;
+import java.util.Collections;
+import java.util.Map;
 
-  public BasicPrincipal(String name) {
-    this.name = name;
-    Objects.requireNonNull(name, "Principal name cannot be null");
+public class DefaultPasswordCredential implements PasswordCredential {
+  private String username;
+  private String password;
+  private Map<String, String> extraInfo;
+
+  public DefaultPasswordCredential(
+      String username, String password, Map<String, String> extraInfo) {
+    this.username = username;
+    this.password = password;
+    this.extraInfo = extraInfo;
   }
 
-  @Override
-  public String getName() {
-    return name;
+  public DefaultPasswordCredential(String username, String password) {
+    this(username, password, Collections.emptyMap());
   }
 
   @Override
-  public String toString() {
-    return name;
+  public String username() {
+    return username;
   }
 
   @Override
-  public boolean equals(Object o) {
-    if (this == o) {
-      return true;
-    }
-    if (o == null || getClass() != o.getClass()) {
-      return false;
-    }
-    BasicPrincipal that = (BasicPrincipal) o;
-    return name.equals(that.name);
+  public String password() {
+    return password;
   }
 
   @Override
-  public int hashCode() {
-    return Objects.hashCode(name);
+  public Map<String, String> extraInfo() {
+    return null == extraInfo ? Collections.emptyMap() : extraInfo;
   }
 }
diff --git 
a/amoro-common/src/main/java/org/apache/amoro/spi/authentication/BasicPrincipal.java
 
b/amoro-ams/src/main/java/org/apache/amoro/server/authentication/DefaultTokenCredential.java
similarity index 53%
copy from 
amoro-common/src/main/java/org/apache/amoro/spi/authentication/BasicPrincipal.java
copy to 
amoro-ams/src/main/java/org/apache/amoro/server/authentication/DefaultTokenCredential.java
index 794682020..61f17d9ce 100644
--- 
a/amoro-common/src/main/java/org/apache/amoro/spi/authentication/BasicPrincipal.java
+++ 
b/amoro-ams/src/main/java/org/apache/amoro/server/authentication/DefaultTokenCredential.java
@@ -16,43 +16,29 @@
  * limitations under the License.
  */
 
-package org.apache.amoro.spi.authentication;
+package org.apache.amoro.server.authentication;
 
-import java.security.Principal;
-import java.util.Objects;
+import org.apache.amoro.authentication.TokenCredential;
 
-public class BasicPrincipal implements Principal {
-  private final String name;
+import java.util.Collections;
+import java.util.Map;
 
-  public BasicPrincipal(String name) {
-    this.name = name;
-    Objects.requireNonNull(name, "Principal name cannot be null");
-  }
+public class DefaultTokenCredential implements TokenCredential {
+  private String token;
+  private Map<String, String> extraInfo;
 
-  @Override
-  public String getName() {
-    return name;
-  }
-
-  @Override
-  public String toString() {
-    return name;
+  public DefaultTokenCredential(String token, Map<String, String> extraInfo) {
+    this.token = token;
+    this.extraInfo = extraInfo;
   }
 
   @Override
-  public boolean equals(Object o) {
-    if (this == o) {
-      return true;
-    }
-    if (o == null || getClass() != o.getClass()) {
-      return false;
-    }
-    BasicPrincipal that = (BasicPrincipal) o;
-    return name.equals(that.name);
+  public String token() {
+    return token;
   }
 
   @Override
-  public int hashCode() {
-    return Objects.hashCode(name);
+  public Map<String, String> extraInfo() {
+    return null == extraInfo ? Collections.emptyMap() : extraInfo;
   }
 }
diff --git 
a/amoro-ams/src/main/java/org/apache/amoro/server/authentication/HttpAuthenticationFactory.java
 
b/amoro-ams/src/main/java/org/apache/amoro/server/authentication/HttpAuthenticationFactory.java
index 568272000..2699741c5 100644
--- 
a/amoro-ams/src/main/java/org/apache/amoro/server/authentication/HttpAuthenticationFactory.java
+++ 
b/amoro-ams/src/main/java/org/apache/amoro/server/authentication/HttpAuthenticationFactory.java
@@ -18,16 +18,36 @@
 
 package org.apache.amoro.server.authentication;
 
+import static org.apache.amoro.authentication.TokenCredential.CLIENT_IP_KEY;
+
+import io.javalin.core.security.BasicAuthCredentials;
+import io.javalin.core.util.Header;
+import io.javalin.http.Context;
+import org.apache.amoro.authentication.PasswdAuthenticationProvider;
+import org.apache.amoro.authentication.PasswordCredential;
+import org.apache.amoro.authentication.TokenAuthenticationProvider;
+import org.apache.amoro.authentication.TokenCredential;
 import org.apache.amoro.config.Configurations;
-import org.apache.amoro.spi.authentication.PasswdAuthenticationProvider;
+import org.apache.amoro.shade.guava32.com.google.common.base.Preconditions;
 import org.apache.amoro.utils.DynConstructors;
 
+import java.util.Collections;
+import java.util.Map;
+import java.util.Optional;
+
 public class HttpAuthenticationFactory {
+  public static final String BEARER_TOKEN_SCHEMA = "BEARER";
+
   public static PasswdAuthenticationProvider getPasswordAuthenticationProvider(
       String providerClass, Configurations conf) {
     return createAuthenticationProvider(providerClass, 
PasswdAuthenticationProvider.class, conf);
   }
 
+  public static TokenAuthenticationProvider getBearerAuthenticationProvider(
+      String providerClass, Configurations conf) {
+    return createAuthenticationProvider(providerClass, 
TokenAuthenticationProvider.class, conf);
+  }
+
   private static <T> T createAuthenticationProvider(
       String className, Class<T> expected, Configurations conf) {
     try {
@@ -40,4 +60,44 @@ public class HttpAuthenticationFactory {
       throw new IllegalStateException(className + " must extend of " + 
expected.getName());
     }
   }
+
+  public static PasswordCredential getPasswordCredential(
+      Context context, String proxyClientIpHeader) {
+    BasicAuthCredentials cred = context.basicAuthCredentials();
+    Preconditions.checkNotNull(cred, "BasicAuthCredentials must not be null");
+    return new DefaultPasswordCredential(
+        cred.getUsername(),
+        cred.getPassword(),
+        getCredentialExtraInfo(context, proxyClientIpHeader));
+  }
+
+  public static TokenCredential getBearerTokenCredential(
+      Context context, String proxyClientIpHeader) {
+    String bearerToken = getBearerToken(context);
+    Preconditions.checkNotNull(bearerToken, "Bearer token must not be null");
+    return new DefaultTokenCredential(
+        bearerToken, getCredentialExtraInfo(context, proxyClientIpHeader));
+  }
+
+  /**
+   * Extracts the Bearer token from the HTTP Authorization header in the 
request context. Returns
+   * the token string if present and valid, otherwise returns null.
+   */
+  private static String getBearerToken(Context context) {
+    String authorization = context.header(Header.AUTHORIZATION);
+    if (authorization != null) {
+      String[] parts = authorization.trim().split("\\s+", 2);
+      if (parts.length == 2 && BEARER_TOKEN_SCHEMA.equalsIgnoreCase(parts[0])) 
{
+        return parts[1].trim();
+      }
+    }
+    return null;
+  }
+
+  private static Map<String, String> getCredentialExtraInfo(
+      Context context, String proxyClientIpHeader) {
+    return Collections.singletonMap(
+        CLIENT_IP_KEY,
+        
Optional.ofNullable(context.header(proxyClientIpHeader)).orElse(context.ip()));
+  }
 }
diff --git 
a/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/DashboardServer.java
 
b/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/DashboardServer.java
index 7e342159f..02395543b 100644
--- 
a/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/DashboardServer.java
+++ 
b/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/DashboardServer.java
@@ -25,12 +25,13 @@ import static io.javalin.apibuilder.ApiBuilder.post;
 import static io.javalin.apibuilder.ApiBuilder.put;
 
 import io.javalin.apibuilder.EndpointGroup;
-import io.javalin.core.security.BasicAuthCredentials;
 import io.javalin.http.ContentType;
 import io.javalin.http.Context;
 import io.javalin.http.HttpCode;
 import io.javalin.http.staticfiles.Location;
 import io.javalin.http.staticfiles.StaticFileConfig;
+import org.apache.amoro.authentication.PasswdAuthenticationProvider;
+import org.apache.amoro.authentication.TokenAuthenticationProvider;
 import org.apache.amoro.config.Configurations;
 import org.apache.amoro.exception.ForbiddenException;
 import org.apache.amoro.exception.SignatureCheckException;
@@ -55,7 +56,6 @@ import org.apache.amoro.server.resource.OptimizerManager;
 import org.apache.amoro.server.table.TableManager;
 import org.apache.amoro.server.terminal.TerminalManager;
 import org.apache.amoro.shade.guava32.com.google.common.base.Preconditions;
-import org.apache.amoro.spi.authentication.PasswdAuthenticationProvider;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -74,6 +74,7 @@ public class DashboardServer {
   public static final Logger LOG = 
LoggerFactory.getLogger(DashboardServer.class);
 
   private static final String AUTH_TYPE_BASIC = "basic";
+  private static final String AUTH_TYPE_BEARER = "bearer";
   private static final String X_REQUEST_SOURCE_HEADER = "X-Request-Source";
   private static final String X_REQUEST_SOURCE_WEB = "Web";
   private final CatalogController catalogController;
@@ -90,6 +91,8 @@ public class DashboardServer {
   private final ApiTokenController apiTokenController;
 
   private final PasswdAuthenticationProvider basicAuthProvider;
+  private final TokenAuthenticationProvider bearerAuthProvider;
+  private final String proxyClientIpHeader;
 
   public DashboardServer(
       Configurations serviceConfig,
@@ -123,6 +126,14 @@ public class DashboardServer {
                 
serviceConfig.get(AmoroManagementConf.HTTP_SERVER_AUTH_BASIC_PROVIDER),
                 serviceConfig)
             : null;
+    this.bearerAuthProvider =
+        AUTH_TYPE_BEARER.equalsIgnoreCase(authType)
+            ? HttpAuthenticationFactory.getBearerAuthenticationProvider(
+                
serviceConfig.get(AmoroManagementConf.HTTP_SERVER_AUTH_BEARER_PROVIDER),
+                serviceConfig)
+            : null;
+    this.proxyClientIpHeader =
+        
serviceConfig.get(AmoroManagementConf.HTTP_SERVER_PROXY_CLIENT_IP_HEADER);
   }
 
   private volatile String indexHtml = null;
@@ -399,10 +410,17 @@ public class DashboardServer {
       }
       return;
     }
-    if (null != basicAuthProvider) {
-      BasicAuthCredentials cred = ctx.basicAuthCredentials();
-      Principal authPrincipal =
-          basicAuthProvider.authenticate(cred.component1(), cred.component2());
+    if (null != basicAuthProvider || null != bearerAuthProvider) {
+      Principal authPrincipal;
+      if (null != basicAuthProvider) {
+        authPrincipal =
+            basicAuthProvider.authenticate(
+                HttpAuthenticationFactory.getPasswordCredential(ctx, 
proxyClientIpHeader));
+      } else {
+        authPrincipal =
+            bearerAuthProvider.authenticate(
+                HttpAuthenticationFactory.getBearerTokenCredential(ctx, 
proxyClientIpHeader));
+      }
       LOG.info(
           "Authenticated principal: {}, URI: {}",
           authPrincipal != null ? authPrincipal.getName() : "null",
diff --git 
a/amoro-ams/src/test/java/org/apache/amoro/server/authentication/HttpAuthenticationFactoryTest.java
 
b/amoro-ams/src/test/java/org/apache/amoro/server/authentication/HttpAuthenticationFactoryTest.java
index 367abb040..a8967a8dc 100644
--- 
a/amoro-ams/src/test/java/org/apache/amoro/server/authentication/HttpAuthenticationFactoryTest.java
+++ 
b/amoro-ams/src/test/java/org/apache/amoro/server/authentication/HttpAuthenticationFactoryTest.java
@@ -20,12 +20,16 @@ package org.apache.amoro.server.authentication;
 
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
+import org.apache.amoro.authentication.PasswdAuthenticationProvider;
+import org.apache.amoro.authentication.TokenAuthenticationProvider;
+import org.apache.amoro.authentication.TokenCredential;
 import org.apache.amoro.config.Configurations;
 import org.apache.amoro.exception.SignatureCheckException;
 import org.apache.amoro.server.AmoroManagementConf;
-import org.apache.amoro.spi.authentication.PasswdAuthenticationProvider;
 import org.junit.jupiter.api.Test;
 
+import java.util.Collections;
+
 public class HttpAuthenticationFactoryTest {
   @Test
   public void testPasswordAuthenticationProvider() {
@@ -44,17 +48,52 @@ public class HttpAuthenticationFactoryTest {
         HttpAuthenticationFactory.getPasswordAuthenticationProvider(
             DefaultPasswdAuthenticationProvider.class.getName(), conf);
 
-    assert passwdAuthenticationProvider.authenticate("admin", 
"password").getName().equals("admin");
+    assert passwdAuthenticationProvider
+        .authenticate(new DefaultPasswordCredential("admin", "password"))
+        .getName()
+        .equals("admin");
 
     assertThrows(
         SignatureCheckException.class,
         () -> {
-          passwdAuthenticationProvider.authenticate("admin", 
"invalidPassword");
+          passwdAuthenticationProvider.authenticate(
+              new DefaultPasswordCredential("admin", "invalidPassword"));
+        });
+    assertThrows(
+        SignatureCheckException.class,
+        () -> {
+          passwdAuthenticationProvider.authenticate(
+              new DefaultPasswordCredential("nonAdmin", "password"));
+        });
+  }
+
+  @Test
+  public void testBearerTokenAuthenticationProvider() {
+    Configurations conf = new Configurations();
+    assertThrows(
+        IllegalStateException.class,
+        () -> {
+          HttpAuthenticationFactory.getBearerAuthenticationProvider(
+              "NonExistentProviderClass", conf);
         });
+
+    TokenAuthenticationProvider tokenAuthenticationProvider =
+        HttpAuthenticationFactory.getBearerAuthenticationProvider(
+            UserDefinedTokenAuthenticationProviderImpl.class.getName(), conf);
+
+    assert tokenAuthenticationProvider
+        .authenticate(
+            new DefaultTokenCredential(
+                "token", 
Collections.singletonMap(TokenCredential.CLIENT_IP_KEY, "localhost")))
+        .getName()
+        .equals("user");
     assertThrows(
         SignatureCheckException.class,
         () -> {
-          passwdAuthenticationProvider.authenticate("nonAdmin", "password");
+          tokenAuthenticationProvider.authenticate(
+              new DefaultTokenCredential(
+                  "invalidToken",
+                  Collections.singletonMap(TokenCredential.CLIENT_IP_KEY, 
"localhost")));
         });
   }
 }
diff --git 
a/amoro-ams/src/main/java/org/apache/amoro/server/authentication/DefaultPasswdAuthenticationProvider.java
 
b/amoro-ams/src/test/java/org/apache/amoro/server/authentication/UserDefinedTokenAuthenticationProviderImpl.java
similarity index 50%
copy from 
amoro-ams/src/main/java/org/apache/amoro/server/authentication/DefaultPasswdAuthenticationProvider.java
copy to 
amoro-ams/src/test/java/org/apache/amoro/server/authentication/UserDefinedTokenAuthenticationProviderImpl.java
index a4b07ab60..7219c5ec8 100644
--- 
a/amoro-ams/src/main/java/org/apache/amoro/server/authentication/DefaultPasswdAuthenticationProvider.java
+++ 
b/amoro-ams/src/test/java/org/apache/amoro/server/authentication/UserDefinedTokenAuthenticationProviderImpl.java
@@ -18,26 +18,28 @@
 
 package org.apache.amoro.server.authentication;
 
-import org.apache.amoro.config.Configurations;
+import org.apache.amoro.authentication.BasicPrincipal;
+import org.apache.amoro.authentication.TokenAuthenticationProvider;
+import org.apache.amoro.authentication.TokenCredential;
 import org.apache.amoro.exception.SignatureCheckException;
-import org.apache.amoro.server.AmoroManagementConf;
-import org.apache.amoro.spi.authentication.BasicPrincipal;
-import org.apache.amoro.spi.authentication.PasswdAuthenticationProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
-public class DefaultPasswdAuthenticationProvider implements 
PasswdAuthenticationProvider {
-  private String basicAuthUser;
-  private String basicAuthPassword;
+import java.security.Principal;
 
-  public DefaultPasswdAuthenticationProvider(Configurations conf) {
-    this.basicAuthUser = conf.get(AmoroManagementConf.ADMIN_USERNAME);
-    this.basicAuthPassword = conf.get(AmoroManagementConf.ADMIN_PASSWORD);
-  }
+public class UserDefinedTokenAuthenticationProviderImpl implements 
TokenAuthenticationProvider {
+  private static final Logger LOG =
+      
LoggerFactory.getLogger(UserDefinedTokenAuthenticationProviderImpl.class);
+  public static final String VALID_TOKEN = "token";
 
   @Override
-  public BasicPrincipal authenticate(String user, String password) throws 
SignatureCheckException {
-    if (!(basicAuthUser.equals(user) && basicAuthPassword.equals(password))) {
-      throw new SignatureCheckException("Failed to authenticate via basic 
authentication");
+  public Principal authenticate(TokenCredential credential) throws 
SignatureCheckException {
+    String clientIp = 
credential.extraInfo().get(TokenCredential.CLIENT_IP_KEY);
+    if (VALID_TOKEN.equals(credential.token())) {
+      LOG.info("Success login with clientIp: {}", clientIp);
+      return new BasicPrincipal("user");
+    } else {
+      throw new SignatureCheckException("Token is not valid!");
     }
-    return new BasicPrincipal(user);
   }
 }
diff --git 
a/amoro-common/src/main/java/org/apache/amoro/spi/authentication/BasicPrincipal.java
 
b/amoro-common/src/main/java/org/apache/amoro/authentication/BasicPrincipal.java
similarity index 97%
copy from 
amoro-common/src/main/java/org/apache/amoro/spi/authentication/BasicPrincipal.java
copy to 
amoro-common/src/main/java/org/apache/amoro/authentication/BasicPrincipal.java
index 794682020..2e5936d3d 100644
--- 
a/amoro-common/src/main/java/org/apache/amoro/spi/authentication/BasicPrincipal.java
+++ 
b/amoro-common/src/main/java/org/apache/amoro/authentication/BasicPrincipal.java
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-package org.apache.amoro.spi.authentication;
+package org.apache.amoro.authentication;
 
 import java.security.Principal;
 import java.util.Objects;
diff --git 
a/amoro-common/src/main/java/org/apache/amoro/spi/authentication/PasswdAuthenticationProvider.java
 
b/amoro-common/src/main/java/org/apache/amoro/authentication/PasswdAuthenticationProvider.java
similarity index 76%
copy from 
amoro-common/src/main/java/org/apache/amoro/spi/authentication/PasswdAuthenticationProvider.java
copy to 
amoro-common/src/main/java/org/apache/amoro/authentication/PasswdAuthenticationProvider.java
index 3352ab17e..8d868237f 100644
--- 
a/amoro-common/src/main/java/org/apache/amoro/spi/authentication/PasswdAuthenticationProvider.java
+++ 
b/amoro-common/src/main/java/org/apache/amoro/authentication/PasswdAuthenticationProvider.java
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-package org.apache.amoro.spi.authentication;
+package org.apache.amoro.authentication;
 
 import org.apache.amoro.exception.SignatureCheckException;
 
@@ -25,13 +25,12 @@ import java.security.Principal;
 public interface PasswdAuthenticationProvider {
   /**
    * The authenticate method is called by the amoro Server authentication 
layer to authenticate
-   * users for their requests. If a user is to be granted, return 
nothing/throw nothing. When a user
-   * is to be disallowed, throw an appropriate [[SignatureCheckException]].
+   * users for their requests. If a user is to be granted, return the 
identifier. When a user is to
+   * be disallowed, throw an appropriate [[SignatureCheckException]].
    *
-   * @param user The username received over the connection request
-   * @param password The password received over the connection request
+   * @param credential The credential received over the connection request
    * @return The identifier associated with the credential
    * @throws SignatureCheckException When a user is found to be invalid by the 
implementation
    */
-  Principal authenticate(String user, String password) throws 
SignatureCheckException;
+  Principal authenticate(PasswordCredential credential) throws 
SignatureCheckException;
 }
diff --git 
a/amoro-common/src/main/java/org/apache/amoro/spi/authentication/BasicPrincipal.java
 
b/amoro-common/src/main/java/org/apache/amoro/authentication/PasswordCredential.java
similarity index 51%
copy from 
amoro-common/src/main/java/org/apache/amoro/spi/authentication/BasicPrincipal.java
copy to 
amoro-common/src/main/java/org/apache/amoro/authentication/PasswordCredential.java
index 794682020..cb49bec39 100644
--- 
a/amoro-common/src/main/java/org/apache/amoro/spi/authentication/BasicPrincipal.java
+++ 
b/amoro-common/src/main/java/org/apache/amoro/authentication/PasswordCredential.java
@@ -16,43 +16,14 @@
  * limitations under the License.
  */
 
-package org.apache.amoro.spi.authentication;
+package org.apache.amoro.authentication;
 
-import java.security.Principal;
-import java.util.Objects;
+import java.util.Map;
 
-public class BasicPrincipal implements Principal {
-  private final String name;
+public interface PasswordCredential {
+  String username();
 
-  public BasicPrincipal(String name) {
-    this.name = name;
-    Objects.requireNonNull(name, "Principal name cannot be null");
-  }
+  String password();
 
-  @Override
-  public String getName() {
-    return name;
-  }
-
-  @Override
-  public String toString() {
-    return name;
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) {
-      return true;
-    }
-    if (o == null || getClass() != o.getClass()) {
-      return false;
-    }
-    BasicPrincipal that = (BasicPrincipal) o;
-    return name.equals(that.name);
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hashCode(name);
-  }
+  Map<String, String> extraInfo();
 }
diff --git 
a/amoro-common/src/main/java/org/apache/amoro/spi/authentication/PasswdAuthenticationProvider.java
 
b/amoro-common/src/main/java/org/apache/amoro/authentication/TokenAuthenticationProvider.java
similarity index 54%
rename from 
amoro-common/src/main/java/org/apache/amoro/spi/authentication/PasswdAuthenticationProvider.java
rename to 
amoro-common/src/main/java/org/apache/amoro/authentication/TokenAuthenticationProvider.java
index 3352ab17e..d8100eebd 100644
--- 
a/amoro-common/src/main/java/org/apache/amoro/spi/authentication/PasswdAuthenticationProvider.java
+++ 
b/amoro-common/src/main/java/org/apache/amoro/authentication/TokenAuthenticationProvider.java
@@ -16,22 +16,22 @@
  * limitations under the License.
  */
 
-package org.apache.amoro.spi.authentication;
+package org.apache.amoro.authentication;
 
 import org.apache.amoro.exception.SignatureCheckException;
 
 import java.security.Principal;
 
-public interface PasswdAuthenticationProvider {
+public interface TokenAuthenticationProvider {
   /**
-   * The authenticate method is called by the amoro Server authentication 
layer to authenticate
-   * users for their requests. If a user is to be granted, return 
nothing/throw nothing. When a user
-   * is to be disallowed, throw an appropriate [[SignatureCheckException]].
+   * TokenAuthenticationProvider is used by the Amoro server authentication 
layer to validate Bearer
+   * tokens, such as JWT (JSON Web Token), provided in client requests. If the 
token is invalid,
+   * expired, or fails signature verification, a {@link 
SignatureCheckException} should be thrown to
+   * deny access.
    *
-   * @param user The username received over the connection request
-   * @param password The password received over the connection request
-   * @return The identifier associated with the credential
-   * @throws SignatureCheckException When a user is found to be invalid by the 
implementation
+   * @param credential The Bearer token credential (e.g., JWT) received in the 
connection request
+   * @return The {@link Principal} associated with the authenticated token
+   * @throws SignatureCheckException If the token is invalid, expired, or 
fails verification
    */
-  Principal authenticate(String user, String password) throws 
SignatureCheckException;
+  Principal authenticate(TokenCredential credential) throws 
SignatureCheckException;
 }
diff --git 
a/amoro-common/src/main/java/org/apache/amoro/spi/authentication/BasicPrincipal.java
 
b/amoro-common/src/main/java/org/apache/amoro/authentication/TokenCredential.java
similarity index 51%
rename from 
amoro-common/src/main/java/org/apache/amoro/spi/authentication/BasicPrincipal.java
rename to 
amoro-common/src/main/java/org/apache/amoro/authentication/TokenCredential.java
index 794682020..b60e53440 100644
--- 
a/amoro-common/src/main/java/org/apache/amoro/spi/authentication/BasicPrincipal.java
+++ 
b/amoro-common/src/main/java/org/apache/amoro/authentication/TokenCredential.java
@@ -16,43 +16,14 @@
  * limitations under the License.
  */
 
-package org.apache.amoro.spi.authentication;
+package org.apache.amoro.authentication;
 
-import java.security.Principal;
-import java.util.Objects;
+import java.util.Map;
 
-public class BasicPrincipal implements Principal {
-  private final String name;
+public interface TokenCredential {
+  String CLIENT_IP_KEY = "clientIp";
 
-  public BasicPrincipal(String name) {
-    this.name = name;
-    Objects.requireNonNull(name, "Principal name cannot be null");
-  }
+  String token();
 
-  @Override
-  public String getName() {
-    return name;
-  }
-
-  @Override
-  public String toString() {
-    return name;
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) {
-      return true;
-    }
-    if (o == null || getClass() != o.getClass()) {
-      return false;
-    }
-    BasicPrincipal that = (BasicPrincipal) o;
-    return name.equals(that.name);
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hashCode(name);
-  }
+  Map<String, String> extraInfo();
 }
diff --git a/docs/admin-guides/deployment.md b/docs/admin-guides/deployment.md
index 23374ec2f..83c81840d 100644
--- a/docs/admin-guides/deployment.md
+++ b/docs/admin-guides/deployment.md
@@ -75,8 +75,10 @@ If you want to use AMS in a production environment, it is 
recommended to modify
 - The `ams.thrift-server.table-service.bind-port` configuration specifies the 
binding port of the Thrift Server that provides the table service. The compute 
engines access AMS through this port, and the default value is 1260.
 - The `ams.thrift-server.optimizing-service.bind-port` configuration specifies 
the binding port of the Thrift Server that provides the optimizing service. The 
optimizers access AMS through this port, and the default value is 1261.
 - The `ams.http-server.bind-port` configuration specifies the port to which 
the HTTP service is bound. The Dashboard and Open API are bound to this port, 
and the default value is 1630.
-- The `ams.http-server.rest-auth-type` configuration specifies the REST API 
auth type, which could be token(default) or basic.
-- The `ams.http-server.auth-basic-provider` configuration specifies the REST 
API basic authentication provider. By default, it uses `ams.admin-username` and 
`ams.admin-password` for authentication. You can also specify a custom 
implementation by providing the fully qualified class name of a class that 
implements the 
`org.apache.amoro.spi.authentication.PasswdAuthenticationProvider` interface.
+- The `ams.http-server.rest-auth-type` configuration specifies the REST API 
auth type, which could be token(default), basic or bearer.
+- The `ams.http-server.auth-basic-provider` configuration specifies the REST 
API basic authentication provider. By default, it uses `ams.admin-username` and 
`ams.admin-password` for authentication. You can also specify a custom 
implementation by providing the fully qualified class name of a class that 
implements the `org.apache.amoro.authentication.PasswdAuthenticationProvider` 
interface.
+- The `ams.http-server.auth-bearer-provider` configuration specifies the REST 
API Bearer token authentication provider. Set this to the fully qualified class 
name of your custom provider implementing the 
`org.apache.amoro.authentication.TokenAuthenticationProvider` interface. This 
is required when `ams.http-server.rest-auth-type` is set to `bearer`.
+- The `ams.http-server.proxy-client-ip-header` configuration specifies the 
HTTP header to use for extracting the real client IP address when AMS is 
deployed behind a reverse proxy (such as Nginx or a load balancer). Common 
values include `X-Forwarded-For` or `X-Real-IP`. If not set, AMS will use the 
remote address from the connection.
 
 ```yaml
 ams:


Reply via email to