You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by GitBox <gi...@apache.org> on 2019/01/16 02:11:48 UTC

[brooklyn-server] Diff for: [GitHub] asfgit merged pull request #1024: Implement REST and UI security as filter not JAAS LoginModule, start OAuth

diff --git a/core/src/test/java/org/apache/brooklyn/core/test/HttpService.java b/core/src/test/java/org/apache/brooklyn/core/test/HttpService.java
index b2ff422f4e..dff408f1e0 100644
--- a/core/src/test/java/org/apache/brooklyn/core/test/HttpService.java
+++ b/core/src/test/java/org/apache/brooklyn/core/test/HttpService.java
@@ -28,10 +28,7 @@
 import java.net.InetAddress;
 import java.security.KeyStore;
 
-import org.eclipse.jetty.security.ConstraintMapping;
-import org.eclipse.jetty.security.ConstraintSecurityHandler;
-import org.eclipse.jetty.security.HashLoginService;
-import org.eclipse.jetty.security.SecurityHandler;
+import org.eclipse.jetty.security.*;
 import org.eclipse.jetty.security.authentication.BasicAuthenticator;
 import org.eclipse.jetty.server.Connector;
 import org.eclipse.jetty.server.Server;
@@ -93,7 +90,9 @@ public HttpService(PortRange portRange, boolean httpsEnabled) {
      */
     public HttpService basicAuthentication(String username, String password) {
         HashLoginService l = new HashLoginService();
-        l.putUser(username, Credential.getCredential(password), new String[]{"user"});
+        UserStore userStore = new UserStore();
+        userStore.addUser(username, Credential.getCredential(password), new String[]{"user"});
+        l.setUserStore(userStore);
         l.setName("test-realm");
 
         Constraint constraint = new Constraint(Constraint.__BASIC_AUTH, "user");
diff --git a/karaf/features/src/main/feature/feature.xml b/karaf/features/src/main/feature/feature.xml
index 42cf4e439f..de2d8961d0 100644
--- a/karaf/features/src/main/feature/feature.xml
+++ b/karaf/features/src/main/feature/feature.xml
@@ -207,6 +207,7 @@
 
         <feature>cxf-jaxrs</feature>
         <bundle dependency="true">mvn:org.apache.cxf/cxf-rt-rs-security-cors/${cxf.version}</bundle>
+        <bundle dependency="true">mvn:org.apache.cxf/cxf-rt-frontend-jaxrs/${cxf.version}</bundle>
 
         <bundle dependency="true">mvn:com.fasterxml.jackson.jaxrs/jackson-jaxrs-json-provider/${fasterxml.jackson.version}</bundle>
 
diff --git a/karaf/jetty-config/pom.xml b/karaf/jetty-config/pom.xml
index bd330b66d6..382a76a8f9 100644
--- a/karaf/jetty-config/pom.xml
+++ b/karaf/jetty-config/pom.xml
@@ -47,7 +47,6 @@
             <Import-Package>
                 org.eclipse.jetty.jaas,
                 org.eclipse.jetty.security,
-                org.apache.brooklyn.rest.security.jaas,
                 *
             </Import-Package>
           </instructions>
diff --git a/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynWebServer.java b/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynWebServer.java
index 9ead29a4ab..7cb7df36fd 100644
--- a/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynWebServer.java
+++ b/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynWebServer.java
@@ -37,7 +37,6 @@
 import java.util.Map;
 
 import javax.annotation.Nullable;
-import javax.security.auth.spi.LoginModule;
 
 import org.apache.brooklyn.api.location.PortRange;
 import org.apache.brooklyn.api.mgmt.ManagementContext;
@@ -47,12 +46,14 @@
 import org.apache.brooklyn.core.location.PortRanges;
 import org.apache.brooklyn.core.mgmt.ShutdownHandler;
 import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
+import org.apache.brooklyn.core.server.BrooklynServerConfig;
 import org.apache.brooklyn.core.server.BrooklynServerPaths;
 import org.apache.brooklyn.core.server.BrooklynServiceAttributes;
 import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation;
 import org.apache.brooklyn.rest.BrooklynWebConfig;
 import org.apache.brooklyn.rest.NopSecurityHandler;
 import org.apache.brooklyn.rest.RestApiSetup;
+import org.apache.brooklyn.rest.filter.BrooklynSecurityProviderFilterJavax;
 import org.apache.brooklyn.rest.filter.CorsImplSupplierFilter;
 import org.apache.brooklyn.rest.filter.CsrfTokenFilter;
 import org.apache.brooklyn.rest.filter.EntitlementContextFilter;
@@ -61,9 +62,7 @@
 import org.apache.brooklyn.rest.filter.NoCacheFilter;
 import org.apache.brooklyn.rest.filter.RequestTaggingFilter;
 import org.apache.brooklyn.rest.filter.RequestTaggingRsFilter;
-import org.apache.brooklyn.rest.security.jaas.BrooklynLoginModule;
-import org.apache.brooklyn.rest.security.jaas.BrooklynLoginModule.RolePrincipal;
-import org.apache.brooklyn.rest.security.jaas.JaasUtils;
+import org.apache.brooklyn.rest.security.provider.AnyoneSecurityProvider;
 import org.apache.brooklyn.rest.util.ManagementContextProvider;
 import org.apache.brooklyn.rest.util.ShutdownHandlerProvider;
 import org.apache.brooklyn.util.collections.MutableMap;
@@ -88,7 +87,6 @@
 import org.apache.brooklyn.util.time.Time;
 import org.apache.brooklyn.util.web.ContextHandlerCollectionHotSwappable;
 import org.eclipse.jetty.http.HttpVersion;
-import org.eclipse.jetty.jaas.JAASLoginService;
 import org.eclipse.jetty.server.Connector;
 import org.eclipse.jetty.server.HttpConfiguration;
 import org.eclipse.jetty.server.HttpConnectionFactory;
@@ -200,14 +198,6 @@
 
     private File webappTempDir;
     
-    /**
-     * @deprecated since 0.9.0, use {@link #consoleSecurity} to disable security or
-     * register an alternative JAAS {@link LoginModule}.
-     * {@link BrooklynLoginModule} used by default.
-     */
-    @Deprecated
-    private Class<org.apache.brooklyn.rest.filter.BrooklynPropertiesSecurityFilter> securityFilterClazz;
-    
     @SetFromFlag
     private boolean skipSecurity = false;
 
@@ -230,7 +220,6 @@ public BrooklynWebServer(Map<?,?> flags, ManagementContext managementContext) {
             log.warn("Ignoring unknown flags " + leftovers);
         
         webappTempDir = BrooklynServerPaths.getBrooklynWebTmpDir(managementContext);
-        JaasUtils.init(managementContext);
     }
 
     public BrooklynWebServer(ManagementContext managementContext, int port) {
@@ -241,12 +230,6 @@ public BrooklynWebServer(ManagementContext managementContext, int port, String w
         this(MutableMap.of("port", port, "war", warUrl), managementContext);
     }
 
-    /** @deprecated since 0.9.0, use {@link #skipSecurity} or {@link BrooklynLoginModule} */
-    @Deprecated
-    public void setSecurityFilter(Class<org.apache.brooklyn.rest.filter.BrooklynPropertiesSecurityFilter> filterClazz) {
-        this.securityFilterClazz = filterClazz;
-    }
-
     public BrooklynWebServer skipSecurity() {
         return skipSecurity(true);
     }
@@ -397,13 +380,6 @@ public synchronized void start() throws Exception {
 
         server = new Server(threadPool);
 
-        // Can be moved to jetty-web.xml inside wars or a global jetty.xml.
-        JAASLoginService loginService = new JAASLoginService();
-        loginService.setName("webconsole");
-        loginService.setLoginModuleName("webconsole");
-        loginService.setRoleClassNames(new String[] {RolePrincipal.class.getName()});
-        server.addBean(loginService);
-
         final ServerConnector connector;
 
         if (getHttpsEnabled()) {
@@ -500,10 +476,8 @@ private WebAppContext deployRestApi(WebAppContext context) {
                 providersListBuilder.build().toArray());
         RestApiSetup.installServletFilters(context,
                 RequestTaggingFilter.class,
+                BrooklynSecurityProviderFilterJavax.class,
                 LoggingFilter.class);
-        if (securityFilterClazz != null) {
-            RestApiSetup.installServletFilters(context, securityFilterClazz);
-        }
         return context;
     }
 
@@ -691,6 +665,18 @@ private void initSecurity(WebAppContext context) {
             //
             // Ignore security config in web.xml.
             context.setSecurityHandler(new NopSecurityHandler());
+            
+            // Above probably not used any more. Handled by the BrooklynSecurityProviderFilter* classes
+            String provider = Strings.toString( ((ManagementContextInternal)managementContext).getBrooklynProperties().getConfigRaw(BrooklynWebConfig.SECURITY_PROVIDER_CLASSNAME).orNull() );
+            if (provider==null) {
+                ((ManagementContextInternal)managementContext).getBrooklynProperties().put(BrooklynWebConfig.SECURITY_PROVIDER_CLASSNAME, 
+                    AnyoneSecurityProvider.class.getName());
+            } else if (AnyoneSecurityProvider.class.getName().equals(provider)) {
+                // already set
+            } else {
+                // mismatch - warn, but continue
+                log.warn("Server told to skip security with unexpected security provider: "+provider);
+            }
         } else {
             // Cover for downstream projects which don't have the changes.
             context.addOverrideDescriptor(getClass().getResource("/web-security.xml").toExternalForm());
diff --git a/launcher/src/main/java/org/apache/brooklyn/launcher/WebAppContextProvider.java b/launcher/src/main/java/org/apache/brooklyn/launcher/WebAppContextProvider.java
index 840e54146e..6e269d1bf0 100644
--- a/launcher/src/main/java/org/apache/brooklyn/launcher/WebAppContextProvider.java
+++ b/launcher/src/main/java/org/apache/brooklyn/launcher/WebAppContextProvider.java
@@ -24,6 +24,8 @@
 import java.io.InputStream;
 import java.util.Map;
 
+import javax.servlet.Servlet;
+
 import org.apache.brooklyn.api.mgmt.ManagementContext;
 import org.apache.brooklyn.core.server.BrooklynServiceAttributes;
 import org.apache.brooklyn.launcher.config.CustomResourceLocator;
@@ -31,7 +33,7 @@
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.os.Os;
 import org.apache.brooklyn.util.text.Identifiers;
-import org.eclipse.jetty.server.SessionManager;
+import org.eclipse.jetty.server.session.SessionHandler;
 import org.eclipse.jetty.webapp.WebAppContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -73,10 +75,12 @@ public WebAppContext get(ManagementContext managementContext, Map<String, Object
 
         final WebAppContext context = new WebAppContext();
         // use a unique session ID to prevent interference with other web apps on same server (esp for localhost);
-        // it might be better to make this brooklyn-only or base on the management-plane ID;
-        // but i think it actually *is* per-server instance, since we don't cache sessions server-side,
-        // so i think this is write. [Alex 2015-09]
-        context.setInitParameter(SessionManager.__SessionCookieProperty, SessionManager.__DefaultSessionCookie + "_" + "BROOKLYN" + Identifiers.makeRandomId(6));
+        // note however this is only run for the legacy launcher
+        // TODO would be nice if the various karaf startups rename the session cookie property (from JSESSIONID)
+        // as the default is likely to conflict with other java-based servers (esp on localhost);
+        // this can be done e.g. on ServletContext.getSessionCookieConfig(), but will be needed for REST and for JS (static) bundles
+        // low priority however, if you /etc/hosts a localhost-brooklyn and use that it will stop conflicting
+        context.setInitParameter(SessionHandler.__SessionCookieProperty, SessionHandler.__DefaultSessionCookie + "_" + "BROOKLYN" + Identifiers.makeRandomId(6));
         context.setInitParameter("org.eclipse.jetty.servlet.Default.dirAllowed", "false");
         context.setAttribute(BrooklynServiceAttributes.BROOKLYN_MANAGEMENT_CONTEXT, managementContext);
         for (Map.Entry<String, Object> attributeEntry : attributes.entrySet()) {
diff --git a/pom.xml b/pom.xml
index dedaf31586..20c0bc8670 100644
--- a/pom.xml
+++ b/pom.xml
@@ -117,7 +117,7 @@
         <kxml2.servicemix.version>2.3.0_3</kxml2.servicemix.version>
         <!-- double-check downstream projects before changing jackson version -->
         <fasterxml.jackson.version>2.9.6</fasterxml.jackson.version>
-        <cxf.version>3.1.10</cxf.version>
+        <cxf.version>3.2.7</cxf.version>
         <httpcomponents.httpclient.version>4.5.2</httpcomponents.httpclient.version>
         <httpcomponents.httpcore.version>4.4.4</httpcomponents.httpcore.version>
         <!-- @deprecated since 0.11 -->
@@ -154,7 +154,7 @@
         <aopalliance.version>1.0</aopalliance.version>
         <commons-configuration.version>1.7</commons-configuration.version>
         <commons-lang.version>2.4</commons-lang.version>
-        <jax-rs-api.version>2.0.1</jax-rs-api.version>
+        <jax-rs-api.version>2.1.1</jax-rs-api.version>
         <maxmind.version>2.8.0-rc1</maxmind.version>
         <maxmind-db.version>1.2.1</maxmind-db.version>
         <winrm4j.version>0.5.0</winrm4j.version>
@@ -162,10 +162,10 @@
         <kubernetes-client.version>1.4.27</kubernetes-client.version>
 
         <!-- Dependencies shipped with vanilla karaf; update these when we update the karaf version -->
-        <karaf.version>4.2.1</karaf.version>
-        <karaf.plugin.version>4.1.4</karaf.plugin.version><!-- held back for KARAF-5932 - fixed in 4.2.2 -->
+        <karaf.version>4.2.2</karaf.version>
+        <karaf.plugin.version>${karaf.version}</karaf.plugin.version>
         <felix.framework.version>5.6.10</felix.framework.version>
-        <jetty.version>9.3.24.v20180605</jetty.version>
+        <jetty.version>9.4.11.v20180605</jetty.version>
 
         <!-- Transitive dependencies, declared explicitly to avoid version mismatch -->
         <clojure.version>1.4.0</clojure.version>
@@ -175,7 +175,7 @@
         <commons-logging.version>1.2</commons-logging.version>
         <jsonSmart.version>2.3</jsonSmart.version>
         <minidev.accessors-smart.version>1.2</minidev.accessors-smart.version>
-        <ow2.asm.version>5.0.4</ow2.asm.version> <!-- from json-path:2.4.0 and cxf-rt-frontend-jaxws:3.1.10; clashes with 5.2 in karaf:4.1.6 -->
+        <ow2.asm.version>5.2</ow2.asm.version> <!-- version should align with cxf-rt-frontend-jaxws; see also json-path and karaf version requirements -->
         <commons-beanutils.version>1.9.3</commons-beanutils.version>
         <commons-collections.version>3.2.2</commons-collections.version>
         <javax.mail.version>1.4.4</javax.mail.version>
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/LogoutApi.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/LogoutApi.java
index f0ca328e85..526a72f03a 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/LogoutApi.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/LogoutApi.java
@@ -36,19 +36,22 @@
     @POST
     @ApiOperation(value = "Request a logout and clean session")
     @ApiResponses(value = {
-            @ApiResponse(code = 307, message = "Redirect to /logout/user, keeping the request method")
+            @ApiResponse(code = 307, message = "Redirect to /logout/{user}, keeping the request method")
     })
     Response logout();
 
-
+    // TODO what is this for?  misleading as it does not unauthorize the _session_ or log out in any way;
+    // deprecating as at 2019-01
+    /** @deprecated since 1.0 */
+    @Deprecated
     @POST
     @Path("/unauthorize")
-    @ApiOperation(value = "Return UNAUTHORIZED 401 response")
+    @ApiOperation(value = "Return UNAUTHORIZED 401 response, but without disabling the session [deprecated]")
     Response unAuthorize();
 
     @POST
     @Path("/{user}")
-    @ApiOperation(value = "Logout and clean session if matching user logged")
+    @ApiOperation(value = "Logout and clean session if matching user logged in")
     Response logoutUser(
         @ApiParam(value = "User to log out", required = true)
         @PathParam("user") final String user);
diff --git a/rest/rest-resources/pom.xml b/rest/rest-resources/pom.xml
index 17effa1d96..5e605ffb64 100644
--- a/rest/rest-resources/pom.xml
+++ b/rest/rest-resources/pom.xml
@@ -76,10 +76,6 @@
             <version>${project.version}</version>
         </dependency>
 
-        <dependency>
-            <groupId>javax.ws.rs</groupId>
-            <artifactId>javax.ws.rs-api</artifactId>
-        </dependency>
         <dependency>
             <groupId>org.apache.cxf</groupId>
             <artifactId>cxf-rt-frontend-jaxrs</artifactId>
@@ -121,6 +117,11 @@
             <artifactId>cxf-rt-rs-security-cors</artifactId>
             <version>${cxf.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.cxf</groupId>
+            <artifactId>cxf-rt-frontend-jaxrs</artifactId>
+            <version>${cxf.version}</version>
+        </dependency>
 
         <dependency>
             <groupId>org.apache.brooklyn</groupId>
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/BrooklynWebConfig.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/BrooklynWebConfig.java
index 79ca0bfc7a..c59df5394b 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/BrooklynWebConfig.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/BrooklynWebConfig.java
@@ -48,6 +48,11 @@
             ExplicitUsersSecurityProvider.class.getCanonicalName());
     public final static ConfigKey<SecurityProvider> SECURITY_PROVIDER_INSTANCE = ConfigKeys.newConfigKey(SecurityProvider.class,
             SECURITY_PROVIDER_CLASSNAME.getName()+".internal.instance", "instance of a pre-configured security provider");
+    // TODO document
+    public final static ConfigKey<String> SECURITY_PROVIDER_BUNDLE = ConfigKeys.newStringConfigKey(
+        SECURITY_PROVIDER_CLASSNAME.getName()+".bundle.symbolicName", "Symbolic-name of the bundle containing the Brooklyn SecurityProvider");
+    public final static ConfigKey<String> SECURITY_PROVIDER_BUNDLE_VERSION = ConfigKeys.newStringConfigKey(
+        SECURITY_PROVIDER_CLASSNAME.getName()+".bundle.version", "Version of the bundle containing the Brooklyn SecurityProvider (optional)");
     
     /**
      * Explicitly set the users/passwords, e.g. in brooklyn.properties:
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/BrooklynSecurityProviderFilterHelper.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/BrooklynSecurityProviderFilterHelper.java
new file mode 100644
index 0000000000..53cb34ac90
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/BrooklynSecurityProviderFilterHelper.java
@@ -0,0 +1,175 @@
+/*
+ * 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.brooklyn.rest.filter;
+
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.ResponseBuilder;
+import javax.ws.rs.core.Response.Status;
+
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.rest.BrooklynWebConfig;
+import org.apache.brooklyn.rest.security.provider.DelegatingSecurityProvider;
+import org.apache.brooklyn.rest.security.provider.SecurityProvider;
+import org.apache.brooklyn.rest.security.provider.SecurityProvider.SecurityProviderDeniedAuthentication;
+import org.apache.brooklyn.util.collections.MutableSet;
+import org.apache.brooklyn.util.text.StringEscapes;
+import org.apache.commons.codec.binary.Base64;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.session.SessionHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Provides a filter that performs authentication with the {@link SecurityProvider}
+ * as configured according to {@link BrooklynWebConfig#SECURITY_PROVIDER_CLASSNAME}.
+ * 
+ * This replaces the JAAS "BrooklynLoginModule" because that login module requires
+ * Basic auth, which is not flexible enough to support redirect-based solutions like Oauth.
+ * 
+ * Unfortunately we seem to need two filters, the Jersey filter for the REST bundle,
+ * and the Javax filter for the static content bundles (in brooklyn-ui/ui-modules).
+ * (We could set up our own Jersey servlet or blueprint for the static content bundles
+ * to re-use the Jersey filter, but that seems like overkill; and surely there's an easy
+ * way to set the Javax filter to run for the REST bundle inside blueprint.xml, but a
+ * few early attempts didn't succeed and the approach of having two filters seems easiest
+ * (especially as they share code for the significant parts, in this class).
+ * 
+ * This does give us the opportunity to differentiate the redirect, so that
+ * jersey (REST) requests don't redirect to the auth site, as the redirect requires human intervention.
+ */
+public class BrooklynSecurityProviderFilterHelper {
+
+    public interface Responder {
+        void error(String message, boolean requiresBasicAuth) throws SecurityProviderDeniedAuthentication;
+    }
+    
+    /**
+     * The session attribute set for authenticated users; for reference
+     * (but should not be relied up to confirm authentication, as
+     * the providers may impose additional criteria such as timeouts,
+     * or a null user (no login) may be permitted)
+     */
+    public static final String AUTHENTICATED_USER_SESSION_ATTRIBUTE = "brooklyn.user";
+
+    public static Set<SessionHandler> SESSION_MANAGER_CACHE = MutableSet.of();
+    
+    private static final Logger log = LoggerFactory.getLogger(BrooklynSecurityProviderFilterHelper.class);
+
+    // TODO this should be parametrisable
+    public static final String BASIC_REALM_NAME = "brooklyn";
+    
+    public static final String BASIC_REALM_HEADER_VALUE = "BASIC realm="+StringEscapes.JavaStringEscapes.wrapJavaString(BASIC_REALM_NAME);
+    
+    /* check all contexts for sessions; surprisingly hard to configure session management for karaf/pax web container.
+     * they _really_ want each servlet to have their own sessions. how you're meant to do oauth for multiple servlets i don't know! */
+    public HttpSession getSession(HttpServletRequest webRequest, ManagementContext mgmt, boolean create) {
+        String requestedSessionId = webRequest.getRequestedSessionId();
+        
+        log.trace("SESSION for {}, wants session {}", webRequest.getRequestURI(), requestedSessionId);
+        
+        if (webRequest instanceof Request) {
+            SessionHandler sm = ((Request)webRequest).getSessionHandler();
+            boolean added = SESSION_MANAGER_CACHE.add( sm );
+            log.trace("SESSION MANAGER found for {}: {} (added={})", webRequest.getRequestURI(), sm, added);
+        } else {
+            log.trace("SESSION MANAGER NOT found for {}: {}", webRequest.getRequestURI(), webRequest);
+        }
+        
+        if (requestedSessionId!=null) {
+            for (SessionHandler m: SESSION_MANAGER_CACHE) {
+                HttpSession s = m.getHttpSession(requestedSessionId);
+                if (s!=null) {
+                    log.trace("SESSION found for {}: {} (valid={})", webRequest.getRequestURI(), s, m.isValid(s));
+                    return s;
+                }
+            }
+        }
+        
+        if (create) {
+            HttpSession session = webRequest.getSession(true);
+            log.trace("SESSION creating for {}: {}", webRequest.getRequestURI(), session);
+            return session;
+        }
+        
+        return null;  // not found
+    }
+    
+    public void run(HttpServletRequest webRequest, ManagementContext mgmt) throws SecurityProviderDeniedAuthentication {
+        SecurityProvider provider = getProvider(mgmt);
+        HttpSession session = getSession(webRequest, mgmt, false);
+        
+        if (provider.isAuthenticated(session)) {
+            return;
+        }
+        
+        String user = null, pass = null;
+        if (provider.requiresUserPass()) {
+            String authorization = webRequest.getHeader("Authorization");
+            if (authorization != null) {
+                String userpass = new String(Base64.decodeBase64(authorization.substring(6)));
+                int idxColon = userpass.indexOf(":");
+                if (idxColon >= 0) {
+                    user = userpass.substring(0, idxColon);
+                    pass = userpass.substring(idxColon + 1);
+                } else {
+                    throw abort("Invalid authorization string", provider.requiresUserPass());
+                }
+            } else {
+                throw abort("Authorization required", provider.requiresUserPass());
+            }
+        }
+        
+        if (session==null) {
+            // only create the session if an auth string is supplied
+            session = getSession(webRequest, mgmt, true);
+        }
+        session.setAttribute(BrooklynWebConfig.REMOTE_ADDRESS_SESSION_ATTRIBUTE, webRequest.getRemoteAddr());
+        
+        if (provider.authenticate(session, user, pass)) {
+            if (user != null) {
+                session.setAttribute(AUTHENTICATED_USER_SESSION_ATTRIBUTE, user);
+            }
+            return;
+        }
+    
+        throw abort("Authentication failed", provider.requiresUserPass());
+    }
+    
+    private SecurityProviderDeniedAuthentication abort(String msg, boolean requiresUserPass) throws SecurityProviderDeniedAuthentication {
+        ResponseBuilder response = Response.status(Status.UNAUTHORIZED);
+        if (requiresUserPass) {
+            response.header(HttpHeader.WWW_AUTHENTICATE.asString(), BASIC_REALM_HEADER_VALUE);
+        }
+        response.header(HttpHeader.CONTENT_TYPE.asString(), MediaType.TEXT_PLAIN);
+        response.entity(msg);
+        throw new SecurityProviderDeniedAuthentication(response.build());
+    }
+
+    protected SecurityProvider getProvider(ManagementContext mgmt) {
+        // we don't cache here (could, it might be faster) but the delegate does use a cache
+        return new DelegatingSecurityProvider(mgmt);
+    }
+
+}
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/BrooklynSecurityProviderFilterJavax.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/BrooklynSecurityProviderFilterJavax.java
new file mode 100644
index 0000000000..eb26d8a621
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/BrooklynSecurityProviderFilterJavax.java
@@ -0,0 +1,91 @@
+/*
+ * 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.brooklyn.rest.filter;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.rest.security.provider.SecurityProvider.SecurityProviderDeniedAuthentication;
+import org.apache.brooklyn.rest.util.ManagementContextProvider;
+import org.apache.brooklyn.util.text.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Preconditions;
+
+/** See {@link BrooklynSecurityProviderFilterHelper} */
+public class BrooklynSecurityProviderFilterJavax implements Filter {
+    
+    private static final Logger log = LoggerFactory.getLogger(BrooklynSecurityProviderFilterJavax.class);
+    
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException {
+        // no init needed
+        log.trace("BrooklynSecurityProviderFilterJavax.init");
+    }
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+            throws IOException, ServletException {
+
+        try {
+            log.trace("BrooklynSecurityProviderFilterJavax.doFilter {}", request);
+            ManagementContext mgmt = new ManagementContextProvider(request.getServletContext()).getManagementContext();
+            Preconditions.checkNotNull(mgmt, "Brooklyn management context not available; cannot authenticate");
+            new BrooklynSecurityProviderFilterHelper().run((HttpServletRequest)request, mgmt);
+
+            chain.doFilter(request, response);
+
+        } catch (SecurityProviderDeniedAuthentication e) {
+            log.trace("BrooklynSecurityProviderFilterJavax.doFilter caught SecurityProviderDeniedAuthentication", e);
+            HttpServletResponse rout = ((HttpServletResponse)response);
+            Response rin = e.getResponse();
+            if (rin==null) rin = Response.status(Status.UNAUTHORIZED).build();
+     
+            rout.setStatus(rin.getStatus());
+
+            // note content-type is explicitly set in some Response objects, but this should set it 
+            rin.getHeaders().forEach((k,v) -> v.forEach(v2 -> rout.addHeader(k, Strings.toString(v2))));
+            
+            Object body = rin.getEntity();
+            if (body!=null) {
+                response.getWriter().write(Strings.toString(body));
+                response.getWriter().flush();
+            }
+        }
+    }
+
+    @Override
+    public void destroy() {
+        // no clean-up needed
+        log.trace("BrooklynSecurityProviderFilterJavax.destroy");
+    }
+    
+}
\ No newline at end of file
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/BrooklynSecurityProviderFilterJersey.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/BrooklynSecurityProviderFilterJersey.java
new file mode 100644
index 0000000000..d5d7cd2f8f
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/BrooklynSecurityProviderFilterJersey.java
@@ -0,0 +1,79 @@
+/*
+ * 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.brooklyn.rest.filter;
+
+import java.io.IOException;
+
+import javax.annotation.Priority;
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.ext.ContextResolver;
+import javax.ws.rs.ext.Provider;
+
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.rest.security.provider.SecurityProvider.SecurityProviderDeniedAuthentication;
+import org.eclipse.jetty.http.HttpHeader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** See {@link BrooklynSecurityProviderFilterHelper} */
+@Provider
+@Priority(100)
+public class BrooklynSecurityProviderFilterJersey implements ContainerRequestFilter {
+
+    private static final Logger log = LoggerFactory.getLogger(BrooklynSecurityProviderFilterJersey.class);
+
+    @Context
+    HttpServletRequest webRequest;
+
+    @Context
+    private ContextResolver<ManagementContext> mgmtC;
+
+    @SuppressWarnings("resource")
+    @Override
+    public void filter(ContainerRequestContext requestContext) throws IOException {
+        log.trace("BrooklynSecurityProviderFilterJersey.filter {}", requestContext);
+        try {
+            new BrooklynSecurityProviderFilterHelper().run(webRequest, mgmtC.getContext(ManagementContext.class));
+        } catch (SecurityProviderDeniedAuthentication e) {
+            Response rin = e.getResponse();
+            if (rin==null) rin = Response.status(Status.UNAUTHORIZED).build();
+            
+            if (rin.getStatus()==Status.FOUND.getStatusCode()) {
+                String location = rin.getHeaderString(HttpHeader.LOCATION.asString());
+                if (location!=null) {
+                    log.trace("Redirect to {} for authentication",location);
+                    final UriBuilder uriBuilder = UriBuilder.fromPath(location);
+                    rin = Response.temporaryRedirect(uriBuilder.build()).entity("Authentication is required at "+location).build();
+                } else {
+                    log.trace("Unauthorized");
+                    rin = Response.status(Status.UNAUTHORIZED).entity("Authentication is required").build();
+                }
+            }
+            requestContext.abortWith(rin);
+        }
+    }
+
+}
+
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/CorsImplSupplierFilter.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/CorsImplSupplierFilter.java
index 51c9e5c2ae..9262c00ac6 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/CorsImplSupplierFilter.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/CorsImplSupplierFilter.java
@@ -59,6 +59,7 @@
  * Apache Brooklyn API calls do not use CORS annotations so findResourceMethod is set to false.
  */
 @Provider
+@SuppressWarnings("serial")
 public class CorsImplSupplierFilter extends CrossOriginResourceSharingFilter {
     /**
      * @see CrossOriginResourceSharingFilter#setAllowOrigins(List<String>)
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/EntitlementContextFilter.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/EntitlementContextFilter.java
index a039b57f0a..d2ce05595a 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/EntitlementContextFilter.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/EntitlementContextFilter.java
@@ -23,6 +23,7 @@
 
 import javax.annotation.Priority;
 import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
 import javax.ws.rs.container.ContainerRequestContext;
 import javax.ws.rs.container.ContainerRequestFilter;
 import javax.ws.rs.container.ContainerResponseContext;
@@ -31,8 +32,10 @@
 import javax.ws.rs.core.SecurityContext;
 import javax.ws.rs.ext.Provider;
 
+import org.apache.brooklyn.api.mgmt.entitlement.EntitlementContext;
 import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
 import org.apache.brooklyn.core.mgmt.entitlement.WebEntitlementContext;
+import org.apache.brooklyn.util.text.Strings;
 
 @Provider
 @Priority(400)
@@ -42,16 +45,37 @@
 
     @Override
     public void filter(ContainerRequestContext requestContext) throws IOException {
+        String userName = null;
+
+        // first see if there is a principal
         SecurityContext securityContext = requestContext.getSecurityContext();
         Principal user = securityContext.getUserPrincipal();
+        if (user!=null) {
+            userName = user.getName();
+        } else {
+
+            // now look in session attribute - because principals hard to set from javax filter
+            if (request!=null) {
+                HttpSession s = request.getSession(false);
+                if (s!=null) {
+                    userName = Strings.toString(s.getAttribute(
+                            BrooklynSecurityProviderFilterHelper.AUTHENTICATED_USER_SESSION_ATTRIBUTE));
+                }
+            }
+        }
+
+        if (userName != null) {
+            EntitlementContext oldEntitlement = Entitlements.getEntitlementContext();
+            if (oldEntitlement!=null && !userName.equals(oldEntitlement.user())) {
+                throw new IllegalStateException("Illegal entitement context switch, from user "+oldEntitlement.user()+" to "+userName);
+            }
+
+            String uri = request.getRequestURI();
+            String remoteAddr = request.getRemoteAddr();
 
-        if (user != null) {
-           String uri = request.getRequestURI();
-           String remoteAddr = request.getRemoteAddr();
-   
-           String uid = RequestTaggingRsFilter.getTag();
-           WebEntitlementContext entitlementContext = new WebEntitlementContext(user.getName(), remoteAddr, uri, uid);
-           Entitlements.setEntitlementContext(entitlementContext);
+            String uid = RequestTaggingRsFilter.getTag();
+            WebEntitlementContext entitlementContext = new WebEntitlementContext(userName, remoteAddr, uri, uid);
+            Entitlements.setEntitlementContext(entitlementContext);
         }
     }
 
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckResourceFilter.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckResourceFilter.java
index 28eac1ca0f..128d06a28f 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckResourceFilter.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckResourceFilter.java
@@ -33,7 +33,6 @@
 
 import org.apache.brooklyn.api.mgmt.ManagementContext;
 import org.apache.brooklyn.api.mgmt.ha.ManagementNodeState;
-import org.apache.brooklyn.rest.util.BrooklynRestResourceUtils;
 import org.apache.brooklyn.util.guava.Maybe;
 import org.apache.brooklyn.util.text.Strings;
 
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/LogoutResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/LogoutResource.java
index e3329d2f8a..a03cb24abf 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/LogoutResource.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/LogoutResource.java
@@ -30,15 +30,11 @@
 import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
 import org.apache.brooklyn.core.mgmt.entitlement.WebEntitlementContext;
 import org.apache.brooklyn.rest.api.LogoutApi;
-import org.apache.brooklyn.rest.security.jaas.BrooklynLoginModule;
+import org.apache.brooklyn.rest.filter.BrooklynSecurityProviderFilterHelper;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 
-import com.google.common.net.HttpHeaders;
-
 public class LogoutResource extends AbstractBrooklynRestResource implements LogoutApi {
     
-    private static final String BASIC_REALM_WEBCONSOLE = "Basic realm=\""+BrooklynLoginModule.DEFAULT_ROLE+"\"";
-    
     @Context HttpServletRequest req;
     @Context UriInfo uri;
 
@@ -49,7 +45,6 @@ public Response logout() {
         if (ctx==null) {
             return Response.status(Status.BAD_REQUEST)
                 .entity("No user logged in")
-                .header(HttpHeaders.WWW_AUTHENTICATE, BASIC_REALM_WEBCONSOLE)
                 .build();            
         }
         
@@ -64,21 +59,21 @@ public Response logout() {
     @Override
     public Response unAuthorize() {
         return Response.status(Status.UNAUTHORIZED)
-            .header(HttpHeaders.WWW_AUTHENTICATE, BASIC_REALM_WEBCONSOLE)
-            .build();
+               // NB: 2019-01 no longer returns a realm (there might not be a realm; in this code we don't know)
+               // method is now deprecated anyway
+               .build();
     }
 
     @Override
     public Response logoutUser(String user) {
-        // Will work when switching users, but will keep re-authenticating if user types in same user name.
-        // Could improve by keeping state in cookies to decide whether to request auth or declare successfull re-auth.
         WebEntitlementContext ctx = (WebEntitlementContext) Entitlements.getEntitlementContext();
         if (user.equals(ctx.user())) {
             doLogout();
 
-            return Response.status(Status.UNAUTHORIZED)
-                    .header(HttpHeaders.WWW_AUTHENTICATE, BASIC_REALM_WEBCONSOLE)
-                    .build();
+            return Response.status(Status.OK)
+                   // 2019-01 no longer returns unauthorized, returns OK to indicate user is successfully logged out
+                   // also the realm  is removed (there might not be a realm; in this code we don't know)
+                   .build();
         } else {
             return Response.temporaryRedirect(uri.getAbsolutePathBuilder().replacePath("/").build()).build();
         }
@@ -86,6 +81,7 @@ public Response logoutUser(String user) {
 
     private void doLogout() {
         try {
+            req.getSession().removeAttribute(BrooklynSecurityProviderFilterHelper.AUTHENTICATED_USER_SESSION_ATTRIBUTE);
             req.logout();
         } catch (ServletException e) {
             Exceptions.propagate(e);
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/jaas/BrooklynLoginModule.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/jaas/BrooklynLoginModule.java
deleted file mode 100644
index 32684bdacb..0000000000
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/jaas/BrooklynLoginModule.java
+++ /dev/null
@@ -1,366 +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.brooklyn.rest.security.jaas;
-
-import org.apache.brooklyn.api.mgmt.ManagementContext;
-import org.apache.brooklyn.config.StringConfigMap;
-import org.apache.brooklyn.rest.BrooklynWebConfig;
-import org.apache.brooklyn.rest.security.provider.DelegatingSecurityProvider;
-import org.apache.brooklyn.rest.security.provider.SecurityProvider;
-import org.apache.brooklyn.util.exceptions.Exceptions;
-import org.apache.brooklyn.util.text.Strings;
-import org.eclipse.jetty.server.HttpChannel;
-import org.eclipse.jetty.server.HttpConnection;
-import org.eclipse.jetty.server.Request;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.security.auth.Subject;
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.NameCallback;
-import javax.security.auth.callback.PasswordCallback;
-import javax.security.auth.callback.UnsupportedCallbackException;
-import javax.security.auth.login.FailedLoginException;
-import javax.security.auth.login.LoginException;
-import javax.security.auth.spi.LoginModule;
-import javax.servlet.http.HttpSession;
-import java.io.IOException;
-import java.lang.reflect.InvocationTargetException;
-import java.security.Principal;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Map;
-import java.util.Optional;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-// http://docs.oracle.com/javase/7/docs/technotes/guides/security/jaas/JAASLMDevGuide.html
-
-/**
- * <p>
- * JAAS module delegating authentication to the {@link SecurityProvider} implementation
- * configured in {@literal brooklyn.properties}, key {@literal brooklyn.webconsole.security.provider}.
- * <p>
- * <p>
- * If used in an OSGi environment only implementations visible from {@literal brooklyn-rest-server} are usable by default.
- * To use a custom security provider add the following configuration to the its bundle in {@literal src/main/resources/OSGI-INF/bundle/security-provider.xml}:
- * <p>
- * <pre>
- * {@code
- * <?xml version="1.0" encoding="UTF-8"?>
- * <blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
- *           xmlns:jaas="http://karaf.apache.org/xmlns/jaas/v1.1.0"
- *           xmlns:ext="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.0.0">
- *
- *    <jaas:config name="karaf" rank="1">
- *        <jaas:module className="org.apache.brooklyn.rest.security.jaas.BrooklynLoginModule"
- *                     flags="required">
- *            brooklyn.webconsole.security.provider.symbolicName=BUNDLE_SYMBOLIC_NAME
- *            brooklyn.webconsole.security.provider.version=BUNDLE_VERSION
- *        </jaas:module>
- *    </jaas:config>
- *
- * </blueprint>
- * }
- * </pre>
- */
-// Needs an explicit "org.apache.karaf.jaas.config" Import-Package in the manifest!
-public class BrooklynLoginModule implements LoginModule {
-    private static final Logger log = LoggerFactory.getLogger(BrooklynLoginModule.class);
-
-    /**
-     * The session attribute set for authenticated users; for reference
-     * (but should not be relied up to confirm authentication, as
-     * the providers may impose additional criteria such as timeouts,
-     * or a null user (no login) may be permitted)
-     */
-    public static final String AUTHENTICATED_USER_SESSION_ATTRIBUTE = "brooklyn.user";
-
-    private static class BasicPrincipal implements Principal {
-        private String name;
-
-        public BasicPrincipal(String name) {
-            this.name = checkNotNull(name, "name");
-        }
-
-        @Override
-        public String getName() {
-            return name;
-        }
-
-        @Override
-        public int hashCode() {
-            return name.hashCode();
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (obj instanceof BasicPrincipal) {
-                return name.equals(((BasicPrincipal) obj).name);
-            }
-            return false;
-        }
-
-        @Override
-        public String toString() {
-            return getClass().getSimpleName() + "[" + name + "]";
-        }
-    }
-
-    public static class UserPrincipal extends BasicPrincipal {
-        public UserPrincipal(String name) {
-            super(name);
-        }
-    }
-
-    public static class RolePrincipal extends BasicPrincipal {
-        public RolePrincipal(String name) {
-            super(name);
-        }
-    }
-
-    public static final String PROPERTY_BUNDLE_SYMBOLIC_NAME = BrooklynWebConfig.SECURITY_PROVIDER_CLASSNAME.getName() + ".symbolicName";
-    public static final String PROPERTY_BUNDLE_VERSION = BrooklynWebConfig.SECURITY_PROVIDER_CLASSNAME.getName() + ".version";
-    /**
-     * SecurityProvider doesn't know about roles, just attach one by default. Use the one specified here or DEFAULT_ROLE
-     */
-    public static final String PROPERTY_ROLE = BrooklynWebConfig.SECURITY_PROVIDER_CLASSNAME.getName() + ".role";
-    public static final String DEFAULT_ROLE = "webconsole";
-
-    private Map<String, ?> options;
-    private BundleContext bundleContext;
-
-    private HttpSession providerSession;
-
-    private SecurityProvider provider;
-    private Subject subject;
-    private CallbackHandler callbackHandler;
-    private boolean loginSuccess;
-    private boolean commitSuccess;
-    private Collection<Principal> principals;
-
-    public BrooklynLoginModule() {
-    }
-
-    private synchronized static SecurityProvider createDefaultSecurityProvider(ManagementContext mgmt) {
-        return new DelegatingSecurityProvider(mgmt);
-    }
-
-    private ManagementContext getManagementContext() {
-        return ManagementContextHolder.getManagementContext();
-    }
-
-    @Override
-    public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) {
-        this.subject = subject;
-        this.callbackHandler = callbackHandler;
-        this.options = options;
-
-        this.bundleContext = (BundleContext) options.get(BundleContext.class.getName());
-
-        loginSuccess = false;
-        commitSuccess = false;
-
-        initProvider();
-    }
-
-    private void initProvider() {
-        StringConfigMap brooklynProperties = getManagementContext().getConfig();
-        provider = brooklynProperties.getConfig(BrooklynWebConfig.SECURITY_PROVIDER_INSTANCE);
-        String symbolicName = (String) options.get(PROPERTY_BUNDLE_SYMBOLIC_NAME);
-        String version = (String) options.get(PROPERTY_BUNDLE_VERSION);
-        String className = (String) options.get(BrooklynWebConfig.SECURITY_PROVIDER_CLASSNAME.getName());
-        if (className != null && symbolicName == null) {
-            throw new IllegalStateException("Missing JAAS module property " + PROPERTY_BUNDLE_SYMBOLIC_NAME + " pointing at the bundle where to load the security provider from.");
-        }
-        if (provider != null) return;
-        provider = getManagementContext().getScratchpad().get(BrooklynWebConfig.SECURITY_PROVIDER_INSTANCE);
-        if (provider != null) return;
-        if (symbolicName != null) {
-            if (className == null) {
-                className = brooklynProperties.getConfig(BrooklynWebConfig.SECURITY_PROVIDER_CLASSNAME);
-            }
-            if (className != null) {
-                try {
-                    Collection<Bundle> bundles = getMatchingBundles(symbolicName, version);
-                    if (bundles.isEmpty()) {
-                        throw new IllegalStateException("No bundle " + symbolicName + ":" + version + " found");
-                    } else if (bundles.size() > 1) {
-                        log.warn("Found multiple bundles matching symbolicName " + symbolicName + " and version " + version +
-                                " while trying to load security provider " + className + ". Will use first one that loads the class successfully.");
-                    }
-                    provider = tryLoadClass(className, bundles);
-                    if (provider == null) {
-                        throw new ClassNotFoundException("Unable to load class " + className + " from bundle " + symbolicName + ":" + version);
-                    }
-                } catch (Exception e) {
-                    Exceptions.propagateIfFatal(e);
-                    throw new IllegalStateException("Can not load or create security provider " + className + " for bundle " + symbolicName + ":" + version, e);
-                }
-            }
-        } else {
-            log.debug("Delegating security provider loading to Brooklyn.");
-            provider = createDefaultSecurityProvider(getManagementContext());
-        }
-
-        log.debug("Using security provider " + provider);
-    }
-
-    private SecurityProvider tryLoadClass(String className, Collection<Bundle> bundles)
-            throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
-        for (Bundle b : bundles) {
-            try {
-                @SuppressWarnings("unchecked")
-                Class<? extends SecurityProvider> securityProviderType = (Class<? extends SecurityProvider>) b.loadClass(className);
-                return DelegatingSecurityProvider.createSecurityProviderInstance(getManagementContext(), securityProviderType);
-            } catch (ClassNotFoundException e) {
-            }
-        }
-        return null;
-    }
-
-    private Collection<Bundle> getMatchingBundles(final String symbolicName, final String version) {
-        Collection<Bundle> bundles = new ArrayList<>();
-        for (Bundle b : bundleContext.getBundles()) {
-            if (b.getSymbolicName().equals(symbolicName) &&
-                    (version == null || b.getVersion().toString().equals(version))) {
-                bundles.add(b);
-            }
-        }
-        return bundles;
-    }
-
-    @Override
-    public boolean login() throws LoginException {
-        if (callbackHandler == null) {
-            loginSuccess = false;
-            throw new FailedLoginException("Username and password not available");
-        }
-
-        NameCallback cbName = new NameCallback("Username: ");
-        PasswordCallback cbPassword = new PasswordCallback("Password: ", false);
-
-        Callback[] callbacks = {cbName, cbPassword};
-
-        try {
-            callbackHandler.handle(callbacks);
-        } catch (IOException ioe) {
-            throw new LoginException(ioe.getMessage());
-        } catch (UnsupportedCallbackException uce) {
-            throw new LoginException(uce.getMessage() + " not available to obtain information from user");
-        }
-        String user = cbName.getName();
-        String password = new String(cbPassword.getPassword());
-
-        providerSession = new SecurityProviderHttpSession();
-
-        Request req = getJettyRequest();
-        if (req != null) {
-            String remoteAddr = req.getRemoteAddr();
-            providerSession.setAttribute(BrooklynWebConfig.REMOTE_ADDRESS_SESSION_ATTRIBUTE, remoteAddr);
-        }
-
-        if (!provider.authenticate(providerSession, user, password)) {
-            loginSuccess = false;
-            throw new FailedLoginException("Incorrect username or password");
-        }
-
-        if (user != null) {
-            providerSession.setAttribute(AUTHENTICATED_USER_SESSION_ATTRIBUTE, user);
-        }
-
-        principals = new ArrayList<>(2);
-        principals.add(new UserPrincipal(user));
-        // Could introduce a new interface SecurityRoleAware, implemented by
-        // the SecurityProviders, returning the roles a user has assigned.
-        // For now a static role is good enough.
-        String role = (String) options.get(PROPERTY_ROLE);
-        if (role == null) {
-            role = DEFAULT_ROLE;
-        }
-        if (Strings.isNonEmpty(role)) {
-            principals.add(new RolePrincipal(role));
-        }
-        loginSuccess = true;
-        return true;
-    }
-
-    @Override
-    public boolean commit() throws LoginException {
-        if (loginSuccess) {
-            if (subject.isReadOnly()) {
-                throw new LoginException("Can't commit read-only subject");
-            }
-            subject.getPrincipals().addAll(principals);
-        }
-
-        commitSuccess = true;
-        return loginSuccess;
-    }
-
-    @Override
-    public boolean abort() throws LoginException {
-        if (loginSuccess && commitSuccess) {
-            removePrincipal();
-        }
-        clear();
-        return loginSuccess;
-    }
-
-    @Override
-    public boolean logout() throws LoginException {
-        Request req = getJettyRequest();
-        if (req != null) {
-            log.info("REST logging {} out",
-                    providerSession.getAttribute(AUTHENTICATED_USER_SESSION_ATTRIBUTE));
-            provider.logout(req.getSession());
-            req.getSession().removeAttribute(AUTHENTICATED_USER_SESSION_ATTRIBUTE);
-        } else {
-            log.error("Request object not available for logout");
-        }
-
-        removePrincipal();
-        clear();
-        return true;
-    }
-
-    private void removePrincipal() throws LoginException {
-        if (subject.isReadOnly()) {
-            throw new LoginException("Read-only subject");
-        }
-        subject.getPrincipals().removeAll(principals);
-    }
-
-    private void clear() {
-        subject = null;
-        callbackHandler = null;
-        principals = null;
-    }
-
-    private Request getJettyRequest() {
-        return Optional.ofNullable(HttpConnection.getCurrentConnection())
-                .map(HttpConnection::getHttpChannel)
-                .map(HttpChannel::getRequest)
-                .orElse(null);
-    }
-
-}
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/jaas/JaasUtils.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/jaas/JaasUtils.java
deleted file mode 100644
index 94aba5de96..0000000000
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/jaas/JaasUtils.java
+++ /dev/null
@@ -1,48 +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.brooklyn.rest.security.jaas;
-
-import java.net.URL;
-
-import org.apache.brooklyn.api.mgmt.ManagementContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class JaasUtils {
-    private static final Logger log = LoggerFactory.getLogger(JaasUtils.class);
-
-    private static final String JAAS_CONFIG = "java.security.auth.login.config";
-
-    public static void init(ManagementContext mgmt) {
-        ManagementContextHolder.setManagementContextStatic(mgmt);
-        String config = System.getProperty(JAAS_CONFIG);
-        if (config == null) {
-            URL configUrl = JaasUtils.class.getResource("/jaas.conf");
-            if (configUrl != null) {
-                log.debug("Using classpath JAAS config from " + configUrl.toExternalForm());
-                System.setProperty(JAAS_CONFIG, configUrl.toExternalForm());
-            } else {
-                log.error("Can't find " + JAAS_CONFIG + " on classpath. Web server authentication will fail.");
-            }
-        } else {
-            log.debug("Using externally configured JAAS at " + config);
-        }
-    }
-
-}
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/jaas/ManagementContextHolder.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/jaas/ManagementContextHolder.java
deleted file mode 100644
index 84704f1547..0000000000
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/jaas/ManagementContextHolder.java
+++ /dev/null
@@ -1,36 +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.brooklyn.rest.security.jaas;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import org.apache.brooklyn.api.mgmt.ManagementContext;
-
-public class ManagementContextHolder {
-    private static ManagementContext mgmt;
-    public static ManagementContext getManagementContext() {
-        return checkNotNull(mgmt, "Management context not set yet");
-    }
-    public void setManagementContext(ManagementContext mgmt) {
-        setManagementContextStatic(mgmt);
-    }
-    public static void setManagementContextStatic(ManagementContext mgmt) {
-        ManagementContextHolder.mgmt = checkNotNull(mgmt, "mgmt");
-    }
-}
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/jaas/SecurityProviderHttpSession.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/jaas/SecurityProviderHttpSession.java
deleted file mode 100644
index 98f1e6d950..0000000000
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/jaas/SecurityProviderHttpSession.java
+++ /dev/null
@@ -1,120 +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.brooklyn.rest.security.jaas;
-
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-import javax.servlet.ServletContext;
-import javax.servlet.http.HttpSession;
-
-import org.apache.brooklyn.util.text.Identifiers;
-
-/** mock session, used only for performing authentication */
-public class SecurityProviderHttpSession implements HttpSession {
-    String id = Identifiers.makeRandomId(5);
-    Map<String, Object> attributes = new ConcurrentHashMap<>();
-
-    @Override
-    public long getCreationTime() {
-        return 0;
-    }
-
-    @Override
-    public String getId() {
-        return id;
-    }
-
-    @Override
-    public long getLastAccessedTime() {
-        return 0;
-    }
-
-    @Override
-    public ServletContext getServletContext() {
-        return null;
-    }
-
-    @Override
-    public void setMaxInactiveInterval(int interval) {
-    }
-
-    @Override
-    public int getMaxInactiveInterval() {
-        return 0;
-    }
-
-    @Override
-    @Deprecated //in interface
-    public javax.servlet.http.HttpSessionContext getSessionContext() {
-        return null;
-    }
-
-    @Override
-    public Object getAttribute(String name) {
-        return attributes.get(name);
-    }
-
-    @Override
-    public Object getValue(String name) {
-        return null;
-    }
-
-    @Override
-    public Enumeration<String> getAttributeNames() {
-        return Collections.enumeration(attributes.keySet());
-    }
-
-    @Override
-    public String[] getValueNames() {
-        return null;
-    }
-
-    @Override
-    public void setAttribute(String name, Object value) {
-        attributes.put(name, value);
-    }
-
-    @Override
-    public void putValue(String name, Object value) {
-    }
-
-    @Override
-    public void removeAttribute(String name) {
-        attributes.remove(name);
-    }
-
-    @Override
-    public void removeValue(String name) {
-    }
-
-    @Override
-    public void invalidate() {
-        id = Identifiers.makeRandomId(5);
-        attributes.clear();
-    }
-
-    @Override
-    public boolean isNew() {
-        return false;
-    }
-
-}
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/AnyoneSecurityProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/AnyoneSecurityProvider.java
index 97b4fe1e97..b15ef3a75e 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/AnyoneSecurityProvider.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/AnyoneSecurityProvider.java
@@ -20,7 +20,8 @@
 
 import javax.servlet.http.HttpSession;
 
-/** provider who allows everyone */
+/** provider who allows all access with no need to supply a user
+ * (see also test case AuthenticateAnyoneSecurityProvider) */
 public class AnyoneSecurityProvider implements SecurityProvider {
 
     @Override
@@ -28,8 +29,15 @@ public boolean isAuthenticated(HttpSession session) {
         return true;
     }
 
+    @Override
+    public boolean requiresUserPass() {
+        // doesn't matter as isAuth always returns true, this should never be called
+        return false;
+    }
+    
     @Override
     public boolean authenticate(HttpSession session, String user, String password) {
+        // doesn't matter as isAuth always returns true, this should never be called
         return true;
     }
 
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/BlackholeSecurityProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/BlackholeSecurityProvider.java
index a9769750fa..5418924046 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/BlackholeSecurityProvider.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/BlackholeSecurityProvider.java
@@ -20,7 +20,7 @@
 
 import javax.servlet.http.HttpSession;
 
-/** provider who disallows everyone */
+/** provider who disallows everyone, though it does require a user/pass */
 public class BlackholeSecurityProvider implements SecurityProvider {
 
     @Override
@@ -37,4 +37,10 @@ public boolean authenticate(HttpSession session, String user, String password) {
     public boolean logout(HttpSession session) { 
         return true;
     }
+
+    @Override
+    public boolean requiresUserPass() {
+        return true;
+    }
+    
 }
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/BrooklynUserWithRandomPasswordSecurityProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/BrooklynUserWithRandomPasswordSecurityProvider.java
index 7b8e4a5ec6..3400d0a05e 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/BrooklynUserWithRandomPasswordSecurityProvider.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/BrooklynUserWithRandomPasswordSecurityProvider.java
@@ -70,4 +70,9 @@ private boolean isRemoteAddressLocalhost(HttpSession session) {
     public String toString() {
         return JavaClassNames.cleanSimpleClassName(this);
     }
+    
+    @Override
+    public boolean requiresUserPass() {
+        return true;
+    }
 }
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/DelegatingSecurityProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/DelegatingSecurityProvider.java
index 94b7894553..c3c74500ad 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/DelegatingSecurityProvider.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/DelegatingSecurityProvider.java
@@ -20,6 +20,8 @@
 
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.concurrent.atomic.AtomicLong;
 
 import javax.servlet.http.HttpSession;
@@ -27,9 +29,12 @@
 import org.apache.brooklyn.api.mgmt.ManagementContext;
 import org.apache.brooklyn.config.StringConfigMap;
 import org.apache.brooklyn.core.internal.BrooklynProperties;
+import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
 import org.apache.brooklyn.rest.BrooklynWebConfig;
 import org.apache.brooklyn.util.core.ClassLoaderUtils;
-import org.apache.brooklyn.util.text.Strings;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -72,37 +77,33 @@ private synchronized SecurityProvider loadDelegate() {
 
         SecurityProvider presetDelegate = brooklynProperties.getConfig(BrooklynWebConfig.SECURITY_PROVIDER_INSTANCE);
         if (presetDelegate!=null) {
-            log.info("REST using pre-set security provider " + presetDelegate);
+            log.trace("Brooklyn security: using pre-set security provider {}", presetDelegate);
             return presetDelegate;
         }
         
         String className = brooklynProperties.getConfig(BrooklynWebConfig.SECURITY_PROVIDER_CLASSNAME);
 
         if (delegate != null && BrooklynWebConfig.hasNoSecurityOptions(mgmt.getConfig())) {
-            log.debug("{} refusing to change from {}: No security provider set in reloaded properties.",
+            log.debug("Brooklyn security: {} refusing to change from {}: No security provider set in reloaded properties.",
                     this, delegate);
             return delegate;
         }
-        log.info("REST using security provider " + className);
 
         try {
-            ClassLoaderUtils clu = new ClassLoaderUtils(this, mgmt);
-            Class<? extends SecurityProvider> clazz;
-            try {
-                clazz = (Class<? extends SecurityProvider>) clu.loadClass(className);
-            } catch (Exception e) {
-                String oldPackage = "brooklyn.web.console.security.";
-                if (className.startsWith(oldPackage)) {
-                    className = Strings.removeFromStart(className, oldPackage);
-                    className = DelegatingSecurityProvider.class.getPackage().getName() + "." + className;
-                    clazz = (Class<? extends SecurityProvider>) clu.loadClass(className);
-                    log.warn("Deprecated package " + oldPackage + " detected; please update security provider to point to " + className);
-                } else throw e;
+            String bundle = brooklynProperties.getConfig(BrooklynWebConfig.SECURITY_PROVIDER_BUNDLE);
+            if (bundle!=null) {
+                String bundleVersion = brooklynProperties.getConfig(BrooklynWebConfig.SECURITY_PROVIDER_BUNDLE_VERSION);
+                log.info("Brooklyn security: using security provider " + className + " from " + bundle+":"+bundleVersion);
+                BundleContext bundleContext = ((ManagementContextInternal)mgmt).getOsgiManager().get().getFramework().getBundleContext();
+                delegate = loadProviderFromBundle(mgmt, bundleContext, bundle, bundleVersion, className);
+            } else {
+                log.info("Brooklyn security: using security provider " + className);
+                ClassLoaderUtils clu = new ClassLoaderUtils(this, mgmt);
+                Class<? extends SecurityProvider> clazz = (Class<? extends SecurityProvider>) clu.loadClass(className);
+                delegate = createSecurityProviderInstance(mgmt, clazz);
             }
-
-            delegate = createSecurityProviderInstance(mgmt, clazz);
         } catch (Exception e) {
-            log.warn("REST unable to instantiate security provider " + className + "; all logins are being disallowed", e);
+            log.warn("Brooklyn security: unable to instantiate security provider " + className + "; all logins are being disallowed", e);
             delegate = new BlackholeSecurityProvider();
         }
 
@@ -113,6 +114,52 @@ private synchronized SecurityProvider loadDelegate() {
         return delegate;
     }
 
+    public static SecurityProvider loadProviderFromBundle(
+        ManagementContext mgmt, BundleContext bundleContext,
+        String symbolicName, String version, String className) {
+        try {
+            Collection<Bundle> bundles = getMatchingBundles(bundleContext, symbolicName, version);
+            if (bundles.isEmpty()) {
+                throw new IllegalStateException("No bundle " + symbolicName + ":" + version + " found");
+            } else if (bundles.size() > 1) {
+                log.warn("Brooklyn security: found multiple bundles matching symbolicName " + symbolicName + " and version " + version +
+                    " while trying to load security provider " + className + ". Will use first one that loads the class successfully.");
+            }
+            SecurityProvider p = tryLoadClass(mgmt, className, bundles);
+            if (p == null) {
+                throw new ClassNotFoundException("Unable to load class " + className + " from bundle " + symbolicName + ":" + version);
+            }
+            return p;
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
+            throw new IllegalStateException("Can not load or create security provider " + className + " for bundle " + symbolicName + ":" + version, e);
+        }
+    }
+
+    private static SecurityProvider tryLoadClass(ManagementContext mgmt, String className, Collection<Bundle> bundles)
+        throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
+        for (Bundle b : bundles) {
+            try {
+                @SuppressWarnings("unchecked")
+                Class<? extends SecurityProvider> securityProviderType = (Class<? extends SecurityProvider>) b.loadClass(className);
+                return DelegatingSecurityProvider.createSecurityProviderInstance(mgmt, securityProviderType);
+            } catch (ClassNotFoundException e) {
+            }
+        }
+        return null;
+    }
+
+    private static Collection<Bundle> getMatchingBundles(BundleContext bundleContext, final String symbolicName, final String version) {
+        Collection<Bundle> bundles = new ArrayList<>();
+        for (Bundle b : bundleContext.getBundles()) {
+            if (b.getSymbolicName().equals(symbolicName) &&
+                (version == null || b.getVersion().toString().equals(version))) {
+                bundles.add(b);
+            }
+        }
+        return bundles;
+    }
+
     public static SecurityProvider createSecurityProviderInstance(ManagementContext mgmt,
             Class<? extends SecurityProvider> clazz) throws NoSuchMethodException, InstantiationException,
                     IllegalAccessException, InvocationTargetException {
@@ -140,19 +187,12 @@ protected void invalidateExistingSessions() {
 
     @Override
     public boolean isAuthenticated(HttpSession session) {
-        if (session == null) return false;
-        Object modCountWhenFirstAuthenticated = session.getAttribute(getModificationCountKey());
-        boolean authenticated = getDelegate().isAuthenticated(session) &&
-                Long.valueOf(modCount.get()).equals(modCountWhenFirstAuthenticated);
-        return authenticated;
+        return getDelegate().isAuthenticated(session);
     }
 
     @Override
-    public boolean authenticate(HttpSession session, String user, String password) {
+    public boolean authenticate(HttpSession session, String user, String password) throws SecurityProviderDeniedAuthentication {
         boolean authenticated = getDelegate().authenticate(session, user, password);
-        if (authenticated) {
-            session.setAttribute(getModificationCountKey(), modCount.get());
-        }
         if (log.isTraceEnabled() && authenticated) {
             log.trace("User {} authenticated with provider {}", user, getDelegate());
         } else if (!authenticated && log.isDebugEnabled()) {
@@ -163,14 +203,17 @@ public boolean authenticate(HttpSession session, String user, String password) {
 
     @Override
     public boolean logout(HttpSession session) { 
-        boolean logout = getDelegate().logout(session);
-        if (logout) {
-            session.removeAttribute(getModificationCountKey());
-        }
-        return logout;
+        return getDelegate().logout(session);
+    }
+
+    @Override
+    public boolean requiresUserPass() {
+        return getDelegate().requiresUserPass();
     }
 
-    private String getModificationCountKey() {
-        return getClass().getName() + ".ModCount";
+    @Override
+    public String toString() {
+        return super.toString()+"["+getDelegate()+"]";
     }
+    
 }
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/ExplicitUsersSecurityProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/ExplicitUsersSecurityProvider.java
index a0795cb9c2..2d9be552a3 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/ExplicitUsersSecurityProvider.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/ExplicitUsersSecurityProvider.java
@@ -114,4 +114,9 @@ public static boolean checkPassword(String candidatePassword, String expectedPas
 
         return false;
     }
+    
+    @Override
+    public boolean requiresUserPass() {
+        return true;
+    }
 }
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/LdapSecurityProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/LdapSecurityProvider.java
index d3636e953c..cc9b013bd5 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/LdapSecurityProvider.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/LdapSecurityProvider.java
@@ -129,4 +129,9 @@ public synchronized static void checkCanLoad() {
             throw Exceptions.propagate(new ClassNotFoundException("Unable to load LDAP classes ("+LDAP_CONTEXT_FACTORY+") required for Brooklyn LDAP security provider"));
         }
     }
+    
+    @Override
+    public boolean requiresUserPass() {
+        return true;
+    }
 }
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/OauthSecurityProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/OauthSecurityProvider.java
new file mode 100644
index 0000000000..70110329da
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/OauthSecurityProvider.java
@@ -0,0 +1,322 @@
+/*
+ * 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.brooklyn.rest.security.provider;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpSession;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.rest.filter.BrooklynSecurityProviderFilterHelper;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.text.Identifiers;
+import org.apache.brooklyn.util.text.Strings;
+import org.apache.brooklyn.util.time.Duration;
+import org.apache.brooklyn.util.yaml.Yamls;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpRequestBase;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.util.EntityUtils;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.HttpConnection;
+import org.eclipse.jetty.server.Request;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.Beta;
+
+/** Configurable OAuth redirect security provider
+ * 
+ *  Redirects all inbound requests to an oath web server unless a session token is specified. */
+@Beta  // work in progress
+public class OauthSecurityProvider implements SecurityProvider {
+
+    public static final Logger log = LoggerFactory.getLogger(OauthSecurityProvider.class);
+
+    private static final String OAUTH_ACCESS_TOKEN_SESSION_KEY = "org.apache.brooklyn.security.oauth.access_token";
+    private static final String OAUTH_ACCESS_TOKEN_EXPIRY_UTC_KEY = "org.apache.brooklyn.security.oauth.access_token_expiry_utc";
+    
+    private static final String OAUTH_AUTH_CODE_PARAMETER_FROM_USER = "code";
+    private static final String OAUTH_AUTH_CODE_PARAMETER_FOR_SERVER = OAUTH_AUTH_CODE_PARAMETER_FROM_USER;
+    
+//    private static String KEY_PREFIX = BrooklynWebConfig.BASE_NAME_SECURITY+".oauth.";
+//    ConfigKey<String> URI_GET_TOKEN_KEY = ConfigKeys.newStringConfigKey(KEY_PREFIX+"uriGetToken", "URL where token can be fetched");
+    // TODO parameterise values below with keys as above
+    
+    // tempting to use getJettyRequest().getRequestURL().toString();
+    // but some oauth providers require this to be declared
+    private String callbackUri = "http://localhost.io:8081/";
+    private String accessTokenResponseKey = "access_token";
+    private String audience = "audience";
+    private Duration validity = Duration.hours(1);
+    
+    // google test data - hard-coded for now
+    private String uriGetToken = "https://accounts.google.com/o/oauth2/token";
+    private String uriAuthorize = "https://accounts.google.com/o/oauth2/auth";
+    private String uriTokenInfo = "https://www.googleapis.com/oauth2/v1/tokeninfo";
+    private String clientId = "789182012565-burd24h3bc0im74g2qemi7lnihvfqd02.apps.googleusercontent.com";
+    private String clientSecret = "X00v-LfU34U4SfsHqPKMWfQl";
+    
+    // github test data
+//    private String uriGetToken = "https://github.com/login/oauth/authorize";
+//    private String uriAuthorize = "https://github.com/login/oauth/authorize";
+//    private String uriTokenInfo = "https://github.com/login/oauth/access_token";
+//    private String clientId = "7f76b9970d8ac15b30b0";
+//    private String clientSecret = "9e15f8dd651f0b1896a3a582f17fa82f049fc910";
+    
+    protected final ManagementContext mgmt;
+
+    public OauthSecurityProvider(ManagementContext mgmt) {
+        this.mgmt = mgmt;
+        initialize();
+    }
+
+    private synchronized void initialize() {
+        // TODO set these keys
+//        Preconditions.checkNotNull(mgmt.getConfig().getConfig(URI_GET_TOKEN_KEY), "URI to get token must be set: "+URI_GET_TOKEN_KEY.getName());
+    }
+    
+    @Override
+    public boolean isAuthenticated(HttpSession session) {
+        // TODO tidy log messages
+        log.info("isAuthenticated 1 "+getJettyRequest().getRequestURI()+" "+session+" ... "+this);
+        if(session==null) return false;
+
+        Object token = session.getAttribute(OAUTH_ACCESS_TOKEN_SESSION_KEY);
+        // TODO is it valid?
+        return token!=null;
+    }
+
+    @Override
+    public boolean authenticate(HttpSession session, String user, String password) throws SecurityProviderDeniedAuthentication {
+        log.info("authenticate "+session+" "+user);
+        
+        if (isAuthenticated(session)) {
+            return true;
+        }
+        
+        Request request = getJettyRequest();
+        // Redirection from the authenticator server
+        String code = request.getParameter(OAUTH_AUTH_CODE_PARAMETER_FROM_USER);
+        // Getting token, if exists, from the current session
+        String token = (String) request.getSession().getAttribute(OAUTH_ACCESS_TOKEN_SESSION_KEY);
+        
+        try {
+            if (Strings.isNonBlank(code)) {
+                return retrieveTokenForAuthCodeFromOauthServer(session, code);
+            } else if (Strings.isNonBlank(token)) {
+                // they have a token but no auth code and not or no longer authenticated; 
+                // we need to check that the token is still valid
+                return validateTokenAgainstOauthServer(token);
+            } else {
+                // no token or code; the user needs to log in
+                return redirectUserToOauthLoginUi();
+            }
+        } catch (SecurityProviderDeniedAuthentication e) {
+            throw e;
+        } catch (Exception e) {
+            log.warn("Error performing OAuth: "+e, e);
+            throw Exceptions.propagate(e);
+        }
+    }
+
+    @Override
+    public boolean logout(HttpSession session) {
+        log.info("logout");
+        session.removeAttribute(OAUTH_ACCESS_TOKEN_SESSION_KEY);
+        session.removeAttribute(OAUTH_ACCESS_TOKEN_EXPIRY_UTC_KEY);
+        return true;
+    }
+    
+    @Override
+    public boolean requiresUserPass() {
+        return false;
+    }
+
+    private boolean retrieveTokenForAuthCodeFromOauthServer(HttpSession session, String code) throws ClientProtocolException, IOException, ServletException, SecurityProviderDeniedAuthentication {
+        // get the access token by post to Google
+        HashMap<String, String> params = new HashMap<String, String>();
+        params.put(OAUTH_AUTH_CODE_PARAMETER_FOR_SERVER, code);
+        params.put("client_id", clientId);
+        params.put("client_secret", clientSecret);
+        params.put("redirect_uri", callbackUri);
+        params.put("grant_type", "authorization_code");
+
+        String body = post(uriGetToken, params);
+
+        Map<?,?> jsonObject = null;
+
+        // get the access token from json and request info from Google
+        try {
+            jsonObject = (Map<?,?>) Yamls.parseAll(body).iterator().next();
+            log.info("Parsed '"+body+"' as "+jsonObject);
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
+            log.info("Unable to parse: '"+body+"'");
+            // throw new RuntimeException("Unable to parse json " + body);
+            return redirectUserToOauthLoginUi();
+        }
+
+        // TODO validate
+        
+        // Put token in session
+        String accessToken = (String) jsonObject.get(accessTokenResponseKey);
+        session.setAttribute(OAUTH_ACCESS_TOKEN_SESSION_KEY, accessToken);
+        
+        // TODO record code?
+//        request.getSession().setAttribute(SESSION_KEY_CODE, code);
+
+        // TODO is it valid?
+        log.debug("Got token/code "+accessToken+"/"+code+" from "+jsonObject);
+        // eg Got token/code 
+        // ya29.GluHBtzZ-R-CaoWMlso6KB6cq3DrbmwX6B3kjMmzWqzU-vO76WjKuNS3Ktog7vt9CJnxSZ63NmqO4p5bg20wl0-M14yO1LuoXNV5JX3qHDmXl2rl-z1LbCPEYJ-o
+        //    /  4/yADFJRSRCxLgZFcpD_KU2jQiCXBGNHTsw0eGZqZ2t6IJJh2O1oWBnBDx4eWl4ZLCRAFJx3QjPYtl7LF9zj_DNlA 
+        // from {
+        //   access_token=ya29.GluHBtzZ-R-CaoWMlso6KB6cq3DrbmwX6B3kjMmzWqzU-vO76WjKuNS3Ktog7vt9CJnxSZ63NmqO4p5bg20wl0-M14yO1LuoXNV5JX3qHDmXl2rl-z1LbCPEYJ-o, 
+        //   expires_in=3600, 
+        //   refresh_token=1/b2Xk2rCVqKFsbz_xePv1tctvihnLoyo0YHsw4YQWK8M, 
+        //   scope=https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/plus.me, 
+        //   token_type=Bearer, 
+        //   id_token=eyJhbGciOiJSUzI1NiIsImtpZCI6Ijc5NzhhOTEzNDcyNjFhMjkxYmQ3MWRjYWI0YTQ2NGJlN2QyNzk2NjYiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwiYXpwIjoiNzg5MTgyMDEyNTY1LWJ1cmQyNGgzYmMwaW03NGcycWVtaTdsbmlodmZxZDAyLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiYXVkIjoiNzg5MTgyMDEyNTY1LWJ1cmQyNGgzYmMwaW03NGcycWVtaTdsbmlodmZxZDAyLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwic3ViIjoiMTA2MDQyNTE3MjU2MTcxNzYyMTU0IiwiaGQiOiJjbG91ZHNvZnRjb3JwLmNvbSIsImVtYWlsIjoiYWxleC5oZW5ldmVsZEBjbG91ZHNvZnRjb3JwLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJhdF9oYXNoIjoiQXpsdHo3YnR2Wk81eTZiMXMtVzRxdyIsImlhdCI6MTU0NjYyNDE2MiwiZXhwIjoxNTQ2NjI3NzYyfQ.E0NWILU7EEHL3GsveVFW1F91sml9iRceWpfVVc9blSKqafAcNwRl08JKT1FXUgfOUvgoYYj6IDIxT4L59-3CObNHS7RtbDJmIk0eWf_h8OFFGTTtd6P2-FTtM-6HVLKkMcKvJHHB07APsqeQj4o3zWY4G3f0QIX6bb424PwxwcDGS6gO8aA9cX2vVyr90h8FgtR9qnbYQxaSrcQNmEmPYHPZiOMzFoxR5WpXhtmPAFc4sMVFjrvQEf8s3GSr6ciMdC7BtKhfBII8s9iYV4LJCRQjxvsCzZ_PfWAZmExNaNOVltfoo5uGVmDPvzCUbSIoPmpj4jpPJVJ0fQtl7E6Tlg}
+        // TODO get the user's ID : https://stackoverflow.com/questions/24442668/google-oauth-api-to-get-users-email-address
+        String user = accessToken;  // wrong, see above
+        session.setAttribute(BrooklynSecurityProviderFilterHelper.AUTHENTICATED_USER_SESSION_ATTRIBUTE, user);
+        
+        return true;
+    }
+
+    private boolean validateTokenAgainstOauthServer(String token) throws ClientProtocolException, IOException {
+        // TODO support validation, and run periodically
+        
+//        HashMap<String, String> params = new HashMap<String, String>();
+//        params.put(OAUTH_ACCESS_TOKEN_KEY, token);
+//
+//        String body = post(uriTokenInfo, params);
+//        
+//        Map<?,?> jsonObject = null;
+//        // get the access token from json and request info from Google
+//        try {
+//            jsonObject = (Map<?,?>) Yamls.parseAll(body).iterator().next();
+//            LOG.info("Parsed '"+body+"' as "+jsonObject);
+//        } catch (Exception e) {
+//            Exceptions.propagateIfFatal(e);
+//            LOG.info("Unable to parse: '"+body+"'");
+//            throw new RuntimeException("Unable to parse json " + body, e);
+//        }
+//
+//        if (!clientId.equals(jsonObject.get(audience))) {
+//            LOG.warn("Oauth not meant for this client ("+clientId+"), redirecting user to login again: "+jsonObject);
+//            return redirectUserToOauthLoginUi();
+//        }
+//        
+//        // TODO
+//        // if (isTokenExpiredOrNearlySo(...) { ... }
+        
+        return true;
+    }
+
+    // TODO these http methods need tidying
+    
+    // makes a GET request to url and returns body as a string
+    public String get(String url) throws ClientProtocolException, IOException {
+        return execute(new HttpGet(url));
+    }
+    
+    // makes a POST request to url with form parameters and returns body as a
+    // string
+    public String post(String url, Map<String, String> formParameters) throws ClientProtocolException, IOException {
+        HttpPost request = new HttpPost(url);
+
+        List<NameValuePair> nvps = new ArrayList<NameValuePair>();
+        for (String key : formParameters.keySet()) {
+            nvps.add(new BasicNameValuePair(key, formParameters.get(key)));
+        }
+        request.setEntity(new UrlEncodedFormEntity(nvps));
+
+        return execute(request);
+    }
+    
+    // makes request and checks response code for 200
+    private String execute(HttpRequestBase request) throws ClientProtocolException, IOException {
+        // TODO tidy
+        HttpClient httpClient = new DefaultHttpClient();
+        HttpResponse response = httpClient.execute(request);
+
+        HttpEntity entity = response.getEntity();
+        String body = EntityUtils.toString(entity);
+
+        if (response.getStatusLine().getStatusCode() != 200) {
+            throw new RuntimeException(
+                    "Expected 200 but got " + response.getStatusLine().getStatusCode() + ", with body " + body);
+        }
+
+        return body;
+    }
+
+    private boolean redirectUserToOauthLoginUi() throws IOException, SecurityProviderDeniedAuthentication {
+        String state=Identifiers.makeRandomId(12); //should be stored in session
+        StringBuilder oauthUrl = new StringBuilder().append(uriAuthorize)
+                .append("?response_type=").append("code")
+                .append("&client_id=").append(clientId) // the client id from the api console registration
+                .append("&redirect_uri=").append(callbackUri) // the servlet that github redirects to after
+                // authorization
+//                .append("&scope=").append("user public_repo")
+                .append("&scope=openid%20email") // scope is the api permissions we
+                .append("&state=").append(state)
+                .append("&access_type=offline") // here we are asking to access to user's data while they are not
+                // signed in
+                .append("&approval_prompt=force"); // this requires them to verify which account to use, if they are
+        // already signed in
+
+        // just for look inside
+//        Collection<String> originalHeaders = response.getHeaderNames();
+
+        throw new SecurityProviderDeniedAuthentication(
+            Response.status(Status.FOUND).header(HttpHeader.LOCATION.asString(), oauthUrl.toString()).build());
+//        response.addHeader("Origin", "http://localhost.io:8081");
+//        response.addHeader("Access-Control-Allow-Origin", "*");
+//        response.addHeader("Access-Control-Request-Method", "GET, POST");
+//        response.addHeader("Access-Control-Request-Headers", "origin, x-requested-with");
+    }
+
+    private Request getJettyRequest() {
+        return Optional.ofNullable(HttpConnection.getCurrentConnection())
+                .map(HttpConnection::getHttpChannel)
+                .map(HttpChannel::getRequest)
+                .orElse(null);
+    }
+
+}
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/SecurityProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/SecurityProvider.java
index 57d1400a73..0854c7f303 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/SecurityProvider.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/SecurityProvider.java
@@ -18,7 +18,10 @@
  */
 package org.apache.brooklyn.rest.security.provider;
 
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 import javax.servlet.http.HttpSession;
+import javax.ws.rs.core.Response;
 
 /**
  * The SecurityProvider is responsible for doing authentication.
@@ -27,9 +30,31 @@
  */
 public interface SecurityProvider {
 
-    public boolean isAuthenticated(HttpSession session);
-    
-    public boolean authenticate(HttpSession session, String user, String password);
-    
+    public boolean isAuthenticated(@Nullable HttpSession session);
+    /** whether this provider requires a user/pass; if this returns false, the framework can
+     * send null/null as the user/pass to {@link #authenticate(HttpSession, String, String)},
+     * and should do that if user/pass info is not immediately available
+     * (ie for things like oauth, the framework should not require basic auth if this method returns false)
+     */
+    public boolean requiresUserPass();
+    /** Perform the authentication. If {@link #requiresUserPass()} returns false, user/pass may be null;
+     * otherwise the framework will guarantee the basic auth is in effect and these values are set.
+     * The provider should not send a response but should throw {@link SecurityProviderDeniedAuthentication}
+     * if a custom response is required. It can include a response in that exception,
+     * e.g. to provide more information or supply a redirect. */
+    public boolean authenticate(@Nonnull HttpSession session, String user, String pass) throws SecurityProviderDeniedAuthentication;
     public boolean logout(HttpSession session);
+    
+    public static class SecurityProviderDeniedAuthentication extends Exception {
+        private static final long serialVersionUID = -3048228939219746783L;
+        private final Response response;
+        public SecurityProviderDeniedAuthentication() { this(null); }
+        public SecurityProviderDeniedAuthentication(Response r) {
+            this.response = r;
+        }
+        public Response getResponse() {
+            return response;
+        }
+    }
+
 }
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/ManagementContextProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/ManagementContextProvider.java
index 46a22385c0..fc80fe89ee 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/ManagementContextProvider.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/ManagementContextProvider.java
@@ -24,13 +24,19 @@
 import javax.ws.rs.ext.Provider;
 
 import org.apache.brooklyn.api.mgmt.ManagementContext;
-import org.apache.brooklyn.core.server.BrooklynServiceAttributes;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
+import com.google.common.annotations.Beta;
 import com.google.common.annotations.VisibleForTesting;
 
 @Provider
 // Needed by tests in rest-resources module and by main code in rest-server
 public class ManagementContextProvider implements ContextResolver<ManagementContext> {
+    
+    @SuppressWarnings("unused")
+    private static final Logger log = LoggerFactory.getLogger(ManagementContextProvider.class);
+    
     @Context
     private ServletContext context;
 
@@ -38,6 +44,11 @@
 
     public ManagementContextProvider() {
     }
+    
+    // for usages that cannot do injection 
+    public ManagementContextProvider(ServletContext context) {
+        this.context = context;
+    }
 
     @VisibleForTesting
     public ManagementContextProvider(ManagementContext mgmt) {
@@ -47,14 +58,18 @@ public ManagementContextProvider(ManagementContext mgmt) {
     @Override
     public ManagementContext getContext(Class<?> type) {
         if (type == ManagementContext.class) {
-            if (mgmt != null) {
-                return mgmt;
-            } else {
-                return (ManagementContext) context.getAttribute(BrooklynServiceAttributes.BROOKLYN_MANAGEMENT_CONTEXT);
-            }
+            return getManagementContext();
         } else {
             return null;
         }
     }
+    
+    @Beta
+    public ManagementContext getManagementContext() {
+        if (mgmt != null) {
+            return mgmt;
+        }
+        return OsgiCompat.getManagementContext(context);
+    }
 
 }
diff --git a/rest/rest-resources/src/main/resources/OSGI-INF/blueprint/service.xml b/rest/rest-resources/src/main/resources/OSGI-INF/blueprint/service.xml
index 5fe7f02da4..1f10b4bafa 100644
--- a/rest/rest-resources/src/main/resources/OSGI-INF/blueprint/service.xml
+++ b/rest/rest-resources/src/main/resources/OSGI-INF/blueprint/service.xml
@@ -34,11 +34,11 @@ limitations under the License.
 
     <cm:property-placeholder persistent-id="org.apache.brooklyn.rest.filter.cors">
         <cm:default-properties>
-            <cm:property name="cors.enabled" value="false"/>
+            <cm:property name="cors.enabled" value="true"/>
             <cm:property name="cors.allow.origins" value=""/>
             <cm:property name="cors.allow.headers" value=""/>
             <cm:property name="cors.expose.headers" value=""/>
-            <cm:property name="cors.allow.credentials" value="false"/>
+            <cm:property name="cors.allow.credentials" value="true"/>
             <cm:property name="cors.max.age" value="-1"/>
             <cm:property name="cors.preflight.error.status" value="200"/>
             <cm:property name="cors.block.if.unauthorized" value="false"/>
@@ -59,16 +59,8 @@ limitations under the License.
     <reference id="localManagementContext"
                interface="org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal"/>
 
-    <jaas:config name="webconsole">
-        <jaas:module className="org.apache.brooklyn.rest.security.jaas.BrooklynLoginModule" flags="required"/>
-    </jaas:config>
-
     <reference id="shutdownHandler" interface="org.apache.brooklyn.core.mgmt.ShutdownHandler"/>
 
-    <bean class="org.apache.brooklyn.rest.security.jaas.ManagementContextHolder">
-        <property name="managementContext" ref="localManagementContext"/>
-    </bean>
-
     <bean id="accessResourceBean" class="org.apache.brooklyn.rest.resources.AccessResource"/>
     <bean id="activityResourceBean" class="org.apache.brooklyn.rest.resources.ActivityResource"/>
     <bean id="adjunctResourceBean" class="org.apache.brooklyn.rest.resources.AdjunctResource"/>
@@ -116,17 +108,10 @@ limitations under the License.
             <bean class="org.apache.brooklyn.rest.util.DefaultExceptionMapper"/>
             <bean class="org.apache.brooklyn.rest.util.json.BrooklynJacksonJsonProvider"/>
             <bean class="org.apache.brooklyn.rest.util.FormMapProvider"/>
-            <bean class="org.apache.cxf.jaxrs.security.JAASAuthenticationFilter">
-                <property name="contextName" value="webconsole"/>
-                <property name="realmName" value="webconsole"/>
-            </bean>
             <bean class="org.apache.brooklyn.rest.util.ManagementContextProvider">
                 <argument ref="localManagementContext"/>
             </bean>
-            <!--
-                TODO ShutdownHandlerProvider, sync with init work.
-                Needs to be custom OSGi implementation?
-            -->
+            <bean class="org.apache.brooklyn.rest.filter.BrooklynSecurityProviderFilterJersey"/>
             <bean class="org.apache.brooklyn.rest.filter.CsrfTokenFilter"/>
             <bean class="org.apache.brooklyn.rest.filter.RequestTaggingRsFilter"/>
             <bean class="org.apache.brooklyn.rest.filter.NoCacheFilter"/>
diff --git a/rest/rest-resources/src/main/resources/jaas.conf b/rest/rest-resources/src/main/resources/jaas.conf
deleted file mode 100644
index bb18334158..0000000000
--- a/rest/rest-resources/src/main/resources/jaas.conf
+++ /dev/null
@@ -1,21 +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.
- */
-webconsole {
-   org.apache.brooklyn.rest.security.jaas.BrooklynLoginModule required;
-};
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/filter/EntitlementContextFilterTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/filter/EntitlementContextFilterTest.java
index 0d37cc8610..b266bd4163 100644
--- a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/filter/EntitlementContextFilterTest.java
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/filter/EntitlementContextFilterTest.java
@@ -29,13 +29,12 @@
 import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
 import org.apache.brooklyn.core.mgmt.entitlement.WebEntitlementContext;
 import org.apache.brooklyn.rest.BrooklynWebConfig;
-import org.apache.brooklyn.rest.security.jaas.JaasUtils;
 import org.apache.brooklyn.rest.security.provider.ExplicitUsersSecurityProvider;
 import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
-import org.apache.cxf.interceptor.security.JAASLoginInterceptor;
 import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
 import org.apache.cxf.jaxrs.client.WebClient;
 import org.apache.http.HttpStatus;
+import org.eclipse.jetty.server.session.SessionHandler;
 import org.testng.annotations.Test;
 
 public class EntitlementContextFilterTest extends BrooklynRestResourceTest {
@@ -58,18 +57,13 @@ protected void configureCXF(JAXRSServerFactoryBean sf) {
         props.put(BrooklynWebConfig.PASSWORD_FOR_USER(USER_PASS), USER_PASS);
         getManagementContext().getScratchpad().put(BrooklynWebConfig.SECURITY_PROVIDER_INSTANCE, new ExplicitUsersSecurityProvider(getManagementContext()));
 
+        sf.setProvider(new SessionHandler());
         super.configureCXF(sf);
-
-        JaasUtils.init(getManagementContext());
-
-        JAASLoginInterceptor jaas = new JAASLoginInterceptor();
-        jaas.setContextName("webconsole");
-        sf.getInInterceptors().add(jaas);
-
     }
 
     @Override
     protected void addBrooklynResources() {
+        addResource(new BrooklynSecurityProviderFilterJersey());
         addResource(new RequestTaggingRsFilter());
         addResource(new EntitlementContextFilter());
         addResource(new EntitlementResource());
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/security/jaas/BrooklynLoginModuleTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/security/jaas/BrooklynLoginModuleTest.java
deleted file mode 100644
index f33e80789d..0000000000
--- a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/security/jaas/BrooklynLoginModuleTest.java
+++ /dev/null
@@ -1,195 +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.brooklyn.rest.security.jaas;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertTrue;
-import static org.testng.Assert.fail;
-
-import java.util.Map;
-
-import javax.security.auth.Subject;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.login.FailedLoginException;
-import javax.security.auth.login.LoginException;
-
-import org.apache.brooklyn.core.internal.BrooklynProperties;
-import org.apache.brooklyn.core.test.BrooklynMgmtUnitTestSupport;
-import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
-import org.apache.brooklyn.rest.BrooklynWebConfig;
-import org.apache.brooklyn.util.collections.MutableMap;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-
-// http://docs.oracle.com/javase/7/docs/technotes/guides/security/jaas/JAASLMDevGuide.html
-public class BrooklynLoginModuleTest extends BrooklynMgmtUnitTestSupport {
-    private static final String ACCEPTED_USER = "user";
-    private static final String ACCEPTED_PASSWORD = "password";
-    private static final String DEFAULT_ROLE = "webconsole";
-    private CallbackHandler GOOD_CB_HANDLER = new TestCallbackHandler(
-            ACCEPTED_USER,
-            ACCEPTED_PASSWORD);
-    private CallbackHandler BAD_CB_HANDLER = new TestCallbackHandler(
-            ACCEPTED_USER + ".invalid",
-            ACCEPTED_PASSWORD + ".invalid");
-
-    private Subject subject;
-    private Map<String, ?> sharedState;
-    private Map<String, ?> options;
-
-    private BrooklynLoginModule module;
-
-    @Override
-    @BeforeMethod(alwaysRun = true)
-    public void setUp() throws Exception {
-        BrooklynProperties properties = BrooklynProperties.Factory.newEmpty();
-        properties.addFrom(ImmutableMap.of(
-                BrooklynWebConfig.USERS, ACCEPTED_USER,
-                BrooklynWebConfig.PASSWORD_FOR_USER("user"), ACCEPTED_PASSWORD));
-        mgmt = LocalManagementContextForTests.builder(true).useProperties(properties).build();
-        ManagementContextHolder.setManagementContextStatic(mgmt);
-
-        super.setUp();
-
-        subject = new Subject();
-        sharedState = MutableMap.of();
-        options = ImmutableMap.of();
-
-        module = new BrooklynLoginModule();
-    }
-
-    @Test
-    public void testMissingCallback() throws LoginException {
-        module.initialize(subject, null, sharedState, options);
-        try {
-            module.login();
-            fail("Login is supposed to fail due to missing callback");
-        } catch (FailedLoginException e) {
-            // Expected, ignore
-        }
-        assertFalse(module.commit(), "commit");
-        assertEmptyPrincipals();
-        assertFalse(module.abort(), "abort");
-    }
-
-    @Test
-    public void testFailedLoginCommitAbort() throws LoginException {
-        badLogin();
-        assertFalse(module.commit(), "commit");
-        assertEmptyPrincipals();
-        assertFalse(module.abort(), "abort");
-    }
-
-    @Test
-    public void testFailedLoginCommitAbortReadOnly() throws LoginException {
-        subject.setReadOnly();
-        badLogin();
-        assertFalse(module.commit(), "commit");
-        assertEmptyPrincipals();
-        assertFalse(module.abort(), "abort");
-    }
-
-    @Test
-    public void testFailedLoginAbort() throws LoginException {
-        badLogin();
-        assertFalse(module.abort(), "abort");
-        assertEmptyPrincipals();
-    }
-
-    @Test
-    public void testSuccessfulLoginCommitLogout() throws LoginException {
-        goodLogin();
-        assertTrue(module.commit(), "commit");
-        assertBrooklynPrincipal();
-        assertTrue(module.logout(), "logout");
-        assertEmptyPrincipals();
-    }
-
-    @Test
-    public void testSuccessfulLoginCommitAbort() throws LoginException {
-        goodLogin();
-        assertTrue(module.commit(), "commit");
-        assertBrooklynPrincipal();
-        assertTrue(module.abort(), "logout");
-        assertEmptyPrincipals();
-    }
-
-    @Test
-    public void testSuccessfulLoginCommitAbortReadOnly() throws LoginException {
-        subject.setReadOnly();
-        goodLogin();
-        try {
-            module.commit();
-            fail("Commit expected to throw");
-        } catch (LoginException e) {
-            // Expected
-        }
-        assertTrue(module.abort());
-    }
-
-    @Test
-    public void testSuccessfulLoginAbort() throws LoginException {
-        goodLogin();
-        assertTrue(module.abort(), "abort");
-        assertEmptyPrincipals();
-    }
-    
-    @Test
-    public void testCustomRole() throws LoginException {
-        String role = "users";
-        options = ImmutableMap.<String, Object>of(BrooklynLoginModule.PROPERTY_ROLE, role);
-        goodLogin();
-        assertTrue(module.commit(), "commit");
-        assertBrooklynPrincipal(role);
-    }
-
-    private void goodLogin() throws LoginException {
-        module.initialize(subject, GOOD_CB_HANDLER, sharedState, options);
-        assertTrue(module.login(), "login");
-        assertEmptyPrincipals();
-    }
-
-    private void badLogin() throws LoginException {
-        module.initialize(subject, BAD_CB_HANDLER, sharedState, options);
-        try {
-            module.login();
-            fail("Login is supposed to fail due to invalid username+password pair");
-        } catch (FailedLoginException e) {
-            // Expected, ignore
-        }
-    }
-
-    private void assertBrooklynPrincipal() {
-        assertBrooklynPrincipal(DEFAULT_ROLE);
-    }
-    private void assertBrooklynPrincipal(String role) {
-        assertEquals(subject.getPrincipals(), ImmutableSet.of(
-                new BrooklynLoginModule.UserPrincipal(ACCEPTED_USER),
-                new BrooklynLoginModule.RolePrincipal(role)));
-    }
-
-    private void assertEmptyPrincipals() {
-        assertEquals(subject.getPrincipals().size(), 0);
-    }
-
-}
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/security/jaas/TestCallbackHandler.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/security/jaas/TestCallbackHandler.java
deleted file mode 100644
index 4854196523..0000000000
--- a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/security/jaas/TestCallbackHandler.java
+++ /dev/null
@@ -1,50 +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.brooklyn.rest.security.jaas;
-
-import java.io.IOException;
-
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.NameCallback;
-import javax.security.auth.callback.PasswordCallback;
-import javax.security.auth.callback.UnsupportedCallbackException;
-
-public class TestCallbackHandler implements CallbackHandler {
-    private String username;
-    private String password;
-
-    public TestCallbackHandler(String username, String password) {
-        this.username = username;
-        this.password = password;
-    }
-
-    @Override
-    public void handle(Callback[] callbacks)
-            throws IOException, UnsupportedCallbackException {
-        for (Callback cb : callbacks) {
-            if (cb instanceof NameCallback) {
-                ((NameCallback)cb).setName(username);
-            } else if (cb instanceof PasswordCallback) {
-                ((PasswordCallback)cb).setPassword(password.toCharArray());
-            }
-        }
-    }
-
-}
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestApiTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestApiTest.java
index 54c9384419..de9199efac 100644
--- a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestApiTest.java
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestApiTest.java
@@ -55,7 +55,9 @@
     
     protected TestShutdownHandler shutdownListener = createShutdownHandler();
     protected final static String ENDPOINT_ADDRESS_LOCAL = "local://";
-    protected final static String ENDPOINT_ADDRESS_HTTP = "http://localhost:9998/";
+    protected final static String ENDPOINT_ADDRESS_HOST = "localhost";
+    protected final static int ENDPOINT_ADDRESS_PORT = 9998;
+    protected final static String ENDPOINT_ADDRESS_HTTP = "http://"+ENDPOINT_ADDRESS_HOST+":"+ENDPOINT_ADDRESS_PORT+"/";
 
     protected Set<Class<?>> resourceClasses;
     protected Set<Object> resourceBeans;
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestResourceTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestResourceTest.java
index 183f3de9fd..a1c1067f07 100644
--- a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestResourceTest.java
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestResourceTest.java
@@ -43,10 +43,13 @@
 import org.apache.brooklyn.util.net.Urls;
 import org.apache.brooklyn.util.repeat.Repeater;
 import org.apache.brooklyn.util.time.Duration;
+import org.apache.cxf.BusFactory;
 import org.apache.cxf.endpoint.Server;
 import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
 import org.apache.cxf.jaxrs.client.WebClient;
 import org.apache.cxf.jaxrs.utils.ResourceUtils;
+import org.apache.cxf.transport.http_jetty.JettyHTTPServerEngine;
+import org.apache.cxf.transport.http_jetty.JettyHTTPServerEngineFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.Assert;
@@ -58,7 +61,8 @@
 
     private static final Logger log = LoggerFactory.getLogger(BrooklynRestResourceTest.class);
 
-    private static Server server;
+    private JettyHTTPServerEngine serverEngine;
+    private Server server;
     protected List<?> clientProviders;
     
     class DefaultTestApp extends javax.ws.rs.core.Application {
@@ -89,7 +93,12 @@ protected void destroyClass() throws Exception {
     protected synchronized void startServer() throws Exception {
         if (server == null) {
             setUpResources();
-            JAXRSServerFactoryBean sf = ResourceUtils.createApplication(createRestApp(), true);
+            
+            // needed to enable session support
+            serverEngine = new JettyHTTPServerEngineFactory().createJettyHTTPServerEngine(
+                ENDPOINT_ADDRESS_HOST, ENDPOINT_ADDRESS_PORT, "http"); 
+            serverEngine.setSessionSupport(true);
+            JAXRSServerFactoryBean sf = ResourceUtils.createApplication(createRestApp(), true,false,false, BusFactory.getDefaultBus());
             if (clientProviders == null) {
                 clientProviders = sf.getProviders();
             }
@@ -115,6 +124,10 @@ public synchronized void stopServer() throws Exception {
             server.destroy();
             server = null;
         }
+        if (serverEngine!=null) {
+            serverEngine.shutdown();
+            serverEngine = null;
+        }
     }
 
 
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/BrooklynPropertiesSecurityFilter.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/BrooklynPropertiesSecurityFilter.java
deleted file mode 100644
index f5089a148e..0000000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/BrooklynPropertiesSecurityFilter.java
+++ /dev/null
@@ -1,179 +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.brooklyn.rest.filter;
-
-import java.io.IOException;
-
-import javax.servlet.Filter;
-import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
-import javax.servlet.RequestDispatcher;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
-
-import org.apache.brooklyn.api.mgmt.ManagementContext;
-import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
-import org.apache.brooklyn.core.mgmt.entitlement.WebEntitlementContext;
-import org.apache.brooklyn.rest.BrooklynWebConfig;
-import org.apache.brooklyn.rest.resources.LogoutResource;
-import org.apache.brooklyn.rest.security.jaas.BrooklynLoginModule;
-import org.apache.brooklyn.rest.security.provider.DelegatingSecurityProvider;
-import org.apache.brooklyn.rest.util.OsgiCompat;
-import org.apache.brooklyn.util.text.Strings;
-import org.apache.commons.codec.binary.Base64;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Provides basic HTTP authentication.
- * 
- * @deprecated since 0.9.0, use JAAS authentication instead, see {@link BrooklynLoginModule}, {@link LogoutResource}, {@link EntitlementContextFilter}.
- */
-@Deprecated
-public class BrooklynPropertiesSecurityFilter implements Filter {
-
-    /**
-     * The session attribute set for authenticated users; for reference
-     * (but should not be relied up to confirm authentication, as
-     * the providers may impose additional criteria such as timeouts,
-     * or a null user (no login) may be permitted)
-     */
-    public static final String AUTHENTICATED_USER_SESSION_ATTRIBUTE = BrooklynLoginModule.AUTHENTICATED_USER_SESSION_ATTRIBUTE;
-
-    private static final Logger log = LoggerFactory.getLogger(BrooklynPropertiesSecurityFilter.class);
-
-    protected DelegatingSecurityProvider provider;
-
-    private static ThreadLocal<String> originalRequest = new ThreadLocal<String>();
-
-    @Override
-    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
-        HttpServletRequest httpRequest = (HttpServletRequest) request;
-        HttpServletResponse httpResponse = (HttpServletResponse) response;
-        String uri = httpRequest.getRequestURI();
-
-        if (provider == null) {
-            log.warn("No security provider available: disallowing web access to brooklyn");
-            httpResponse.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
-            return;
-        }
-
-        if (originalRequest.get() != null) {
-            // clear the entitlement context before setting to avoid warnings
-            Entitlements.clearEntitlementContext();
-        } else {
-            originalRequest.set(uri);
-        }
-
-        boolean authenticated = provider.isAuthenticated(httpRequest.getSession());
-        if ("/logout".equals(uri) || "/v1/logout".equals(uri)) {
-            httpResponse.setHeader("WWW-Authenticate", "Basic realm=\"brooklyn\"");
-            if (authenticated && httpRequest.getSession().getAttributeNames().hasMoreElements()) {
-                logout(httpRequest);
-                httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
-            } else {
-                RequestDispatcher dispatcher = httpRequest.getRequestDispatcher("/");
-                log.debug("Not authenticated, forwarding request for {} to {}", uri, dispatcher);
-                dispatcher.forward(httpRequest, httpResponse);
-            }
-            return;
-        }
-
-        if (!(httpRequest.getSession().getAttributeNames().hasMoreElements() && provider.isAuthenticated(httpRequest.getSession())) ||
-                "/logout".equals(originalRequest.get())) {
-            authenticated = authenticate(httpRequest);
-        }
-
-        if (!authenticated) {
-            httpResponse.setHeader("WWW-Authenticate", "Basic realm=\"brooklyn\"");
-            httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
-            return;
-        }
-
-        // Note that the attribute AUTHENTICATED_USER_SESSION_ATTRIBUTE is only set in the call to authenticate(httpRequest),
-        // so must not try to get the user until that is done.
-        String uid = RequestTaggingFilter.getTag();
-        String user = Strings.toString(httpRequest.getSession().getAttribute(AUTHENTICATED_USER_SESSION_ATTRIBUTE));
-        try {
-            WebEntitlementContext entitlementContext = new WebEntitlementContext(user, httpRequest.getRemoteAddr(), uri, uid);
-            Entitlements.setEntitlementContext(entitlementContext);
-
-            chain.doFilter(request, response);
-        } catch (Throwable e) {
-            if (!response.isCommitted()) {
-                httpResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
-            }
-        } finally {
-            originalRequest.remove();
-            Entitlements.clearEntitlementContext();
-        }
-    }
-
-    protected boolean authenticate(HttpServletRequest request) {
-        HttpSession session = request.getSession();
-        if (provider.isAuthenticated(session)) {
-            return true;
-        }
-        session.setAttribute(BrooklynWebConfig.REMOTE_ADDRESS_SESSION_ATTRIBUTE, request.getRemoteAddr());
-        String user = null, pass = null;
-        String authorization = request.getHeader("Authorization");
-        if (authorization != null) {
-            String userpass = new String(Base64.decodeBase64(authorization.substring(6)));
-            int idxColon = userpass.indexOf(":");
-            if (idxColon >= 0) {
-                user = userpass.substring(0, idxColon);
-                pass = userpass.substring(idxColon + 1);
-            } else {
-                return false;
-            }
-        }
-        if (provider.authenticate(session, user, pass)) {
-            if (user != null) {
-                session.setAttribute(AUTHENTICATED_USER_SESSION_ATTRIBUTE, user);
-            }
-            return true;
-        }
-
-        return false;
-    }
-
-    @Override
-    public void init(FilterConfig config) throws ServletException {
-        ManagementContext mgmt = OsgiCompat.getManagementContext(config.getServletContext());
-        provider = new DelegatingSecurityProvider(mgmt);
-    }
-
-    @Override
-    public void destroy() {
-    }
-
-    protected void logout(HttpServletRequest request) {
-        log.info("REST logging {} out of session {}",
-                request.getSession().getAttribute(AUTHENTICATED_USER_SESSION_ATTRIBUTE), request.getSession().getId());
-        provider.logout(request.getSession());
-        request.getSession().removeAttribute(AUTHENTICATED_USER_SESSION_ATTRIBUTE);
-        request.getSession().removeAttribute(BrooklynWebConfig.REMOTE_ADDRESS_SESSION_ATTRIBUTE);
-        request.getSession().invalidate();
-    }
-
-}
diff --git a/rest/rest-server/src/main/resources/web-security.xml b/rest/rest-server/src/main/resources/web-security.xml
index 231145878c..c6a3974eae 100644
--- a/rest/rest-server/src/main/resources/web-security.xml
+++ b/rest/rest-server/src/main/resources/web-security.xml
@@ -21,31 +21,6 @@
          xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
          version="3.1">
 
-  <security-constraint>
-    <web-resource-collection>
-      <web-resource-name>Logout</web-resource-name>
-      <url-pattern>/v1/logout</url-pattern>
-    </web-resource-collection>
-  </security-constraint>
-
-  <!-- Ignored programmatically if noConsoleSecurity -->
-  <security-constraint>
-    <web-resource-collection>
-      <web-resource-name>All</web-resource-name>
-      <url-pattern>/</url-pattern>
-    </web-resource-collection>
-    <auth-constraint>
-      <role-name>webconsole</role-name>
-    </auth-constraint>
-  </security-constraint>
-
-  <login-config>
-    <auth-method>BASIC</auth-method>
-    <realm-name>webconsole</realm-name>
-  </login-config>
-
-  <security-role>
-    <role-name>webconsole</role-name>
-  </security-role>
-
+    <!-- now done with filters -->
+    
 </web-app>
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncher.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncher.java
index eaa20f4bf2..7063531a9b 100644
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncher.java
+++ b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncher.java
@@ -38,16 +38,7 @@
 import org.apache.brooklyn.core.server.BrooklynServerConfig;
 import org.apache.brooklyn.core.server.BrooklynServiceAttributes;
 import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
-import org.apache.brooklyn.rest.filter.CorsImplSupplierFilter;
-import org.apache.brooklyn.rest.filter.CsrfTokenFilter;
-import org.apache.brooklyn.rest.filter.EntitlementContextFilter;
-import org.apache.brooklyn.rest.filter.HaHotCheckResourceFilter;
-import org.apache.brooklyn.rest.filter.LoggingFilter;
-import org.apache.brooklyn.rest.filter.NoCacheFilter;
-import org.apache.brooklyn.rest.filter.RequestTaggingFilter;
-import org.apache.brooklyn.rest.filter.RequestTaggingRsFilter;
-import org.apache.brooklyn.rest.security.jaas.BrooklynLoginModule.RolePrincipal;
-import org.apache.brooklyn.rest.security.jaas.JaasUtils;
+import org.apache.brooklyn.rest.filter.*;
 import org.apache.brooklyn.rest.security.provider.AnyoneSecurityProvider;
 import org.apache.brooklyn.rest.security.provider.SecurityProvider;
 import org.apache.brooklyn.rest.util.ManagementContextProvider;
@@ -58,7 +49,6 @@
 import org.apache.brooklyn.util.guava.Maybe;
 import org.apache.brooklyn.util.net.Networking;
 import org.apache.brooklyn.util.text.WildcardGlobs;
-import org.eclipse.jetty.jaas.JAASLoginService;
 import org.eclipse.jetty.server.NetworkConnector;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.handler.ContextHandler;
@@ -218,6 +208,7 @@ private WebAppContext servletContextHandler(ManagementContext managementContext)
                 new ShutdownHandlerProvider(shutdownListener),
                 new RequestTaggingRsFilter(),
                 new NoCacheFilter(),
+                new BrooklynSecurityProviderFilterJersey(),
                 new HaHotCheckResourceFilter(),
                 new EntitlementContextFilter(),
                 new CsrfTokenFilter());
@@ -281,7 +272,7 @@ private static Server startServer(ManagementContext mgmt, ContextHandler context
     private static Server startServer(ManagementContext mgmt, ContextHandler context, String summary, InetSocketAddress bindLocation) {
         Server server = new Server(bindLocation);
 
-        initJaas(mgmt, server);
+        initAuth(mgmt, server);
 
         server.setHandler(context);
         try {
@@ -296,17 +287,8 @@ private static Server startServer(ManagementContext mgmt, ContextHandler context
     }
 
     // TODO Why parallel code for server init here and in BrooklynWebServer?
-    private static void initJaas(ManagementContext mgmt, Server server) {
-        JaasUtils.init(mgmt);
-        initJaasLoginService(server);
-    }
-
-    public static void initJaasLoginService(Server server) {
-        JAASLoginService loginService = new JAASLoginService();
-        loginService.setName("webconsole");
-        loginService.setLoginModuleName("webconsole");
-        loginService.setRoleClassNames(new String[] {RolePrincipal.class.getName()});
-        server.addBean(loginService);
+    private static void initAuth(ManagementContext mgmt, Server server) {
+        server.addBean(new BrooklynSecurityProviderFilterHelper());
     }
 
     public static BrooklynRestApiLauncher launcher() {
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/AuthenticateAnyoneSecurityProvider.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/AuthenticateAnyoneSecurityProvider.java
index b7264b2668..5ed763d9ae 100644
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/AuthenticateAnyoneSecurityProvider.java
+++ b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/AuthenticateAnyoneSecurityProvider.java
@@ -20,8 +20,11 @@
 
 import javax.servlet.http.HttpSession;
 
+import org.apache.brooklyn.rest.security.provider.AnyoneSecurityProvider;
 import org.apache.brooklyn.rest.security.provider.SecurityProvider;
 
+/** allows anyone to access, but does require a non-null user (any password) to be supplied via Basic auth,
+ * in contrast to {@link AnyoneSecurityProvider} */
 public class AuthenticateAnyoneSecurityProvider implements SecurityProvider {
 
     @Override
@@ -38,4 +41,9 @@ public boolean authenticate(HttpSession session, String user, String password) {
     public boolean logout(HttpSession session) {
         return false;
     }
-}
\ No newline at end of file
+
+    @Override
+    public boolean requiresUserPass() {
+        return true;
+    }
+}
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/security/provider/TestSecurityProvider.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/security/provider/TestSecurityProvider.java
index cad251f0f3..d6350d01d6 100644
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/security/provider/TestSecurityProvider.java
+++ b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/security/provider/TestSecurityProvider.java
@@ -43,4 +43,9 @@ public boolean authenticate(HttpSession session, String user, String password) {
     public boolean logout(HttpSession session) {
         return false;
     }
+    
+    @Override
+    public boolean requiresUserPass() {
+        return true;
+    }
 }
diff --git a/software/base/pom.xml b/software/base/pom.xml
index 426012035a..1e66329e2c 100644
--- a/software/base/pom.xml
+++ b/software/base/pom.xml
@@ -158,6 +158,11 @@
             <artifactId>mx4j-tools</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.cxf</groupId>
+            <artifactId>cxf-core</artifactId>
+            <version>${cxf.version}</version>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/software/winrm/pom.xml b/software/winrm/pom.xml
index b4a3f7eec9..8882c03719 100644
--- a/software/winrm/pom.xml
+++ b/software/winrm/pom.xml
@@ -52,6 +52,14 @@
                     <groupId>org.slf4j</groupId>
                     <artifactId>slf4j-api</artifactId>
                 </exclusion>
+                <exclusion>
+                    <groupId>org.apache.cxf</groupId>
+                    <artifactId>cxf-core</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.apache.cxf</groupId>
+                    <artifactId>cxf-rt-transports-http</artifactId>
+                </exclusion>
             </exclusions>
         </dependency>
         <!--
diff --git a/utils/rt-felix/src/main/java/org/apache/brooklyn/rt/felix/EmbeddedFelixFramework.java b/utils/rt-felix/src/main/java/org/apache/brooklyn/rt/felix/EmbeddedFelixFramework.java
index f8152723ed..42b7e142dc 100644
--- a/utils/rt-felix/src/main/java/org/apache/brooklyn/rt/felix/EmbeddedFelixFramework.java
+++ b/utils/rt-felix/src/main/java/org/apache/brooklyn/rt/felix/EmbeddedFelixFramework.java
@@ -15,7 +15,6 @@
  */
 package org.apache.brooklyn.rt.felix;
 
-import com.google.common.base.Stopwatch;
 import java.io.BufferedReader;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
@@ -24,13 +23,16 @@
 import java.io.InputStreamReader;
 import java.net.URL;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Enumeration;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
 import java.util.jar.Attributes;
 import java.util.jar.JarOutputStream;
 import java.util.jar.Manifest;
+
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.collections.MutableSet;
 import org.apache.brooklyn.util.exceptions.Exceptions;
@@ -48,6 +50,8 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.base.Stopwatch;
+
 /**
  * Functions for starting an Apache Felix OSGi framework inside a non-OSGi Brooklyn distro.
  * 
@@ -61,6 +65,17 @@
     private static final String MANIFEST_PATH = "META-INF/MANIFEST.MF";
     private static final Set<String> SYSTEM_BUNDLES = MutableSet.of();
 
+    private static final Set<URL> BOOT_BUNDLES;
+    
+    static {
+        try {
+            BOOT_BUNDLES = MutableSet.copyOf(Collections.list(
+                EmbeddedFelixFramework.class.getClassLoader().getResources(MANIFEST_PATH))).asUnmodifiable();
+        } catch (Exception e) {
+            // should never happen; weird classloading problem
+            throw Exceptions.propagate(e);
+        }
+    }
 
     // -------- creating
 
@@ -132,16 +147,17 @@ public static void stopFramework(Framework framework) throws RuntimeException {
     private static void installBootBundles(Framework framework) {
         Stopwatch timer = Stopwatch.createStarted();
         LOG.debug("Installing OSGi boot bundles from "+EmbeddedFelixFramework.class.getClassLoader()+"...");
-        Enumeration<URL> resources;
-        try {
-            resources = EmbeddedFelixFramework.class.getClassLoader().getResources(MANIFEST_PATH);
-        } catch (IOException e) {
-            throw Exceptions.propagate(e);
-        }
+        
+        Iterator<URL> resources = BOOT_BUNDLES.iterator();
+        // previously we evaluated this each time, but lately (discovered in 2019,
+        // possibly the case for a long time before) it seems to grow, accessing ad hoc dirs
+        // in cache/* made by tests, which get deleted, logging lots of errors.
+        // so now we statically populate it at load time.
+        
         BundleContext bundleContext = framework.getBundleContext();
         Map<String, Bundle> installedBundles = getInstalledBundlesById(bundleContext);
-        while(resources.hasMoreElements()) {
-            URL url = resources.nextElement();
+        while (resources.hasNext()) {
+            URL url = resources.next();
             ReferenceWithError<?> installResult = installExtensionBundle(bundleContext, url, installedBundles, OsgiUtils.getVersionedId(framework));
             if (installResult.hasError() && !installResult.masksErrorIfPresent()) {
                 // it's reported as a critical error, so warn here


With regards,
Apache Git Services