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

joewitt pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/main by this push:
     new 5f72313003 NIFI-14115 Set Standard HTTP Headers for Framework 
Responses This closes #9598
5f72313003 is described below

commit 5f72313003fe8b0142657892ebd94bfee2a053d4
Author: exceptionfactory <[email protected]>
AuthorDate: Thu Dec 26 22:17:21 2024 -0600

    NIFI-14115 Set Standard HTTP Headers for Framework Responses
    This closes #9598
    
    - Added HeaderWriterHandler implementing Jetty Handler methods for setting 
standard HTTP response headers
    - Removed HeaderWriterFilter approach for writing standard HTTP headers
    - Refactored Jetty Server instantiation to StandardServerProvider for 
streamlined configuration and testing
    - Simplified Server start test method to avoid timing issues
    - Removed unnecessary Timeout annotation
    
    Signed-off-by: Joseph Witt <[email protected]>
---
 .../org/apache/nifi/web/server/JettyServer.java    | 109 ++++------------
 .../org/apache/nifi/web/server/ServerProvider.java |  33 +++++
 .../nifi/web/server/StandardServerProvider.java    | 138 +++++++++++++++++++++
 .../filter/StandardRequestFilterProvider.java      |  27 ----
 .../web/server/handler/HeaderWriterHandler.java    |  64 ++++++++++
 .../web/server/StandardServerProviderTest.java     | 131 +++++++++++++++++++
 .../filter/RestApiRequestFilterProviderTest.java   |   2 -
 .../filter/StandardRequestFilterProviderTest.java  |   4 +-
 8 files changed, 388 insertions(+), 120 deletions(-)

diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java
 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java
index 27f17894b6..21e21e434a 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java
@@ -22,11 +22,7 @@ import java.io.File;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.OutputStreamWriter;
-import java.io.UncheckedIOException;
 import java.lang.reflect.InvocationTargetException;
-import java.net.InetAddress;
-import java.net.NetworkInterface;
-import java.net.SocketException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.nio.file.Files;
@@ -36,7 +32,6 @@ import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -103,14 +98,11 @@ import org.apache.nifi.util.NiFiProperties;
 import org.apache.nifi.web.ContentAccess;
 import org.apache.nifi.web.NiFiWebConfigurationContext;
 import org.apache.nifi.web.UiExtensionType;
-import org.apache.nifi.web.server.connector.FrameworkServerConnectorFactory;
 import org.apache.nifi.web.server.filter.FilterParameter;
 import org.apache.nifi.web.server.filter.LogoutCompleteRedirectFilter;
 import org.apache.nifi.web.server.filter.RequestFilterProvider;
 import org.apache.nifi.web.server.filter.RestApiRequestFilterProvider;
 import org.apache.nifi.web.server.filter.StandardRequestFilterProvider;
-import org.apache.nifi.web.server.log.RequestLogProvider;
-import org.apache.nifi.web.server.log.StandardRequestLogProvider;
 import org.eclipse.jetty.deploy.App;
 import org.eclipse.jetty.deploy.AppProvider;
 import org.eclipse.jetty.deploy.DeploymentManager;
@@ -121,7 +113,6 @@ import org.eclipse.jetty.ee10.webapp.MetaInfConfiguration;
 import org.eclipse.jetty.rewrite.handler.RedirectPatternRule;
 import org.eclipse.jetty.rewrite.handler.RewriteHandler;
 import org.eclipse.jetty.server.Handler;
-import org.eclipse.jetty.server.RequestLog;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.ServerConnector;
 import org.eclipse.jetty.server.SslConnectionFactory;
@@ -130,7 +121,6 @@ import 
org.eclipse.jetty.server.handler.ContextHandlerCollection;
 import org.eclipse.jetty.ee10.servlet.ErrorPageErrorHandler;
 import org.eclipse.jetty.ee10.servlet.FilterHolder;
 import org.eclipse.jetty.ee10.servlet.ServletHolder;
-import org.eclipse.jetty.util.thread.QueuedThreadPool;
 import org.eclipse.jetty.ee10.webapp.WebAppClassLoader;
 import org.eclipse.jetty.ee10.webapp.WebAppContext;
 import org.slf4j.Logger;
@@ -222,42 +212,29 @@ public class JettyServer implements NiFiServer, 
ExtensionUiLoader {
 
     public void init() {
         clearWorkingDirectory();
-        final QueuedThreadPool threadPool = new 
QueuedThreadPool(props.getWebThreads());
-        threadPool.setName("NiFi Web Server");
-        this.server = new Server(threadPool);
-        final FrameworkSslContextProvider sslContextProvider = new 
FrameworkSslContextProvider(props);
-        this.sslContext = sslContextProvider.loadSslContext().orElse(null);
-
-        configureConnectors(server);
-
-        final ContextHandlerCollection handlerCollection = new 
ContextHandlerCollection();
-        final Handler standardHandler = getStandardHandler(handlerCollection);
-        server.setHandler(standardHandler);
-
-        final RewriteHandler defaultRewriteHandler = new RewriteHandler();
-        final RedirectPatternRule redirectDefault = new 
RedirectPatternRule("/*", "/nifi");
-        defaultRewriteHandler.addRule(redirectDefault);
-        server.setDefaultHandler(defaultRewriteHandler);
-
-        deploymentManager.setContexts(handlerCollection);
-        server.addBean(deploymentManager);
-
-        final String requestLogFormat = 
props.getProperty(NiFiProperties.WEB_REQUEST_LOG_FORMAT);
-        final RequestLogProvider requestLogProvider = new 
StandardRequestLogProvider(requestLogFormat);
-        final RequestLog requestLog = requestLogProvider.getRequestLog();
-        server.setRequestLog(requestLog);
-    }
 
-    private Handler getStandardHandler(final ContextHandlerCollection 
handlerCollection) {
-        // Only restrict the host header if running in HTTPS mode
-        if (props.isHTTPSConfigured()) {
-            final HostHeaderHandler hostHeaderHandler = new 
HostHeaderHandler(props);
-            handlerCollection.addHandler(hostHeaderHandler);
-        }
+        try {
+            final FrameworkSslContextProvider sslContextProvider = new 
FrameworkSslContextProvider(props);
+            sslContext = sslContextProvider.loadSslContext().orElse(null);
 
-        final Handler warHandlers = loadInitialWars(bundles);
-        handlerCollection.addHandler(warHandlers);
-        return handlerCollection;
+            final ServerProvider serverProvider = new 
StandardServerProvider(sslContext);
+            server = serverProvider.getServer(props);
+
+            final Handler serverHandler = server.getHandler();
+            if (serverHandler instanceof Handler.Collection 
serverHandlerCollection) {
+                final ContextHandlerCollection contextHandlerCollection = new 
ContextHandlerCollection();
+                final Handler warHandlers = loadInitialWars(bundles);
+                contextHandlerCollection.addHandler(warHandlers);
+                deploymentManager.setContexts(contextHandlerCollection);
+                server.addBean(deploymentManager);
+
+                serverHandlerCollection.addHandler(contextHandlerCollection);
+            } else {
+                throw new IllegalStateException("Server Handler not 
Handler.Collection: Server Provider configuration failed");
+            }
+        } catch (final Throwable e) {
+            startUpFailure(e);
+        }
     }
 
     private void clearWorkingDirectory() {
@@ -771,50 +748,6 @@ public class JettyServer implements NiFiServer, 
ExtensionUiLoader {
         return webApiDocsDir;
     }
 
-    private void configureConnectors(final Server server) {
-        try {
-            final FrameworkServerConnectorFactory serverConnectorFactory = new 
FrameworkServerConnectorFactory(server, props);
-            if (props.isHTTPSConfigured()) {
-                serverConnectorFactory.setSslContext(sslContext);
-            }
-
-            final Map<String, String> interfaces = props.isHTTPSConfigured() ? 
props.getHttpsNetworkInterfaces() : props.getHttpNetworkInterfaces();
-            final Set<String> interfaceNames = 
interfaces.values().stream().filter(StringUtils::isNotBlank).collect(Collectors.toSet());
-            // Add Server Connectors based on configured Network Interface 
Names
-            if (interfaceNames.isEmpty()) {
-                final ServerConnector serverConnector = 
serverConnectorFactory.getServerConnector();
-                final String host = props.isHTTPSConfigured() ? 
props.getProperty(NiFiProperties.WEB_HTTPS_HOST) : 
props.getProperty(NiFiProperties.WEB_HTTP_HOST);
-                if (StringUtils.isNotBlank(host)) {
-                    serverConnector.setHost(host);
-                }
-                server.addConnector(serverConnector);
-            } else {
-                interfaceNames.stream()
-                        // Map interface name properties to Network Interfaces
-                        .map(interfaceName -> {
-                            try {
-                                return 
NetworkInterface.getByName(interfaceName);
-                            } catch (final SocketException e) {
-                                throw new 
UncheckedIOException(String.format("Network Interface [%s] not found", 
interfaceName), e);
-                            }
-                        })
-                        // Map Network Interfaces to host addresses
-                        .filter(Objects::nonNull)
-                        .flatMap(networkInterface -> 
Collections.list(networkInterface.getInetAddresses()).stream())
-                        .map(InetAddress::getHostAddress)
-                        // Map host addresses to Server Connectors
-                        .map(host -> {
-                            final ServerConnector serverConnector = 
serverConnectorFactory.getServerConnector();
-                            serverConnector.setHost(host);
-                            return serverConnector;
-                        })
-                        .forEach(server::addConnector);
-            }
-        } catch (final Throwable e) {
-            startUpFailure(e);
-        }
-    }
-
     protected List<URI> getApplicationUrls() {
         return Arrays.stream(server.getConnectors())
                 .map(connector -> (ServerConnector) connector)
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/ServerProvider.java
 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/ServerProvider.java
new file mode 100644
index 0000000000..3c8cf1e01f
--- /dev/null
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/ServerProvider.java
@@ -0,0 +1,33 @@
+/*
+ * 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.nifi.web.server;
+
+import org.apache.nifi.util.NiFiProperties;
+import org.eclipse.jetty.server.Server;
+
+/**
+ * Abstraction for configuring Server instances based on application properties
+ */
+interface ServerProvider {
+    /**
+     * Get Server configured using application properties
+     *
+     * @param properties Application properties
+     * @return Server
+     */
+    Server getServer(NiFiProperties properties);
+}
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/StandardServerProvider.java
 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/StandardServerProvider.java
new file mode 100644
index 0000000000..5fb29c001a
--- /dev/null
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/StandardServerProvider.java
@@ -0,0 +1,138 @@
+/*
+ * 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.nifi.web.server;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.web.server.connector.FrameworkServerConnectorFactory;
+import org.apache.nifi.web.server.handler.HeaderWriterHandler;
+import org.apache.nifi.web.server.log.RequestLogProvider;
+import org.apache.nifi.web.server.log.StandardRequestLogProvider;
+import org.eclipse.jetty.rewrite.handler.RedirectPatternRule;
+import org.eclipse.jetty.rewrite.handler.RewriteHandler;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.RequestLog;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+
+import javax.net.ssl.SSLContext;
+import java.io.UncheckedIOException;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Standard implementation of Server Provider with default Handlers
+ */
+class StandardServerProvider implements ServerProvider {
+    private static final String ALL_PATHS_PATTERN = "/*";
+
+    private static final String FRONTEND_CONTEXT_PATH = "/nifi";
+
+    private final SSLContext sslContext;
+
+    StandardServerProvider(final SSLContext sslContext) {
+        this.sslContext = sslContext;
+    }
+
+    @Override
+    public Server getServer(final NiFiProperties properties) {
+        Objects.requireNonNull(properties, "Properties required");
+
+        final QueuedThreadPool threadPool = new 
QueuedThreadPool(properties.getWebThreads());
+        threadPool.setName("NiFi Web Server");
+        final Server server = new Server(threadPool);
+        addConnectors(server, properties, sslContext);
+
+        final Handler standardHandler = getStandardHandler(properties);
+        server.setHandler(standardHandler);
+
+        final RewriteHandler defaultRewriteHandler = new RewriteHandler();
+        final RedirectPatternRule redirectDefault = new 
RedirectPatternRule(ALL_PATHS_PATTERN, FRONTEND_CONTEXT_PATH);
+        defaultRewriteHandler.addRule(redirectDefault);
+        server.setDefaultHandler(defaultRewriteHandler);
+
+        final String requestLogFormat = 
properties.getProperty(NiFiProperties.WEB_REQUEST_LOG_FORMAT);
+        final RequestLogProvider requestLogProvider = new 
StandardRequestLogProvider(requestLogFormat);
+        final RequestLog requestLog = requestLogProvider.getRequestLog();
+        server.setRequestLog(requestLog);
+
+        return server;
+    }
+
+    private void addConnectors(final Server server, final NiFiProperties 
properties, final SSLContext sslContext) {
+        final FrameworkServerConnectorFactory serverConnectorFactory = new 
FrameworkServerConnectorFactory(server, properties);
+        if (properties.isHTTPSConfigured()) {
+            serverConnectorFactory.setSslContext(sslContext);
+        }
+
+        final Map<String, String> interfaces = properties.isHTTPSConfigured() 
? properties.getHttpsNetworkInterfaces() : 
properties.getHttpNetworkInterfaces();
+        final Set<String> interfaceNames = 
interfaces.values().stream().filter(StringUtils::isNotBlank).collect(Collectors.toSet());
+        // Add Server Connectors based on configured Network Interface Names
+        if (interfaceNames.isEmpty()) {
+            final ServerConnector serverConnector = 
serverConnectorFactory.getServerConnector();
+            final String host = properties.isHTTPSConfigured() ? 
properties.getProperty(NiFiProperties.WEB_HTTPS_HOST) : 
properties.getProperty(NiFiProperties.WEB_HTTP_HOST);
+            if (StringUtils.isNotBlank(host)) {
+                serverConnector.setHost(host);
+            }
+            server.addConnector(serverConnector);
+        } else {
+            interfaceNames.stream()
+                    // Map interface name properties to Network Interfaces
+                    .map(interfaceName -> {
+                        try {
+                            return NetworkInterface.getByName(interfaceName);
+                        } catch (final SocketException e) {
+                            throw new 
UncheckedIOException(String.format("Network Interface [%s] not found", 
interfaceName), e);
+                        }
+                    })
+                    // Map Network Interfaces to host addresses
+                    .filter(Objects::nonNull)
+                    .flatMap(networkInterface -> 
Collections.list(networkInterface.getInetAddresses()).stream())
+                    .map(InetAddress::getHostAddress)
+                    // Map host addresses to Server Connectors
+                    .map(host -> {
+                        final ServerConnector serverConnector = 
serverConnectorFactory.getServerConnector();
+                        serverConnector.setHost(host);
+                        return serverConnector;
+                    })
+                    .forEach(server::addConnector);
+        }
+    }
+
+    private Handler getStandardHandler(final NiFiProperties properties) {
+        // Standard Handler supporting an ordered sequence of Handlers invoked 
until completion
+        final Handler.Collection standardHandler = new Handler.Sequence();
+
+        // Set Handler for standard response headers
+        standardHandler.addHandler(new HeaderWriterHandler());
+
+        // Validate Host Header when running with HTTPS enabled
+        if (properties.isHTTPSConfigured()) {
+            final HostHeaderHandler hostHeaderHandler = new 
HostHeaderHandler(properties);
+            standardHandler.addHandler(hostHeaderHandler);
+        }
+
+        return standardHandler;
+    }
+}
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/filter/StandardRequestFilterProvider.java
 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/filter/StandardRequestFilterProvider.java
index 471d2b3194..364773734c 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/filter/StandardRequestFilterProvider.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/filter/StandardRequestFilterProvider.java
@@ -24,17 +24,9 @@ import 
org.apache.nifi.web.security.requests.ContentLengthFilter;
 import org.apache.nifi.web.server.log.RequestAuthenticationFilter;
 import org.eclipse.jetty.ee10.servlet.FilterHolder;
 import org.eclipse.jetty.ee10.servlets.DoSFilter;
-import org.springframework.security.web.header.HeaderWriter;
-import org.springframework.security.web.header.HeaderWriterFilter;
-import 
org.springframework.security.web.header.writers.ContentSecurityPolicyHeaderWriter;
-import org.springframework.security.web.header.writers.HstsHeaderWriter;
-import 
org.springframework.security.web.header.writers.XContentTypeOptionsHeaderWriter;
-import 
org.springframework.security.web.header.writers.XXssProtectionHeaderWriter;
-import 
org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter;
 
 import jakarta.servlet.Filter;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.TimeUnit;
@@ -45,8 +37,6 @@ import java.util.concurrent.TimeUnit;
 public class StandardRequestFilterProvider implements RequestFilterProvider {
     private static final int MAX_CONTENT_SIZE_DISABLED = 0;
 
-    private static final String STANDARD_CONTENT_POLICY = "frame-ancestors 
'self'";
-
     /**
      * Get Filters using provided NiFi Properties
      *
@@ -63,8 +53,6 @@ public class StandardRequestFilterProvider implements 
RequestFilterProvider {
             filters.add(getFilterHolder(RequestAuthenticationFilter.class));
         }
 
-        filters.add(getHeaderWriterFilter());
-
         final int maxContentSize = getMaxContentSize(properties);
         if (maxContentSize > MAX_CONTENT_SIZE_DISABLED) {
             final FilterHolder contentLengthFilter = 
getContentLengthFilter(maxContentSize);
@@ -93,21 +81,6 @@ public class StandardRequestFilterProvider implements 
RequestFilterProvider {
         return filter;
     }
 
-    private FilterHolder getHeaderWriterFilter() {
-        final List<HeaderWriter> headerWriters = Arrays.asList(
-                new ContentSecurityPolicyHeaderWriter(STANDARD_CONTENT_POLICY),
-                new HstsHeaderWriter(),
-                new XContentTypeOptionsHeaderWriter(),
-                new 
XFrameOptionsHeaderWriter(XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN),
-                new XXssProtectionHeaderWriter()
-        );
-
-        final HeaderWriterFilter headerWriterFilter = new 
HeaderWriterFilter(headerWriters);
-        final FilterHolder filterHolder = new FilterHolder(headerWriterFilter);
-        filterHolder.setName(HeaderWriterFilter.class.getSimpleName());
-        return filterHolder;
-    }
-
     private FilterHolder getFilterHolder(final Class<? extends Filter> 
filterClass) {
         final FilterHolder filter = new FilterHolder(filterClass);
         filter.setName(filterClass.getSimpleName());
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/handler/HeaderWriterHandler.java
 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/handler/HeaderWriterHandler.java
new file mode 100644
index 0000000000..e2db859c54
--- /dev/null
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/handler/HeaderWriterHandler.java
@@ -0,0 +1,64 @@
+/*
+ * 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.nifi.web.server.handler;
+
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Handler;
+
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.util.Callback;
+
+/**
+ * HTTP Response Header Writer Handler applies standard headers to HTTP 
responses
+ */
+public class HeaderWriterHandler extends Handler.Wrapper {
+    private static final String CONTENT_SECURITY_POLICY_HEADER = 
"Content-Security-Policy";
+    private static final String CONTENT_SECURITY_POLICY = "frame-ancestors 
'self'";
+
+    private static final String FRAME_OPTIONS_HEADER = "X-Frame-Options";
+    private static final String FRAME_OPTIONS = "SAMEORIGIN";
+
+    private static final String STRICT_TRANSPORT_SECURITY_HEADER = 
"Strict-Transport-Security";
+    private static final String STRICT_TRANSPORT_SECURITY = "max-age=31540000";
+
+    private static final String XSS_PROTECTION_HEADER = "X-XSS-Protection";
+    private static final String XSS_PROTECTION = "1; mode=block";
+
+    /**
+     * Handle requests and set HTTP response headers
+     *
+     * @param request Jetty Request
+     * @param response Jetty Response
+     * @param callback Jetty Callback
+     * @return Handled status
+     * @throws Exception Thrown on failures from subsequent handlers
+     */
+    @Override
+    public boolean handle(final Request request, final Response response, 
final Callback callback) throws Exception {
+        final HttpFields.Mutable responseHeaders = response.getHeaders();
+        responseHeaders.put(CONTENT_SECURITY_POLICY_HEADER, 
CONTENT_SECURITY_POLICY);
+        responseHeaders.put(FRAME_OPTIONS_HEADER, FRAME_OPTIONS);
+        responseHeaders.put(XSS_PROTECTION_HEADER, XSS_PROTECTION);
+
+        if (request.isSecure()) {
+            responseHeaders.put(STRICT_TRANSPORT_SECURITY_HEADER, 
STRICT_TRANSPORT_SECURITY);
+        }
+
+        return super.handle(request, response, callback);
+    }
+}
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/StandardServerProviderTest.java
 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/StandardServerProviderTest.java
new file mode 100644
index 0000000000..093fb0ffae
--- /dev/null
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/StandardServerProviderTest.java
@@ -0,0 +1,131 @@
+/*
+ * 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.nifi.web.server;
+
+import org.apache.nifi.jetty.configuration.connector.ApplicationLayerProtocol;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.web.server.handler.HeaderWriterHandler;
+import org.eclipse.jetty.rewrite.handler.RewriteHandler;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.RequestLog;
+import org.eclipse.jetty.server.Server;
+import org.junit.jupiter.api.Test;
+
+import javax.net.ssl.SSLContext;
+import java.security.NoSuchAlgorithmException;
+import java.util.List;
+import java.util.Properties;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+class StandardServerProviderTest {
+
+    private static final String RANDOM_PORT = "0";
+
+    private static final String SSL_PROTOCOL = "ssl";
+
+    @Test
+    void testGetServer() {
+        final Properties applicationProperties = new Properties();
+        applicationProperties.setProperty(NiFiProperties.WEB_HTTP_PORT, 
RANDOM_PORT);
+        final NiFiProperties properties = 
NiFiProperties.createBasicNiFiProperties(null, applicationProperties);
+
+        final StandardServerProvider provider = new 
StandardServerProvider(null);
+
+        final Server server = provider.getServer(properties);
+
+        assertStandardConfigurationFound(server);
+        assertHttpConnectorFound(server);
+    }
+
+    @Test
+    void testGetServerHttps() throws NoSuchAlgorithmException {
+        final Properties applicationProperties = new Properties();
+        applicationProperties.setProperty(NiFiProperties.WEB_HTTPS_PORT, 
RANDOM_PORT);
+        final NiFiProperties properties = 
NiFiProperties.createBasicNiFiProperties(null, applicationProperties);
+
+        final SSLContext sslContext = SSLContext.getDefault();
+        final StandardServerProvider provider = new 
StandardServerProvider(sslContext);
+
+        final Server server = provider.getServer(properties);
+
+        assertStandardConfigurationFound(server);
+        assertHttpsConnectorFound(server);
+    }
+
+    @Test
+    void testGetServerStart() throws Exception {
+        final Properties applicationProperties = new Properties();
+        applicationProperties.setProperty(NiFiProperties.WEB_HTTP_PORT, 
RANDOM_PORT);
+        final NiFiProperties properties = 
NiFiProperties.createBasicNiFiProperties(null, applicationProperties);
+
+        final StandardServerProvider provider = new 
StandardServerProvider(null);
+
+        final Server server = provider.getServer(properties);
+
+        assertStandardConfigurationFound(server);
+        assertHttpConnectorFound(server);
+
+        try {
+            server.start();
+
+            assertFalse(server.isFailed());
+        } finally {
+            server.stop();
+        }
+    }
+
+    void assertHttpConnectorFound(final Server server) {
+        final Connector[] connectors = server.getConnectors();
+        assertNotNull(connectors);
+        final Connector connector = connectors[0];
+        final List<String> protocols = connector.getProtocols();
+        assertEquals(ApplicationLayerProtocol.HTTP_1_1.getProtocol(), 
protocols.getFirst());
+    }
+
+    void assertHttpsConnectorFound(final Server server) {
+        final Connector[] connectors = server.getConnectors();
+        assertNotNull(connectors);
+        final Connector connector = connectors[0];
+        final List<String> protocols = connector.getProtocols();
+        assertEquals(SSL_PROTOCOL, protocols.getFirst());
+    }
+
+    void assertStandardConfigurationFound(final Server server) {
+        assertNotNull(server);
+        assertHandlersFound(server);
+
+        final RequestLog requestLog = server.getRequestLog();
+        assertNotNull(requestLog);
+    }
+
+    void assertHandlersFound(final Server server) {
+        final Handler serverHandler = server.getHandler();
+        assertInstanceOf(Handler.Collection.class, serverHandler);
+
+        Handler defaultHandler = server.getDefaultHandler();
+        assertInstanceOf(RewriteHandler.class, defaultHandler);
+
+        final Handler.Collection handlerCollection = (Handler.Collection) 
serverHandler;
+        final HeaderWriterHandler headerWriterHandler = 
handlerCollection.getDescendant(HeaderWriterHandler.class);
+        assertNotNull(headerWriterHandler);
+    }
+}
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/filter/RestApiRequestFilterProviderTest.java
 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/filter/RestApiRequestFilterProviderTest.java
index bf7059532a..cc05b119f5 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/filter/RestApiRequestFilterProviderTest.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/filter/RestApiRequestFilterProviderTest.java
@@ -21,7 +21,6 @@ import org.eclipse.jetty.ee10.servlet.FilterHolder;
 import org.eclipse.jetty.ee10.servlets.DoSFilter;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
-import org.springframework.security.web.header.HeaderWriterFilter;
 
 import jakarta.servlet.Filter;
 import java.util.Collections;
@@ -56,7 +55,6 @@ public class RestApiRequestFilterProviderTest {
         assertNotNull(filters);
         assertFalse(filters.isEmpty());
 
-        assertFilterClassFound(filters, HeaderWriterFilter.class);
         assertFilterClassFound(filters, DataTransferExcludedDoSFilter.class);
     }
 
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/filter/StandardRequestFilterProviderTest.java
 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/filter/StandardRequestFilterProviderTest.java
index a0dc2b8e90..51380af793 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/filter/StandardRequestFilterProviderTest.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/filter/StandardRequestFilterProviderTest.java
@@ -22,7 +22,6 @@ import 
org.apache.nifi.web.server.log.RequestAuthenticationFilter;
 import org.eclipse.jetty.ee10.servlet.FilterHolder;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
-import org.springframework.security.web.header.HeaderWriterFilter;
 
 import jakarta.servlet.Filter;
 import java.util.Collections;
@@ -81,7 +80,7 @@ public class StandardRequestFilterProviderTest {
 
         assertFilterClassFound(filters, RequestAuthenticationFilter.class);
 
-        final FilterHolder firstFilterHolder = filters.get(0);
+        final FilterHolder firstFilterHolder = filters.getFirst();
         final Class<? extends Filter> firstFilterClass = 
firstFilterHolder.getHeldClass();
         assertEquals(RequestAuthenticationFilter.class, firstFilterClass);
     }
@@ -90,7 +89,6 @@ public class StandardRequestFilterProviderTest {
         assertNotNull(filters);
         assertFalse(filters.isEmpty());
 
-        assertFilterClassFound(filters, HeaderWriterFilter.class);
         assertFilterClassFound(filters, DataTransferExcludedDoSFilter.class);
     }
 


Reply via email to