This is an automated email from the ASF dual-hosted git repository.
fanjia pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/seatunnel.git
The following commit(s) were added to refs/heads/dev by this push:
new 19871a7314 [Feature][Zeta] Support basic authentication for web ui
(#9171)
19871a7314 is described below
commit 19871a7314db802f43606e2eaa1f99606b0cc4c6
Author: CosmosNi <[email protected]>
AuthorDate: Thu Apr 17 13:38:21 2025 +0800
[Feature][Zeta] Support basic authentication for web ui (#9171)
---
config/seatunnel.yaml | 4 +
docs/en/seatunnel-engine/security.md | 21 ++
docs/zh/seatunnel-engine/security.md | 21 ++
.../engine/e2e/BasicAuthenticationIT.java | 266 +++++++++++++++++++++
.../src/test/resources/basic-auth}/seatunnel.yaml | 36 +--
.../config/YamlSeaTunnelDomConfigProcessor.java | 12 +
.../engine/common/config/server/HttpConfig.java | 12 +
.../common/config/server/ServerConfigOptions.java | 18 ++
.../seatunnel/engine/server/JettyService.java | 14 +-
.../engine/server/rest/filter/BasicAuthFilter.java | 105 ++++++++
10 files changed, 489 insertions(+), 20 deletions(-)
diff --git a/config/seatunnel.yaml b/config/seatunnel.yaml
index 79a713a71e..6f6c5ef4ce 100644
--- a/config/seatunnel.yaml
+++ b/config/seatunnel.yaml
@@ -44,3 +44,7 @@ seatunnel:
enable-http: true
port: 8080
enable-dynamic-port: false
+ # Uncomment the following lines to enable basic authentication for web UI
+ # enable-basic-auth: true
+ # basic-auth-username: admin
+ # basic-auth-password: admin
diff --git a/docs/en/seatunnel-engine/security.md
b/docs/en/seatunnel-engine/security.md
index 363f1b9200..b3c1b4257c 100644
--- a/docs/en/seatunnel-engine/security.md
+++ b/docs/en/seatunnel-engine/security.md
@@ -1,5 +1,26 @@
# Security
+## Basic Authentication
+
+You can secure your Web UI by enabling basic authentication. This will require
users to enter a username and password when accessing the web interface.
+
+| Parameter Name | Required | Description |
+|----------------|----------|-------------|
+| `enable-basic-auth` | No | Whether to enable basic authentication, default
is `false` |
+| `basic-auth-username` | No | The username for basic authentication, default
is `admin` |
+| `basic-auth-password` | No | The password for basic authentication, default
is `admin` |
+
+```yaml
+seatunnel:
+ engine:
+ http:
+ enable-http: true
+ port: 8080
+ enable-basic-auth: true
+ basic-auth-username: "your_username"
+ basic-auth-password: "your_password"
+```
+
## HTTPS Configuration
You can secure your REST-API-V2 service by enabling HTTPS. Both HTTP and HTTPS
can be enabled simultaneously, or only one of them can be enabled.
diff --git a/docs/zh/seatunnel-engine/security.md
b/docs/zh/seatunnel-engine/security.md
index d4d228880e..565771fd1d 100644
--- a/docs/zh/seatunnel-engine/security.md
+++ b/docs/zh/seatunnel-engine/security.md
@@ -4,6 +4,27 @@ sidebar_position: 16
# Security
+## Basic 认证
+
+您可以通过开启 Basic 认证来保护您的 Web UI。这将要求用户在访问 Web 界面时输入用户名和密码。
+
+| 参数名称 | 是否必填 | 参数描述 |
+|--------|---------|--------|
+| `enable-basic-auth` | 否 | 是否开启Basic 认证,默认为 `false` |
+| `basic-auth-username` | 否 | Basic 认证的用户名,默认为 `admin` |
+| `basic-auth-password` | 否 | Basic 认证的密码,默认为 `admin` |
+
+```yaml
+seatunnel:
+ engine:
+ http:
+ enable-http: true
+ port: 8080
+ enable-basic-auth: true
+ basic-auth-username: "your_username"
+ basic-auth-password: "your_password"
+```
+
## HTTPS 配置
您可以通过开启 HTTPS 来保护您的 API 服务。HTTP 和 HTTPS 可同时开启,也可以只开启其中一个。
diff --git
a/seatunnel-e2e/seatunnel-engine-e2e/connector-seatunnel-e2e-base/src/test/java/org/apache/seatunnel/engine/e2e/BasicAuthenticationIT.java
b/seatunnel-e2e/seatunnel-engine-e2e/connector-seatunnel-e2e-base/src/test/java/org/apache/seatunnel/engine/e2e/BasicAuthenticationIT.java
new file mode 100644
index 0000000000..4c0402f5cb
--- /dev/null
+++
b/seatunnel-e2e/seatunnel-engine-e2e/connector-seatunnel-e2e-base/src/test/java/org/apache/seatunnel/engine/e2e/BasicAuthenticationIT.java
@@ -0,0 +1,266 @@
+/*
+ * 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.seatunnel.engine.e2e;
+
+import org.apache.seatunnel.engine.server.rest.RestConstant;
+
+import org.awaitility.Awaitility;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.testcontainers.containers.GenericContainer;
+
+import io.restassured.http.ContentType;
+import io.restassured.response.Response;
+
+import java.io.IOException;
+import java.util.Base64;
+import java.util.concurrent.TimeUnit;
+
+import static io.restassured.RestAssured.given;
+import static
org.apache.seatunnel.e2e.common.util.ContainerUtil.PROJECT_ROOT_PATH;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.notNullValue;
+
+/** Integration test for basic authentication in SeaTunnel Engine. */
+public class BasicAuthenticationIT extends SeaTunnelEngineContainer {
+
+ private static final String HTTP = "http://";
+ private static final String COLON = ":";
+ private static final String USERNAME = "testuser";
+ private static final String PASSWORD = "testpassword";
+ private static final String BASIC_AUTH_HEADER = "Authorization";
+ private static final String BASIC_AUTH_PREFIX = "Basic ";
+
+ @Override
+ @BeforeEach
+ public void startUp() throws Exception {
+ // Create server with basic authentication enabled
+
+ server = createSeaTunnelContainerWithBasicAuth();
+ // Wait for server to be ready
+ Awaitility.await()
+ .atMost(2, TimeUnit.MINUTES)
+ .until(
+ () -> {
+ try {
+ // Try to access with correct credentials
+ String credentials = USERNAME + ":" + PASSWORD;
+ String encodedCredentials =
+
Base64.getEncoder().encodeToString(credentials.getBytes());
+
+ given().header(
+ BASIC_AUTH_HEADER,
+ BASIC_AUTH_PREFIX +
encodedCredentials)
+ .get(
+ HTTP
+ + server.getHost()
+ + COLON
+ +
server.getMappedPort(8080)
+ + "/")
+ .then()
+ .statusCode(200);
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ });
+ }
+
+ @Override
+ @AfterEach
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ /**
+ * Test that accessing the web UI without authentication credentials
returns 401 Unauthorized.
+ */
+ @Test
+ public void testAccessWithoutCredentials() {
+ given().get(HTTP + server.getHost() + COLON +
server.getMappedPort(8080) + "/")
+ .then()
+ .statusCode(401);
+ }
+
+ /** Test that accessing the web UI with incorrect credentials returns 401
Unauthorized. */
+ @Test
+ public void testAccessWithIncorrectCredentials() {
+ String credentials = "wronguser:wrongpassword";
+ String encodedCredentials =
Base64.getEncoder().encodeToString(credentials.getBytes());
+
+ given().header(BASIC_AUTH_HEADER, BASIC_AUTH_PREFIX +
encodedCredentials)
+ .get(HTTP + server.getHost() + COLON +
server.getMappedPort(8080) + "/")
+ .then()
+ .statusCode(401);
+ }
+
+ /** Test that accessing the web UI with correct credentials returns 200
OK. */
+ @Test
+ public void testAccessWithCorrectCredentials() {
+ String credentials = USERNAME + ":" + PASSWORD;
+ String encodedCredentials =
Base64.getEncoder().encodeToString(credentials.getBytes());
+
+ given().header(BASIC_AUTH_HEADER, BASIC_AUTH_PREFIX +
encodedCredentials)
+ .get(HTTP + server.getHost() + COLON +
server.getMappedPort(8080) + "/")
+ .then()
+ .statusCode(200)
+ .contentType(containsString("text/html"))
+ .body(containsString("<title>Seatunnel Engine UI</title>"));
+ }
+
+ /** Test that accessing the REST API with correct credentials returns 200
OK. */
+ @Test
+ public void testRestApiAccessWithCorrectCredentials() {
+ String credentials = USERNAME + ":" + PASSWORD;
+ String encodedCredentials =
Base64.getEncoder().encodeToString(credentials.getBytes());
+
+ given().header(BASIC_AUTH_HEADER, BASIC_AUTH_PREFIX +
encodedCredentials)
+ .get(
+ HTTP
+ + server.getHost()
+ + COLON
+ + server.getMappedPort(8080)
+ + RestConstant.REST_URL_OVERVIEW)
+ .then()
+ .statusCode(200)
+ .body("projectVersion", notNullValue());
+ }
+
+ /** Test that accessing the REST API with Incorrect credentials returns
200 OK. */
+ @Test
+ public void testRestApiAccessWithIncorrectCredentials() {
+ String credentials = "wronguser:wrongpassword";
+ String encodedCredentials =
Base64.getEncoder().encodeToString(credentials.getBytes());
+
+ given().header(BASIC_AUTH_HEADER, BASIC_AUTH_PREFIX +
encodedCredentials)
+ .get(
+ HTTP
+ + server.getHost()
+ + COLON
+ + server.getMappedPort(8080)
+ + RestConstant.REST_URL_OVERVIEW)
+ .then()
+ .statusCode(401);
+ }
+
+ /** Test submitting a job via REST API with correct credentials. */
+ @Test
+ public void testSubmitJobWithCorrectCredentials() {
+ String credentials = USERNAME + ":" + PASSWORD;
+ String encodedCredentials =
Base64.getEncoder().encodeToString(credentials.getBytes());
+
+ // Simple batch job configuration
+ String jobConfig =
+ "{\n"
+ + " \"env\": {\n"
+ + " \"job.mode\": \"batch\"\n"
+ + " },\n"
+ + " \"source\": [\n"
+ + " {\n"
+ + " \"plugin_name\": \"FakeSource\",\n"
+ + " \"plugin_output\": \"fake\",\n"
+ + " \"row.num\": 100,\n"
+ + " \"schema\": {\n"
+ + " \"fields\": {\n"
+ + " \"name\": \"string\",\n"
+ + " \"age\": \"int\",\n"
+ + " \"card\": \"int\"\n"
+ + " }\n"
+ + " }\n"
+ + " }\n"
+ + " ],\n"
+ + " \"transform\": [\n"
+ + " ],\n"
+ + " \"sink\": [\n"
+ + " {\n"
+ + " \"plugin_name\": \"InMemory\",\n"
+ + " \"plugin_input\": \"fake\",\n"
+ + " \"throw_exception\": true\n"
+ + " }\n"
+ + " ]\n"
+ + "}";
+
+ Response response =
+ given().header(BASIC_AUTH_HEADER, BASIC_AUTH_PREFIX +
encodedCredentials)
+ .contentType(ContentType.JSON)
+ .body(jobConfig)
+ .post(
+ HTTP
+ + server.getHost()
+ + COLON
+ + server.getMappedPort(8080)
+ + RestConstant.REST_URL_SUBMIT_JOB);
+
+ response.then().statusCode(200).body("jobId", notNullValue());
+ }
+
+ /** Test submitting a job via REST API with incorrect credentials. */
+ @Test
+ public void testSubmitJobWithIncorrectCredentials() {
+ String credentials = "wronguser:wrongpassword";
+ String encodedCredentials =
Base64.getEncoder().encodeToString(credentials.getBytes());
+
+ // Simple batch job configuration
+ String jobConfig =
+ "{\n"
+ + " \"env\": {\n"
+ + " \"job.mode\": \"BATCH\"\n"
+ + " },\n"
+ + " \"source\": {\n"
+ + " \"FakeSource\": {\n"
+ + " \"plugin_output\": \"fake\",\n"
+ + " \"row.num\": 100,\n"
+ + " \"schema\": {\n"
+ + " \"fields\": {\n"
+ + " \"id\": \"int\",\n"
+ + " \"name\": \"string\"\n"
+ + " }\n"
+ + " }\n"
+ + " }\n"
+ + " },\n"
+ + " \"sink\": {\n"
+ + " \"Console\": {\n"
+ + " \"plugin_input\": \"fake\"\n"
+ + " }\n"
+ + " }\n"
+ + "}";
+
+ given().header(BASIC_AUTH_HEADER, BASIC_AUTH_PREFIX +
encodedCredentials)
+ .contentType(ContentType.JSON)
+ .body(jobConfig)
+ .post(
+ HTTP
+ + server.getHost()
+ + COLON
+ + server.getMappedPort(8080)
+ + RestConstant.REST_URL_SUBMIT_JOB)
+ .then()
+ .statusCode(401);
+ }
+
+ /** Create a SeaTunnel container with basic authentication enabled. */
+ private GenericContainer<?> createSeaTunnelContainerWithBasicAuth()
+ throws IOException, InterruptedException {
+ String configPath =
+ PROJECT_ROOT_PATH
+ +
"/seatunnel-e2e/seatunnel-engine-e2e/connector-seatunnel-e2e-base/src/test/resources/basic-auth/seatunnel.yaml";
+
+ return
createSeaTunnelContainerWithFakeSourceAndInMemorySink(configPath);
+ }
+}
diff --git a/config/seatunnel.yaml
b/seatunnel-e2e/seatunnel-engine-e2e/connector-seatunnel-e2e-base/src/test/resources/basic-auth/seatunnel.yaml
similarity index 64%
copy from config/seatunnel.yaml
copy to
seatunnel-e2e/seatunnel-engine-e2e/connector-seatunnel-e2e-base/src/test/resources/basic-auth/seatunnel.yaml
index 79a713a71e..2f27e3e19f 100644
--- a/config/seatunnel.yaml
+++
b/seatunnel-e2e/seatunnel-engine-e2e/connector-seatunnel-e2e-base/src/test/resources/basic-auth/seatunnel.yaml
@@ -17,30 +17,30 @@
seatunnel:
engine:
- classloader-cache-mode: true
- history-job-expire-minutes: 1440
- backup-count: 1
+ history-job-expire-minutes: 1
+ backup-count: 2
queue-type: blockingqueue
- print-execution-info-interval: 60
- print-job-metrics-info-interval: 60
+ print-execution-info-interval: 10
+ classloader-cache-mode: false
slot-service:
dynamic-slot: true
checkpoint:
- interval: 10000
- timeout: 60000
+ interval: 300000
+ timeout: 100000
storage:
- type: hdfs
+ type: localfile
max-retained: 3
plugin-config:
- namespace: /tmp/seatunnel/checkpoint_snapshot
- storage.type: hdfs
- fs.defaultFS: file:///tmp/ # Ensure that the directory has written
permission
+ namespace: /tmp/seatunnel/checkpoint_snapshot/
+ http:
+ enable-http: true
+ port: 8080
+ enable-dynamic-port: false
+ enable-basic-auth: true
+ basic-auth-username: "testuser"
+ basic-auth-password: "testpassword"
telemetry:
metric:
- enabled: false
- log:
- scheduled-deletion-enable: true
- http:
- enable-http: true
- port: 8080
- enable-dynamic-port: false
+ enabled: false
+ logs:
+ scheduled-deletion-enable: false
diff --git
a/seatunnel-engine/seatunnel-engine-common/src/main/java/org/apache/seatunnel/engine/common/config/YamlSeaTunnelDomConfigProcessor.java
b/seatunnel-engine/seatunnel-engine-common/src/main/java/org/apache/seatunnel/engine/common/config/YamlSeaTunnelDomConfigProcessor.java
index c54b2305de..f4aec7dfc3 100644
---
a/seatunnel-engine/seatunnel-engine-common/src/main/java/org/apache/seatunnel/engine/common/config/YamlSeaTunnelDomConfigProcessor.java
+++
b/seatunnel-engine/seatunnel-engine-common/src/main/java/org/apache/seatunnel/engine/common/config/YamlSeaTunnelDomConfigProcessor.java
@@ -549,6 +549,18 @@ public class YamlSeaTunnelDomConfigProcessor extends
AbstractDomConfigProcessor
.key()
.equals(name)) {
httpConfig.setTrustStorePassword(getTextContent(node));
+ } else if
(ServerConfigOptions.MasterServerConfigOptions.ENABLE_BASIC_AUTH
+ .key()
+ .equals(name)) {
+
httpConfig.setEnableBasicAuth(getBooleanValue(getTextContent(node)));
+ } else if
(ServerConfigOptions.MasterServerConfigOptions.BASIC_AUTH_USERNAME
+ .key()
+ .equals(name)) {
+ httpConfig.setBasicAuthUsername(getTextContent(node));
+ } else if
(ServerConfigOptions.MasterServerConfigOptions.BASIC_AUTH_PASSWORD
+ .key()
+ .equals(name)) {
+ httpConfig.setBasicAuthPassword(getTextContent(node));
} else {
LOGGER.warning("Unrecognized element: " + name);
}
diff --git
a/seatunnel-engine/seatunnel-engine-common/src/main/java/org/apache/seatunnel/engine/common/config/server/HttpConfig.java
b/seatunnel-engine/seatunnel-engine-common/src/main/java/org/apache/seatunnel/engine/common/config/server/HttpConfig.java
index ba7c8e8492..fe7324bf31 100644
---
a/seatunnel-engine/seatunnel-engine-common/src/main/java/org/apache/seatunnel/engine/common/config/server/HttpConfig.java
+++
b/seatunnel-engine/seatunnel-engine-common/src/main/java/org/apache/seatunnel/engine/common/config/server/HttpConfig.java
@@ -66,6 +66,18 @@ public class HttpConfig implements Serializable {
private int portRange =
ServerConfigOptions.MasterServerConfigOptions.PORT_RANGE.defaultValue();
+ /** Whether to enable basic authentication. */
+ private boolean enableBasicAuth =
+
ServerConfigOptions.MasterServerConfigOptions.ENABLE_BASIC_AUTH.defaultValue();
+
+ /** The username for basic authentication. */
+ private String basicAuthUsername =
+
ServerConfigOptions.MasterServerConfigOptions.BASIC_AUTH_USERNAME.defaultValue();
+
+ /** The password for basic authentication. */
+ private String basicAuthPassword =
+
ServerConfigOptions.MasterServerConfigOptions.BASIC_AUTH_PASSWORD.defaultValue();
+
public void setPort(int port) {
checkPositive(port, ServerConfigOptions.MasterServerConfigOptions.HTTP
+ " must be > 0");
this.port = port;
diff --git
a/seatunnel-engine/seatunnel-engine-common/src/main/java/org/apache/seatunnel/engine/common/config/server/ServerConfigOptions.java
b/seatunnel-engine/seatunnel-engine-common/src/main/java/org/apache/seatunnel/engine/common/config/server/ServerConfigOptions.java
index cc7fb3503a..9deb98f29b 100644
---
a/seatunnel-engine/seatunnel-engine-common/src/main/java/org/apache/seatunnel/engine/common/config/server/ServerConfigOptions.java
+++
b/seatunnel-engine/seatunnel-engine-common/src/main/java/org/apache/seatunnel/engine/common/config/server/ServerConfigOptions.java
@@ -255,6 +255,24 @@ public class ServerConfigOptions {
.withDescription(
"The port range of the http server. If
enable-dynamic-port is true, We will use the unused port in the range");
+ public static final Option<Boolean> ENABLE_BASIC_AUTH =
+ Options.key("enable-basic-auth")
+ .booleanType()
+ .defaultValue(false)
+ .withDescription("Whether to enable basic
authentication for the web UI.");
+
+ public static final Option<String> BASIC_AUTH_USERNAME =
+ Options.key("basic-auth-username")
+ .stringType()
+ .defaultValue("admin")
+ .withDescription("The username for basic
authentication.");
+
+ public static final Option<String> BASIC_AUTH_PASSWORD =
+ Options.key("basic-auth-password")
+ .stringType()
+ .defaultValue("admin")
+ .withDescription("The password for basic
authentication.");
+
public static final Option<HttpConfig> HTTP =
Options.key("http")
.type(new TypeReference<HttpConfig>() {})
diff --git
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/JettyService.java
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/JettyService.java
index 7f88c25662..1d98f25843 100644
---
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/JettyService.java
+++
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/JettyService.java
@@ -27,6 +27,7 @@ import
org.apache.seatunnel.shade.org.eclipse.jetty.util.ssl.SslContextFactory;
import org.apache.seatunnel.engine.common.config.SeaTunnelConfig;
import org.apache.seatunnel.engine.common.config.server.HttpConfig;
+import org.apache.seatunnel.engine.server.rest.filter.BasicAuthFilter;
import org.apache.seatunnel.engine.server.rest.filter.ExceptionHandlingFilter;
import org.apache.seatunnel.engine.server.rest.servlet.AllLogNameServlet;
import org.apache.seatunnel.engine.server.rest.servlet.AllNodeLogServlet;
@@ -149,8 +150,17 @@ public class JettyService {
ServletContextHandler context = new
ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath(seaTunnelConfig.getEngineConfig().getHttpConfig().getContextPath());
- FilterHolder filterHolder = new FilterHolder(new
ExceptionHandlingFilter());
- context.addFilter(filterHolder, "/*",
EnumSet.of(DispatcherType.REQUEST));
+ // Add exception handling filter
+ FilterHolder exceptionFilterHolder = new FilterHolder(new
ExceptionHandlingFilter());
+ context.addFilter(exceptionFilterHolder, "/*",
EnumSet.of(DispatcherType.REQUEST));
+
+ // Add basic authentication filter if enabled
+ HttpConfig httpConfig =
seaTunnelConfig.getEngineConfig().getHttpConfig();
+ if (httpConfig.isEnableBasicAuth()) {
+ log.info("Basic authentication is enabled for web UI");
+ FilterHolder basicAuthFilterHolder = new FilterHolder(new
BasicAuthFilter(httpConfig));
+ context.addFilter(basicAuthFilterHolder, "/*",
EnumSet.of(DispatcherType.REQUEST));
+ }
ServletHolder defaultServlet = new ServletHolder("default",
DefaultServlet.class);
URL uiResource = JettyService.class.getClassLoader().getResource("ui");
diff --git
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/rest/filter/BasicAuthFilter.java
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/rest/filter/BasicAuthFilter.java
new file mode 100644
index 0000000000..373b41babb
--- /dev/null
+++
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/rest/filter/BasicAuthFilter.java
@@ -0,0 +1,105 @@
+/*
+ * 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.seatunnel.engine.server.rest.filter;
+
+import org.apache.seatunnel.engine.common.config.server.HttpConfig;
+
+import org.apache.commons.codec.binary.Base64;
+
+import lombok.extern.slf4j.Slf4j;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+/** Basic authentication filter for the web UI. */
+@Slf4j
+public class BasicAuthFilter implements Filter {
+
+ private final HttpConfig httpConfig;
+ private static final String AUTHORIZATION_HEADER = "Authorization";
+ private static final String BASIC_PREFIX = "Basic ";
+ private static final String WWW_AUTHENTICATE_HEADER = "WWW-Authenticate";
+ private static final String BASIC_REALM = "Basic realm=\"SeaTunnel Web
UI\"";
+
+ public BasicAuthFilter(HttpConfig httpConfig) {
+ this.httpConfig = httpConfig;
+ }
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+ // No initialization needed
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)
+ throws IOException, ServletException {
+
+ // Skip authentication if not enabled
+ if (!httpConfig.isEnableBasicAuth()) {
+ chain.doFilter(request, response);
+ return;
+ }
+
+ HttpServletRequest httpRequest = (HttpServletRequest) request;
+ HttpServletResponse httpResponse = (HttpServletResponse) response;
+
+ // Get the Authorization header from the request
+ String authHeader = httpRequest.getHeader(AUTHORIZATION_HEADER);
+
+ // Check if the Authorization header exists and starts with "Basic "
+ if (authHeader != null && authHeader.startsWith(BASIC_PREFIX)) {
+ // Extract the Base64 encoded username:password
+ String base64Credentials =
authHeader.substring(BASIC_PREFIX.length());
+ String credentials =
+ new String(Base64.decodeBase64(base64Credentials),
StandardCharsets.UTF_8);
+
+ // Split the username and password
+ final String[] values = credentials.split(":", 2);
+ if (values.length == 2) {
+ String username = values[0];
+ String password = values[1];
+
+ // Check if the username and password match the configured
values
+ if (username.equals(httpConfig.getBasicAuthUsername())
+ && password.equals(httpConfig.getBasicAuthPassword()))
{
+ // Authentication successful, proceed with the request
+ chain.doFilter(request, response);
+ return;
+ }
+ }
+ }
+
+ // Authentication failed, send 401 Unauthorized response
+ httpResponse.setHeader(WWW_AUTHENTICATE_HEADER, BASIC_REALM);
+ httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED,
"Unauthorized");
+ }
+
+ @Override
+ public void destroy() {
+ // No resources to release
+ }
+}