keycloak-uncached

Changes

Details

diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java
index c600e1a..402ed48 100644
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java
@@ -159,7 +159,7 @@ public abstract class AbstractPolicyEnforcer {
                             LOGGER.debugf("Authorization GRANTED for path [%s]. Permissions [%s].", actualPathConfig, grantedPermissions);
                         }
                         if (HTTP_METHOD_DELETE.equalsIgnoreCase(request.getMethod()) && actualPathConfig.isInstance()) {
-                            policyEnforcer.getPaths().remove(actualPathConfig);
+                            policyEnforcer.getPathMatcher().removeFromCache(getPath(request));
                         }
                         return true;
                     }
@@ -281,7 +281,7 @@ public abstract class AbstractPolicyEnforcer {
     }
 
     private AuthorizationContext createAuthorizationContext(AccessToken accessToken, PathConfig pathConfig) {
-        return new ClientAuthorizationContext(accessToken, pathConfig, policyEnforcer.getPaths(), getAuthzClient());
+        return new ClientAuthorizationContext(accessToken, pathConfig, getAuthzClient());
     }
 
     private boolean isResourcePermission(PathConfig actualPathConfig, Permission permission) {
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PathCache.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PathCache.java
index cf8815c..dfeb48e 100644
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PathCache.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PathCache.java
@@ -40,6 +40,7 @@ public class PathCache {
     private final AtomicBoolean writing = new AtomicBoolean(false);
 
     private final long maxAge;
+    private final boolean enabled;
 
     /**
      * Creates a new instance.
@@ -55,9 +56,14 @@ public class PathCache {
             }
         };
         this.maxAge = maxAge;
+        this.enabled = maxAge > 0;
     }
 
     public void put(String uri, PathConfig newValue) {
+        if (!enabled) {
+            return;
+        }
+
         try {
             if (parkForWriteAndCheckInterrupt()) {
                 return;
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PolicyEnforcer.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PolicyEnforcer.java
index 275315e..2ccdf28 100644
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PolicyEnforcer.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PolicyEnforcer.java
@@ -38,6 +38,7 @@ import org.keycloak.authorization.client.resource.ProtectedResource;
 import org.keycloak.common.util.PathMatcher;
 import org.keycloak.representations.adapters.config.AdapterConfig;
 import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
+import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathCacheConfig;
 import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
 import org.keycloak.representations.idm.authorization.Permission;
 import org.keycloak.representations.idm.authorization.ResourceRepresentation;
@@ -52,8 +53,8 @@ public class PolicyEnforcer {
     private final KeycloakDeployment deployment;
     private final AuthzClient authzClient;
     private final PolicyEnforcerConfig enforcerConfig;
+    private final PathConfigMatcher pathMatcher;
     private final Map<String, PathConfig> paths;
-    private final PathMatcher pathMatcher;
 
     public PolicyEnforcer(KeycloakDeployment deployment, AdapterConfig adapterConfig) {
         this.deployment = deployment;
@@ -70,8 +71,8 @@ public class PolicyEnforcer {
             }
         });
 
-        this.paths = configurePaths(this.authzClient.protection().resource(), this.enforcerConfig);
-        this.pathMatcher = createPathMatcher(authzClient);
+        paths = configurePaths(this.authzClient.protection().resource(), this.enforcerConfig);
+        pathMatcher = new PathConfigMatcher(paths, enforcerConfig, authzClient);
 
         if (LOGGER.isDebugEnabled()) {
             LOGGER.debug("Initialization complete. Path configurations:");
@@ -117,7 +118,7 @@ public class PolicyEnforcer {
         return paths;
     }
 
-    public PathMatcher<PathConfig> getPathMatcher() {
+    public PathConfigMatcher getPathMatcher() {
         return pathMatcher;
     }
 
@@ -216,71 +217,90 @@ public class PolicyEnforcer {
         return paths;
     }
 
-    private PathMatcher<PathConfig> createPathMatcher(final AuthzClient authzClient) {
-        final PathCache pathCache = new PathCache(100, 30000);
+    public class PathConfigMatcher extends PathMatcher<PathConfig> {
 
-        return new PathMatcher<PathConfig>() {
-            @Override
-            public PathConfig matches(String targetUri) {
-                PathConfig pathConfig = pathCache.get(targetUri);
+        private final Map<String, PathConfig> paths;
+        private final PathCache pathCache;
+        private final AuthzClient authzClient;
+        private final PolicyEnforcerConfig enforcerConfig;
 
-                if (pathCache.containsKey(targetUri) || pathConfig != null) {
-                    return pathConfig;
-                }
+        public PathConfigMatcher(Map<String, PathConfig> paths, PolicyEnforcerConfig enforcerConfig, AuthzClient authzClient) {
+            this.paths = paths;
+            this.enforcerConfig = enforcerConfig;
+            PathCacheConfig cacheConfig = enforcerConfig.getPathCacheConfig();
+
+            if (cacheConfig == null) {
+                cacheConfig = new PathCacheConfig();
+            }
+
+            pathCache = new PathCache(cacheConfig.getMaxEntries(), cacheConfig.getLifespan());
+            this.authzClient = authzClient;
+        }
+
+        @Override
+        public PathConfig matches(String targetUri) {
+            PathConfig pathConfig = pathCache.get(targetUri);
+
+            if (pathCache.containsKey(targetUri) || pathConfig != null) {
+                return pathConfig;
+            }
 
-                pathConfig = super.matches(targetUri);
+            pathConfig = super.matches(targetUri);
 
-                if (enforcerConfig.getLazyLoadPaths() && (pathConfig == null || pathConfig.getPath().contains("*"))) {
+            if (enforcerConfig.getLazyLoadPaths() || enforcerConfig.getPathCacheConfig() != null) {
+                if ((pathConfig == null || (pathConfig.getPath().contains("*")))) {
                     try {
                         List<ResourceRepresentation> matchingResources = authzClient.protection().resource().findByMatchingUri(targetUri);
 
                         if (!matchingResources.isEmpty()) {
                             pathConfig = PathConfig.createPathConfig(matchingResources.get(0));
-                            paths.put(pathConfig.getPath(), pathConfig);
                         }
                     } catch (Exception cause) {
-                        LOGGER.errorf(cause, "Could not lazy load paths from server");
+                        LOGGER.errorf(cause, "Could not lazy load resource with path [" + targetUri + "] from server");
                         return null;
                     }
                 }
+            }
 
-                pathCache.put(targetUri, pathConfig);
+            pathCache.put(targetUri, pathConfig);
 
-                return pathConfig;
-            }
+            return pathConfig;
+        }
 
-            @Override
-            protected String getPath(PathConfig entry) {
-                return entry.getPath();
-            }
+        @Override
+        protected String getPath(PathConfig entry) {
+            return entry.getPath();
+        }
 
-            @Override
-            protected Collection<PathConfig> getPaths() {
-                return paths.values();
-            }
+        @Override
+        protected Collection<PathConfig> getPaths() {
+            return paths.values();
+        }
 
-            @Override
-            protected PathConfig resolvePathConfig(PathConfig originalConfig, String path) {
-                if (originalConfig.hasPattern()) {
-                    ProtectedResource resource = authzClient.protection().resource();
-                    List<ResourceRepresentation> search = resource.findByUri(path);
-
-                    if (!search.isEmpty()) {
-                        // resource does exist on the server, cache it
-                        ResourceRepresentation targetResource = search.get(0);
-                        PathConfig config = PathConfig.createPathConfig(targetResource);
-
-                        config.setScopes(originalConfig.getScopes());
-                        config.setMethods(originalConfig.getMethods());
-                        config.setParentConfig(originalConfig);
-                        config.setEnforcementMode(originalConfig.getEnforcementMode());
-
-                        return config;
-                    }
-                }
+        @Override
+        protected PathConfig resolvePathConfig(PathConfig originalConfig, String path) {
+            if (originalConfig.hasPattern()) {
+                ProtectedResource resource = authzClient.protection().resource();
+                List<ResourceRepresentation> search = resource.findByUri(path);
+
+                if (!search.isEmpty()) {
+                    ResourceRepresentation targetResource = search.get(0);
+                    PathConfig config = PathConfig.createPathConfig(targetResource);
 
-                return null;
+                    config.setScopes(originalConfig.getScopes());
+                    config.setMethods(originalConfig.getMethods());
+                    config.setParentConfig(originalConfig);
+                    config.setEnforcementMode(originalConfig.getEnforcementMode());
+
+                    return config;
+                }
             }
-        };
-    }
+
+            return null;
+        }
+
+        public void removeFromCache(String pathConfig) {
+            pathCache.remove(pathConfig);
+        }
+    };
 }
diff --git a/authz/client/src/main/java/org/keycloak/authorization/client/ClientAuthorizationContext.java b/authz/client/src/main/java/org/keycloak/authorization/client/ClientAuthorizationContext.java
index a46e511..d848b25 100644
--- a/authz/client/src/main/java/org/keycloak/authorization/client/ClientAuthorizationContext.java
+++ b/authz/client/src/main/java/org/keycloak/authorization/client/ClientAuthorizationContext.java
@@ -30,8 +30,8 @@ public class ClientAuthorizationContext extends AuthorizationContext {
 
     private final AuthzClient client;
 
-    public ClientAuthorizationContext(AccessToken authzToken, PolicyEnforcerConfig.PathConfig current, Map<String, PolicyEnforcerConfig.PathConfig> paths, AuthzClient client) {
-        super(authzToken, current, paths);
+    public ClientAuthorizationContext(AccessToken authzToken, PolicyEnforcerConfig.PathConfig current, AuthzClient client) {
+        super(authzToken, current);
         this.client = client;
     }
 
diff --git a/core/src/main/java/org/keycloak/AuthorizationContext.java b/core/src/main/java/org/keycloak/AuthorizationContext.java
index 538a70f..0bc7b44 100644
--- a/core/src/main/java/org/keycloak/AuthorizationContext.java
+++ b/core/src/main/java/org/keycloak/AuthorizationContext.java
@@ -33,18 +33,16 @@ public class AuthorizationContext {
 
     private final AccessToken authzToken;
     private final PathConfig current;
-    private final Map<String, PathConfig> paths;
     private boolean granted;
 
-    public AuthorizationContext(AccessToken authzToken, PathConfig current, Map<String, PathConfig> paths) {
+    public AuthorizationContext(AccessToken authzToken, PathConfig current) {
         this.authzToken = authzToken;
         this.current = current;
-        this.paths = paths;
         this.granted = true;
     }
 
     public AuthorizationContext() {
-        this(null, null, null);
+        this(null, null);
         this.granted = false;
     }
 
diff --git a/core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java b/core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java
index 71b44a4..d01d7c5 100644
--- a/core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java
+++ b/core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java
@@ -38,6 +38,10 @@ public class PolicyEnforcerConfig {
     @JsonInclude(JsonInclude.Include.NON_EMPTY)
     private List<PathConfig> paths = new ArrayList<>();
 
+    @JsonProperty("path-cache")
+    @JsonInclude(JsonInclude.Include.NON_EMPTY)
+    private PathCacheConfig pathCacheConfig;
+
     @JsonProperty("lazy-load-paths")
     private Boolean lazyLoadPaths = Boolean.FALSE;
 
@@ -53,6 +57,10 @@ public class PolicyEnforcerConfig {
         return this.paths;
     }
 
+    public PathCacheConfig getPathCacheConfig() {
+        return pathCacheConfig;
+    }
+
     public Boolean getLazyLoadPaths() {
         return lazyLoadPaths;
     }
@@ -77,6 +85,10 @@ public class PolicyEnforcerConfig {
         this.paths = paths;
     }
 
+    public void setPathCacheConfig(PathCacheConfig pathCacheConfig) {
+        this.pathCacheConfig = pathCacheConfig;
+    }
+
     public String getOnDenyRedirectTo() {
         return onDenyRedirectTo;
     }
@@ -250,6 +262,29 @@ public class PolicyEnforcerConfig {
         }
     }
 
+    public static class PathCacheConfig {
+
+        @JsonProperty("max-entries")
+        int maxEntries = 1000;
+        long lifespan = 30000;
+
+        public int getMaxEntries() {
+            return maxEntries;
+        }
+
+        public void setMaxEntries(int maxEntries) {
+            this.maxEntries = maxEntries;
+        }
+
+        public long getLifespan() {
+            return lifespan;
+        }
+
+        public void setLifespan(long lifespan) {
+            this.lifespan = lifespan;
+        }
+    }
+
     public enum EnforcementMode {
         PERMISSIVE,
         ENFORCING,
diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/AbstractPolicyRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/authorization/AbstractPolicyRepresentation.java
index ddac66d..ada763c 100644
--- a/core/src/main/java/org/keycloak/representations/idm/authorization/AbstractPolicyRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/authorization/AbstractPolicyRepresentation.java
@@ -99,6 +99,12 @@ public class AbstractPolicyRepresentation {
         this.policies.addAll(Arrays.asList(id));
     }
 
+    public void removePolicy(String policy) {
+        if (policies != null) {
+            policies.remove(policy);
+        }
+    }
+
     public Set<String> getResources() {
         return resources;
     }
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/keycloak-cache-disabled-authz-service.json b/testsuite/integration-arquillian/test-apps/servlet-authz/keycloak-cache-disabled-authz-service.json
new file mode 100644
index 0000000..29979d8
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/keycloak-cache-disabled-authz-service.json
@@ -0,0 +1,26 @@
+{
+  "realm": "servlet-authz",
+  "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+  "auth-server-url" : "http://localhost:8180/auth",
+  "ssl-required" : "external",
+  "resource" : "servlet-authz-app",
+  "public-client" : false,
+  "credentials": {
+    "secret": "secret"
+  },
+  "policy-enforcer": {
+    "on-deny-redirect-to" : "/servlet-authz-app/accessDenied.jsp",
+    "path-cache": {
+      "lifespan": 0,
+      "max-entries": 1000
+    },
+    "paths": [
+      {
+        "name": "Premium Resource",
+        "path": "/protected/premium/pep-disabled.jsp",
+        "enforcement-mode": "DISABLED"
+      }
+    ]
+
+  }
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/keycloak-cache-lifespan-authz-service.json b/testsuite/integration-arquillian/test-apps/servlet-authz/keycloak-cache-lifespan-authz-service.json
new file mode 100644
index 0000000..d4b4af7
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/keycloak-cache-lifespan-authz-service.json
@@ -0,0 +1,26 @@
+{
+  "realm": "servlet-authz",
+  "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+  "auth-server-url" : "http://localhost:8180/auth",
+  "ssl-required" : "external",
+  "resource" : "servlet-authz-app",
+  "public-client" : false,
+  "credentials": {
+    "secret": "secret"
+  },
+  "policy-enforcer": {
+    "on-deny-redirect-to" : "/servlet-authz-app/accessDenied.jsp",
+    "path-cache": {
+      "lifespan": 5000,
+      "max-entries": 1000
+    },
+    "paths": [
+      {
+        "name": "Premium Resource",
+        "path": "/protected/premium/pep-disabled.jsp",
+        "enforcement-mode": "DISABLED"
+      }
+    ]
+
+  }
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletCacheDisabledAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletCacheDisabledAdapterTest.java
new file mode 100644
index 0000000..8bc641a
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletCacheDisabledAdapterTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.testsuite.adapter.example.authorization;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.Test;
+import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation;
+import org.keycloak.representations.idm.authorization.ResourceRepresentation;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public abstract class AbstractServletCacheDisabledAdapterTest extends AbstractServletAuthzFunctionalAdapterTest {
+
+    @Deployment(name = RESOURCE_SERVER_ID, managed = false)
+    public static WebArchive deployment() throws IOException {
+        return exampleDeployment(RESOURCE_SERVER_ID)
+                .addAsWebInfResource(new File(TEST_APPS_HOME_DIR + "/servlet-authz-app/keycloak-cache-disabled-authz-service.json"), "keycloak.json");
+    }
+
+    @Test
+    public void testCreateNewResource() {
+        performTests(() -> {
+            login("alice", "alice");
+            assertFalse(wasDenied());
+
+            this.driver.navigate().to(getResourceServerUrl() + "/new-resource");
+            assertFalse(wasDenied());
+
+            ResourceRepresentation resource = new ResourceRepresentation();
+
+            resource.setName("New Resource");
+            resource.setUri("/new-resource");
+
+            getAuthorizationResource().resources().create(resource);
+
+            ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation();
+
+            permission.setName(resource.getName() + " Permission");
+            permission.addResource(resource.getName());
+            permission.addPolicy("Deny Policy");
+
+            permission = getAuthorizationResource().permissions().resource().create(permission).readEntity(ResourcePermissionRepresentation.class);
+
+            login("alice", "alice");
+            assertFalse(wasDenied());
+
+            this.driver.navigate().to(getResourceServerUrl() + "/new-resource");
+            assertTrue(wasDenied());
+
+            permission = getAuthorizationResource().permissions().resource().findById(permission.getId()).toRepresentation();
+
+            permission.removePolicy("Deny Policy");
+            permission.addPolicy("Any User Policy");
+
+            getAuthorizationResource().permissions().resource().findById(permission.getId()).update(permission);
+
+            login("alice", "alice");
+            assertFalse(wasDenied());
+
+            this.driver.navigate().to(getResourceServerUrl() + "/new-resource");
+            assertFalse(wasDenied());
+        });
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletCacheLifespanAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletCacheLifespanAdapterTest.java
new file mode 100644
index 0000000..6477cd4
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletCacheLifespanAdapterTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.testsuite.adapter.example.authorization;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.Test;
+import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation;
+import org.keycloak.representations.idm.authorization.ResourceRepresentation;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public abstract class AbstractServletCacheLifespanAdapterTest extends AbstractServletAuthzFunctionalAdapterTest {
+
+    @Deployment(name = RESOURCE_SERVER_ID, managed = false)
+    public static WebArchive deployment() throws IOException {
+        return exampleDeployment(RESOURCE_SERVER_ID)
+                .addAsWebInfResource(new File(TEST_APPS_HOME_DIR + "/servlet-authz-app/keycloak-cache-lifespan-authz-service.json"), "keycloak.json");
+    }
+
+    @Test
+    public void testCreateNewResourceWaitExpiration() {
+        performTests(() -> {
+            login("alice", "alice");
+            assertFalse(wasDenied());
+
+            this.driver.navigate().to(getResourceServerUrl() + "/new-resource");
+            assertFalse(wasDenied());
+
+            ResourceRepresentation resource = new ResourceRepresentation();
+
+            resource.setName("New Resource");
+            resource.setUri("/new-resource");
+
+            getAuthorizationResource().resources().create(resource);
+
+            ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation();
+
+            permission.setName(resource.getName() + " Permission");
+            permission.addResource(resource.getName());
+            permission.addPolicy("Deny Policy");
+
+            permission = getAuthorizationResource().permissions().resource().create(permission).readEntity(ResourcePermissionRepresentation.class);
+
+            login("alice", "alice");
+            assertFalse(wasDenied());
+
+            this.driver.navigate().to(getResourceServerUrl() + "/new-resource");
+            assertFalse(wasDenied());
+
+            Thread.sleep(5000);
+
+            login("alice", "alice");
+            assertFalse(wasDenied());
+
+            this.driver.navigate().to(getResourceServerUrl() + "/new-resource");
+            assertTrue(wasDenied());
+        });
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/authorization/WildflyServletCacheDisabledAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/authorization/WildflyServletCacheDisabledAdapterTest.java
new file mode 100644
index 0000000..c4aec94
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/authorization/WildflyServletCacheDisabledAdapterTest.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.testsuite.adapter.example.authorization;
+
+import org.jboss.arquillian.container.test.api.RunAsClient;
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+
+/**
+ *
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+@RunAsClient
+@AppServerContainer("app-server-wildfly")
+//@AdapterLibsLocationProperty("adapter.libs.wildfly")
+public class WildflyServletCacheDisabledAdapterTest extends AbstractServletCacheDisabledAdapterTest {
+
+}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/authorization/WildflyServletCacheLifespanAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/authorization/WildflyServletCacheLifespanAdapterTest.java
new file mode 100644
index 0000000..c3e75ef
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/authorization/WildflyServletCacheLifespanAdapterTest.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.testsuite.adapter.example.authorization;
+
+import org.jboss.arquillian.container.test.api.RunAsClient;
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+
+/**
+ *
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+@RunAsClient
+@AppServerContainer("app-server-wildfly")
+//@AdapterLibsLocationProperty("adapter.libs.wildfly")
+public class WildflyServletCacheLifespanAdapterTest extends AbstractServletCacheLifespanAdapterTest {
+
+}