You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by cz...@apache.org on 2023/08/23 04:07:41 UTC

[felix-dev] branch jakarta-servlet-6 updated: Port changes from main branch

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

cziegeler pushed a commit to branch jakarta-servlet-6
in repository https://gitbox.apache.org/repos/asf/felix-dev.git


The following commit(s) were added to refs/heads/jakarta-servlet-6 by this push:
     new 3012a1863a Port changes from main branch
3012a1863a is described below

commit 3012a1863a338c39085bd67d1e1da41a7e9a86e8
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Wed Aug 23 06:07:35 2023 +0200

    Port changes from main branch
---
 .../felix/webconsole/DefaultVariableResolver.java  | 109 -----------
 .../apache/felix/webconsole/VariableResolver.java  |  58 ------
 .../webconsole/WebConsoleSecurityProvider.java     |  58 ------
 .../webconsole/WebConsoleSecurityProvider2.java    |  74 -------
 .../webconsole/WebConsoleSecurityProvider3.java    |  55 ------
 .../{ => internal}/DefaultBrandingPlugin.java      |  70 +++----
 .../webconsole/internal/OsgiManagerActivator.java  |  28 +--
 .../webconsole/internal/OsgiManagerPlugin.java     |   7 +-
 .../internal/WebConsolePluginAdapter.java          |  11 +-
 .../internal/configuration/ConfigManager.java      |   4 +-
 .../webconsole/internal/core/BundlesServlet.java   |   2 +-
 .../webconsole/internal/core/ServicesServlet.java  |   5 +-
 .../internal/filter/FilteringResponseWrapper.java  |   3 +-
 .../webconsole/internal/misc/LicenseServlet.java   |   2 +-
 .../internal/servlet/AbstractWebConsolePlugin.java |   9 +-
 .../servlet/BasicWebConsoleSecurityProvider.java   |  18 +-
 .../internal/servlet/JakartaServletAdapter.java    |  18 +-
 .../webconsole/internal/servlet/OsgiManager.java   | 212 ++++++++++-----------
 .../internal/servlet/OsgiManagerHttpContext.java   | 107 +----------
 .../felix/webconsole/internal/servlet/Plugin.java  |   7 +-
 .../webconsole/internal/servlet/PluginHolder.java  |   4 +-
 .../webconsole/internal/system/VMStatPlugin.java   | 170 ++++++-----------
 .../felix/webconsole/servlet/AbstractServlet.java  |  31 +++
 .../servlet/RequestVariableResolver.java           |  17 +-
 .../felix/webconsole/servlet/ServletConstants.java |  22 +++
 .../felix/webconsole/{ => servlet}/User.java       |   3 +-
 .../felix/webconsole/{ => spi}/BrandingPlugin.java |  18 +-
 .../felix/webconsole/spi/SecurityProvider.java     |  84 ++++++++
 .../apache/felix/webconsole/spi/package-info.java  |   2 +-
 .../servlet/OsgiManagerHttpContextTest.java        |  88 ---------
 .../internal/servlet/OsgiManagerTest.java          |  17 +-
 31 files changed, 419 insertions(+), 894 deletions(-)

diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/DefaultVariableResolver.java b/webconsole/src/main/java/org/apache/felix/webconsole/DefaultVariableResolver.java
deleted file mode 100755
index 40fe2a6207..0000000000
--- a/webconsole/src/main/java/org/apache/felix/webconsole/DefaultVariableResolver.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * 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.felix.webconsole;
-
-
-import java.util.HashMap;
-import java.util.Map;
-
-import org.apache.felix.webconsole.internal.servlet.WebConsoleUtil;
-
-
-/**
- * The <code>DefaultVariableResolver</code> is a <code>HashMap</code> based
- * default implementation of the {@link VariableResolver} interface. It may
- * be used by plugins to implement the interface for the request and is also
- * used by the
- * {@link WebConsoleUtil#getVariableResolver(jakarta.servlet.ServletRequest)}
- * as the variable resolver if none has yet been assigned to the request.
- */
-@SuppressWarnings({ "rawtypes" })
-public class DefaultVariableResolver extends HashMap implements VariableResolver
-{
-
-    private static final long serialVersionUID = 4148807223433047780L;
-
-
-    /**
-     * Creates a new variable resolver with default capacity.
-     */
-    public DefaultVariableResolver()
-    {
-        super();
-    }
-
-
-    /**
-     * Creates a new variable resolver and initializes both - capacity &amp; load factor
-     * 
-     * @param initialCapacity  the initial capacity of the variable container
-     * @param loadFactor the load factor of the variable container
-     * @see HashMap#HashMap(int, float)
-     */
-    public DefaultVariableResolver( final int initialCapacity, final float loadFactor )
-    {
-        super( initialCapacity, loadFactor );
-    }
-
-
-    /**
-     * Creates a new variable resolver with specified initial capacity
-     * 
-     * @param initialCapacity  the initial capacity of the variable container
-     * @see HashMap#HashMap(int)
-     */
-    public DefaultVariableResolver( final int initialCapacity )
-    {
-        super( initialCapacity );
-    }
-
-
-    /**
-     * Creates a new variable resolver copying the variables from the given map.
-     * 
-     * @param source  the map whose variables are to be placed in this resolver.
-     * @see HashMap#HashMap(Map)
-     */
-    @SuppressWarnings({"unchecked"})
-    public DefaultVariableResolver( final Map source )
-    {
-        super( source );
-    }
-
-
-    /**
-     * Returns the string representation of the value stored under the variable
-     * name in this map. If no value is stored under the variable name,
-     * <code>null</code> is returned.
-     *
-     * @param variable The name of the variable whose value is to be returned.
-     * @return The variable value or <code>null</code> if there is no entry
-     *      with the given name in this map.
-     */
-    public String resolve( final String variable )
-    {
-        Object value = get( variable );
-        if ( value != null )
-        {
-            return value.toString();
-        }
-        return null;
-    }
-
-}
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/VariableResolver.java b/webconsole/src/main/java/org/apache/felix/webconsole/VariableResolver.java
deleted file mode 100755
index 47bd0cf12d..0000000000
--- a/webconsole/src/main/java/org/apache/felix/webconsole/VariableResolver.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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.felix.webconsole;
-
-import org.apache.felix.webconsole.internal.servlet.WebConsoleUtil;
-
-/**
- * The <code>VariableResolver</code> interface defines the API for an object
- * which may be provided by plugins to provide replacement values for
- * variables in the generated content.
- * <p>
- * Plugins should call the
- * {@link WebConsoleUtil#setVariableResolver(jakarta.servlet.ServletRequest, VariableResolver)}
- * method to provide their implementation for variable resolution.
- * <p>
- * The main use of such a variable resolver is when a plugin is using a static
- * template which provides slots to place dynamically generated content
- * parts.
- * <p>
- * <b>Note</b>: The variable resolver must be set in the request <b>before</b>
- * the response writer is retrieved calling the
- * <code>ServletRequest.getWriter()</code> method. Otherwise the variable
- * resolver will not be used for resolving variables.
- *
- * @see WebConsoleUtil#getVariableResolver(jakarta.servlet.ServletRequest)
- * @see WebConsoleUtil#setVariableResolver(jakarta.servlet.ServletRequest, VariableResolver)
- */
-public interface VariableResolver
-{
-
-    /**
-     * Returns a replacement value for the named variable or <code>null</code>
-     * if no replacement is available.
-     *
-     * @param variable The name of the variable for which to return a
-     *      replacement.
-     * @return The replacement value or <code>null</code> if no replacement is
-     *      available.
-     */
-    String resolve( String variable );
-
-}
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/WebConsoleSecurityProvider.java b/webconsole/src/main/java/org/apache/felix/webconsole/WebConsoleSecurityProvider.java
deleted file mode 100755
index 39b0e07da6..0000000000
--- a/webconsole/src/main/java/org/apache/felix/webconsole/WebConsoleSecurityProvider.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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.felix.webconsole;
-
-
-/**
- * The <code>WebConsoleSecurityProvider</code> is a service interface allowing
- * to use an external system to authenticate users before granting access to the
- * Web Console.
- *
- * @since 3.1.0; Web Console Bundle 3.1.0
- */
-public interface WebConsoleSecurityProvider
-{
-
-    /**
-     * Authenticates the user with the given user name and password.
-     *
-     * @param username The name of the user presented by the client
-     * @param password The password presented by the client
-     * @return Some object representing the authenticated user indicating general
-     *         access to be granted to the web console. If the user cannot be
-     *         authenticated (e.g. unknown user name or wrong password) or the
-     *         user must not be allowed access to the web console at all
-     *         <code>null</code> must be returned from this method.
-     */
-    public Object authenticate( String username, String password );
-
-
-    /**
-     * Checks whether the authenticated user has the given role permission.
-     *
-     * @param user The object referring to the authenticated user. This is the
-     *      object returned from the {@link #authenticate(String, String)}
-     *      method and will never be <code>null</code>.
-     * @param role The requested role
-     * @return <code>true</code> if the user is given permission for the given
-     *      role.
-     */
-    public boolean authorize( Object user, String role );
-
-}
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/WebConsoleSecurityProvider2.java b/webconsole/src/main/java/org/apache/felix/webconsole/WebConsoleSecurityProvider2.java
deleted file mode 100755
index f27939eda8..0000000000
--- a/webconsole/src/main/java/org/apache/felix/webconsole/WebConsoleSecurityProvider2.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * 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.felix.webconsole;
-
-
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-
-
-/**
- * The <code>WebConsoleSecurityProvider2</code> extends the
- * {@link WebConsoleSecurityProvider} interface allowing for full control of
- * the authentication process to access the Web Console.
- * <p>
- * If a registered {@link WebConsoleSecurityProvider} service implements this
- * interface the {@link #authenticate(HttpServletRequest, HttpServletResponse)}
- * method is called instead of the
- * {@link WebConsoleSecurityProvider#authenticate(String, String)} method.
- *
- * @since 3.1.2; Web Console Bundle 3.1.4
- */
-public interface WebConsoleSecurityProvider2 extends WebConsoleSecurityProvider
-{
-
-    /**
-     * The name of the request attribute providing the object representing the
-     * authenticated user. This object is used to call the
-     * {@link WebConsoleSecurityProvider#authorize(Object, String)} to
-     * authorize access for certain roles.
-     */
-    String USER_ATTRIBUTE = "org.apache.felix.webconsole.user"; //$NON-NLS-1$
-
-
-    /**
-     * Authenticates the given request or asks the client for credentials.
-     * <p>
-     * Implementations of this method are expected to respect and implement
-     * the semantics of the <code>HttpContext.handleSecurity</code> method
-     * as specified in the OSGi HTTP Service specification.
-     * <p>
-     * If this method returns <code>true</code> it is assumed the request
-     * provided valid credentials identifying the user as accepted to access
-     * the web console. In addition, the {@link #USER_ATTRIBUTE} request
-     * attribute must be set to a non-<code>null</code> object reference
-     * identifying the authenticated user.
-     * <p>
-     * If this method returns <code>false</code> the request to the web console
-     * is terminated without any more response sent back to the client. That is
-     * the implementation is expected to have informed the client in case of
-     * non-granted access.
-     *
-     * @param request The request object
-     * @param response The response object
-     * @return <code>true</code> If the request provided valid credentials.
-     */
-    public boolean authenticate( HttpServletRequest request, HttpServletResponse response );
-
-}
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/WebConsoleSecurityProvider3.java b/webconsole/src/main/java/org/apache/felix/webconsole/WebConsoleSecurityProvider3.java
deleted file mode 100755
index 735e86d7fb..0000000000
--- a/webconsole/src/main/java/org/apache/felix/webconsole/WebConsoleSecurityProvider3.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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.felix.webconsole;
-
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-
-import org.osgi.service.servlet.context.ServletContextHelper;
-
-/**
- * The <code>WebConsoleSecurityProvider3</code> extends the
- * {@link WebConsoleSecurityProvider2} interface and adds the ability to perform log-out operation.
- * <p>
- * If a registered {@link WebConsoleSecurityProvider} service implements this
- * interface the {@link #logout(HttpServletRequest, HttpServletResponse)}
- * method is called when the user clicks the logout button.
- * 
- * If this service is missing and basic authentication is used, then new authentication is requested.
- * 
- * In any case, the logout procedure will invalidate the current session and will remove the 
- * {@link ServletContextHelper#REMOTE_USER}, {@link ServletContextHelper#AUTHORIZATION} attributes from the request and the session.
- * 
- * @since 4.2.8; Web Console Bundle 4.2.8
- */
-public interface WebConsoleSecurityProvider3 extends WebConsoleSecurityProvider2
-{
-
-    /**
-     * This method will be called by the web console when the user clicks the logout button. The security provider
-     * shouldn't invalidate the session, it will be invalidated after this method exits.
-     * 
-     * However the security provider must delete any cookies or objects, that matters during the authorization process.
-     * 
-     * @param request the request
-     * @param response the response
-     */
-    void logout(HttpServletRequest request, HttpServletResponse response);
-}
-
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/DefaultBrandingPlugin.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/DefaultBrandingPlugin.java
similarity index 85%
rename from webconsole/src/main/java/org/apache/felix/webconsole/DefaultBrandingPlugin.java
rename to webconsole/src/main/java/org/apache/felix/webconsole/internal/DefaultBrandingPlugin.java
index 0da020b530..720e30084c 100755
--- a/webconsole/src/main/java/org/apache/felix/webconsole/DefaultBrandingPlugin.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/DefaultBrandingPlugin.java
@@ -16,13 +16,13 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.felix.webconsole;
-
+package org.apache.felix.webconsole.internal;
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.Properties;
 
