This is an automated email from the ASF dual-hosted git repository.
jinsongzhou 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 c2219ca1e [AMORO-3872] Support to customize basic authentication
implementation (#3871)
c2219ca1e is described below
commit c2219ca1efaf30764a62f86d29b350e741cceb71
Author: Fei Wang <[email protected]>
AuthorDate: Wed Nov 5 19:53:11 2025 -0800
[AMORO-3872] Support to customize basic authentication implementation
(#3871)
* support to customize basic auth"
* save
* docs
* common
* address comments
* nit
---------
Co-authored-by: Xu Bai <[email protected]>
---
.../apache/amoro/server/AmoroManagementConf.java | 9 ++++
.../DefaultPasswdAuthenticationProvider.java | 43 ++++++++++++++++
.../authentication/HttpAuthenticationFactory.java | 43 ++++++++++++++++
.../amoro/server/dashboard/DashboardServer.java | 30 ++++++-----
.../HttpAuthenticationFactoryTest.java | 60 ++++++++++++++++++++++
.../amoro/spi/authentication/BasicPrincipal.java | 58 +++++++++++++++++++++
.../PasswdAuthenticationProvider.java | 37 +++++++++++++
docs/admin-guides/deployment.md | 3 +-
8 files changed, 270 insertions(+), 13 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 ff95d8d0d..58bb886d7 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
@@ -20,6 +20,7 @@ package org.apache.amoro.server;
import org.apache.amoro.config.ConfigOption;
import org.apache.amoro.config.ConfigOptions;
+import
org.apache.amoro.server.authentication.DefaultPasswdAuthenticationProvider;
import org.apache.amoro.utils.MemorySize;
import java.time.Duration;
@@ -282,6 +283,14 @@ public class AmoroManagementConf {
.defaultValue(Duration.ofDays(7))
.withDescription("Timeout for http session.");
+ public static final ConfigOption<String> HTTP_SERVER_AUTH_BASIC_PROVIDER =
+ ConfigOptions.key("http-server.auth-basic-provider")
+ .stringType()
+ .defaultValue(DefaultPasswdAuthenticationProvider.class.getName())
+ .withDescription(
+ "User-defined password authentication implementation of"
+ + "
org.apache.amoro.spi.authentication.PasswdAuthenticationProvider");
+
public static final ConfigOption<Integer> OPTIMIZING_COMMIT_THREAD_COUNT =
ConfigOptions.key("self-optimizing.commit-thread-count")
.intType()
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
new file mode 100644
index 000000000..a4b07ab60
--- /dev/null
+++
b/amoro-ams/src/main/java/org/apache/amoro/server/authentication/DefaultPasswdAuthenticationProvider.java
@@ -0,0 +1,43 @@
+/*
+ * 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.amoro.server.authentication;
+
+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;
+ private String basicAuthPassword;
+
+ public DefaultPasswdAuthenticationProvider(Configurations conf) {
+ this.basicAuthUser = conf.get(AmoroManagementConf.ADMIN_USERNAME);
+ this.basicAuthPassword = conf.get(AmoroManagementConf.ADMIN_PASSWORD);
+ }
+
+ @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");
+ }
+ return new BasicPrincipal(user);
+ }
+}
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
new file mode 100644
index 000000000..568272000
--- /dev/null
+++
b/amoro-ams/src/main/java/org/apache/amoro/server/authentication/HttpAuthenticationFactory.java
@@ -0,0 +1,43 @@
+/*
+ * 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.amoro.server.authentication;
+
+import org.apache.amoro.config.Configurations;
+import org.apache.amoro.spi.authentication.PasswdAuthenticationProvider;
+import org.apache.amoro.utils.DynConstructors;
+
+public class HttpAuthenticationFactory {
+ public static PasswdAuthenticationProvider getPasswordAuthenticationProvider(
+ String providerClass, Configurations conf) {
+ return createAuthenticationProvider(providerClass,
PasswdAuthenticationProvider.class, conf);
+ }
+
+ private static <T> T createAuthenticationProvider(
+ String className, Class<T> expected, Configurations conf) {
+ try {
+ return DynConstructors.builder(expected)
+ .impl(className, Configurations.class)
+ .impl(className)
+ .<T>buildChecked()
+ .newInstance(conf);
+ } catch (Exception e) {
+ throw new IllegalStateException(className + " must extend of " +
expected.getName());
+ }
+ }
+}
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 616b397a3..7e342159f 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
@@ -36,6 +36,7 @@ import org.apache.amoro.exception.ForbiddenException;
import org.apache.amoro.exception.SignatureCheckException;
import org.apache.amoro.server.AmoroManagementConf;
import org.apache.amoro.server.RestCatalogService;
+import org.apache.amoro.server.authentication.HttpAuthenticationFactory;
import org.apache.amoro.server.catalog.CatalogManager;
import org.apache.amoro.server.dashboard.controller.ApiTokenController;
import org.apache.amoro.server.dashboard.controller.CatalogController;
@@ -54,6 +55,7 @@ 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;
@@ -63,6 +65,7 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
+import java.security.Principal;
import java.util.Objects;
import java.util.function.Consumer;
@@ -86,9 +89,7 @@ public class DashboardServer {
private final OverviewController overviewController;
private final ApiTokenController apiTokenController;
- private final String authType;
- private final String basicAuthUser;
- private final String basicAuthPassword;
+ private final PasswdAuthenticationProvider basicAuthProvider;
public DashboardServer(
Configurations serviceConfig,
@@ -115,9 +116,13 @@ public class DashboardServer {
APITokenManager apiTokenManager = new APITokenManager();
this.apiTokenController = new ApiTokenController(apiTokenManager);
- this.authType =
serviceConfig.get(AmoroManagementConf.HTTP_SERVER_REST_AUTH_TYPE);
- this.basicAuthUser = serviceConfig.get(AmoroManagementConf.ADMIN_USERNAME);
- this.basicAuthPassword =
serviceConfig.get(AmoroManagementConf.ADMIN_PASSWORD);
+ String authType =
serviceConfig.get(AmoroManagementConf.HTTP_SERVER_REST_AUTH_TYPE);
+ this.basicAuthProvider =
+ AUTH_TYPE_BASIC.equalsIgnoreCase(authType)
+ ? HttpAuthenticationFactory.getPasswordAuthenticationProvider(
+
serviceConfig.get(AmoroManagementConf.HTTP_SERVER_AUTH_BASIC_PROVIDER),
+ serviceConfig)
+ : null;
}
private volatile String indexHtml = null;
@@ -394,13 +399,14 @@ public class DashboardServer {
}
return;
}
- if (AUTH_TYPE_BASIC.equalsIgnoreCase(authType)) {
+ if (null != basicAuthProvider) {
BasicAuthCredentials cred = ctx.basicAuthCredentials();
- if (!(basicAuthUser.equals(cred.component1())
- && basicAuthPassword.equals(cred.component2()))) {
- throw new SignatureCheckException(
- "Failed to authenticate via basic authentication for url:" +
uriPath);
- }
+ Principal authPrincipal =
+ basicAuthProvider.authenticate(cred.component1(), cred.component2());
+ LOG.info(
+ "Authenticated principal: {}, URI: {}",
+ authPrincipal != null ? authPrincipal.getName() : "null",
+ uriPath);
} else {
apiTokenController.checkApiToken(ctx);
}
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
new file mode 100644
index 000000000..367abb040
--- /dev/null
+++
b/amoro-ams/src/test/java/org/apache/amoro/server/authentication/HttpAuthenticationFactoryTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.amoro.server.authentication;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+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;
+
+public class HttpAuthenticationFactoryTest {
+ @Test
+ public void testPasswordAuthenticationProvider() {
+ Configurations conf = new Configurations();
+ conf.set(AmoroManagementConf.ADMIN_USERNAME, "admin");
+ conf.set(AmoroManagementConf.ADMIN_PASSWORD, "password");
+
+ assertThrows(
+ IllegalStateException.class,
+ () -> {
+ HttpAuthenticationFactory.getPasswordAuthenticationProvider(
+ "NonExistentProviderClass", conf);
+ });
+
+ PasswdAuthenticationProvider passwdAuthenticationProvider =
+ HttpAuthenticationFactory.getPasswordAuthenticationProvider(
+ DefaultPasswdAuthenticationProvider.class.getName(), conf);
+
+ assert passwdAuthenticationProvider.authenticate("admin",
"password").getName().equals("admin");
+
+ assertThrows(
+ SignatureCheckException.class,
+ () -> {
+ passwdAuthenticationProvider.authenticate("admin",
"invalidPassword");
+ });
+ assertThrows(
+ SignatureCheckException.class,
+ () -> {
+ passwdAuthenticationProvider.authenticate("nonAdmin", "password");
+ });
+ }
+}
diff --git
a/amoro-common/src/main/java/org/apache/amoro/spi/authentication/BasicPrincipal.java
b/amoro-common/src/main/java/org/apache/amoro/spi/authentication/BasicPrincipal.java
new file mode 100644
index 000000000..794682020
--- /dev/null
+++
b/amoro-common/src/main/java/org/apache/amoro/spi/authentication/BasicPrincipal.java
@@ -0,0 +1,58 @@
+/*
+ * 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.amoro.spi.authentication;
+
+import java.security.Principal;
+import java.util.Objects;
+
+public class BasicPrincipal implements Principal {
+ private final String name;
+
+ public BasicPrincipal(String name) {
+ this.name = name;
+ Objects.requireNonNull(name, "Principal name cannot be null");
+ }
+
+ @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);
+ }
+}
diff --git
a/amoro-common/src/main/java/org/apache/amoro/spi/authentication/PasswdAuthenticationProvider.java
b/amoro-common/src/main/java/org/apache/amoro/spi/authentication/PasswdAuthenticationProvider.java
new file mode 100644
index 000000000..3352ab17e
--- /dev/null
+++
b/amoro-common/src/main/java/org/apache/amoro/spi/authentication/PasswdAuthenticationProvider.java
@@ -0,0 +1,37 @@
+/*
+ * 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.amoro.spi.authentication;
+
+import org.apache.amoro.exception.SignatureCheckException;
+
+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]].
+ *
+ * @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
+ */
+ Principal authenticate(String user, String password) throws
SignatureCheckException;
+}
diff --git a/docs/admin-guides/deployment.md b/docs/admin-guides/deployment.md
index bbdd68123..23374ec2f 100644
--- a/docs/admin-guides/deployment.md
+++ b/docs/admin-guides/deployment.md
@@ -75,7 +75,8 @@ 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 basic auth would reuse
`ams.admin-username` and `ams.admin-password` for authentication.
+- 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.
```yaml
ams: