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

SbloodyS pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/dolphinscheduler.git


The following commit(s) were added to refs/heads/dev by this push:
     new 511823be90 [Fix-18582][Authentication] Align actuator endpoint 
matching (#18583)
511823be90 is described below

commit 511823be90edb050c256d09a49df12972fde10e4
Author: Wenjun Ruan <[email protected]>
AuthorDate: Tue Aug 25 09:32:14 2026 +0800

    [Fix-18582][Authentication] Align actuator endpoint matching (#18583)
---
 .../pom.xml                                        |  17 +++
 .../ActuatorAuthenticationAutoConfiguration.java   |  15 +--
 ...ctuatorAuthenticationAutoConfigurationTest.java | 128 +++++++++++++++++++++
 3 files changed, 149 insertions(+), 11 deletions(-)

diff --git 
a/dolphinscheduler-authentication/dolphinscheduler-actuator-authentication/pom.xml
 
b/dolphinscheduler-authentication/dolphinscheduler-actuator-authentication/pom.xml
index c1751855ee..799a0182e3 100644
--- 
a/dolphinscheduler-authentication/dolphinscheduler-actuator-authentication/pom.xml
+++ 
b/dolphinscheduler-authentication/dolphinscheduler-actuator-authentication/pom.xml
@@ -38,10 +38,27 @@
             <artifactId>spring-boot-starter-security</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-actuator-autoconfigure</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>jakarta.servlet</groupId>
             <artifactId>jakarta.servlet-api</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>org.apache.dolphinscheduler</groupId>
+            <artifactId>dolphinscheduler-meter</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
     </dependencies>
 </project>
diff --git 
a/dolphinscheduler-authentication/dolphinscheduler-actuator-authentication/src/main/java/org/apache/dolphinscheduler/authentication/actuator/config/ActuatorAuthenticationAutoConfiguration.java
 
b/dolphinscheduler-authentication/dolphinscheduler-actuator-authentication/src/main/java/org/apache/dolphinscheduler/authentication/actuator/config/ActuatorAuthenticationAutoConfiguration.java
index bf70c56d04..9f95d84a19 100644
--- 
a/dolphinscheduler-authentication/dolphinscheduler-actuator-authentication/src/main/java/org/apache/dolphinscheduler/authentication/actuator/config/ActuatorAuthenticationAutoConfiguration.java
+++ 
b/dolphinscheduler-authentication/dolphinscheduler-actuator-authentication/src/main/java/org/apache/dolphinscheduler/authentication/actuator/config/ActuatorAuthenticationAutoConfiguration.java
@@ -26,6 +26,7 @@ import lombok.Data;
 import lombok.ToString;
 import lombok.extern.slf4j.Slf4j;
 
+import 
org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import 
org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -37,7 +38,6 @@ import 
org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.security.core.userdetails.UserDetailsService;
 import org.springframework.security.provisioning.InMemoryUserDetailsManager;
 import org.springframework.security.web.SecurityFilterChain;
-import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 
 /**
  * Security configuration for Actuator endpoints.
@@ -57,8 +57,6 @@ import 
org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 @Slf4j
 public class ActuatorAuthenticationAutoConfiguration {
 
-    private static final String ACTUATOR_PATH_PATTERN_1 = 
"/dolphinscheduler/actuator/";
-    private static final String ACTUATOR_PATH_PATTERN_2 = "/actuator/";
     private static final String ROLE_ACTUATOR = "ACTUATOR";
 
     @Bean
@@ -68,9 +66,8 @@ public class ActuatorAuthenticationAutoConfiguration {
         log.info(
                 "Initialize ActuatorSecurityConfiguration, 
management.security.enabled: {}, management.security.exclude: {}",
                 properties.isEnabled(), properties.getExclude());
-        // Restrict this security configuration to requests starting with 
actuator paths
-        http.requestMatcher(request -> 
request.getRequestURI().startsWith(ACTUATOR_PATH_PATTERN_1) ||
-                request.getRequestURI().startsWith(ACTUATOR_PATH_PATTERN_2));
+        // Restrict this security configuration to registered actuator 
endpoints
+        http.requestMatcher(EndpointRequest.toAnyEndpoint());
 
         if (properties.isEnabled()) {
             http.authorizeHttpRequests(authz -> {
@@ -78,11 +75,7 @@ public class ActuatorAuthenticationAutoConfiguration {
                 for (String endpoint : properties.getExclude()) {
                     if (StringUtils.isNotBlank(endpoint)) {
                         String cleanEndpoint = endpoint.trim();
-                        // Match both standard and prefixed actuator paths
-                        authz.requestMatchers(
-                                new 
AntPathRequestMatcher(ACTUATOR_PATH_PATTERN_2 + cleanEndpoint)).permitAll();
-                        authz.requestMatchers(
-                                new 
AntPathRequestMatcher(ACTUATOR_PATH_PATTERN_1 + cleanEndpoint)).permitAll();
+                        
authz.requestMatchers(EndpointRequest.to(cleanEndpoint)).permitAll();
                     }
                 }
                 // All other actuator requests require the ACTUATOR role
diff --git 
a/dolphinscheduler-authentication/dolphinscheduler-actuator-authentication/src/test/java/org/apache/dolphinscheduler/authentication/actuator/config/ActuatorAuthenticationAutoConfigurationTest.java
 
b/dolphinscheduler-authentication/dolphinscheduler-actuator-authentication/src/test/java/org/apache/dolphinscheduler/authentication/actuator/config/ActuatorAuthenticationAutoConfigurationTest.java
new file mode 100644
index 0000000000..6362407f24
--- /dev/null
+++ 
b/dolphinscheduler-authentication/dolphinscheduler-actuator-authentication/src/test/java/org/apache/dolphinscheduler/authentication/actuator/config/ActuatorAuthenticationAutoConfigurationTest.java
@@ -0,0 +1,128 @@
+/*
+ * 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.dolphinscheduler.authentication.actuator.config;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.net.URI;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.SpringBootConfiguration;
+import org.springframework.boot.WebApplicationType;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.boot.test.web.client.TestRestTemplate;
+import 
org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+
+class ActuatorAuthenticationAutoConfigurationTest {
+
+    private static final String CONTEXT_PATH = "/dolphinscheduler";
+    private static final String USERNAME = "actuator-user";
+    private static final String PASSWORD = "actuator-password";
+
+    private final TestRestTemplate restTemplate = new TestRestTemplate();
+
+    @Test
+    void shouldAuthenticateEquivalentActuatorPaths() {
+        try (ConfigurableApplicationContext context = startApplication(true)) {
+            assertThat(get(context, "/actuator/prometheus", null, 
null).getStatusCode())
+                    .isEqualTo(HttpStatus.UNAUTHORIZED);
+            assertThat(get(context, "/%61ctuator/prometheus", null, 
null).getStatusCode())
+                    .isEqualTo(HttpStatus.UNAUTHORIZED);
+            assertThat(get(context, "/%61ctuator/prometheus", USERNAME, 
"wrong-password").getStatusCode())
+                    .isEqualTo(HttpStatus.UNAUTHORIZED);
+            assertThat(get(context, "/%61ctuator/prometheus", USERNAME, 
PASSWORD).getStatusCode())
+                    .isEqualTo(HttpStatus.OK);
+            assertThat(get(context, "/actuator/health", null, 
null).getStatusCode())
+                    .isEqualTo(HttpStatus.OK);
+            assertThat(get(context, "/actuator/%68ealth", null, 
null).getStatusCode())
+                    .isEqualTo(HttpStatus.OK);
+        }
+    }
+
+    @Test
+    void shouldAuthenticateActuatorPathsWithoutContextPath() {
+        try (ConfigurableApplicationContext context = startApplication(true, 
"")) {
+            assertThat(get(context, "/actuator/prometheus", null, 
null).getStatusCode())
+                    .isEqualTo(HttpStatus.UNAUTHORIZED);
+            assertThat(get(context, "/%61ctuator/prometheus", null, 
null).getStatusCode())
+                    .isEqualTo(HttpStatus.UNAUTHORIZED);
+            assertThat(get(context, "/%61ctuator/prometheus", USERNAME, 
PASSWORD).getStatusCode())
+                    .isEqualTo(HttpStatus.OK);
+        }
+    }
+
+    @Test
+    void shouldPermitActuatorEndpointsWhenAuthenticationIsDisabled() {
+        try (ConfigurableApplicationContext context = startApplication(false)) 
{
+            assertThat(get(context, "/actuator/prometheus", null, 
null).getStatusCode())
+                    .isEqualTo(HttpStatus.OK);
+            assertThat(get(context, "/%61ctuator/prometheus", null, 
null).getStatusCode())
+                    .isEqualTo(HttpStatus.OK);
+        }
+    }
+
+    private ConfigurableApplicationContext startApplication(boolean 
securityEnabled) {
+        return startApplication(securityEnabled, CONTEXT_PATH);
+    }
+
+    private ConfigurableApplicationContext startApplication(boolean 
securityEnabled, String contextPath) {
+        SpringApplication application = new 
SpringApplicationBuilder(TestApplication.class)
+                .web(WebApplicationType.SERVLET)
+                .properties(
+                        "server.port=0",
+                        "server.servlet.context-path=" + contextPath,
+                        "management.security.enabled=" + securityEnabled,
+                        "management.security.username=" + USERNAME,
+                        "management.security.password=" + PASSWORD,
+                        "management.security.exclude=health,metrics",
+                        
"management.endpoints.web.exposure.include=health,metrics,prometheus",
+                        "spring.main.banner-mode=off")
+                .build();
+        return application.run();
+    }
+
+    private ResponseEntity<String> get(
+                                       ConfigurableApplicationContext context,
+                                       String path,
+                                       String username,
+                                       String password) {
+        ServletWebServerApplicationContext webServerApplicationContext =
+                (ServletWebServerApplicationContext) context;
+        int port = webServerApplicationContext.getWebServer().getPort();
+        String contextPath = 
webServerApplicationContext.getServletContext().getContextPath();
+        URI uri = URI.create("http://127.0.0.1:"; + port + contextPath + path);
+        HttpHeaders headers = new HttpHeaders();
+        if (username != null) {
+            headers.setBasicAuth(username, password);
+        }
+        return restTemplate.exchange(uri, HttpMethod.GET, new 
HttpEntity<>(headers), String.class);
+    }
+
+    @SpringBootConfiguration
+    @EnableAutoConfiguration
+    static class TestApplication {
+    }
+}

Reply via email to