+import org.apache.felix.webconsole.spi.BrandingPlugin;
 
 /**
  * The <code>DefaultBrandingPlugin</code> class is the default implementation
@@ -87,14 +87,13 @@ import java.util.Properties;
  * settings according to the property names listed above. The easiest way to
  * add such a properties file is to provide a fragment bundle with the file.
  */
-public class DefaultBrandingPlugin implements BrandingPlugin
-{
+public class DefaultBrandingPlugin implements BrandingPlugin {
 
     /**
      * The name of the bundle entry providing branding properties for this
      * default branding plugin (value is "/META-INF/webconsole.properties").
      */
-    private static final String BRANDING_PROPERTIES = "/META-INF/webconsole.properties"; //$NON-NLS-1$
+    private static final String BRANDING_PROPERTIES = "/META-INF/webconsole.properties";
 
     private static DefaultBrandingPlugin instance;
 
@@ -131,110 +130,89 @@ public class DefaultBrandingPlugin implements BrandingPlugin
         }
 
         // set the fields from the properties now
-        brandName = props.getProperty( "webconsole.brand.name", "Apache Felix Web Console" ); //$NON-NLS-1$
-        productName = props.getProperty( "webconsole.product.name", "Apache Felix" ); //$NON-NLS-1$
-        productURL = props.getProperty( "webconsole.product.url", "http://felix.apache.org" ); //$NON-NLS-1$
-        productImage = props.getProperty( "webconsole.product.image", "/res/imgs/logo.png" ); //$NON-NLS-1$
-        vendorName = props.getProperty( "webconsole.vendor.name", "The Apache Software Foundation" ); //$NON-NLS-1$
-        vendorURL = props.getProperty( "webconsole.vendor.url", "http://www.apache.org" ); //$NON-NLS-1$
-        vendorImage = props.getProperty( "webconsole.vendor.image", "/res/imgs/logo.png" ); //$NON-NLS-1$
-        favIcon = props.getProperty( "webconsole.favicon", "/res/imgs/favicon.ico" ); //$NON-NLS-1$
-        mainStyleSheet = props.getProperty( "webconsole.stylesheet", "/res/ui/webconsole.css" ); //$NON-NLS-1$
+        brandName = props.getProperty( "webconsole.brand.name", "Apache Felix Web Console" );
+        productName = props.getProperty( "webconsole.product.name", "Apache Felix" );
+        productURL = props.getProperty( "webconsole.product.url", "http://felix.apache.org" );
+        productImage = props.getProperty( "webconsole.product.image", "/res/imgs/logo.png" );
+        vendorName = props.getProperty( "webconsole.vendor.name", "The Apache Software Foundation" );
+        vendorURL = props.getProperty( "webconsole.vendor.url", "http://www.apache.org" );
+        vendorImage = props.getProperty( "webconsole.vendor.image", "/res/imgs/logo.png" );
+        favIcon = props.getProperty( "webconsole.favicon", "/res/imgs/favicon.ico" );
+        mainStyleSheet = props.getProperty( "webconsole.stylesheet", "/res/ui/webconsole.css" );
     }
 
-
     /**
      * Retrieves the shared instance
      * 
      * @return the singleton instance of the object
      */
-    public static DefaultBrandingPlugin getInstance()
-    {
-        if ( instance == null )
-        {
+    public static DefaultBrandingPlugin getInstance() {
+        if ( instance == null ) {
             instance = new DefaultBrandingPlugin();
         }
         return instance;
     }
 
-
     /**
      * @see org.apache.felix.webconsole.BrandingPlugin#getBrandName()
      */
-    public String getBrandName()
-    {
+    public String getBrandName() {
         return brandName;
     }
 
-
     /**
      * @see org.apache.felix.webconsole.BrandingPlugin#getProductName()
      */
-    public String getProductName()
-    {
+    public String getProductName() {
         return productName;
     }
 
-
     /**
      * @see org.apache.felix.webconsole.BrandingPlugin#getProductURL()
      */
-    public String getProductURL()
-    {
+    public String getProductURL() {
         return productURL;
     }
 
-
     /**
      * @see org.apache.felix.webconsole.BrandingPlugin#getProductImage()
      */
-    public String getProductImage()
-    {
+    public String getProductImage() {
         return productImage;
     }
 
-
     /**
      * @see org.apache.felix.webconsole.BrandingPlugin#getVendorName()
      */
-    public String getVendorName()
-    {
+    public String getVendorName() {
         return vendorName;
     }
 
-
     /**
      * @see org.apache.felix.webconsole.BrandingPlugin#getVendorURL()
      */
-    public String getVendorURL()
-    {
+    public String getVendorURL() {
         return vendorURL;
     }
 
-
     /**
      * @see org.apache.felix.webconsole.BrandingPlugin#getVendorImage()
      */
-    public String getVendorImage()
-    {
+    public String getVendorImage() {
         return vendorImage;
     }
 
-
     /**
      * @see org.apache.felix.webconsole.BrandingPlugin#getFavIcon()
      */
-    public String getFavIcon()
-    {
+    public String getFavIcon() {
         return favIcon;
     }
 
-
     /**
      * @see org.apache.felix.webconsole.BrandingPlugin#getMainStyleSheet()
      */
-    public String getMainStyleSheet()
-    {
+    public String getMainStyleSheet() {
         return mainStyleSheet;
     }
 }
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/OsgiManagerActivator.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/OsgiManagerActivator.java
index 8509c62976..c242e22edc 100755
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/OsgiManagerActivator.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/OsgiManagerActivator.java
@@ -30,8 +30,7 @@ import org.osgi.service.servlet.whiteboard.annotations.RequireHttpWhiteboard;
  * the Apache Web Console upon bundle lifecycle requests.
  */
 @RequireHttpWhiteboard
-public class OsgiManagerActivator implements BundleActivator
-{
+public class OsgiManagerActivator implements BundleActivator {
 
     private OsgiManager osgiManager;
 
@@ -42,42 +41,31 @@ public class OsgiManagerActivator implements BundleActivator
     /**
      * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
      */
-    public void start( final BundleContext bundleContext ) throws Exception
-    {
+    public void start( final BundleContext bundleContext ) throws Exception {
         osgiManager = new OsgiManager( bundleContext );
-        try
-        {
+        try {
             final Class<?> activatorClass = bundleContext.getBundle().loadClass(STATUS_ACTIVATOR);
             this.statusActivator = (BundleActivator) activatorClass.getDeclaredConstructor().newInstance();
-
-        }
-        catch (Throwable t)
-        {
+        } catch (Throwable t) {
             // we ignore this as the status activator is only available if the web console
             // bundle contains the status bundle.
         }
-        if ( this.statusActivator != null)
-        {
+        if ( this.statusActivator != null) {
             this.statusActivator.start(bundleContext);
         }
     }
 
-
     /**
      * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
      */
-    public void stop( final BundleContext bundleContext ) throws Exception
-    {
-        if ( this.statusActivator != null)
-        {
+    public void stop( final BundleContext bundleContext ) throws Exception {
+        if ( this.statusActivator != null) {
             this.statusActivator.stop(bundleContext);
             this.statusActivator = null;
         }
 
-        if ( osgiManager != null )
-        {
+        if ( osgiManager != null ) {
             osgiManager.dispose();
         }
     }
-
 }
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/OsgiManagerPlugin.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/OsgiManagerPlugin.java
index 35bdf04a2d..87613b1d8d 100755
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/OsgiManagerPlugin.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/OsgiManagerPlugin.java
@@ -18,17 +18,14 @@
  */
 package org.apache.felix.webconsole.internal;
 
-
 import org.osgi.framework.BundleContext;
 
-
 /**
  * OsgiManagerPlugin is an internal interface. When a plugin implements this
  * interface, the Web Console will run it's {@link #activate(BundleContext)} method upon
  * initialization and {@link #deactivate()}, when disposed.
  */
-public interface OsgiManagerPlugin
-{
+public interface OsgiManagerPlugin {
 
     /**
      * Category used for Web Console specific plugins.
@@ -52,11 +49,9 @@ public interface OsgiManagerPlugin
      */
     void activate( BundleContext bundleContext );
 
-
     /**
      * This method is called, by the Web Console to de-activate the plugin and release
      * all used resources.
      */
     void deactivate();
-
 }
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/WebConsolePluginAdapter.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/WebConsolePluginAdapter.java
index ef2c37ff90..4752edb6af 100755
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/WebConsolePluginAdapter.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/WebConsolePluginAdapter.java
@@ -20,6 +20,7 @@ package org.apache.felix.webconsole.internal;
 
 
 import java.io.IOException;
+import java.net.URL;
 import java.util.*;
 
 import org.apache.felix.webconsole.WebConsoleConstants;
@@ -137,9 +138,17 @@ public class WebConsolePluginAdapter extends AbstractWebConsolePlugin
      */
     protected Object getResourceProvider()
     {
-        return plugin;
+        return this;
     }
 
+    public URL getResource(final String path) {
+        final String prefix = "/".concat(this.getLabel());
+        final String resStart = prefix.concat("/res/");
+        if (path != null && path.startsWith(resStart)) {
+            return this.plugin.getClass().getResource(path.substring(prefix.length()));
+        }
+        return null;
+    }
 
     //---------- Servlet API overwrite
 
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/ConfigManager.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/ConfigManager.java
index cef84138a3..aa4a69fca6 100755
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/ConfigManager.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/ConfigManager.java
@@ -413,7 +413,7 @@ public class ConfigManager extends SimpleWebConsolePlugin implements OsgiManager
         jw.object();
         final ConfigAdminSupport cas = getConfigurationAdminSupport();
         // check for osgi installer plugin
-        @SuppressWarnings("unchecked")
+        @SuppressWarnings({"unchecked", "deprecation" })
         final Map<String, Object> labelMap = (Map<String, Object>) request.getAttribute(WebConsoleConstants.ATTR_LABEL_MAP);
         jw.key("jsonsupport").value( labelMap.containsKey("osgi-installer-config-printer") ); //$NON-NLS-1$
         final boolean hasMetatype = cas.getMetaTypeSupport() != null;
@@ -443,7 +443,7 @@ public class ConfigManager extends SimpleWebConsolePlugin implements OsgiManager
         final String referer = request.getParameter( REFERER );
         final boolean factoryCreate = "true".equals( request.getParameter(FACTORY_CREATE) );
 
-        final RequestVariableResolver vars = WebConsoleUtil.getRequestVariableResolver(request);
+        final RequestVariableResolver vars = this.getVariableResolver(request);
         vars.put( "__data__", json.toString() ); 
         vars.put( "selectedPid", pid != null ? pid : "" );
         vars.put( "configurationReferer", referer != null ? referer : "" );
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/BundlesServlet.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/BundlesServlet.java
index 206e8c47a4..f0f7849769 100755
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/BundlesServlet.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/BundlesServlet.java
@@ -529,7 +529,7 @@ public class BundlesServlet extends SimpleWebConsolePlugin implements OsgiManage
         final int startLevel = fsl.getInitialBundleStartLevel();
 
         // prepare variables
-        final RequestVariableResolver vars = WebConsoleUtil.getRequestVariableResolver(request);
+        final RequestVariableResolver vars = this.getVariableResolver(request);
         vars.put( "startLevel", String.valueOf(startLevel));
         vars.put( "drawDetails", reqInfo.bundleRequested ? Boolean.TRUE : Boolean.FALSE );
         vars.put( "currentBundle", (reqInfo.bundleRequested && reqInfo.bundle != null ? String.valueOf(reqInfo.bundle.getBundleId()) : "null"));
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/ServicesServlet.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/ServicesServlet.java
index e5aa362dff..855184553f 100755
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/ServicesServlet.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/ServicesServlet.java
@@ -406,8 +406,7 @@ public class ServicesServlet extends SimpleWebConsolePlugin implements OsgiManag
     /**
      * @see org.apache.felix.webconsole.internal.servlet.AbstractWebConsolePlugin#renderContent(jakarta.servlet.http.HttpServletRequest, jakarta.servlet.http.HttpServletResponse)
      */
-    protected void renderContent( HttpServletRequest request, HttpServletResponse response ) throws IOException
-    {
+    protected void renderContent( HttpServletRequest request, HttpServletResponse response ) throws IOException {
         // get request info from request attribute
         final RequestInfo reqInfo = getRequestInfo( request );
 
@@ -417,7 +416,7 @@ public class ServicesServlet extends SimpleWebConsolePlugin implements OsgiManag
         writeJSON(w, reqInfo.service, request.getLocale(), filter);
 
         // prepare variables
-        final RequestVariableResolver vars = WebConsoleUtil.getRequestVariableResolver(request);
+        final RequestVariableResolver vars = this.getVariableResolver(request);
         vars.put( "bundlePath", appRoot +  "/" + BundlesServlet.NAME + "/" );
         vars.put( "drawDetails", String.valueOf(reqInfo.serviceRequested));
         vars.put( "__data__", w.toString() );
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/filter/FilteringResponseWrapper.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/filter/FilteringResponseWrapper.java
index 899ddef4dc..717b79ec58 100755
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/filter/FilteringResponseWrapper.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/filter/FilteringResponseWrapper.java
@@ -27,7 +27,6 @@ import jakarta.servlet.ServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 import jakarta.servlet.http.HttpServletResponseWrapper;
 
-import org.apache.felix.webconsole.internal.servlet.WebConsoleUtil;
 import org.apache.felix.webconsole.servlet.RequestVariableResolver;
 
 
@@ -81,7 +80,7 @@ public class FilteringResponseWrapper extends HttpServletResponseWrapper {
         if ( writer == null ) {
             final PrintWriter base = super.getWriter();
             if ( doWrap() ) {
-                final RequestVariableResolver vars = WebConsoleUtil.getRequestVariableResolver(request);
+                final RequestVariableResolver vars = (RequestVariableResolver) request.getAttribute(RequestVariableResolver.REQUEST_ATTRIBUTE);
                 final ResourceFilteringWriter filter = new ResourceFilteringWriter( base, locale, vars );
                 writer = new PrintWriter( filter );
             } else {
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/LicenseServlet.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/LicenseServlet.java
index 869ae3b782..0376579b21 100755
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/LicenseServlet.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/LicenseServlet.java
@@ -115,7 +115,7 @@ public final class LicenseServlet extends SimpleWebConsolePlugin implements Osgi
         Util.sort( bundles, request.getLocale() );
 
         // prepare variables
-        final RequestVariableResolver vars = WebConsoleUtil.getRequestVariableResolver(request);
+        final RequestVariableResolver vars = this.getVariableResolver(request);
         vars.put( "__data__", getBundleData( bundles, request.getLocale() ));
 
         res.getWriter().print(TEMPLATE);
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/AbstractWebConsolePlugin.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/AbstractWebConsolePlugin.java
index fffdf69d08..ed45ecc608 100755
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/AbstractWebConsolePlugin.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/AbstractWebConsolePlugin.java
@@ -40,14 +40,15 @@ import java.util.TreeMap;
 import jakarta.servlet.ServletConfig;
 import jakarta.servlet.ServletContext;
 import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
 import jakarta.servlet.http.HttpServlet;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 
-import org.apache.felix.webconsole.BrandingPlugin;
-import org.apache.felix.webconsole.DefaultBrandingPlugin;
 import org.apache.felix.webconsole.WebConsoleConstants;
+import org.apache.felix.webconsole.internal.DefaultBrandingPlugin;
 import org.apache.felix.webconsole.servlet.RequestVariableResolver;
+import org.apache.felix.webconsole.spi.BrandingPlugin;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.service.log.LogService;
@@ -997,6 +998,10 @@ public abstract class AbstractWebConsolePlugin extends HttpServlet
         return sortedMap;
     }
 
+    protected RequestVariableResolver getVariableResolver(final ServletRequest request) {
+        return (RequestVariableResolver) request.getAttribute(RequestVariableResolver.REQUEST_ATTRIBUTE);
+    }
+
     @SuppressWarnings({ "rawtypes" })
     private static class MenuItem
     {
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/BasicWebConsoleSecurityProvider.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/BasicWebConsoleSecurityProvider.java
index f6720ca23e..0af828a09f 100755
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/BasicWebConsoleSecurityProvider.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/BasicWebConsoleSecurityProvider.java
@@ -22,7 +22,7 @@ import java.io.UnsupportedEncodingException;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 
-import org.apache.felix.webconsole.WebConsoleSecurityProvider2;
+import org.apache.felix.webconsole.spi.SecurityProvider;
 import org.osgi.framework.BundleContext;
 import org.osgi.service.servlet.context.ServletContextHelper;
 
@@ -30,7 +30,7 @@ import org.osgi.service.servlet.context.ServletContextHelper;
  * Basic implementation of WebConsoleSecurityProvider to replace logic that
  * was previously in OsgiManagerHttpContext
  */
-public class BasicWebConsoleSecurityProvider implements WebConsoleSecurityProvider2 {
+public class BasicWebConsoleSecurityProvider implements SecurityProvider {
 
     static final String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
 
@@ -75,7 +75,7 @@ public class BasicWebConsoleSecurityProvider implements WebConsoleSecurityProvid
     }
 
     @Override
-    public boolean authenticate(HttpServletRequest request, HttpServletResponse response) {
+    public Object authenticate(HttpServletRequest request, HttpServletResponse response) {
         // Return immediately if the header is missing
         String authHeader = request.getHeader( HEADER_AUTHORIZATION );
         if ( authHeader != null && authHeader.length() > 0 )
@@ -106,11 +106,8 @@ public class BasicWebConsoleSecurityProvider implements WebConsoleSecurityProvid
                             request.setAttribute( ServletContextHelper.AUTHENTICATION_TYPE, HttpServletRequest.BASIC_AUTH );
                             request.setAttribute( ServletContextHelper.REMOTE_USER, username );
 
-                            // set web console user attribute
-                            request.setAttribute( WebConsoleSecurityProvider2.USER_ATTRIBUTE, username );
-
                             // succeed
-                            return true;
+                            return username;
                         }
                     }
                     catch ( Exception e )
@@ -135,7 +132,12 @@ public class BasicWebConsoleSecurityProvider implements WebConsoleSecurityProvid
         }
 
         // inform HttpService that authentication failed
-        return false;
+        return null;
+    }
+
+    @Override
+    public void logout(HttpServletRequest request, HttpServletResponse response) {
+        // nothing to do
     }
 
     static byte[][] base64Decode( String srcString )
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/JakartaServletAdapter.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/JakartaServletAdapter.java
index 73dc625749..f1e6b19b3d 100755
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/JakartaServletAdapter.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/JakartaServletAdapter.java
@@ -94,7 +94,7 @@ public class JakartaServletAdapter extends AbstractWebConsolePlugin {
     @Override
     protected void renderContent( final HttpServletRequest req, final HttpServletResponse res )
     throws ServletException, IOException {
-        plugin.renderContent(req, res); 
+        plugin.renderContent(req, res);
     }
 
     /**
@@ -102,9 +102,9 @@ public class JakartaServletAdapter extends AbstractWebConsolePlugin {
      * <code>getResource()</code> method on that object for this plugin to
      * provide additional resources.
      *
-     * @see org.apache.felix.webconsole.internal.servlet.AbstractWebConsolePlugin#getResourceProvider()
+     * @see org.apache.felix.webconsole.AbstractWebConsolePlugin#getResourceProvider()
      */
-    public Object getResourceProvider() {
+    protected Object getResourceProvider() {
         return plugin;
     }
 
@@ -124,7 +124,7 @@ public class JakartaServletAdapter extends AbstractWebConsolePlugin {
             super.init( config );
 
             // plugin initialization
-            plugin.init( config );
+            plugin.init(config);
         } catch ( ServletException se ) {
             // if init fails, the plugin will not be destroyed and thus
             // the plugin not deactivated. Do it here
@@ -176,10 +176,11 @@ public class JakartaServletAdapter extends AbstractWebConsolePlugin {
             super.setStatus(sc);
         }
 
-        public void setStatus(final int sc, final String sm) {
+        @Override
+        public void setContentType(final String type) {
             this.done = true;
-            super.setStatus(sc);
-        }        
+            super.setContentType(type);
+        }
     }
 
     /**
@@ -195,7 +196,8 @@ public class JakartaServletAdapter extends AbstractWebConsolePlugin {
     throws ServletException, IOException {
         final CheckHttpServletResponse checkResponse = new CheckHttpServletResponse(resp);
         // call plugin first
-        plugin.service(req, resp); 
+        plugin.service(req, resp);
+
         // if a GET request and plugin did not create a response yet, call super to get full HTML response
         if ( !checkResponse.isDone() && req.getMethod().equals( "GET" ) ) {
             // handle the GET request here and call into plugin on renderContent
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java
index 522dc9b38c..7d70eeb202 100755
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java
@@ -39,12 +39,7 @@ import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentSkipListSet;
 
-import org.apache.felix.webconsole.BrandingPlugin;
-import org.apache.felix.webconsole.User;
 import org.apache.felix.webconsole.WebConsoleConstants;
-import org.apache.felix.webconsole.WebConsoleSecurityProvider;
-import org.apache.felix.webconsole.WebConsoleSecurityProvider2;
-import org.apache.felix.webconsole.WebConsoleSecurityProvider3;
 import org.apache.felix.webconsole.internal.OsgiManagerPlugin;
 import org.apache.felix.webconsole.internal.Util;
 import org.apache.felix.webconsole.internal.core.BundlesServlet;
@@ -52,6 +47,10 @@ import org.apache.felix.webconsole.internal.filter.FilteringResponseWrapper;
 import org.apache.felix.webconsole.internal.i18n.ResourceBundleManager;
 import org.apache.felix.webconsole.internal.servlet.Plugin.InternalPlugin;
 import org.apache.felix.webconsole.servlet.RequestVariableResolver;
+import org.apache.felix.webconsole.servlet.ServletConstants;
+import org.apache.felix.webconsole.servlet.User;
+import org.apache.felix.webconsole.spi.BrandingPlugin;
+import org.apache.felix.webconsole.spi.SecurityProvider;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
@@ -94,8 +93,7 @@ public class OsgiManager extends GenericServlet {
      * @deprecated use {@link WebConsoleConstants#ATTR_APP_ROOT} instead
      */
     @Deprecated
-    private static final String ATTR_APP_ROOT_OLD = OsgiManager.class.getName()
-        + ".appRoot";
+    private static final String ATTR_APP_ROOT_OLD = OsgiManager.class.getName() + ".appRoot";
 
     /**
      * Old name of the request attribute providing the mappings from label to
@@ -105,13 +103,14 @@ public class OsgiManager extends GenericServlet {
      * @deprecated use {@link WebConsoleConstants#ATTR_LABEL_MAP} instead
      */
     @Deprecated
-    private static final String ATTR_LABEL_MAP_OLD = OsgiManager.class.getName()
-        + ".labelMap";
+    private static final String ATTR_LABEL_MAP_OLD = OsgiManager.class.getName() + ".labelMap";
 
     /**
      * The name of the (internal) request attribute providing the categorized
      * label map structure.
+     * @deprecated use {@link WebConsoleConstants#ATTR_LABEL_MAP_CATEGORIZED} instead
      */
+    @Deprecated
     public static final String ATTR_LABEL_MAP_CATEGORIZED = WebConsoleConstants.ATTR_LABEL_MAP + ".categorized";
 
     /**
@@ -125,49 +124,49 @@ public class OsgiManager extends GenericServlet {
      * The name of the cookie storing user-configured locale
      * See https://issues.apache.org/jira/browse/FELIX-2267
      */
-    private static final String COOKIE_LOCALE = "felix-webconsole-locale"; //$NON-NLS-1$
+    private static final String COOKIE_LOCALE = "felix-webconsole-locale";
 
-    private static final String FRAMEWORK_PROP_MANAGER_ROOT = "felix.webconsole.manager.root"; //$NON-NLS-1$
+    private static final String FRAMEWORK_PROP_MANAGER_ROOT = "felix.webconsole.manager.root";
 
-    private static final String FRAMEWORK_PROP_REALM = "felix.webconsole.realm"; //$NON-NLS-1$
+    private static final String FRAMEWORK_PROP_REALM = "felix.webconsole.realm";
 
-    private static final String FRAMEWORK_PROP_USER_NAME = "felix.webconsole.username"; //$NON-NLS-1$
+    private static final String FRAMEWORK_PROP_USER_NAME = "felix.webconsole.username";
 
-    private static final String FRAMEWORK_PROP_PASSWORD = "felix.webconsole.password"; //$NON-NLS-1$
+    private static final String FRAMEWORK_PROP_PASSWORD = "felix.webconsole.password";
 
-    private static final String FRAMEWORK_PROP_LOG_LEVEL = "felix.webconsole.loglevel"; //$NON-NLS-1$
+    private static final String FRAMEWORK_PROP_LOG_LEVEL = "felix.webconsole.loglevel";
 
-    private static final String FRAMEWORK_PROP_LOCALE = "felix.webconsole.locale"; //$NON-NLS-1$
+    private static final String FRAMEWORK_PROP_LOCALE = "felix.webconsole.locale";
 
-    private static final String FRAMEWORK_SHUTDOWN_TIMEOUT = "felix.webconsole.shutdown.timeout"; //$NON-NLS-1$
+    private static final String FRAMEWORK_SHUTDOWN_TIMEOUT = "felix.webconsole.shutdown.timeout";
 
-    private static final String FRAMEWORK_RELOAD_TIMEOUT = "felix.webconsole.reload.timeout"; //$NON-NLS-1$
+    private static final String FRAMEWORK_RELOAD_TIMEOUT = "felix.webconsole.reload.timeout";
 
-    static final String FRAMEWORK_PROP_SECURITY_PROVIDERS = "felix.webconsole.security.providers"; //$NON-NLS-1$
+    static final String FRAMEWORK_PROP_SECURITY_PROVIDERS = "felix.webconsole.security.providers";
 
-    static final String SECURITY_PROVIDER_PROPERTY_NAME = "webconsole.security.provider.id"; //$NON-NLS-1$
+    static final String SECURITY_PROVIDER_PROPERTY_NAME = "webconsole.security.provider.id";
 
-    static final String PROP_MANAGER_ROOT = "manager.root"; //$NON-NLS-1$
+    static final String PROP_MANAGER_ROOT = "manager.root";
 
-    static final String PROP_DEFAULT_RENDER = "default.render"; //$NON-NLS-1$
+    static final String PROP_DEFAULT_RENDER = "default.render";
 
-    static final String PROP_REALM = "realm"; //$NON-NLS-1$
+    static final String PROP_REALM = "realm";
 
-    static final String PROP_USER_NAME = "username"; //$NON-NLS-1$
+    static final String PROP_USER_NAME = "username";
 
-    static final String PROP_PASSWORD = "password"; //$NON-NLS-1$
+    static final String PROP_PASSWORD = "password";
 
-    static final String PROP_CATEGORY = "category"; //$NON-NLS-1$
+    static final String PROP_CATEGORY = "category";
 
-    static final String PROP_ENABLED_PLUGINS = "plugins"; //$NON-NLS-1$
+    static final String PROP_ENABLED_PLUGINS = "plugins";
 
-    static final String PROP_LOG_LEVEL = "loglevel"; //$NON-NLS-1$
+    static final String PROP_LOG_LEVEL = "loglevel";
 
-    static final String PROP_LOCALE = "locale"; //$NON-NLS-1$
+    static final String PROP_LOCALE = "locale";
 
-    static final String PROP_ENABLE_SECRET_HEURISTIC = "secret.heuristic.enabled"; //$NON-NLS-1$
+    static final String PROP_ENABLE_SECRET_HEURISTIC = "secret.heuristic.enabled";
 
-    static final String PROP_HTTP_SERVICE_SELECTOR = "http.service.filter"; //$NON-NLS-1$
+    static final String PROP_HTTP_SERVICE_SELECTOR = "http.service.filter";
     
     /** The framework shutdown timeout */
     public static final String PROP_SHUTDOWN_TIMEOUT = "shutdown.timeout";
@@ -179,53 +178,53 @@ public class OsgiManager extends GenericServlet {
 
     static final String DEFAULT_PAGE = BundlesServlet.NAME;
 
-    static final String DEFAULT_REALM = "OSGi Management Console"; //$NON-NLS-1$
+    static final String DEFAULT_REALM = "OSGi Management Console";
 
-    static final String DEFAULT_USER_NAME = "admin"; //$NON-NLS-1$
+    static final String DEFAULT_USER_NAME = "admin";
 
-    static final String DEFAULT_PASSWORD = "{sha-256}jGl25bVBBBW96Qi9Te4V37Fnqchz/Eu4qB9vKrRIqRg="; //$NON-NLS-1$
+    static final String DEFAULT_PASSWORD = "{sha-256}jGl25bVBBBW96Qi9Te4V37Fnqchz/Eu4qB9vKrRIqRg=";
 
-    static final String DEFAULT_CATEGORY = "Main"; //$NON-NLS-1$
+    static final String DEFAULT_CATEGORY = "Main";
 
-    static final int DEFAULT_SHUTDOWN_TIMEOUT = 5; //$NON-NLS-1$
+    static final int DEFAULT_SHUTDOWN_TIMEOUT = 5;
 
-    static final int DEFAULT_RELOAD_TIMEOUT = 40; //$NON-NLS-1$
+    static final int DEFAULT_RELOAD_TIMEOUT = 40;
 
     /** Default value for secret heuristics */
     public static final boolean DEFAULT_ENABLE_SECRET_HEURISTIC = false;
 
-    private static final String HEADER_AUTHORIZATION = "Authorization"; //$NON-NLS-1$
+    private static final String HEADER_AUTHORIZATION = "Authorization";
 
-    private static final String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate"; //$NON-NLS-1$
+    private static final String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
 
     /**
      * The default value for the {@link #PROP_MANAGER_ROOT} configuration
      * property (value is "/system/console").
      */
-    static final String DEFAULT_MANAGER_ROOT = "/system/console"; //$NON-NLS-1$
+    static final String DEFAULT_MANAGER_ROOT = "/system/console";
 
-    private static final String OLD_CONFIG_MANAGER_CLASS = "org.apache.felix.webconsole.internal.compendium.ConfigManager"; //$NON-NLS-1$
-    private static final String NEW_CONFIG_MANAGER_CLASS = "org.apache.felix.webconsole.internal.configuration.ConfigManager"; //$NON-NLS-1$
+    private static final String OLD_CONFIG_MANAGER_CLASS = "org.apache.felix.webconsole.internal.compendium.ConfigManager";
+    private static final String NEW_CONFIG_MANAGER_CLASS = "org.apache.felix.webconsole.internal.configuration.ConfigManager";
 
     static final String[] PLUGIN_CLASSES = {
-            "org.apache.felix.webconsole.internal.configuration.ConfigurationAdminConfigurationPrinter", //$NON-NLS-1$
-            "org.apache.felix.webconsole.internal.compendium.PreferencesConfigurationPrinter", //$NON-NLS-1$
-            "org.apache.felix.webconsole.internal.compendium.WireAdminConfigurationPrinter", //$NON-NLS-1$
-            "org.apache.felix.webconsole.internal.core.BundlesConfigurationPrinter", //$NON-NLS-1$
-            "org.apache.felix.webconsole.internal.core.CapabilitiesPrinter", //$NON-NLS-1$
-            "org.apache.felix.webconsole.internal.core.FrameworkPropertiesPrinter", //$NON-NLS-1$
-            "org.apache.felix.webconsole.internal.core.PermissionsConfigurationPrinter", //$NON-NLS-1$
-            "org.apache.felix.webconsole.internal.core.ServicesConfigurationPrinter", //$NON-NLS-1$
-            "org.apache.felix.webconsole.internal.misc.SystemPropertiesPrinter", //$NON-NLS-1$
-            "org.apache.felix.webconsole.internal.misc.ThreadPrinter", }; //$NON-NLS-1$
+            "org.apache.felix.webconsole.internal.configuration.ConfigurationAdminConfigurationPrinter",
+            "org.apache.felix.webconsole.internal.compendium.PreferencesConfigurationPrinter",
+            "org.apache.felix.webconsole.internal.compendium.WireAdminConfigurationPrinter",
+            "org.apache.felix.webconsole.internal.core.BundlesConfigurationPrinter",
+            "org.apache.felix.webconsole.internal.core.CapabilitiesPrinter",
+            "org.apache.felix.webconsole.internal.core.FrameworkPropertiesPrinter",
+            "org.apache.felix.webconsole.internal.core.PermissionsConfigurationPrinter",
+            "org.apache.felix.webconsole.internal.core.ServicesConfigurationPrinter",
+            "org.apache.felix.webconsole.internal.misc.SystemPropertiesPrinter",
+            "org.apache.felix.webconsole.internal.misc.ThreadPrinter", };
 
     static final String[] PLUGIN_MAP = {
-            NEW_CONFIG_MANAGER_CLASS, "configMgr", //$NON-NLS-1$ //$NON-NLS-2$
-            "org.apache.felix.webconsole.internal.compendium.LogServlet", "logs", //$NON-NLS-1$ //$NON-NLS-2$
-            "org.apache.felix.webconsole.internal.core.BundlesServlet", "bundles", //$NON-NLS-1$ //$NON-NLS-2$
-            "org.apache.felix.webconsole.internal.core.ServicesServlet", "services", //$NON-NLS-1$ //$NON-NLS-2$
-            "org.apache.felix.webconsole.internal.misc.LicenseServlet", "licenses", //$NON-NLS-1$ //$NON-NLS-2$
-            "org.apache.felix.webconsole.internal.system.VMStatPlugin", "vmstat", //$NON-NLS-1$ //$NON-NLS-2$
+            NEW_CONFIG_MANAGER_CLASS, "configMgr",
+            "org.apache.felix.webconsole.internal.compendium.LogServlet", "logs",
+            "org.apache.felix.webconsole.internal.core.BundlesServlet", "bundles",
+            "org.apache.felix.webconsole.internal.core.ServicesServlet", "services",
+            "org.apache.felix.webconsole.internal.misc.LicenseServlet", "licenses",
+            "org.apache.felix.webconsole.internal.system.VMStatPlugin", "vmstat",
     };
 
     private static final String SERVLEXT_CONTEXT_NAME = "org.apache.felix.webconsole";
@@ -239,7 +238,7 @@ public class OsgiManager extends GenericServlet {
 
     private ServiceTracker<BrandingPlugin, BrandingPlugin> brandingTracker;
 
-    private ServiceTracker<WebConsoleSecurityProvider, WebConsoleSecurityProvider> securityProviderTracker;
+    private ServiceTracker<SecurityProvider, SecurityProvider> securityProviderTracker;
 
     private ServiceRegistration configurationListener;
 
@@ -250,7 +249,7 @@ public class OsgiManager extends GenericServlet {
     private volatile String webManagerRoot;
 
     // not-null when the BasicWebConsoleSecurityProvider service is registered
-    private ServiceRegistration<WebConsoleSecurityProvider> basicSecurityServiceRegistration;
+    private ServiceRegistration<SecurityProvider> basicSecurityServiceRegistration;
 
     // not-null when the ServletContextHelper service is registered
     private volatile ServiceRegistration<ServletContextHelper> servletContextRegistration;
@@ -294,28 +293,19 @@ public class OsgiManager extends GenericServlet {
 
         // setup the included plugins
         ClassLoader classLoader = getClass().getClassLoader();
-        for (int i = 0; i < PLUGIN_CLASSES.length; i++)
-        {
-            String pluginClassName = PLUGIN_CLASSES[i];
+        for (int i = 0; i < PLUGIN_CLASSES.length; i++) {
+            final String pluginClassName = PLUGIN_CLASSES[i];
 
-            try
-            {
+            try {
                 final Class<?> pluginClass = classLoader.loadClass(pluginClassName);
                 final Object plugin = pluginClass.getDeclaredConstructor().newInstance();
 
-                if (plugin instanceof OsgiManagerPlugin)
-                {
+                if (plugin instanceof OsgiManagerPlugin) {
                     final OsgiManagerPlugin p = (OsgiManagerPlugin)plugin;
                     p.activate(bundleContext);
                     osgiManagerPlugins.add(p);
                 }
-                if (plugin instanceof BrandingPlugin)
-                {
-                    AbstractWebConsolePlugin.setBrandingPlugin((BrandingPlugin) plugin);
-                }
-            }
-            catch (NoClassDefFoundError ncdfe)
-            {
+            } catch (NoClassDefFoundError ncdfe) {
                 String message = ncdfe.getMessage();
                 if (message == null)
                 {
@@ -350,7 +340,7 @@ public class OsgiManager extends GenericServlet {
         this.requiredSecurityProviders = splitCommaSeparatedString(bundleContext.getProperty(FRAMEWORK_PROP_SECURITY_PROVIDERS));
 
         // add support for pluggable security
-        securityProviderTracker = new ServiceTracker<>(bundleContext, WebConsoleSecurityProvider.class,
+        securityProviderTracker = new ServiceTracker<>(bundleContext, SecurityProvider.class,
                                           new UpdateDependenciesStateCustomizer());
         securityProviderTracker.open();
 
@@ -377,7 +367,7 @@ public class OsgiManager extends GenericServlet {
         updateConfiguration(null);
 
         // register managed service as a service factory
-        this.configurationListener = bundleContext.registerService( "org.osgi.service.cm.ManagedService", //$NON-NLS-1$
+        this.configurationListener = bundleContext.registerService( "org.osgi.service.cm.ManagedService",
             new ServiceFactory()
             {
                 @Override
@@ -412,8 +402,8 @@ public class OsgiManager extends GenericServlet {
             }, new Hashtable<String, Object>()
             {
                 {
-                    put( Constants.SERVICE_VENDOR, "The Apache Software Foundation" ); //$NON-NLS-1$
-                    put( Constants.SERVICE_DESCRIPTION, "OSGi Management Console Configuration Receiver" ); //$NON-NLS-1$
+                    put( Constants.SERVICE_VENDOR, "The Apache Software Foundation" );
+                    put( Constants.SERVICE_DESCRIPTION, "OSGi Management Console Configuration Receiver" );
                     put( Constants.SERVICE_PID, getConfigurationPid() );
                 }
             } );
@@ -479,7 +469,7 @@ public class OsgiManager extends GenericServlet {
     //---------- Servlet API
 
     /**
-     * @see jakarta.servlet.GenericServlet#init()
+     * @see javax.servlet.GenericServlet#init()
      */
     @Override
     public void init()
@@ -491,13 +481,9 @@ public class OsgiManager extends GenericServlet {
 
     }
 
-    /**
-     * @see jakarta.servlet.GenericServlet#service(jakarta.servlet.ServletRequest, jakarta.servlet.ServletResponse)
-     */
     @Override
     public void service(final ServletRequest req, final ServletResponse res)
-        throws ServletException, IOException
-    {
+    throws ServletException, IOException {
         // don't really expect to be called within a non-HTTP environment
         try
         {
@@ -577,7 +563,7 @@ public class OsgiManager extends GenericServlet {
             return;
         }
 
-        int slash = pathInfo.indexOf("/", 1); //$NON-NLS-1$
+        int slash = pathInfo.indexOf("/", 1);
         if (slash < 2)
         {
             slash = pathInfo.length();
@@ -591,11 +577,11 @@ public class OsgiManager extends GenericServlet {
         {
             final String body404 = MessageFormat.format(
                 resourceBundleManager.getResourceBundle(bundleContext.getBundle(), locale).getString(
-                    "404"), //$NON-NLS-1$
+                    "404"),
                 new Object[] { request.getContextPath() + request.getServletPath() + '/'
                     + BundlesServlet.NAME });
-            response.setCharacterEncoding("utf-8"); //$NON-NLS-1$
-            response.setContentType("text/html"); //$NON-NLS-1$
+            response.setCharacterEncoding("utf-8");
+            response.setContentType("text/html");
             response.setStatus(HttpServletResponse.SC_NOT_FOUND);
             response.getWriter().println(body404);
 
@@ -604,22 +590,20 @@ public class OsgiManager extends GenericServlet {
 
         @SuppressWarnings("rawtypes")
         final Map labelMap = holder.getLocalizedLabelMap( resourceBundleManager, locale, this.defaultCategory );
-        final Object flatLabelMap = labelMap.remove( WebConsoleConstants.ATTR_LABEL_MAP );
+        final Object flatLabelMap = labelMap.remove( PluginHolder.ATTR_FLAT_LABEL_MAP );
 
         // the official request attributes
         request.setAttribute(WebConsoleConstants.ATTR_LANG_MAP, getLangMap());
         request.setAttribute(WebConsoleConstants.ATTR_LABEL_MAP, flatLabelMap);
         request.setAttribute( ATTR_LABEL_MAP_CATEGORIZED, labelMap );
-        request.setAttribute(WebConsoleConstants.ATTR_APP_ROOT,
-            request.getContextPath() + request.getServletPath());
-        request.setAttribute(WebConsoleConstants.ATTR_PLUGIN_ROOT,
-            request.getContextPath() + request.getServletPath() + '/' + label);
-        request.setAttribute(WebConsoleConstants.ATTR_CONFIGURATION, configuration);
+        final String appRoot = request.getContextPath().concat(request.getServletPath());
+        request.setAttribute(ServletConstants.ATTR_APP_ROOT, appRoot);
+        request.setAttribute(ServletConstants.ATTR_PLUGIN_ROOT, appRoot.concat("/").concat(label));
+        request.setAttribute(ServletConstants.ATTR_CONFIGURATION, configuration);
 
         // deprecated request attributes
         request.setAttribute(ATTR_LABEL_MAP_OLD, flatLabelMap);
-        request.setAttribute(ATTR_APP_ROOT_OLD,
-            request.getContextPath() + request.getServletPath());
+        request.setAttribute(ATTR_APP_ROOT_OLD, appRoot);
 
         // fix for https://issues.apache.org/jira/browse/FELIX-3408
         ensureLocaleCookieSet(request, response, locale);
@@ -637,16 +621,17 @@ public class OsgiManager extends GenericServlet {
     private void initRequestVariableResolver(final HttpServletRequest request) {
         final RequestVariableResolver resolver = new RequestVariableResolver();
         request.setAttribute(RequestVariableResolver.REQUEST_ATTRIBUTE, resolver);
-        resolver.put( "appRoot", (String) request.getAttribute( WebConsoleConstants.ATTR_APP_ROOT ) );
-        resolver.put( "pluginRoot", (String) request.getAttribute( WebConsoleConstants.ATTR_PLUGIN_ROOT ) );
+        resolver.put( RequestVariableResolver.KEY_APP_ROOT, (String) request.getAttribute( ServletConstants.ATTR_APP_ROOT ) );
+        resolver.put( RequestVariableResolver.KEY_PLUGIN_ROOT, (String) request.getAttribute( ServletConstants.ATTR_PLUGIN_ROOT ) );
     }
 
     private final void logout(HttpServletRequest request, HttpServletResponse response)
         throws IOException {
-        final Object securityProvider = securityProviderTracker.getService();
-        if (securityProvider instanceof WebConsoleSecurityProvider3) {
-            ((WebConsoleSecurityProvider3) securityProvider).logout(request, response);
-        } else {
+        final SecurityProvider securityProvider = securityProviderTracker.getService();
+        if (securityProvider != null) {
+            securityProvider.logout(request, response);
+        }
+        if (!response.isCommitted()) {
             // check if special logout cookie is set, this is used to prevent
             // from an endless loop with basic auth
             final Cookie[] cookies = request.getCookies();
@@ -687,7 +672,6 @@ public class OsgiManager extends GenericServlet {
         // clean-up
         request.removeAttribute(ServletContextHelper.REMOTE_USER);
         request.removeAttribute(ServletContextHelper.AUTHORIZATION);
-        request.removeAttribute(WebConsoleSecurityProvider2.USER_ATTRIBUTE);
         request.removeAttribute(User.USER_ATTRIBUTE);
     }
 
@@ -695,7 +679,7 @@ public class OsgiManager extends GenericServlet {
     {
         // backwards compatibility for the former "install" action which is
         // used by the Maven Sling Plugin
-        if ("install".equals(label)) //$NON-NLS-1$
+        if ("install".equals(label))
         {
             return holder.getPlugin(BundlesServlet.NAME);
         }
@@ -912,7 +896,7 @@ public class OsgiManager extends GenericServlet {
                 final Dictionary<String, Object> serviceProperties = new Hashtable<>(); // NOSONAR
                 // this is a last resort service, so use a low service ranking to prefer all other services over this one
                 serviceProperties.put(Constants.SERVICE_RANKING, Integer.MIN_VALUE);
-                this.basicSecurityServiceRegistration = bundleContext.registerService(WebConsoleSecurityProvider.class,
+                this.basicSecurityServiceRegistration = bundleContext.registerService(SecurityProvider.class,
                         service, serviceProperties);
             }
 
@@ -1016,8 +1000,8 @@ public class OsgiManager extends GenericServlet {
 
         // get the web manager root path
         String newWebManagerRoot = ConfigurationUtil.getProperty(config, PROP_MANAGER_ROOT, DEFAULT_MANAGER_ROOT);
-        if (!newWebManagerRoot.startsWith("/")) { //$NON-NLS-1$
-            newWebManagerRoot = "/".concat(newWebManagerRoot); //$NON-NLS-1$
+        if (!newWebManagerRoot.startsWith("/")) {
+            newWebManagerRoot = "/".concat(newWebManagerRoot);
         }
 
         // default category
@@ -1104,7 +1088,7 @@ public class OsgiManager extends GenericServlet {
             return langMap;
         final Map<String, String> map = new HashMap<>();
         final Bundle bundle = bundleContext.getBundle();
-        final Enumeration<URL> e = bundle.findEntries("res/flags", null, false); //$NON-NLS-1$
+        final Enumeration<URL> e = bundle.findEntries("res/flags", null, false);
         while (e != null && e.hasMoreElements())
         {
             final URL img = e.nextElement();
@@ -1113,7 +1097,7 @@ public class OsgiManager extends GenericServlet {
                 final int lastSlash = path.lastIndexOf('/');
                 final int dot = path.indexOf('.', lastSlash);
                 final String name = (dot == -1 ? path.substring(lastSlash+1) : path.substring(lastSlash + 1, dot));
-                final String locale = new Locale(name, "").getDisplayLanguage(); //$NON-NLS-1$
+                final String locale = new Locale(name, "").getDisplayLanguage();
                 map.put(name, null != locale ? locale : name);
             }
             catch (Throwable t) {
@@ -1123,13 +1107,13 @@ public class OsgiManager extends GenericServlet {
         return langMap = map;
     }
 
-    class UpdateDependenciesStateCustomizer implements ServiceTrackerCustomizer<WebConsoleSecurityProvider, WebConsoleSecurityProvider> {
+    class UpdateDependenciesStateCustomizer implements ServiceTrackerCustomizer<SecurityProvider, SecurityProvider> {
 
         private final Map<Long, String> registeredProviders = new ConcurrentHashMap<>();
 
         @Override
-        public WebConsoleSecurityProvider addingService(ServiceReference<WebConsoleSecurityProvider> reference) {
-            final WebConsoleSecurityProvider provider = bundleContext.getService(reference);
+        public SecurityProvider addingService(ServiceReference<SecurityProvider> reference) {
+            final SecurityProvider provider = bundleContext.getService(reference);
             if (provider != null) {
                 final Object nameObj = reference.getProperty(SECURITY_PROVIDER_PROPERTY_NAME);
                 if (nameObj instanceof String) {
@@ -1144,13 +1128,13 @@ public class OsgiManager extends GenericServlet {
         }
 
         @Override
-        public void modifiedService(ServiceReference<WebConsoleSecurityProvider> reference, WebConsoleSecurityProvider service) {
+        public void modifiedService(ServiceReference<SecurityProvider> reference, SecurityProvider service) {
             removedService(reference, service);
             addingService(reference);
         }
 
         @Override
-        public void removedService(ServiceReference<WebConsoleSecurityProvider> reference, WebConsoleSecurityProvider service) {
+        public void removedService(ServiceReference<SecurityProvider> reference, SecurityProvider service) {
             final String name = registeredProviders.remove(reference.getProperty(Constants.SERVICE_ID));
             if (name != null) {
                 registeredSecurityProviders.remove(name);
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerHttpContext.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerHttpContext.java
index 913d67d346..27d0f69121 100755
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerHttpContext.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerHttpContext.java
@@ -17,16 +17,10 @@
 package org.apache.felix.webconsole.internal.servlet;
 
 
-import static org.apache.felix.webconsole.internal.servlet.BasicWebConsoleSecurityProvider.AUTHENTICATION_SCHEME_BASIC;
-import static org.apache.felix.webconsole.internal.servlet.BasicWebConsoleSecurityProvider.HEADER_AUTHORIZATION;
-import static org.apache.felix.webconsole.internal.servlet.BasicWebConsoleSecurityProvider.HEADER_WWW_AUTHENTICATE;
-
-import java.io.IOException;
 import java.net.URL;
 
-import org.apache.felix.webconsole.User;
-import org.apache.felix.webconsole.WebConsoleSecurityProvider;
-import org.apache.felix.webconsole.WebConsoleSecurityProvider2;
+import org.apache.felix.webconsole.servlet.User;
+import org.apache.felix.webconsole.spi.SecurityProvider;
 import org.osgi.framework.Bundle;
 import org.osgi.service.servlet.context.ServletContextHelper;
 import org.osgi.util.tracker.ServiceTracker;
@@ -37,18 +31,15 @@ import jakarta.servlet.http.HttpServletResponse;
 
 final class OsgiManagerHttpContext extends ServletContextHelper
 {
-    private final ServiceTracker<WebConsoleSecurityProvider, WebConsoleSecurityProvider> tracker;
-
-    private final String realm;
+    private final ServiceTracker<SecurityProvider, SecurityProvider> tracker;
 
     private final Bundle bundle;
 
     OsgiManagerHttpContext(final Bundle webConsoleBundle,
-            final ServiceTracker<WebConsoleSecurityProvider, WebConsoleSecurityProvider> tracker,
+            final ServiceTracker<SecurityProvider, SecurityProvider> tracker,
             final String realm) {
         super(webConsoleBundle);
         this.tracker = tracker;
-        this.realm = realm;
         this.bundle = webConsoleBundle;
     }
 
@@ -62,7 +53,7 @@ final class OsgiManagerHttpContext extends ServletContextHelper
 
     @Override
     public boolean handleSecurity( final HttpServletRequest r, final HttpServletResponse response ) {
-        final WebConsoleSecurityProvider provider = tracker.getService();
+        final SecurityProvider provider = tracker.getService();
 
         // for compatibility we have to adjust a few methods on the request
         final HttpServletRequest request = new HttpServletRequestWrapper(r) {
@@ -84,14 +75,9 @@ final class OsgiManagerHttpContext extends ServletContextHelper
         };
 
         // check whether the security provider can fully handle the request
-        final boolean result;
-        if ( provider instanceof WebConsoleSecurityProvider2 ) {
-            result = ( ( WebConsoleSecurityProvider2 ) provider ).authenticate( request, response );
-        } else {
-            result = handleSecurity(provider, request, response);
-        }
+        final Object result = provider == null ? null : provider.authenticate( request, response );
 
-        if ( result ) {
+        if ( result != null) {
             request.setAttribute(User.USER_ATTRIBUTE, new User(){
 
 				@Override
@@ -101,6 +87,7 @@ final class OsgiManagerHttpContext extends ServletContextHelper
                         // no user object in request, deny
                         return false;
                     }
+                    final SecurityProvider provider = tracker.getService();
 					if ( provider == null ) {
                         // no provider, allow (compatibility)
                         return true;
@@ -110,84 +97,10 @@ final class OsgiManagerHttpContext extends ServletContextHelper
 
 				@Override
 				public Object getUserObject() {
-					return request.getAttribute(WebConsoleSecurityProvider2.USER_ATTRIBUTE);
+					return result;
 				}
-                
             });
         }
-        return result;
-    }
-
-    /**
-     * Handle security with an optional web console security provider
-     */
-    private boolean handleSecurity( final WebConsoleSecurityProvider provider, 
-        final HttpServletRequest request,
-        final HttpServletResponse response) {
-        // Return immediately if the header is missing
-        String authHeader = request.getHeader( HEADER_AUTHORIZATION );
-        if ( authHeader != null && authHeader.length() > 0 )
-        {
-
-            // Get the authType (Basic, Digest) and authInfo (user/password)
-            // from
-            // the header
-            authHeader = authHeader.trim();
-            int blank = authHeader.indexOf( ' ' );
-            if ( blank > 0 )
-            {
-                String authType = authHeader.substring( 0, blank );
-                String authInfo = authHeader.substring( blank ).trim();
-
-                // Check whether authorization type matches
-                if ( authType.equalsIgnoreCase( AUTHENTICATION_SCHEME_BASIC ) )
-                {
-                    try
-                    {
-                        byte[][] userPass = BasicWebConsoleSecurityProvider.base64Decode( authInfo );
-                        final String username = BasicWebConsoleSecurityProvider.toString( userPass[0] );
-
-                        // authenticate
-                        if ( authenticate( provider, username, userPass[1] ) )
-                        {
-                            // as per the spec, set attributes
-                            request.setAttribute( AUTHENTICATION_TYPE, HttpServletRequest.BASIC_AUTH );
-                            request.setAttribute( REMOTE_USER, username );
-
-                            // set web console user attribute
-                            request.setAttribute( WebConsoleSecurityProvider2.USER_ATTRIBUTE, username );
-
-                            // succeed
-                            return true;
-                        }
-                    }
-                    catch ( Exception e )
-                    {
-                        // Ignore
-                    }
-                }
-            }
-        }
-
-        // request authentication
-        try {
-            response.setHeader( HEADER_WWW_AUTHENTICATE, AUTHENTICATION_SCHEME_BASIC + " realm=\"" + this.realm + "\"" );
-            response.setStatus( HttpServletResponse.SC_UNAUTHORIZED );
-            response.setContentLength( 0 );
-            response.flushBuffer();
-        } catch ( IOException ioe ) {
-            // failed sending the response ... cannot do anything about it
-        }
-
-        // inform HttpService that authentication failed
-        return false;
-    }
-
-    private boolean authenticate( WebConsoleSecurityProvider provider, String username, byte[] password ) {
-        if ( provider != null )
-        {
-            return provider.authenticate( username, BasicWebConsoleSecurityProvider.toString( password ) ) != null;
-        }
-        return false;
+        return result != null;
     }
 }
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/Plugin.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/Plugin.java
index 815c557b28..db6ef4424d 100755
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/Plugin.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/Plugin.java
@@ -199,13 +199,10 @@ public abstract class Plugin implements ServletConfig, Comparable<Plugin> {
         }
 
         protected AbstractWebConsolePlugin doGetConsolePlugin() {
-            Servlet service = getService();
+            final Servlet service = getService();
             if ( service != null ) {
                 this.title = Util.getStringProperty( this.getServiceReference(), WebConsoleConstants.PLUGIN_TITLE );
                 this.category = Util.getStringProperty( this.getServiceReference(), WebConsoleConstants.PLUGIN_CATEGORY );
-                if ( service instanceof AbstractServlet ) {
-                    service = new JakartaServletAdapter((AbstractServlet)service, this.getServiceReference());
-                }
                 final AbstractWebConsolePlugin servlet;
                 if ( service instanceof AbstractWebConsolePlugin ) {
                     servlet = ( AbstractWebConsolePlugin ) service;
@@ -215,6 +212,8 @@ public abstract class Plugin implements ServletConfig, Comparable<Plugin> {
                     if (this.category == null) {
                         this.category = servlet.getCategory();
                     }
+                } else if ( service instanceof AbstractServlet ) {
+                    servlet = new JakartaServletAdapter((AbstractServlet)service, this.getServiceReference());
                 } else {
                     servlet = new WebConsolePluginAdapter( getLabel(), service, this.getServiceReference() );
                 }
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/PluginHolder.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/PluginHolder.java
index 0e22c614f1..0a82e0299b 100755
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/PluginHolder.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/PluginHolder.java
@@ -50,6 +50,8 @@ import jakarta.servlet.ServletContext;
  */
 class PluginHolder implements ServiceTrackerCustomizer<Servlet, Plugin> {
 
+    public static final String ATTR_FLAT_LABEL_MAP = PluginHolder.class.getName() + ".flatLabelMap";
+
     private final OsgiManager osgiManager;
 
     // The Web Console's bundle context to access the plugin services
@@ -245,7 +247,7 @@ class PluginHolder implements ServiceTrackerCustomizer<Servlet, Plugin> {
         }
 
         // flat map of labels to titles (FELIX-3833)
-        map.put( WebConsoleConstants.ATTR_LABEL_MAP, flatMap );
+        map.put( ATTR_FLAT_LABEL_MAP, flatMap );
 
         return map;
     }
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/system/VMStatPlugin.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/system/VMStatPlugin.java
index 25929bfd0a..3770cf0a7b 100755
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/system/VMStatPlugin.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/system/VMStatPlugin.java
@@ -39,34 +39,29 @@ import org.apache.felix.webconsole.internal.servlet.WebConsoleUtil;
 import org.apache.felix.webconsole.servlet.RequestVariableResolver;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleException;
-import org.osgi.service.startlevel.StartLevel;
-
+import org.osgi.framework.Constants;
+import org.osgi.framework.startlevel.FrameworkStartLevel;
 
 /**
  * VMStatPlugin provides the System Information tab. This particular plugin uses
  * more than one templates.
  */
-public class VMStatPlugin extends SimpleWebConsolePlugin implements OsgiManagerPlugin
-{
+public class VMStatPlugin extends SimpleWebConsolePlugin implements OsgiManagerPlugin {
 
     private static final long serialVersionUID = 2293375003997163600L;
 
-    private static final String LABEL = "vmstat"; //$NON-NLS-1$
-    private static final String TITLE = "%vmstat.pluginTitle"; //$NON-NLS-1$
+    private static final String LABEL = "vmstat";
+    private static final String TITLE = "%vmstat.pluginTitle";
     private static final String CSS[] = null;
 
-    private static final String ATTR_TERMINATED = "terminated"; //$NON-NLS-1$
+    private static final String ATTR_TERMINATED = "terminated";
 
-    private static final String PARAM_SHUTDOWN_TIMER = "shutdown_timer"; //$NON-NLS-1$
-    private static final String PARAM_SHUTDOWN_TYPE = "shutdown_type"; //$NON-NLS-1$
-    private static final String PARAM_SHUTDOWN_TYPE_RESTART = "Restart"; //$NON-NLS-1$
-    //private static final String PARAM_SHUTDOWN_TYPE_STOP = "Stop";
+    private static final String PARAM_SHUTDOWN_TIMER = "shutdown_timer";
+    private static final String PARAM_SHUTDOWN_TYPE = "shutdown_type";
+    private static final String PARAM_SHUTDOWN_TYPE_RESTART = "Restart";
 
     private static final long startDate = System.currentTimeMillis();
 
-    // from BaseWebConsolePlugin
-    private static String START_LEVEL_NAME = StartLevel.class.getName();
-
     // templates
     private final String TPL_VM_MAIN;
     private final String TPL_VM_STOP;
@@ -74,85 +69,63 @@ public class VMStatPlugin extends SimpleWebConsolePlugin implements OsgiManagerP
 
 
     /** Default constructor */
-    public VMStatPlugin()
-    {
+    public VMStatPlugin() {
         super( LABEL, TITLE, CATEGORY_OSGI_MANAGER, CSS );
 
         // load templates
-        TPL_VM_MAIN = readTemplateFile(  "/templates/vmstat.html"  ); //$NON-NLS-1$
-        TPL_VM_STOP = readTemplateFile( "/templates/vmstat_stop.html" ); //$NON-NLS-1$
-        TPL_VM_RESTART = readTemplateFile( "/templates/vmstat_restart.html" ); //$NON-NLS-1$
+        TPL_VM_MAIN = readTemplateFile(  "/templates/vmstat.html"  );
+        TPL_VM_STOP = readTemplateFile( "/templates/vmstat_stop.html" );
+        TPL_VM_RESTART = readTemplateFile( "/templates/vmstat_restart.html" );
     }
 
-
     /**
      * @see jakarta.servlet.http.HttpServlet#doPost(jakarta.servlet.http.HttpServletRequest, jakarta.servlet.http.HttpServletResponse)
      */
-    protected void doPost( HttpServletRequest request, HttpServletResponse response ) throws ServletException,
-    IOException
-    {
-        final String action = request.getParameter( "action"); //$NON-NLS-1$
-
-        if ( "setStartLevel".equals( action )) //$NON-NLS-1$
-        {
-            StartLevel sl = getStartLevel();
-            if ( sl != null )
-            {
+    protected void doPost( HttpServletRequest request, HttpServletResponse response )
+    throws ServletException, IOException {
+        final String action = request.getParameter( "action");
+
+        if ( "setStartLevel".equals( action )) {
+            final FrameworkStartLevel fsl = this.getBundleContext().getBundle(Constants.SYSTEM_BUNDLE_LOCATION).adapt(FrameworkStartLevel.class);
+            if ( fsl != null ){
                 int bundleSL = WebConsoleUtil.getParameterInt( request, "bundleStartLevel", -1 );
-                if ( bundleSL > 0 && bundleSL != sl.getInitialBundleStartLevel() )
-                {
-                    sl.setInitialBundleStartLevel( bundleSL );
+                if ( bundleSL > 0 && bundleSL != fsl.getInitialBundleStartLevel() ) {
+                    fsl.setInitialBundleStartLevel( bundleSL );
                 }
 
                 int systemSL = WebConsoleUtil.getParameterInt( request, "systemStartLevel", -1 );
-                if ( systemSL > 0 && systemSL != sl.getStartLevel() )
-                {
-                    sl.setStartLevel( systemSL );
+                if ( systemSL > 0 && systemSL != fsl.getStartLevel() ) {
+                    fsl.setStartLevel( systemSL );
                 }
             }
-        }
-        else if ( "gc".equals( action ) ) //$NON-NLS-1$
-        {
+        } else if ( "gc".equals( action ) )  {
             System.gc();
             System.gc(); // twice for sure
-        }
-        else if ( request.getParameter( PARAM_SHUTDOWN_TIMER ) == null )
-        {
+        } else if ( request.getParameter( PARAM_SHUTDOWN_TIMER ) == null ) {
 
             // whether to stop or restart the framework
             final boolean restart = PARAM_SHUTDOWN_TYPE_RESTART.equals( request.getParameter( PARAM_SHUTDOWN_TYPE ) );
 
             // simply terminate VM in case of shutdown :-)
             final Bundle systemBundle = getBundleContext().getBundle( 0 );
-            Thread t = new Thread( "Stopper" )
-            {
-                public void run()
-                {
-                    try
-                    {
+            Thread t = new Thread( "Stopper" ) {
+                public void run() {
+                    try {
                         Thread.sleep( 2000L );
-                    }
-                    catch ( InterruptedException ie )
-                    {
+                    } catch ( InterruptedException ie ) {
                         // ignore
                     }
 
                     log( "Shutting down server now!" );
 
                     // stopping bundle 0 (system bundle) stops the framework
-                    try
-                    {
-                        if ( restart )
-                        {
+                    try {
+                        if ( restart ) {
                             systemBundle.update();
-                        }
-                        else
-                        {
+                        } else {
                             systemBundle.stop();
                         }
-                    }
-                    catch ( BundleException be )
-                    {
+                    } catch ( BundleException be ) {
                         log( "Problem stopping or restarting the Framework", be );
                     }
                 }
@@ -160,27 +133,24 @@ public class VMStatPlugin extends SimpleWebConsolePlugin implements OsgiManagerP
             t.start();
 
             request.setAttribute( ATTR_TERMINATED, ATTR_TERMINATED );
-            request.setAttribute( PARAM_SHUTDOWN_TYPE, new Boolean( restart ) );
+            request.setAttribute( PARAM_SHUTDOWN_TYPE, restart );
         }
 
         // render the response without redirecting
         doGet( request, response );
     }
 
+    @Override
+    @SuppressWarnings("unchecked")
+    protected void renderContent( HttpServletRequest request, HttpServletResponse response ) throws IOException {
+        final FrameworkStartLevel fsl = this.getBundleContext().getBundle(Constants.SYSTEM_BUNDLE_LOCATION).adapt(FrameworkStartLevel.class);
 
-    /**
-     * @see org.apache.felix.webconsole.internal.servlet.AbstractWebConsolePlugin#renderContent(jakarta.servlet.http.HttpServletRequest, jakarta.servlet.http.HttpServletResponse)
-     */
-    protected void renderContent( HttpServletRequest request, HttpServletResponse response ) throws IOException
-    {
         Map<String, Object> configuration = (Map<String, Object>) request.getAttribute( WebConsoleConstants.ATTR_CONFIGURATION );
         String body;
 
-        if ( request.getAttribute( ATTR_TERMINATED ) != null )
-        {
+        if ( request.getAttribute( ATTR_TERMINATED ) != null ) {
             Object restart = request.getAttribute( PARAM_SHUTDOWN_TYPE );
-            if ( ( restart instanceof Boolean ) && ( ( Boolean ) restart ).booleanValue() )
-            {
+            if ( ( restart instanceof Boolean ) && ( ( Boolean ) restart ).booleanValue() ) {
                 StringWriter json = new StringWriter();
 
                 int reloadTimeout = (int) configuration.get( OsgiManager.PROP_RELOAD_TIMEOUT );
@@ -190,13 +160,11 @@ public class VMStatPlugin extends SimpleWebConsolePlugin implements OsgiManagerP
                 jw.endObject();
                 jw.flush();
 
-                final RequestVariableResolver vars = WebConsoleUtil.getRequestVariableResolver(request);
+                final RequestVariableResolver vars = this.getVariableResolver(request);
                 vars.put( "data", json.toString() );
 
                 body = TPL_VM_RESTART;
-            }
-            else
-            {
+            } else {
                 body = TPL_VM_STOP;
             }
             response.getWriter().print( body );
@@ -211,8 +179,9 @@ public class VMStatPlugin extends SimpleWebConsolePlugin implements OsgiManagerP
 
         boolean shutdownTimer = request.getParameter( PARAM_SHUTDOWN_TIMER ) != null;
         String shutdownType = request.getParameter( PARAM_SHUTDOWN_TYPE );
-        if ( shutdownType == null )
+        if ( shutdownType == null ) {
             shutdownType = "";
+        }
 
         DateFormat format = DateFormat.getDateTimeInstance( DateFormat.LONG, DateFormat.LONG, request.getLocale() );
         final String startTime = format.format( new Date( startDate ) );
@@ -222,8 +191,8 @@ public class VMStatPlugin extends SimpleWebConsolePlugin implements OsgiManagerP
         JSONWriter jw = new JSONWriter(json);
         jw.object();
 
-        jw.key( "systemStartLevel").value(getStartLevel().getStartLevel() );
-        jw.key( "bundleStartLevel").value(getStartLevel().getInitialBundleStartLevel() );
+        jw.key( "systemStartLevel").value(fsl.getStartLevel() );
+        jw.key( "bundleStartLevel").value(fsl.getInitialBundleStartLevel() );
         jw.key( "lastStarted").value(startTime );
         jw.key( "upTime").value(upTime );
         jw.key( "runtime").value(sysProp( "java.runtime.name" ) + "(build "
@@ -241,8 +210,7 @@ public class VMStatPlugin extends SimpleWebConsolePlugin implements OsgiManagerP
 
         // only add the processors if the number is available
         final int processors = getAvailableProcessors();
-        if ( processors > 0 )
-        {
+        if ( processors > 0 ) {
             jw.key( "processors").value(processors );
         }
 
@@ -250,58 +218,38 @@ public class VMStatPlugin extends SimpleWebConsolePlugin implements OsgiManagerP
 
         jw.flush();
 
-        final RequestVariableResolver vars = WebConsoleUtil.getRequestVariableResolver(request);
+        final RequestVariableResolver vars = this.getVariableResolver(request);
         vars.put( "startData", json.toString() );
 
         response.getWriter().print( body );
     }
 
-    private static final String sysProp( String name )
-    {
+    private static final String sysProp( String name ) {
         String ret = System.getProperty( name );
         if ( null == ret || ret.length() == 0 ) {
-            ret = "n/a"; //$NON-NLS-1$
+            ret = "n/a";
         }
         return ret;
     }
 
-
-    private static final String formatPeriod( final long period )
-    {
-        final Long msecs = new Long( period % 1000 );
-        final Long secs = new Long( period / 1000 % 60 );
-        final Long mins = new Long( period / 1000 / 60 % 60 );
-        final Long hours = new Long( period / 1000 / 60 / 60 % 24 );
-        final Long days = new Long( period / 1000 / 60 / 60 / 24 );
+    private static final String formatPeriod( final long period ) {
+        final long msecs = period % 1000;
+        final long secs = period / 1000 % 60;
+        final long mins = period / 1000 / 60 % 60;
+        final long hours = period / 1000 / 60 / 60 % 24;
+        final long days = period / 1000 / 60 / 60 / 24;
         return MessageFormat.format(
                 "{0,number} '${vmstat.upTime.format.days}' {1,number,00}:{2,number,00}:{3,number,00}.{4,number,000}",
                 new Object[]
                         { days, hours, mins, secs, msecs } );
     }
 
-
-    private final StartLevel getStartLevel()
-    {
-        return ( StartLevel ) getService( START_LEVEL_NAME );
-    }
-
-
     /**
      * Returns the number of processor available on Java 1.4 and newer runtimes.
      * If the Runtime.availableProcessors() method is not available, this
      * method returns -1.
      */
-    private static final int getAvailableProcessors()
-    {
-        try
-        {
-            return Runtime.getRuntime().availableProcessors();
-        }
-        catch ( Throwable t )
-        {
-            // NoSuchMethodError on pre-1.4 runtimes
-        }
-
-        return -1;
+    private static final int getAvailableProcessors() {
+        return Runtime.getRuntime().availableProcessors();
     }
 }
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/servlet/AbstractServlet.java b/webconsole/src/main/java/org/apache/felix/webconsole/servlet/AbstractServlet.java
index 031ad59cdc..bd7ff0d1d5 100755
--- a/webconsole/src/main/java/org/apache/felix/webconsole/servlet/AbstractServlet.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/servlet/AbstractServlet.java
@@ -38,10 +38,22 @@ import jakarta.servlet.http.HttpServletResponse;
  * This class can be used as a base class for a web console plugin.
  * The plugin (servlet) needs to be registered as a servlet service
  * with at least the label property.
+ * <p>
+ * Extending this servlet is one way of providing a plugin. The other
+ * is to simply direclty implement the servlet interface. In both cases
+ * requests to resources with a path of "LABEL/res/*" are automatically
+ * handled.
+ * <p>
+ * For html (content) requests, the web console automatically draws the header,
+ * footer and navigation.
+ * <p>
+ * Support for Jakarta servlets requires that the Jakarta Servlet API and the
+ * Apache Felix Http Wrappers are available in the runtime.
  *
  * @see ServletConstants#PLUGIN_LABEL
  * @see ServletConstants#PLUGIN_TITLE
  * @see ServletConstants#PLUGIN_CATEGORY
+ * @see ServletConstants#PLUGIN_CSS_REFERENCES
  */
 public abstract class AbstractServlet extends HttpServlet {
 
@@ -198,4 +210,23 @@ public abstract class AbstractServlet extends HttpServlet {
     protected RequestVariableResolver getVariableResolver(final HttpServletRequest request) {
         return (RequestVariableResolver) request.getAttribute(RequestVariableResolver.REQUEST_ATTRIBUTE);
     }
+
+    /**
+     * Sets response headers to force the client to not cache the response
+     * sent back. This method must be called before the response is committed
+     * otherwise it will have no effect.
+     * <p>
+     * This method sets the <code>Cache-Control</code>, <code>Expires</code>,
+     * and <code>Pragma</code> headers.
+     *
+     * @param response The response for which to set the cache prevention
+     */
+    public static final void setNoCache(final HttpServletResponse response) {
+        response.setHeader("Cache-Control", "no-cache");
+        response.addHeader("Cache-Control", "no-store");
+        response.addHeader("Cache-Control", "must-revalidate");
+        response.addHeader("Cache-Control", "max-age=0");
+        response.setHeader("Expires", "Thu, 01 Jan 1970 01:00:00 GMT");
+        response.setHeader("Pragma", "no-cache");
+    }
 }
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/servlet/RequestVariableResolver.java b/webconsole/src/main/java/org/apache/felix/webconsole/servlet/RequestVariableResolver.java
index 4efdc3aacd..e80dd8fb75 100755
--- a/webconsole/src/main/java/org/apache/felix/webconsole/servlet/RequestVariableResolver.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/servlet/RequestVariableResolver.java
@@ -31,11 +31,24 @@ public class RequestVariableResolver extends HashMap<String, Object> {
     /**
      * The name of the request attribute holding the {@link RequestVariableResolver}
      * for the request (value is "felix.webconsole.variable.resolver").
-     *
-     * @since 3.0
+     * This attribute is guaaranteed to be set for plugins.
      */
     public static final String REQUEST_ATTRIBUTE = "felix.webconsole.variable.resolver";
 
+    /**
+     * The name of the key providing the absolute path of the Web Console root.
+     * This key is guaaranteed to be set for plugins.
+     * @see ServletConstants.ATTR_APP_ROOT
+     */
+    public static final String KEY_APP_ROOT = "appRoot";
+
+    /**
+     * The name of the key providing the absolute path of the current plugin.
+     * This key is guaaranteed to be set for plugins.
+     * @see ServletConstants.ATTR_PLUGIN_ROOT
+     */
+    public static final String KEY_PLUGIN_ROOT = "pluginRoot";
+
     /**
      * Creates a new variable resolver with default capacity.
      */
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/servlet/ServletConstants.java b/webconsole/src/main/java/org/apache/felix/webconsole/servlet/ServletConstants.java
index 7b5f2f1431..4a10889274 100755
--- a/webconsole/src/main/java/org/apache/felix/webconsole/servlet/ServletConstants.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/servlet/ServletConstants.java
@@ -72,4 +72,26 @@ public abstract class ServletConstants {
      * The type of this request attribute is <code>String</code>.
      */
     public static final String ATTR_APP_ROOT = "felix.webconsole.appRoot";
+
+    /**
+     * The name of the request attribute providing the absolute path of the
+     * current plugin (value is "felix.webconsole.pluginRoot"). This consists of
+     * the servlet context path (from <code>ServletRequest.getContextPath()</code>),
+     * the configured path of the web console root (<code>/system/console</code>
+     * by default) and the plugin label {@link #PLUGIN_LABEL}.
+     * <p>
+     * The type of this request attribute is <code>String</code>.
+     */
+    public static final String ATTR_PLUGIN_ROOT = "felix.webconsole.pluginRoot";
+
+    /**
+     * The name of the request attribute holding the configuration params {@link java.util.Map}
+     * for the request (value is "felix.webconsole.configuration").
+     * <p>
+     * The type of this request attribute is <code>Map&lt;String, Object&gt;</code>.
+     * <p>
+     * This map contains the web console configuration params managed by the web console.
+     * It can be used to access to the configuration values while processing requests.
+     */
+    public static final String ATTR_CONFIGURATION = "felix.webconsole.configuration";
 }
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/User.java b/webconsole/src/main/java/org/apache/felix/webconsole/servlet/User.java
similarity index 97%
rename from webconsole/src/main/java/org/apache/felix/webconsole/User.java
rename to webconsole/src/main/java/org/apache/felix/webconsole/servlet/User.java
index 0e56790943..cb7240a517 100755
--- a/webconsole/src/main/java/org/apache/felix/webconsole/User.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/servlet/User.java
@@ -16,14 +16,13 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.felix.webconsole;
+package org.apache.felix.webconsole.servlet;
 
 import org.osgi.annotation.versioning.ProviderType;
 
 /**
  * Representation of a user.
  * The user object can be used by plugins to {@link #authorize(String)} the user.
- * @since 3.4.0
  */
 @ProviderType
 public interface User {
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/BrandingPlugin.java b/webconsole/src/main/java/org/apache/felix/webconsole/spi/BrandingPlugin.java
similarity index 94%
rename from webconsole/src/main/java/org/apache/felix/webconsole/BrandingPlugin.java
rename to webconsole/src/main/java/org/apache/felix/webconsole/spi/BrandingPlugin.java
index d28edbdbf1..e73ff420b8 100755
--- a/webconsole/src/main/java/org/apache/felix/webconsole/BrandingPlugin.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/spi/BrandingPlugin.java
@@ -16,17 +16,19 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.felix.webconsole;
+package org.apache.felix.webconsole.spi;
 
+import org.osgi.annotation.versioning.ConsumerType;
 
 /**
  * The <code>BrandingPlugin</code> is the service interface for the most
  * elaborate way of branding the web console.
  *
- * @see DefaultBrandingPlugin
+ * @since 1.2.0
  */
-public interface BrandingPlugin
-{
+@ConsumerType
+public interface BrandingPlugin {
+
     /**
      * Returns an indicative name of the branding plugin. This value is used
      * as the Window/Page title together with the title of the respective
@@ -36,7 +38,6 @@ public interface BrandingPlugin
      */
     String getBrandName();
 
-
     /**
      * Returns the name of the product in which the web console is contained
      * and to which the web console is branded.
@@ -45,7 +46,6 @@ public interface BrandingPlugin
      */
     String getProductName();
 
-
     /**
      * Returns an (absolute) URL to a web site representing the product to
      * which the web console is branded.
@@ -54,7 +54,6 @@ public interface BrandingPlugin
      */
     String getProductURL();
 
-
     /**
      * Returns an absolute path to an image to be rendered as the logo of the
      * branding product.
@@ -63,7 +62,6 @@ public interface BrandingPlugin
      */
     String getProductImage();
 
-
     /**
      * Returns the name of the branding product vendor.
      *
@@ -71,7 +69,6 @@ public interface BrandingPlugin
      */
     String getVendorName();
 
-
     /**
      * Returns an (absolute) URL to the web site of the branding product
      * vendor.
@@ -80,7 +77,6 @@ public interface BrandingPlugin
      */
     String getVendorURL();
 
-
     /**
      * Returns an absolute path to an image to be rendered as the logo of the
      * branding product vendor.
@@ -89,7 +85,6 @@ public interface BrandingPlugin
      */
     String getVendorImage();
 
-
     /**
      * Returns the absolute path to an icon to be used as the web console
      * "favicon".
@@ -98,7 +93,6 @@ public interface BrandingPlugin
      */
     String getFavIcon();
 
-
     /**
      * Returns the absolute path to a CSS file to be used as the main CSS for
      * the basic admin site.
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/spi/SecurityProvider.java b/webconsole/src/main/java/org/apache/felix/webconsole/spi/SecurityProvider.java
new file mode 100755
index 0000000000..2546aae0ea
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/spi/SecurityProvider.java
@@ -0,0 +1,84 @@
+/*
+ * 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.felix.webconsole.spi;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import org.osgi.annotation.versioning.ConsumerType;
+
+/**
+ * The <code>SecurityProvider</code> is a service interface allowing
+ * to use an external system to authenticate users before granting access to the
+ * Web Console.
+ * <p>
+ * Support for Jakarta servlets requires that the Jakarta Servlet API and the
+ * Apache Felix Http Wrappers are available in the runtime.
+ *
+ * @since 1.2.0
+ */
+@ConsumerType
+public interface SecurityProvider {
+
+    /**
+     * Checks whether the authenticated user has the given role permission.
+     *
+     * @param user The object referring to the authenticated user. This is the
+     *      object returned from the {@link #authenticate(HttpServletRequest, HttpServletResponse)}
+     *      method and will never be <code>null</code>.
+     * @param role The requested role
+     * @return <code>true</code> if the user is given permission for the given
+     *      role.
+     */
+    boolean authorize( Object user, String role );
+
+    /**
+     * Authenticates the given request or asks the client for credentials.
+     * <p>
+     * Implementations of this method are expected to respect and implement
+     * the semantics of the <code>ServletContextHelper.handleSecurity</code> method
+     * as specified in the OSGi HTTP Service specification.
+     * <p>
+     * If this method returns an object (non null) it is assumed the request
+     * provided valid credentials identifying the user as accepted to access
+     * the web console.
+     * <p>
+     * If this method returns {@code null} the request to the web console
+     * is terminated without any more response sent back to the client. That is
+     * the implementation is expected to have informed the client in case of
+     * non-granted access.
+     *
+     * @param request The request object
+     * @param response The response object
+     * @return An object representing the user if the request provided valid credentials.
+     *   Otherwise return {@code null}.
+     */
+    Object authenticate( HttpServletRequest request, HttpServletResponse response );
+
+    /**
+     * This method will be called by the web console when the user clicks the logout button. The security provider
+     * shouldn't invalidate the session, it will be invalidated after this method exits.
+     * 
+     * However the security provider must delete any cookies or objects, that matters during the authorization process.
+     * 
+     * @param request the request
+     * @param response the response
+     */
+    void logout(HttpServletRequest request, HttpServletResponse response);
+}
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/spi/package-info.java b/webconsole/src/main/java/org/apache/felix/webconsole/spi/package-info.java
index da3a407a37..966e63eb77 100755
--- a/webconsole/src/main/java/org/apache/felix/webconsole/spi/package-info.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/spi/package-info.java
@@ -16,6 +16,6 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-@org.osgi.annotation.versioning.Version("1.1.0")
+@org.osgi.annotation.versioning.Version("1.2.0")
 package org.apache.felix.webconsole.spi;
 
diff --git a/webconsole/src/test/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerHttpContextTest.java b/webconsole/src/test/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerHttpContextTest.java
deleted file mode 100755
index 6b9a6c7848..0000000000
--- a/webconsole/src/test/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerHttpContextTest.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * 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.felix.webconsole.internal.servlet;
-
-import org.apache.felix.webconsole.WebConsoleSecurityProvider;
-import org.junit.Test;
-import org.mockito.Mockito;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-
-import java.lang.reflect.Method;
-
-import static org.junit.Assert.assertEquals;
-
-public class OsgiManagerHttpContextTest {
-    @Test
-    public void testAuthenticate() throws Exception {
-        BundleContext bc = Mockito.mock(BundleContext.class);
-        Bundle bundle = Mockito.mock(Bundle.class);
-        OsgiManagerHttpContext ctx = new OsgiManagerHttpContext(bundle, null, "blah");
-
-        Method authenticateMethod = OsgiManagerHttpContext.class.getDeclaredMethod(
-                "authenticate", new Class [] {WebConsoleSecurityProvider.class, String.class, byte[].class});
-        authenticateMethod.setAccessible(true);
-
-        BasicWebConsoleSecurityProvider lastResortSp = new BasicWebConsoleSecurityProvider(bc, "foo", "bar", "blah");
-        assertEquals(true, authenticateMethod.invoke(ctx, lastResortSp, "foo", "bar".getBytes()));
-        assertEquals(false, authenticateMethod.invoke(ctx, lastResortSp, "foo", "blah".getBytes()));
-
-        WebConsoleSecurityProvider sp = new TestSecurityProvider();
-        assertEquals(true, authenticateMethod.invoke(ctx, sp, "xxx", "yyy".getBytes()));
-        assertEquals("The default username and password should not be accepted with security provider",
-                false, authenticateMethod.invoke(ctx, sp, "foo", "bar".getBytes()));
-    }
-
-    @Test
-    public void testAuthenticatePwdDisabledWithRequiredSecurityProvider() throws Exception {
-        BundleContext bc = Mockito.mock(BundleContext.class);
-        Mockito.when(bc.getProperty(OsgiManager.FRAMEWORK_PROP_SECURITY_PROVIDERS)).thenReturn("a");
-
-        Bundle bundle = Mockito.mock(Bundle.class);
-        OsgiManagerHttpContext ctx = new OsgiManagerHttpContext(bundle, null, "blah");
-
-        Method authenticateMethod = OsgiManagerHttpContext.class.getDeclaredMethod(
-                "authenticate", new Class [] {WebConsoleSecurityProvider.class, String.class, byte[].class});
-        authenticateMethod.setAccessible(true);
-
-        assertEquals("A required security provider is configured, logging in using "
-                + "username and password should be disabled",
-                false, authenticateMethod.invoke(ctx, null, "foo", "bar".getBytes()));
-        assertEquals(false, authenticateMethod.invoke(ctx, null, "foo", "blah".getBytes()));
-        assertEquals(false, authenticateMethod.invoke(ctx, null, "blah", "bar".getBytes()));
-
-        WebConsoleSecurityProvider sp = new TestSecurityProvider();
-        assertEquals(true, authenticateMethod.invoke(ctx, sp, "xxx", "yyy".getBytes()));
-        assertEquals(false, authenticateMethod.invoke(ctx, sp, "foo", "bar".getBytes()));
-    }
-
-    private static class TestSecurityProvider implements WebConsoleSecurityProvider {
-        @Override
-        public Object authenticate(String username, String password) {
-            if ("xxx".equals(username) && "yyy".equals(password))
-                return new Object();
-            return null;
-        }
-
-        @Override
-        public boolean authorize(Object user, String role) {
-            return false;
-        }
-    }
-}
diff --git a/webconsole/src/test/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerTest.java b/webconsole/src/test/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerTest.java
index 248bc172e0..a914ba84ed 100755
--- a/webconsole/src/test/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerTest.java
+++ b/webconsole/src/test/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerTest.java
@@ -36,7 +36,8 @@ import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
 
 import jakarta.servlet.Servlet;
-import org.apache.felix.webconsole.WebConsoleSecurityProvider;
+
+import org.apache.felix.webconsole.spi.SecurityProvider;
 import org.junit.Test;
 import org.mockito.Mockito;
 import org.mockito.invocation.InvocationOnMock;
@@ -91,7 +92,7 @@ public class OsgiManagerTest {
         ServiceReference sref2 = Mockito.mock(ServiceReference.class);
         Mockito.when(sref2.getProperty(OsgiManager.SECURITY_PROVIDER_PROPERTY_NAME)).thenReturn("xyz");
         Mockito.when(sref2.getProperty(Constants.SERVICE_ID)).thenReturn(1L);
-        Mockito.when(bc.getService(sref2)).thenReturn(Mockito.mock(WebConsoleSecurityProvider.class));
+        Mockito.when(bc.getService(sref2)).thenReturn(Mockito.mock(SecurityProvider.class));
         stc.addingService(sref2);
         assertEquals(Collections.singleton("xyz"), mgr.registeredSecurityProviders);
         assertEquals(1, updateCalled.size());
@@ -112,12 +113,12 @@ public class OsgiManagerTest {
         ServiceReference sref1 = Mockito.mock(ServiceReference.class);
         Mockito.when(sref1.getProperty(OsgiManager.SECURITY_PROVIDER_PROPERTY_NAME)).thenReturn("abc");
         Mockito.when(sref1.getProperty(Constants.SERVICE_ID)).thenReturn(1L);
-        Mockito.when(bc.getService(sref1)).thenReturn(Mockito.mock(WebConsoleSecurityProvider.class));
+        Mockito.when(bc.getService(sref1)).thenReturn(Mockito.mock(SecurityProvider.class));
 
         ServiceReference sref2 = Mockito.mock(ServiceReference.class);
         Mockito.when(sref2.getProperty(OsgiManager.SECURITY_PROVIDER_PROPERTY_NAME)).thenReturn("xyz");
         Mockito.when(sref2.getProperty(Constants.SERVICE_ID)).thenReturn(2L);
-        Mockito.when(bc.getService(sref2)).thenReturn(Mockito.mock(WebConsoleSecurityProvider.class));
+        Mockito.when(bc.getService(sref2)).thenReturn(Mockito.mock(SecurityProvider.class));
 
         ServiceTrackerCustomizer stc = mgr.new UpdateDependenciesStateCustomizer();
         stc.addingService(sref1);
@@ -148,13 +149,13 @@ public class OsgiManagerTest {
         final List<String> invocations = new ArrayList<String>();
         ServiceTrackerCustomizer stc = mgr.new UpdateDependenciesStateCustomizer() {
             @Override
-            public WebConsoleSecurityProvider addingService(ServiceReference<WebConsoleSecurityProvider> reference) {
+            public SecurityProvider addingService(ServiceReference<SecurityProvider> reference) {
                 invocations.add("added:" + reference);
                 return null;
             }
 
             @Override
-            public void removedService(ServiceReference<WebConsoleSecurityProvider> reference, WebConsoleSecurityProvider service) {
+            public void removedService(ServiceReference<SecurityProvider> reference, SecurityProvider service) {
                 invocations.add("removed:" + reference);
             }
         };
@@ -244,7 +245,7 @@ public class OsgiManagerTest {
         final OsgiManager mgr = new OsgiManager(bc);
 
         Mockito.verify(bc, Mockito.times(1))
-            .registerService(Mockito.eq(WebConsoleSecurityProvider.class), Mockito.isA(WebConsoleSecurityProvider.class), Mockito.isA(Dictionary.class));
+            .registerService(Mockito.eq(SecurityProvider.class), Mockito.isA(SecurityProvider.class), Mockito.isA(Dictionary.class));
         Mockito.verify(bc, Mockito.times(1))
             .registerService(Mockito.eq(ServletContextHelper.class), Mockito.isA(ServletContextHelper.class), Mockito.isA(Dictionary.class));
         Mockito.verify(bc, Mockito.times(1))
@@ -254,7 +255,7 @@ public class OsgiManagerTest {
 
         // Should not re-register the services, as they were already registered
         Mockito.verify(bc, Mockito.times(1))
-            .registerService(Mockito.eq(WebConsoleSecurityProvider.class), Mockito.isA(WebConsoleSecurityProvider.class), Mockito.isA(Dictionary.class));
+            .registerService(Mockito.eq(SecurityProvider.class), Mockito.isA(SecurityProvider.class), Mockito.isA(Dictionary.class));
         Mockito.verify(bc, Mockito.times(1))
             .registerService(Mockito.eq(ServletContextHelper.class), Mockito.isA(ServletContextHelper.class), Mockito.isA(Dictionary.class));
         Mockito.verify(bc, Mockito.times(1))