keycloak-aplcache

Merge pull request #3082 from pedroigor/master [KEYCLOAK-3372]

7/29/2016 7:28:01 PM

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 6b1fe19..0c0fc23 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
@@ -126,11 +126,13 @@ public abstract class AbstractPolicyEnforcer {
         List<Permission> permissions = authorization.getPermissions();
 
         for (Permission permission : permissions) {
-            Set<String> allowedScopes = permission.getScopes();
-
             if (permission.getResourceSetId() != null) {
                 if (isResourcePermission(actualPathConfig, permission)) {
-                    if (((allowedScopes == null || allowedScopes.isEmpty()) && requiredScopes.isEmpty()) || allowedScopes.containsAll(requiredScopes)) {
+                    if (actualPathConfig.isInstance() && !matchResourcePermission(actualPathConfig, permission)) {
+                        continue;
+
+                    }
+                    if (hasResourceScopePermission(requiredScopes, permission, actualPathConfig)) {
                         LOGGER.debugf("Authorization GRANTED for path [%s]. Permissions [%s].", actualPathConfig, permissions);
                         if (request.getMethod().equalsIgnoreCase("DELETE") && actualPathConfig.isInstance()) {
                             this.paths.remove(actualPathConfig);
@@ -138,11 +140,6 @@ public abstract class AbstractPolicyEnforcer {
                         return true;
                     }
                 }
-            } else {
-                if ((allowedScopes.isEmpty() && requiredScopes.isEmpty()) || allowedScopes.containsAll(requiredScopes)) {
-                    LOGGER.debugf("Authorization GRANTED for path [%s]. Permissions [%s].", actualPathConfig, permissions);
-                    return true;
-                }
             }
         }
 
@@ -151,6 +148,11 @@ public abstract class AbstractPolicyEnforcer {
         return false;
     }
 
+    private boolean hasResourceScopePermission(Set<String> requiredScopes, Permission permission, PathConfig actualPathConfig) {
+        Set<String> allowedScopes = permission.getScopes();
+        return (allowedScopes.containsAll(requiredScopes) || allowedScopes.isEmpty());
+    }
+
     protected AuthzClient getAuthzClient() {
         return this.authzClient;
     }
@@ -210,7 +212,6 @@ public abstract class AbstractPolicyEnforcer {
                 config.setPath(targetResource.getUri());
                 config.setScopes(originalConfig.getScopes());
                 config.setMethods(originalConfig.getMethods());
-                config.setInstance(true);
                 config.setParentConfig(originalConfig);
 
                 this.paths.add(config);
@@ -244,13 +245,17 @@ public abstract class AbstractPolicyEnforcer {
 
     private boolean isResourcePermission(PathConfig actualPathConfig, Permission permission) {
         // first we try a match using resource id
-        boolean resourceMatch = permission.getResourceSetId().equals(actualPathConfig.getId());
+        boolean resourceMatch = matchResourcePermission(actualPathConfig, permission);
 
         // as a fallback, check if the current path is an instance and if so, check if parent's id matches the permission
         if (!resourceMatch && actualPathConfig.isInstance()) {
-            resourceMatch = permission.getResourceSetId().equals(actualPathConfig.getParentConfig().getId());
+            resourceMatch = matchResourcePermission(actualPathConfig.getParentConfig(), permission);
         }
 
         return resourceMatch;
     }
+
+    private boolean matchResourcePermission(PathConfig actualPathConfig, Permission permission) {
+        return permission.getResourceSetId().equals(actualPathConfig.getId());
+    }
 }
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java
index e151f76..a776762 100644
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java
@@ -50,7 +50,7 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
         int retry = 2;
         AccessToken original = accessToken;
 
-        while (retry >= 0) {
+        while (retry > 0) {
             if (super.isAuthorized(pathConfig, requiredScopes, accessToken, httpFacade)) {
                 original.setAuthorization(accessToken.getAuthorization());
                 return true;
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 5c26124..aa6d3d2 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
@@ -45,7 +45,6 @@ public class PolicyEnforcer {
     private static Logger LOGGER = Logger.getLogger(PolicyEnforcer.class);
 
     private final KeycloakDeployment deployment;
-    private final PathMatcher pathMatcher;
     private final AuthzClient authzClient;
     private final PolicyEnforcerConfig enforcerConfig;
     private final List<PathConfig> paths;
@@ -54,7 +53,6 @@ public class PolicyEnforcer {
         this.deployment = deployment;
         this.enforcerConfig = adapterConfig.getPolicyEnforcerConfig();
         this.authzClient = AuthzClient.create(new Configuration(adapterConfig.getAuthServerUrl(), adapterConfig.getRealm(), adapterConfig.getResource(), adapterConfig.getCredentials(), deployment.getClient()));
-        this.pathMatcher = new PathMatcher();
         this.paths = configurePaths(this.authzClient.protection().resource(), this.enforcerConfig);
 
         if (LOGGER.isDebugEnabled()) {
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 b2c5757..9cf710a 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
@@ -109,7 +109,6 @@ public class PolicyEnforcerConfig {
         private List<MethodConfig> methods = new ArrayList<>();
         private List<String> scopes = Collections.emptyList();
         private String id;
-        private boolean instance;
 
         @JsonIgnore
         private PathConfig parentConfig;
@@ -178,11 +177,7 @@ public class PolicyEnforcerConfig {
         }
 
         public boolean isInstance() {
-            return instance;
-        }
-
-        public void setInstance(boolean instance) {
-            this.instance = instance;
+            return this.parentConfig != null;
         }
 
         public void setParentConfig(PathConfig parentConfig) {
diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/PolicyRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/authorization/PolicyRepresentation.java
index dd21901..9a51031 100644
--- a/core/src/main/java/org/keycloak/representations/idm/authorization/PolicyRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/authorization/PolicyRepresentation.java
@@ -16,6 +16,8 @@
  */
 package org.keycloak.representations.idm.authorization;
 
+import com.fasterxml.jackson.annotation.JsonInclude;
+
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -35,6 +37,7 @@ public class PolicyRepresentation {
     private DecisionStrategy decisionStrategy = DecisionStrategy.UNANIMOUS;
     private Map<String, String> config = new HashMap();
     private List<PolicyRepresentation> dependentPolicies;
+    @JsonInclude(JsonInclude.Include.NON_EMPTY)
     private List<PolicyRepresentation> associatedPolicies = new ArrayList<>();
 
     public String getId() {
diff --git a/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java b/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java
index c28bca3..a5d7f16 100644
--- a/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java
+++ b/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java
@@ -109,7 +109,12 @@ public class AlbumService {
 
     private void createProtectedResource(Album album) {
         try {
-            ResourceRepresentation albumResource = new ResourceRepresentation(album.getName(), new HashSet(), "/album/" + album.getId(), "http://photoz.com/album");
+            HashSet<ScopeRepresentation> scopes = new HashSet<>();
+
+            scopes.add(new ScopeRepresentation(SCOPE_ALBUM_VIEW));
+            scopes.add(new ScopeRepresentation(SCOPE_ALBUM_DELETE));
+
+            ResourceRepresentation albumResource = new ResourceRepresentation(album.getName(), scopes, "/album/" + album.getId(), "http://photoz.com/album");
 
             albumResource.setOwner(album.getUserId());
 
diff --git a/examples/authz/photoz/photoz-restful-api/src/main/webapp/WEB-INF/keycloak.json b/examples/authz/photoz/photoz-restful-api/src/main/webapp/WEB-INF/keycloak.json
index 95fb58b..6849d07 100644
--- a/examples/authz/photoz/photoz-restful-api/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/authz/photoz/photoz-restful-api/src/main/webapp/WEB-INF/keycloak.json
@@ -9,17 +9,18 @@
     "secret": "secret"
   },
   "policy-enforcer": {
+    "user-managed-access" : {},
     "paths": [
       {
         "path" : "/album/*",
         "methods" : [
           {
-            "method": "GET",
-            "scopes" : ["urn:photoz.com:scopes:album:view"]
-          },
-          {
             "method": "POST",
             "scopes" : ["urn:photoz.com:scopes:album:create"]
+          },
+          {
+            "method": "GET",
+            "scopes" : ["urn:photoz.com:scopes:album:view"]
           }
         ]
       },
@@ -30,6 +31,10 @@
           {
             "method": "DELETE",
             "scopes" : ["urn:photoz.com:scopes:album:delete"]
+          },
+          {
+            "method": "GET",
+            "scopes" : ["urn:photoz.com:scopes:album:view"]
           }
         ]
       },
diff --git a/examples/authz/photoz/photoz-restful-api-authz-service.json b/examples/authz/photoz/photoz-restful-api-authz-service.json
index 6547d2f..455948c 100644
--- a/examples/authz/photoz/photoz-restful-api-authz-service.json
+++ b/examples/authz/photoz/photoz-restful-api-authz-service.json
@@ -52,7 +52,6 @@
         "sessionName": "MainOwnerSession",
         "mavenArtifactGroupId": "org.keycloak",
         "moduleName": "PhotozAuthzOwnerPolicy",
-        "applyPolicies": "[]",
         "scannerPeriod": "1",
         "scannerPeriodUnit": "Hours"
       }
@@ -64,7 +63,6 @@
       "logic": "POSITIVE",
       "decisionStrategy": "UNANIMOUS",
       "config": {
-        "applyPolicies": "[]",
         "roles": "[{\"id\":\"admin\",\"required\":true}]"
       }
     },
@@ -75,7 +73,6 @@
       "logic": "POSITIVE",
       "decisionStrategy": "UNANIMOUS",
       "config": {
-        "applyPolicies": "[]",
         "roles": "[{\"id\":\"user\"},{\"id\":\"manage-albums\",\"required\":true}]"
       }
     },
@@ -86,7 +83,6 @@
       "logic": "POSITIVE",
       "decisionStrategy": "UNANIMOUS",
       "config": {
-        "applyPolicies": "[]",
         "code": "var contextAttributes = $evaluation.getContext().getAttributes();\n\nif (contextAttributes.containsValue('kc.client.network.ip_address', '127.0.0.1')) {\n    $evaluation.grant();\n}"
       }
     },
@@ -117,7 +113,6 @@
       "logic": "POSITIVE",
       "decisionStrategy": "UNANIMOUS",
       "config": {
-        "applyPolicies": "[]",
         "code": "var context = $evaluation.getContext();\nvar identity = context.getIdentity();\nvar attributes = identity.getAttributes();\nvar email = attributes.getValue('email').asString(0);\n\nif (identity.hasRole('admin') || email.endsWith('@keycloak.org')) {\n    $evaluation.grant();\n}"
       }
     },
diff --git a/examples/authz/servlet-authz/servlet-authz-app-config.json b/examples/authz/servlet-authz/servlet-authz-app-config.json
index d5fb1cb..43ebde4 100644
--- a/examples/authz/servlet-authz/servlet-authz-app-config.json
+++ b/examples/authz/servlet-authz/servlet-authz-app-config.json
@@ -54,7 +54,7 @@
       "description": "Defines that adminsitrators can do something",
       "type": "role",
       "config": {
-        "roles": "[\"admin\"]"
+        "roles": "[{\"id\":\"admin\"}]"
       }
     },
     {
@@ -62,7 +62,7 @@
       "description": "Defines that any user can do something",
       "type": "role",
       "config": {
-        "roles": "[\"user\"]"
+        "roles": "[{\"id\":\"user\"}]"
       }
     },
     {
@@ -71,7 +71,7 @@
       "type": "role",
       "logic": "POSITIVE",
       "config": {
-        "roles": "[\"user_premium\"]"
+        "roles": "[{\"id\":\"user_premium\"}]"
       }
     },
     {
diff --git a/examples/authz/servlet-authz/src/main/webapp/logout-include.jsp b/examples/authz/servlet-authz/src/main/webapp/logout-include.jsp
index 95365ea..364d887 100644
--- a/examples/authz/servlet-authz/src/main/webapp/logout-include.jsp
+++ b/examples/authz/servlet-authz/src/main/webapp/logout-include.jsp
@@ -7,5 +7,5 @@
     String contextPath = request.getContextPath();
     String redirectUri = scheme + "://" + host + ":" + port + contextPath;
 %>
-<h2>Click <a href="<%= KeycloakUriBuilder.fromUri("http://localhost:8080/auth").path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH)
-            .queryParam("redirect_uri", redirectUri).build("servlet-authz").toString()%>">here</a> to logout.</h2>
\ No newline at end of file
+<h2>Click here <a href="<%= KeycloakUriBuilder.fromUri("http://localhost:8080/auth").path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH)
+            .queryParam("redirect_uri", redirectUri).build("servlet-authz").toString()%>">Sign Out</a></h2>
\ No newline at end of file
diff --git a/server-spi/src/main/java/org/keycloak/authorization/attribute/Attributes.java b/server-spi/src/main/java/org/keycloak/authorization/attribute/Attributes.java
index a70d3c0..a0a7b6c 100644
--- a/server-spi/src/main/java/org/keycloak/authorization/attribute/Attributes.java
+++ b/server-spi/src/main/java/org/keycloak/authorization/attribute/Attributes.java
@@ -67,7 +67,8 @@ public interface Attributes {
      * @return true if any attribute with <code>name</code> and <code>value</code> exist. Otherwise, returns false.
      */
     default boolean containsValue(String name, String value) {
-        return toMap().getOrDefault(name, emptyList()).stream().anyMatch(value::equals);
+        Collection<String> values = toMap().get(name);
+        return values != null && values.stream().anyMatch(value::equals);
     }
 
     /**
diff --git a/server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultPolicyEvaluator.java b/server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultPolicyEvaluator.java
index e2ef2f9..d5fa9cc 100644
--- a/server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultPolicyEvaluator.java
+++ b/server-spi/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultPolicyEvaluator.java
@@ -32,8 +32,10 @@ import org.keycloak.authorization.store.StoreFactory;
 import org.keycloak.representations.idm.authorization.PolicyEnforcementMode;
 
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
@@ -130,27 +132,49 @@ public class DefaultPolicyEvaluator implements PolicyEvaluator {
             return true;
         }
 
-        if (policy.getScopes().isEmpty()) {
-            return true;
-        }
+        Set<Scope> scopes = new HashSet<>(policy.getScopes());
 
-        boolean hasScope = true;
+        if (scopes.isEmpty()) {
+            Set<Resource> resources = new HashSet<>();
 
-        for (Scope givenScope : policy.getScopes()) {
-            boolean hasGivenScope = false;
+            resources.addAll(policy.getResources());
 
-            for (Scope scope : permission.getScopes()) {
-                if (givenScope.getId().equals(scope.getId())) {
-                    hasGivenScope = true;
-                    break;
-                }
+            for (Resource resource : resources) {
+                scopes.addAll(resource.getScopes());
             }
 
-            if (!hasGivenScope) {
+            if (!resources.isEmpty() && scopes.isEmpty()) {
                 return false;
             }
+
+            if (scopes.isEmpty()) {
+                Resource resource = permission.getResource();
+                String type = resource.getType();
+
+                if (type != null) {
+                    List<Resource> resourcesByType = authorization.getStoreFactory().getResourceStore().findByType(type);
+
+                    for (Resource resourceType : resourcesByType) {
+                        if (resourceType.getOwner().equals(resource.getResourceServer().getClientId())) {
+                            resources.add(resourceType);
+                        }
+                    }
+                }
+            }
+
+            for (Resource resource : resources) {
+                scopes.addAll(resource.getScopes());
+            }
+        }
+
+        for (Scope givenScope : scopes) {
+            for (Scope scope : permission.getScopes()) {
+                if (givenScope.getId().equals(scope.getId())) {
+                    return true;
+                }
+            }
         }
 
-        return hasScope;
+        return false;
     }
 }
diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java
index 8e4ec5e..6712a9d 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java
@@ -240,10 +240,16 @@ public class PolicyEvaluationService {
                         }
                     }
 
-                    accessToken.addAccess(clientModel.getClientId());
-                    AccessToken.Access resourceAccess = accessToken.getResourceAccess(clientModel.getClientId());
+                    AccessToken.Access clientAccess = accessToken.addAccess(clientModel.getClientId());
+                    clientAccess.roles(new HashSet<>());
 
-                    userModel.getClientRoleMappings(clientModel).stream().map(RoleModel::getName).forEach(roleName -> resourceAccess.addRole(roleName));
+                    userModel.getClientRoleMappings(clientModel).stream().map(RoleModel::getName).forEach(roleName -> clientAccess.addRole(roleName));
+
+                    ClientModel resourceServerClient = realm.getClientById(resourceServer.getClientId());
+                    AccessToken.Access resourceServerAccess = accessToken.addAccess(resourceServerClient.getClientId());
+                    resourceServerAccess.roles(new HashSet<>());
+
+                    userModel.getClientRoleMappings(resourceServerClient).stream().map(RoleModel::getName).forEach(roleName -> resourceServerAccess.addRole(roleName));
                 }
             }
         }
diff --git a/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java b/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java
index 405675a..0fd3474 100644
--- a/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java
+++ b/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java
@@ -17,6 +17,7 @@
 package org.keycloak.authorization.authorization;
 
 import org.jboss.resteasy.spi.HttpRequest;
+import org.keycloak.OAuth2Constants;
 import org.keycloak.OAuthErrorException;
 import org.keycloak.authorization.AuthorizationProvider;
 import org.keycloak.authorization.authorization.representation.AuthorizationRequest;
@@ -50,8 +51,6 @@ import javax.ws.rs.container.Suspended;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.Status;
-import java.util.Arrays;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -104,8 +103,12 @@ public class AuthorizationTokenService {
                 List<Permission> entitlements = Permissions.allPermits(results);
 
                 if (entitlements.isEmpty()) {
+                    HashMap<Object, Object> error = new HashMap<>();
+
+                    error.put(OAuth2Constants.ERROR, "not_authorized");
+
                     asyncResponse.resume(Cors.add(httpRequest, Response.status(Status.FORBIDDEN)
-                            .entity(new ErrorResponseException("not_authorized", "Authorization denied.", Status.FORBIDDEN)))
+                            .entity(error))
                             .allowedOrigins(identity.getAccessToken())
                             .exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build());
                 } else {
@@ -193,14 +196,7 @@ public class AuthorizationTokenService {
         return permissionsToEvaluate.entrySet().stream()
                 .flatMap((Function<Entry<String, Set<String>>, Stream<ResourcePermission>>) entry -> {
                     Resource entryResource = storeFactory.getResourceStore().findById(entry.getKey());
-                    if (entry.getValue().isEmpty()) {
-                        return Arrays.asList(new ResourcePermission(entryResource, Collections.emptyList(), entryResource.getResourceServer())).stream();
-                    } else {
-                        return entry.getValue().stream()
-                                .map(scopeName -> storeFactory.getScopeStore().findByName(scopeName, entryResource.getResourceServer().getId()))
-                                .filter(scope -> scope != null)
-                                .map(scope -> new ResourcePermission(entryResource, Arrays.asList(scope), entryResource.getResourceServer()));
-                    }
+                    return Permissions.createResourcePermissions(entryResource, entry.getValue(), authorization).stream();
                 }).collect(Collectors.toList());
     }
 
diff --git a/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java b/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java
index ccc457d..e5986b7 100644
--- a/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java
+++ b/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java
@@ -18,6 +18,7 @@
 package org.keycloak.authorization.entitlement;
 
 import org.jboss.resteasy.spi.HttpRequest;
+import org.keycloak.OAuth2Constants;
 import org.keycloak.OAuthErrorException;
 import org.keycloak.authorization.AuthorizationProvider;
 import org.keycloak.authorization.common.KeycloakEvaluationContext;
@@ -55,8 +56,6 @@ import javax.ws.rs.container.Suspended;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.Status;
-import java.util.Arrays;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -119,8 +118,12 @@ public class EntitlementService {
                 List<Permission> entitlements = Permissions.allPermits(results);
 
                 if (entitlements.isEmpty()) {
+                    HashMap<Object, Object> error = new HashMap<>();
+
+                    error.put(OAuth2Constants.ERROR, "not_authorized");
+
                     asyncResponse.resume(Cors.add(request, Response.status(Status.FORBIDDEN)
-                            .entity(new ErrorResponseException("not_authorized", "Authorization denied.", Status.FORBIDDEN)))
+                            .entity(error))
                             .allowedOrigins(identity.getAccessToken())
                             .exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build());
                 } else {
@@ -249,15 +252,7 @@ public class EntitlementService {
         return permissionsToEvaluate.entrySet().stream()
                 .flatMap((Function<Map.Entry<String, Set<String>>, Stream<ResourcePermission>>) entry -> {
                     Resource entryResource = storeFactory.getResourceStore().findById(entry.getKey());
-
-                    if (entry.getValue().isEmpty()) {
-                        return Arrays.asList(new ResourcePermission(entryResource, Collections.emptyList(), entryResource.getResourceServer())).stream();
-                    } else {
-                        return entry.getValue().stream()
-                                .map(scopeName -> storeFactory.getScopeStore().findByName(scopeName, entryResource.getResourceServer().getId()))
-                                .filter(scope -> scope != null)
-                                .map(scope -> new ResourcePermission(entryResource, Arrays.asList(scope), entryResource.getResourceServer()));
-                    }
+                    return Permissions.createResourcePermissions(entryResource, entry.getValue(), authorization).stream();
                 }).collect(Collectors.toList());
     }
 }
diff --git a/services/src/main/java/org/keycloak/authorization/util/Permissions.java b/services/src/main/java/org/keycloak/authorization/util/Permissions.java
index ed49697..ebc57c2 100644
--- a/services/src/main/java/org/keycloak/authorization/util/Permissions.java
+++ b/services/src/main/java/org/keycloak/authorization/util/Permissions.java
@@ -27,6 +27,7 @@ import org.keycloak.authorization.model.Scope;
 import org.keycloak.authorization.permission.ResourcePermission;
 import org.keycloak.authorization.policy.evaluation.Result;
 import org.keycloak.authorization.store.ResourceStore;
+import org.keycloak.authorization.store.ScopeStore;
 import org.keycloak.authorization.store.StoreFactory;
 import org.keycloak.representations.idm.authorization.Permission;
 
@@ -61,91 +62,95 @@ public final class Permissions {
         StoreFactory storeFactory = authorization.getStoreFactory();
         ResourceStore resourceStore = storeFactory.getResourceStore();
 
-        resourceStore.findByOwner(resourceServer.getClientId()).stream().forEach(resource -> permissions.addAll(createResourcePermissions(resource, resourceServer, authorization)));
-        resourceStore.findByOwner(identity.getId()).stream().forEach(resource -> permissions.addAll(createResourcePermissions(resource, resourceServer, authorization)));
+        resourceStore.findByOwner(resourceServer.getClientId()).stream().forEach(resource -> permissions.addAll(createResourcePermissions(resource, resource.getScopes().stream().map(Scope::getName).collect(Collectors.toSet()), authorization)));
+        resourceStore.findByOwner(identity.getId()).stream().forEach(resource -> permissions.addAll(createResourcePermissions(resource, resource.getScopes().stream().map(Scope::getName).collect(Collectors.toSet()), authorization)));
 
         return permissions;
     }
 
-    public static List<ResourcePermission> createResourcePermissions(Resource resource, ResourceServer resourceServer, AuthorizationProvider authorization) {
+    public static List<ResourcePermission> createResourcePermissions(Resource resource, Set<String> requestedScopes, AuthorizationProvider authorization) {
         List<ResourcePermission> permissions = new ArrayList<>();
-        List<Scope> scopes = resource.getScopes();
-
-        if (scopes.isEmpty()) {
-            String type = resource.getType();
+        String type = resource.getType();
+        ResourceServer resourceServer = resource.getResourceServer();
+        List<Scope> scopes;
 
+        if (requestedScopes.isEmpty()) {
+            scopes = resource.getScopes();
             // check if there is a typed resource whose scopes are inherited by the resource being requested. In this case, we assume that parent resource
             // is owned by the resource server itself
-            if (type != null) {
+            if (type != null && !resource.getOwner().equals(resourceServer.getClientId())) {
                 StoreFactory storeFactory = authorization.getStoreFactory();
                 ResourceStore resourceStore = storeFactory.getResourceStore();
                 resourceStore.findByType(type).forEach(resource1 -> {
                     if (resource1.getOwner().equals(resourceServer.getClientId())) {
-                        scopes.addAll(resource1.getScopes());
+                        for (Scope typeScope : resource1.getScopes()) {
+                            if (!scopes.contains(typeScope)) {
+                                scopes.add(typeScope);
+                            }
+                        }
                     }
                 });
             }
+        } else {
+            ScopeStore scopeStore = authorization.getStoreFactory().getScopeStore();
+            scopes = requestedScopes.stream().map(scopeName -> {
+                Scope byName = scopeStore.findByName(scopeName, resource.getResourceServer().getId());
+                return byName;
+            }).collect(Collectors.toList());
         }
 
-        if (scopes.size() > 1) {
+        if (scopes.isEmpty()) {
+            permissions.add(new ResourcePermission(resource, Collections.emptyList(), resource.getResourceServer()));
+        } else {
             for (Scope scope : scopes) {
                 permissions.add(new ResourcePermission(resource, Arrays.asList(scope), resource.getResourceServer()));
             }
-        } else {
-            permissions.add(new ResourcePermission(resource, Collections.emptyList(), resource.getResourceServer()));
         }
 
         return permissions;
     }
 
     public static List<Permission> allPermits(List<Result> evaluation) {
-        List<Permission> permissions = evaluation.stream()
-                .filter(evaluationResult -> evaluationResult.getEffect().equals(Effect.PERMIT))
-                .map(evaluationResult -> {
-                    ResourcePermission permission = evaluationResult.getPermission();
-                    String resourceId = null;
-                    String resourceName = null;
-
-                    Resource resource = permission.getResource();
-
-                    if (resource != null) {
-                        resourceId = resource.getId();
-                        resourceName = resource.getName();
-                    }
+        Map<String, Permission> permissions = new HashMap<>();
 
-                    Set<String> scopes = permission.getScopes().stream().map(Scope::getName).collect(Collectors.toSet());
-
-                    return new Permission(resourceId, resourceName, scopes);
-                }).collect(Collectors.toList());
-
-        Map<String, Permission> perms = new HashMap<>();
-
-        permissions.forEach(permission -> {
-            Permission evalPermission = perms.get(permission.getResourceSetId());
-
-            if (evalPermission == null) {
-                evalPermission = permission;
-                perms.put(permission.getResourceSetId(), evalPermission);
+        for (Result evaluationResult : evaluation) {
+            ResourcePermission permission = evaluationResult.getPermission();
+            Set<String> scopes = permission.getScopes().stream().map(Scope::getName).collect(Collectors.toSet());
+            if (evaluationResult.getEffect().equals(Effect.DENY)) {
+                continue;
             }
+            Resource resource = permission.getResource();
 
-            Set<String> permissionScopes = permission.getScopes();
-
-            if (permissionScopes != null && !permissionScopes.isEmpty()) {
-                Set<String> scopes = evalPermission.getScopes();
+            if (resource != null) {
+                String resourceId = resource.getId();
+                String resourceName = resource.getName();
+                Permission evalPermission = permissions.get(resource.getId());
 
-                if (scopes == null) {
-                    scopes = new HashSet();
-                    evalPermission.setScopes(scopes);
+                if (evalPermission == null) {
+                    evalPermission = new Permission(resourceId, resourceName, scopes);
+                    permissions.put(resourceId, evalPermission);
                 }
 
-                for (String scopeName : permissionScopes) {
-                    if (!scopes.contains(scopeName)) {
-                        scopes.add(scopeName);
+                if (scopes != null && !scopes.isEmpty()) {
+                    Set<String> finalScopes = evalPermission.getScopes();
+
+                    if (finalScopes == null) {
+                        finalScopes = new HashSet();
+                        evalPermission.setScopes(finalScopes);
+                    }
+
+                    for (String scopeName : scopes) {
+                        if (!finalScopes.contains(scopeName)) {
+                            finalScopes.add(scopeName);
+                        }
                     }
                 }
+            } else {
+                Permission scopePermission = new Permission(null, null, scopes);
+                permissions.put(scopePermission.toString(), scopePermission);
             }
-        });
+        }
 
-        return perms.values().stream().collect(Collectors.toList());
+        return permissions.values().stream().collect(Collectors.toList());
     }
 }
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java
index c28bca3..a5d7f16 100644
--- a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java
+++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java
@@ -109,7 +109,12 @@ public class AlbumService {
 
     private void createProtectedResource(Album album) {
         try {
-            ResourceRepresentation albumResource = new ResourceRepresentation(album.getName(), new HashSet(), "/album/" + album.getId(), "http://photoz.com/album");
+            HashSet<ScopeRepresentation> scopes = new HashSet<>();
+
+            scopes.add(new ScopeRepresentation(SCOPE_ALBUM_VIEW));
+            scopes.add(new ScopeRepresentation(SCOPE_ALBUM_DELETE));
+
+            ResourceRepresentation albumResource = new ResourceRepresentation(album.getName(), scopes, "/album/" + album.getId(), "http://photoz.com/album");
 
             albumResource.setOwner(album.getUserId());
 
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/webapp/WEB-INF/keycloak.json b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/webapp/WEB-INF/keycloak.json
index 95fb58b..a3ac697 100644
--- a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/webapp/WEB-INF/keycloak.json
+++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/webapp/WEB-INF/keycloak.json
@@ -1,7 +1,7 @@
 {
   "realm": "photoz",
   "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
-  "auth-server-url": "http://localhost:8080/auth",
+  "auth-server-url": "http://localhost:8180/auth",
   "ssl-required": "external",
   "resource": "photoz-restful-api",
   "bearer-only" : true,
@@ -9,17 +9,18 @@
     "secret": "secret"
   },
   "policy-enforcer": {
+    "user-managed-access" : {},
     "paths": [
       {
         "path" : "/album/*",
         "methods" : [
           {
-            "method": "GET",
-            "scopes" : ["urn:photoz.com:scopes:album:view"]
-          },
-          {
             "method": "POST",
             "scopes" : ["urn:photoz.com:scopes:album:create"]
+          },
+          {
+            "method": "GET",
+            "scopes" : ["urn:photoz.com:scopes:album:view"]
           }
         ]
       },
@@ -30,6 +31,10 @@
           {
             "method": "DELETE",
             "scopes" : ["urn:photoz.com:scopes:album:delete"]
+          },
+          {
+            "method": "GET",
+            "scopes" : ["urn:photoz.com:scopes:album:view"]
           }
         ]
       },
diff --git a/testsuite/integration-arquillian/test-apps/pom.xml b/testsuite/integration-arquillian/test-apps/pom.xml
index 391e0d8..fa4b0c1 100644
--- a/testsuite/integration-arquillian/test-apps/pom.xml
+++ b/testsuite/integration-arquillian/test-apps/pom.xml
@@ -20,5 +20,6 @@
         <module>js-database</module>
         <module>photoz</module>
         <module>hello-world-authz-service</module>
+        <module>servlet-authz</module>
     </modules>
 </project>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/pom.xml b/testsuite/integration-arquillian/test-apps/servlet-authz/pom.xml
new file mode 100755
index 0000000..fc9f6b0
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/pom.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.keycloak.testsuite</groupId>
+        <artifactId>integration-arquillian-test-apps</artifactId>
+        <version>2.1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>servlet-authz-app</artifactId>
+    <packaging>war</packaging>
+
+    <name>Keycloak Authz: Servlet Authorization Test</name>
+    <description>Servlet Authorization Test</description>
+
+
+    <dependencies>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-authz-client</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-core</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <finalName>${project.artifactId}</finalName>
+        <plugins>
+            <plugin>
+                <groupId>org.jboss.as.plugins</groupId>
+                <artifactId>jboss-as-maven-plugin</artifactId>
+                <configuration>
+                    <skip>false</skip>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.wildfly.plugins</groupId>
+                <artifactId>wildfly-maven-plugin</artifactId>
+                <configuration>
+                    <skip>false</skip>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/README.md b/testsuite/integration-arquillian/test-apps/servlet-authz/README.md
new file mode 100644
index 0000000..f93acb5
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/README.md
@@ -0,0 +1,54 @@
+# About the Example Application
+
+This is a simple Servlet-based application that will introduce you to some of the main concepts around Keycloak Authorization Services.
+
+For this application, users can be regular users, premium users or administrators, where:
+
+* Regular users have very limited access.
+* Premium users have access to the *premium area*
+* Administrators have access to the *administration area*
+
+In Keycloak, all the paths being protected are resources on the server.
+
+This application will also show you how to create a dynamic menu with the permissions granted to an user.
+
+## Create the Example Realm and a Resource Server
+
+Considering that your Keycloak Server is up and running, log in to the Keycloak Administration Console.
+
+Now, create a new realm based on the following configuration file:
+
+    examples/authz/servlet-authz/servlet-authz-realm.json
+    
+That will import a pre-configured realm with everything you need to run this example. For more details about how to import a realm 
+into Keycloak, check the Keycloak's reference documentation.
+
+After importing that file, you'll have a new realm called ``servlet-authz``. 
+
+Now, let's import another configuration using the Administration Console in order to configure the client application ``servlet-authz-app`` as a resource server with all resources, scopes, permissions and policies.
+
+Click on ``Clients`` on the left side menu. Click on the ``servlet-authz-app`` on the client listing page. This will
+open the ``Client Details`` page. Once there, click on the `Authorization` tab. 
+
+Click on the ``Select file`` button, which means you want to import a resource server configuration. Now select the file that is located at:
+
+    examples/authz/servlet-authz/servlet-authz-app-config.json
+    
+Now click ``Upload`` and the resource server will be updated accordingly.
+
+## Deploy and Run the Example Applications
+
+To deploy the example application, follow these steps:
+
+    cd examples/authz/servlet-authz
+    mvn clean package wildfly:deploy
+    
+Now, try to access the client application using the following URL:
+
+    http://localhost:8080/servlet-authz-app
+
+If everything is correct, you will be redirect to Keycloak login page. You can login to the application with the following credentials:
+
+* username: jdoe / password: jdoe
+* username: alice / password: alice
+* username: admin / password: admin
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/servlet-authz-app-authz-service.json b/testsuite/integration-arquillian/test-apps/servlet-authz/servlet-authz-app-authz-service.json
new file mode 100644
index 0000000..43ebde4
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/servlet-authz-app-authz-service.json
@@ -0,0 +1,147 @@
+{
+  "allowRemoteResourceManagement": true,
+  "policyEnforcementMode": "ENFORCING",
+  "resources": [
+    {
+      "name": "Admin Resource",
+      "uri": "/protected/admin/*",
+      "type": "http://servlet-authz/protected/admin",
+      "scopes": [
+        {
+          "name": "urn:servlet-authz:protected:admin:access"
+        }
+      ]
+    },
+    {
+      "name": "Protected Resource",
+      "uri": "/*",
+      "type": "http://servlet-authz/protected/resource",
+      "scopes": [
+        {
+          "name": "urn:servlet-authz:protected:resource:access"
+        }
+      ]
+    },
+    {
+      "name": "Premium Resource",
+      "uri": "/protected/premium/*",
+      "type": "urn:servlet-authz:protected:resource",
+      "scopes": [
+        {
+          "name": "urn:servlet-authz:protected:premium:access"
+        }
+      ]
+    },
+    {
+      "name": "Main Page",
+      "type": "urn:servlet-authz:protected:resource",
+      "scopes": [
+        {
+          "name": "urn:servlet-authz:page:main:actionForAdmin"
+        },
+        {
+          "name": "urn:servlet-authz:page:main:actionForUser"
+        },
+        {
+          "name": "urn:servlet-authz:page:main:actionForPremiumUser"
+        }
+      ]
+    }
+  ],
+  "policies": [
+    {
+      "name": "Any Admin Policy",
+      "description": "Defines that adminsitrators can do something",
+      "type": "role",
+      "config": {
+        "roles": "[{\"id\":\"admin\"}]"
+      }
+    },
+    {
+      "name": "Any User Policy",
+      "description": "Defines that any user can do something",
+      "type": "role",
+      "config": {
+        "roles": "[{\"id\":\"user\"}]"
+      }
+    },
+    {
+      "name": "Only Premium User Policy",
+      "description": "Defines that only premium users can do something",
+      "type": "role",
+      "logic": "POSITIVE",
+      "config": {
+        "roles": "[{\"id\":\"user_premium\"}]"
+      }
+    },
+    {
+      "name": "All Users Policy",
+      "description": "Defines that all users can do something",
+      "type": "aggregate",
+      "decisionStrategy": "AFFIRMATIVE",
+      "config": {
+        "applyPolicies": "[\"Any User Policy\",\"Any Admin Policy\",\"Only Premium User Policy\"]"
+      }
+    },
+    {
+      "name": "Premium Resource Permission",
+      "description": "A policy that defines access to premium resources",
+      "type": "resource",
+      "decisionStrategy": "UNANIMOUS",
+      "config": {
+        "resources": "[\"Premium Resource\"]",
+        "applyPolicies": "[\"Only Premium User Policy\"]"
+      }
+    },
+    {
+      "name": "Administrative Resource Permission",
+      "description": "A policy that defines access to administrative resources",
+      "type": "resource",
+      "decisionStrategy": "UNANIMOUS",
+      "config": {
+        "resources": "[\"Admin Resource\"]",
+        "applyPolicies": "[\"Any Admin Policy\"]"
+      }
+    },
+    {
+      "name": "Protected Resource Permission",
+      "description": "A policy that defines access to any protected resource",
+      "type": "resource",
+      "decisionStrategy": "AFFIRMATIVE",
+      "config": {
+        "resources": "[\"Protected Resource\"]",
+        "applyPolicies": "[\"All Users Policy\"]"
+      }
+    },
+    {
+      "name": "Action 1 on Main Page Resource Permission",
+      "description": "A policy that defines access to action 1 on the main page",
+      "type": "scope",
+      "decisionStrategy": "AFFIRMATIVE",
+      "config": {
+        "scopes": "[\"urn:servlet-authz:page:main:actionForAdmin\"]",
+        "applyPolicies": "[\"Any Admin Policy\"]"
+      }
+    },
+    {
+      "name": "Action 2 on Main Page Resource Permission",
+      "description": "A policy that defines access to action 2 on the main page",
+      "type": "scope",
+      "decisionStrategy": "AFFIRMATIVE",
+      "config": {
+        "scopes": "[\"urn:servlet-authz:page:main:actionForUser\"]",
+        "applyPolicies": "[\"Any User Policy\"]"
+      }
+    },
+    {
+      "name": "Action 3 on Main Page Resource Permission",
+      "description": "A policy that defines access to action 3 on the main page",
+      "type": "scope",
+      "decisionStrategy": "AFFIRMATIVE",
+      "config": {
+        "scopes": "[\"urn:servlet-authz:page:main:actionForPremiumUser\"]",
+        "applyPolicies": "[\"Only Premium User Policy\"]"
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/servlet-authz-realm.json b/testsuite/integration-arquillian/test-apps/servlet-authz/servlet-authz-realm.json
new file mode 100644
index 0000000..371e451
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/servlet-authz-realm.json
@@ -0,0 +1,95 @@
+{
+  "realm": "servlet-authz",
+  "enabled": true,
+  "privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
+  "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+  "requiredCredentials": [
+    "password"
+  ],
+  "users": [
+    {
+      "username": "alice",
+      "enabled": true,
+      "credentials": [
+        {
+          "type": "password",
+          "value": "alice"
+        }
+      ],
+      "realmRoles": [
+        "user"
+      ]
+    },
+    {
+      "username": "jdoe",
+      "enabled": true,
+      "credentials": [
+        {
+          "type": "password",
+          "value": "jdoe"
+        }
+      ],
+      "realmRoles": [
+        "user",
+        "user_premium"
+      ]
+    },
+    {
+      "username": "admin",
+      "enabled": true,
+      "credentials": [
+        {
+          "type": "password",
+          "value": "admin"
+        }
+      ],
+      "realmRoles": [
+        "user",
+        "admin"
+      ],
+      "clientRoles": {
+        "realm-management": [
+          "realm-admin"
+        ]
+      }
+    },
+    {
+      "username": "service-account-servlet-authz-app",
+      "enabled": true,
+      "serviceAccountClientId": "servlet-authz-app",
+      "clientRoles": {
+        "servlet-authz-app" : ["uma_protection"]
+      }
+    }
+  ],
+  "roles": {
+    "realm": [
+      {
+        "name": "user",
+        "description": "User privileges"
+      },
+      {
+        "name": "admin",
+        "description": "Administrator privileges"
+      },
+      {
+        "name": "user_premium",
+        "description": "User Premium privileges"
+      }
+    ]
+  },
+  "clients": [
+    {
+      "clientId": "servlet-authz-app",
+      "enabled": true,
+      "baseUrl": "/servlet-authz-app",
+      "adminUrl": "/servlet-authz-app",
+      "bearerOnly": false,
+      "authorizationServicesEnabled": true,
+      "redirectUris": [
+        "/servlet-authz-app/*"
+      ],
+      "secret": "secret"
+    }
+  ]
+}
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/accessDenied.jsp b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/accessDenied.jsp
new file mode 100644
index 0000000..6f25023
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/accessDenied.jsp
@@ -0,0 +1,6 @@
+<html>
+    <body>
+        <h2 style="color: red">You can not access this resource.</h2>
+        <%@include file="logout-include.jsp"%>
+    </body>
+</html>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/index.jsp b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/index.jsp
new file mode 100755
index 0000000..3fbfca2
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/index.jsp
@@ -0,0 +1,35 @@
+<%@page import="org.keycloak.AuthorizationContext" %>
+<%@ page import="org.keycloak.KeycloakSecurityContext" %>
+<%@ page import="org.keycloak.representations.idm.authorization.Permission" %>
+
+<%
+    KeycloakSecurityContext keycloakSecurityContext = (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());
+    AuthorizationContext authzContext = keycloakSecurityContext.getAuthorizationContext();
+%>
+
+<html>
+<body>
+    <%@include file="logout-include.jsp"%>
+    <h2>This is a public resource. Try to access one of these <i>protected</i> resources:</h2>
+
+    <p><a href="protected/dynamicMenu.jsp">Dynamic Menu</a></p>
+    <p><a href="protected/premium/onlyPremium.jsp">User Premium</a></p>
+    <p><a href="protected/admin/onlyAdmin.jsp">Administration</a></p>
+
+    <h3>Your permissions are:</h3>
+
+    <ul>
+        <%
+            for (Permission permission : authzContext.getPermissions()) {
+        %>
+        <li>
+            <p>Resource: <%= permission.getResourceSetName() %></p>
+            <p>ID: <%= permission.getResourceSetId() %></p>
+            <p>Scopes: <%= permission.getScopes() %></p>
+        </li>
+        <%
+            }
+        %>
+    </ul>
+</body>
+</html>
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/logout-include.jsp b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/logout-include.jsp
new file mode 100644
index 0000000..21ef2ed
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/logout-include.jsp
@@ -0,0 +1,11 @@
+<%@ page import="org.keycloak.common.util.KeycloakUriBuilder" %>
+<%@ page import="org.keycloak.constants.ServiceUrlConstants" %>
+<%
+    String scheme = request.getScheme();
+    String host = request.getServerName();
+    int port = request.getServerPort();
+    String contextPath = request.getContextPath();
+    String redirectUri = scheme + "://" + host + ":" + port + contextPath;
+%>
+<h2>Click here <a href="<%= KeycloakUriBuilder.fromUri("http://localhost:8180/auth").path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH)
+            .queryParam("redirect_uri", redirectUri).build("servlet-authz").toString()%>">Sign Out</a></h2>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/META-INF/jboss-deployment-structure.xml b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/META-INF/jboss-deployment-structure.xml
new file mode 100644
index 0000000..515ffa5
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/META-INF/jboss-deployment-structure.xml
@@ -0,0 +1,25 @@
+<!--
+  ~  Copyright 2016 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.
+  ~
+  -->
+
+<jboss-deployment-structure>
+    <deployment>
+        <dependencies>
+            <module name="org.keycloak.keycloak-authz-client" services="import"/>
+        </dependencies>
+    </deployment>
+</jboss-deployment-structure>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/protected/admin/onlyAdmin.jsp b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/protected/admin/onlyAdmin.jsp
new file mode 100644
index 0000000..5946cd6
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/protected/admin/onlyAdmin.jsp
@@ -0,0 +1,6 @@
+<html>
+<body>
+    <h2>Only Administrators can access this page.</h2>
+    <%@include file="../../logout-include.jsp"%>
+</body>
+</html>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/protected/dynamicMenu.jsp b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/protected/dynamicMenu.jsp
new file mode 100644
index 0000000..1473d22
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/protected/dynamicMenu.jsp
@@ -0,0 +1,48 @@
+<%@page import="org.keycloak.AuthorizationContext" %>
+<%@ page import="org.keycloak.KeycloakSecurityContext" %>
+
+<%
+    KeycloakSecurityContext keycloakSecurityContext = (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());
+    AuthorizationContext authzContext = keycloakSecurityContext.getAuthorizationContext();
+%>
+
+<html>
+<body>
+<h2>Any authenticated user can access this page.</h2>
+<%@include file="../logout-include.jsp"%>
+
+<p>Here is a dynamic menu built from the permissions returned by the server:</p>
+
+<ul>
+    <%
+        if (authzContext.hasResourcePermission("Protected Resource")) {
+    %>
+    <li>
+        Do user thing
+    </li>
+    <%
+        }
+    %>
+
+    <%
+        if (authzContext.hasResourcePermission("Premium Resource")) {
+    %>
+    <li>
+        Do  user premium thing
+    </li>
+    <%
+        }
+    %>
+
+    <%
+        if (authzContext.hasPermission("Admin Resource", "urn:servlet-authz:protected:admin:access")) {
+    %>
+    <li>
+        Do administration thing
+    </li>
+    <%
+        }
+    %>
+</ul>
+</body>
+</html>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/protected/premium/onlyPremium.jsp b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/protected/premium/onlyPremium.jsp
new file mode 100644
index 0000000..9244f9c
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/protected/premium/onlyPremium.jsp
@@ -0,0 +1,6 @@
+<html>
+<body>
+<h2>Only for premium users.</h2>
+<%@include file="../../logout-include.jsp"%>
+</body>
+</html>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/WEB-INF/keycloak.json b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/WEB-INF/keycloak.json
new file mode 100644
index 0000000..7b362a7
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/WEB-INF/keycloak.json
@@ -0,0 +1,14 @@
+{
+  "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"
+  }
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/WEB-INF/web.xml b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..14d0615
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app xmlns="http://java.sun.com/xml/ns/javaee"
+		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+		 version="3.0">
+
+	<module-name>servlet-authz-app</module-name>
+
+	<security-constraint>
+		<web-resource-collection>
+			<web-resource-name>All Resources</web-resource-name>
+			<url-pattern>/*</url-pattern>
+		</web-resource-collection>
+		<auth-constraint>
+			<role-name>user</role-name>
+		</auth-constraint>
+	</security-constraint>
+
+	<security-constraint>
+		<web-resource-collection>
+			<web-resource-name>All Resources</web-resource-name>
+			<url-pattern>/*</url-pattern>
+		</web-resource-collection>
+		<auth-constraint>
+			<role-name>admin</role-name>
+		</auth-constraint>
+	</security-constraint>
+
+	<login-config>
+		<auth-method>KEYCLOAK</auth-method>
+		<realm-name>servlet-authz</realm-name>
+	</login-config>
+
+	<security-role>
+		<role-name>admin</role-name>
+	</security-role>
+
+	<security-role>
+		<role-name>user</role-name>
+	</security-role>
+
+	<error-page>
+		<error-code>403</error-code>
+		<location>/accessDenied.jsp</location>
+	</error-page>
+
+</web-app>
diff --git a/testsuite/integration-arquillian/test-apps/test-apps-dist/build.xml b/testsuite/integration-arquillian/test-apps/test-apps-dist/build.xml
index 9621ae5..3e90544 100755
--- a/testsuite/integration-arquillian/test-apps/test-apps-dist/build.xml
+++ b/testsuite/integration-arquillian/test-apps/test-apps-dist/build.xml
@@ -43,5 +43,13 @@
                 <exclude name="**/subsystem-config.xml"/>
             </fileset>
         </copy>
+        <copy todir="target/test-apps/servlet-authz-app" overwrite="true">
+            <fileset dir="../servlet-authz">
+                <exclude name="**/target/**"/>
+                <exclude name="**/*.iml"/>
+                <exclude name="**/*.unconfigured"/>
+                <exclude name="**/subsystem-config.xml"/>
+            </fileset>
+        </copy>
     </target>
 </project>
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/PhotozClientAuthzTestApp.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/PhotozClientAuthzTestApp.java
index 4721737..0e07157 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/PhotozClientAuthzTestApp.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/PhotozClientAuthzTestApp.java
@@ -50,7 +50,10 @@ public class PhotozClientAuthzTestApp extends AbstractPageWithInjectedUrl {
     protected ConsentPage consentPage;
 
     public void createAlbum(String name) {
-        this.driver.findElement(By.id("create-album")).click();
+        navigateTo();
+        By id = By.id("create-album");
+        WaitUtils.waitUntilElement(id);
+        this.driver.findElement(id).click();
         Form.setInputValue(this.driver.findElement(By.id("album.name")), name);
         this.driver.findElement(By.id("save-album")).click();
         pause(500);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractDefaultAuthzConfigAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractDefaultAuthzConfigAdapterTest.java
index 82b3ec4..4666674 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractDefaultAuthzConfigAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractDefaultAuthzConfigAdapterTest.java
@@ -59,31 +59,28 @@ public abstract class AbstractDefaultAuthzConfigAdapterTest extends AbstractExam
 
     @Test
     public void testDefaultAuthzConfig() throws Exception {
-        configureAuthorizationServices();
-        deploy();
-        navigateToResourceServer();
-        login();
+        try {
+            this.deployer.deploy(RESOURCE_SERVER_ID);
+            configureAuthorizationServices();
 
-        assertTrue(this.driver.getPageSource().contains("Your permissions are"));
-        assertTrue(this.driver.getPageSource().contains("Default Resource"));
-    }
+            login();
 
-    private void login() {
-        this.loginPage.form().login("alice", "alice");
+            assertTrue(this.driver.getPageSource().contains("Your permissions are"));
+            assertTrue(this.driver.getPageSource().contains("Default Resource"));
+        } finally {
+            this.deployer.undeploy(RESOURCE_SERVER_ID);
+        }
     }
 
-    private void navigateToResourceServer() throws MalformedURLException {
+    private void login() throws MalformedURLException {
         this.driver.navigate().to(getResourceServerUrl());
+        this.loginPage.form().login("alice", "alice");
     }
 
     private URL getResourceServerUrl() throws MalformedURLException {
         return this.appServerContextRootPage.getUriBuilder().path(RESOURCE_SERVER_ID).build().toURL();
     }
 
-    private void deploy() {
-        this.deployer.deploy(RESOURCE_SERVER_ID);
-    }
-
     private void configureAuthorizationServices() {
         ClientsResource clients = realmsResouce().realm(REALM_NAME).clients();
         ClientRepresentation client = clients.findByClientId(RESOURCE_SERVER_ID).get(0);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java
index 8a8d483..9a0fb26 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java
@@ -25,6 +25,7 @@ import org.junit.Test;
 import org.keycloak.admin.client.resource.AuthorizationResource;
 import org.keycloak.admin.client.resource.ClientResource;
 import org.keycloak.admin.client.resource.ClientsResource;
+import org.keycloak.admin.client.resource.ResourcesResource;
 import org.keycloak.admin.client.resource.RoleResource;
 import org.keycloak.admin.client.resource.UserResource;
 import org.keycloak.admin.client.resource.UsersResource;
@@ -43,6 +44,10 @@ import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Consumer;
@@ -94,7 +99,8 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
         importResourceServerSettings();
     }
 
-    public void testCreateDeleteAlbum() throws Exception {
+    @Test
+    public void testUserCanCreateAndDeleteAlbum() throws Exception {
         try {
             this.deployer.deploy(RESOURCE_SERVER_ID);
 
@@ -102,29 +108,28 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
             this.clientPage.createAlbum("Alice Family Album");
 
             List<ResourceRepresentation> resources = getAuthorizationResource().resources().resources();
-
             assertFalse(resources.stream().filter(resource -> resource.getOwner().getName().equals("alice")).collect(Collectors.toList()).isEmpty());
 
             this.clientPage.deleteAlbum("Alice Family Album");
 
             resources = getAuthorizationResource().resources().resources();
-
             assertTrue(resources.stream().filter(resource -> resource.getOwner().getName().equals("alice")).collect(Collectors.toList()).isEmpty());
         } finally {
             this.deployer.undeploy(RESOURCE_SERVER_ID);
         }
     }
 
+    @Test
     public void testOnlyOwnerCanDeleteAlbum() throws Exception {
         try {
             this.deployer.deploy(RESOURCE_SERVER_ID);
             this.clientPage.login("alice", "alice");
             this.clientPage.createAlbum("Alice-Family-Album");
+
             this.clientPage.login("admin", "admin");
             this.clientPage.navigateToAdminAlbum();
 
             List<ResourceRepresentation> resources = getAuthorizationResource().resources().resources();
-
             assertFalse(resources.stream().filter(resource -> resource.getOwner().getName().equals("alice")).collect(Collectors.toList()).isEmpty());
 
             for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
@@ -135,11 +140,11 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
             }
 
             this.clientPage.login("admin", "admin");
+
             this.clientPage.navigateToAdminAlbum();
             this.clientPage.deleteAlbum("Alice-Family-Album");
-
+            assertTrue(this.clientPage.wasDenied());
             resources = getAuthorizationResource().resources().resources();
-
             assertFalse(resources.stream().filter(resource -> resource.getOwner().getName().equals("alice")).collect(Collectors.toList()).isEmpty());
 
             for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
@@ -151,33 +156,34 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
 
             this.clientPage.navigateToAdminAlbum();
             this.clientPage.deleteAlbum("Alice-Family-Album");
-
+            assertFalse(this.clientPage.wasDenied());
             resources = getAuthorizationResource().resources().resources();
-
             assertTrue(resources.stream().filter(resource -> resource.getOwner().getName().equals("alice")).collect(Collectors.toList()).isEmpty());
         } finally {
             this.deployer.undeploy(RESOURCE_SERVER_ID);
         }
     }
 
+    @Test
     public void testRegularUserCanNotAccessAdminResources() throws Exception {
         try {
             this.deployer.deploy(RESOURCE_SERVER_ID);
+
             this.clientPage.login("alice", "alice");
             this.clientPage.navigateToAdminAlbum();
-
             assertTrue(this.clientPage.wasDenied());
         } finally {
             this.deployer.undeploy(RESOURCE_SERVER_ID);
         }
     }
 
+    @Test
     public void testAdminOnlyFromSpecificAddress() throws Exception {
         try {
             this.deployer.deploy(RESOURCE_SERVER_ID);
+
             this.clientPage.login("admin", "admin");
             this.clientPage.navigateToAdminAlbum();
-
             assertFalse(this.clientPage.wasDenied());
 
             for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
@@ -189,7 +195,6 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
             }
 
             this.clientPage.navigateToAdminAlbum();
-
             assertTrue(this.clientPage.wasDenied());
         } finally {
             this.deployer.undeploy(RESOURCE_SERVER_ID);
@@ -200,16 +205,15 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
     public void testAdminWithoutPermissionsToTypedResource() throws Exception {
         try {
             this.deployer.deploy(RESOURCE_SERVER_ID);
+
             this.clientPage.login("alice", "alice");
             this.clientPage.createAlbum("Alice Family Album");
 
             this.clientPage.login("admin", "admin");
             this.clientPage.navigateToAdminAlbum();
-
             assertFalse(this.clientPage.wasDenied());
 
             this.clientPage.viewAlbum("Alice Family Album");
-
             assertFalse(this.clientPage.wasDenied());
 
             for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
@@ -238,7 +242,6 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
 
             this.clientPage.navigateToAdminAlbum();
             this.clientPage.viewAlbum("Alice Family Album");
-
             assertTrue(this.clientPage.wasDenied());
 
             for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
@@ -250,37 +253,32 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
 
             this.clientPage.navigateToAdminAlbum();
             this.clientPage.viewAlbum("Alice Family Album");
-
             assertFalse(this.clientPage.wasDenied());
 
             this.clientPage.navigateToAdminAlbum();
             this.clientPage.deleteAlbum("Alice Family Album");
-
             List<ResourceRepresentation> resources = getAuthorizationResource().resources().resources();
-
             assertTrue(resources.stream().filter(resource -> resource.getOwner().getName().equals("alice")).collect(Collectors.toList()).isEmpty());
         } finally {
             this.deployer.undeploy(RESOURCE_SERVER_ID);
         }
     }
 
-    public void testAdminWithoutPermissionsToDeleteScopePermission() throws Exception {
+    @Test
+    public void testAdminWithoutPermissionsToDeleteAlbum() throws Exception {
         try {
             this.deployer.deploy(RESOURCE_SERVER_ID);
+
             this.clientPage.login("alice", "alice");
             this.clientPage.createAlbum("Alice Family Album");
 
             this.clientPage.login("admin", "admin");
             this.clientPage.navigateToAdminAlbum();
-
             assertFalse(this.clientPage.wasDenied());
 
             this.clientPage.deleteAlbum("Alice Family Album");
-
             assertFalse(this.clientPage.wasDenied());
-
             List<ResourceRepresentation> resources = getAuthorizationResource().resources().resources();
-
             assertTrue(resources.stream().filter(resource -> resource.getOwner().getName().equals("alice")).collect(Collectors.toList()).isEmpty());
 
             for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
@@ -296,14 +294,11 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
             this.clientPage.login("admin", "admin");
             this.clientPage.navigateToAdminAlbum();
             this.clientPage.viewAlbum("Alice Family Album");
-
             assertFalse(this.clientPage.wasDenied());
             resources = getAuthorizationResource().resources().resources();
-
             assertFalse(resources.stream().filter(resource -> resource.getOwner().getName().equals("alice")).collect(Collectors.toList()).isEmpty());
 
             this.clientPage.navigateToAdminAlbum();
-
             this.clientPage.deleteAlbum("Alice Family Album");
             assertTrue(this.clientPage.wasDenied());
 
@@ -316,22 +311,20 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
 
             this.clientPage.navigateToAdminAlbum();
             this.clientPage.deleteAlbum("Alice Family Album");
-
             assertFalse(this.clientPage.wasDenied());
-
             resources = getAuthorizationResource().resources().resources();
-
             assertTrue(resources.stream().filter(resource -> resource.getOwner().getName().equals("alice")).collect(Collectors.toList()).isEmpty());
         } finally {
             this.deployer.undeploy(RESOURCE_SERVER_ID);
         }
     }
 
+    @Test
     public void testClientRoleRepresentingUserConsent() throws Exception {
         try {
             this.deployer.deploy(RESOURCE_SERVER_ID);
-            this.clientPage.login("alice", "alice");
 
+            this.clientPage.login("alice", "alice");
             assertFalse(this.clientPage.wasDenied());
 
             UsersResource usersResource = realmsResouce().realm(REALM_NAME).users();
@@ -355,20 +348,20 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
             roleResource.update(roleRepresentation);
 
             this.clientPage.login("alice", "alice");
-
             assertTrue(this.clientPage.wasDenied());
 
             this.clientPage.loginWithScopes("alice", "alice", RESOURCE_SERVER_ID + "/manage-albums");
-
             assertFalse(this.clientPage.wasDenied());
         } finally {
             this.deployer.undeploy(RESOURCE_SERVER_ID);
         }
     }
 
+    @Test
     public void testClientRoleNotRequired() throws Exception {
         try {
             this.deployer.deploy(RESOURCE_SERVER_ID);
+
             this.clientPage.login("alice", "alice");
 
             assertFalse(this.clientPage.wasDenied());
@@ -394,32 +387,202 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
             manageAlbumRole.update(roleRepresentation);
 
             this.clientPage.login("alice", "alice");
-
             assertTrue(this.clientPage.wasDenied());
 
             for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
                 if ("Any User Policy".equals(policy.getName())) {
                     List<Map> roles = JsonSerialization.readValue(policy.getConfig().get("roles"), List.class);
 
-                    roles.forEach(new Consumer<Map>() {
-                        @Override
-                        public void accept(Map role) {
-                            String roleId = (String) role.get("id");
-                            if (roleId.equals(manageAlbumRole.toRepresentation().getId())) {
-                                role.put("required", false);
-                            }
+                    roles.forEach(role -> {
+                        String roleId = (String) role.get("id");
+                        if (roleId.equals(manageAlbumRole.toRepresentation().getId())) {
+                            role.put("required", false);
                         }
                     });
 
                     policy.getConfig().put("roles", JsonSerialization.writeValueAsString(roles));
-
                     getAuthorizationResource().policies().policy(policy.getId()).update(policy);
                 }
             }
 
             this.clientPage.login("alice", "alice");
+            assertFalse(this.clientPage.wasDenied());
+        } finally {
+            this.deployer.undeploy(RESOURCE_SERVER_ID);
+        }
+    }
+
+    @Test
+    public void testOverridePermissionFromResourceParent() throws Exception {
+        try {
+            this.deployer.deploy(RESOURCE_SERVER_ID);
+
+            this.clientPage.login("alice", "alice");
+            String resourceName = "My Resource Instance";
+            this.clientPage.createAlbum(resourceName);
+            assertFalse(this.clientPage.wasDenied());
+
+            this.clientPage.viewAlbum(resourceName);
+            assertFalse(this.clientPage.wasDenied());
+
+            this.clientPage.navigateTo();
+            this.clientPage.deleteAlbum(resourceName);
+            assertFalse(this.clientPage.wasDenied());
+
+            this.clientPage.createAlbum(resourceName);
+
+            this.clientPage.login("admin", "admin");
+
+            this.clientPage.navigateToAdminAlbum();
+            this.clientPage.viewAlbum(resourceName);
+            assertFalse(this.clientPage.wasDenied());
+
+            this.clientPage.navigateToAdminAlbum();;
+            this.clientPage.deleteAlbum(resourceName);
+            assertFalse(this.clientPage.wasDenied());
+
+            this.clientPage.login("alice", "alice");
+            this.clientPage.createAlbum(resourceName);
+            assertFalse(this.clientPage.wasDenied());
+
+            getAuthorizationResource().resources().resources().forEach(resource -> {
+                if (resource.getName().equals(resourceName)) {
+                    try {
+                        PolicyRepresentation resourceInstancePermission = new PolicyRepresentation();
+
+                        resourceInstancePermission.setName(resourceName + "Permission");
+                        resourceInstancePermission.setType("resource");
+
+                        Map<String, String> config = new HashMap<>();
+
+                        config.put("resources", JsonSerialization.writeValueAsString(Arrays.asList(resource.getId())));
+                        config.put("applyPolicies", JsonSerialization.writeValueAsString(Arrays.asList("Only Owner Policy")));
+
+                        resourceInstancePermission.setConfig(config);
+                        getAuthorizationResource().policies().create(resourceInstancePermission);
+                    } catch (Exception e) {
+                        throw new RuntimeException("Error creating policy.", e);
+                    }
+                }
+            });
+
+            this.clientPage.login("admin", "admin");
+
+            this.clientPage.navigateToAdminAlbum();
+            this.clientPage.viewAlbum(resourceName);
+            assertTrue(this.clientPage.wasDenied());
+
+            this.clientPage.navigateToAdminAlbum();
+            this.clientPage.deleteAlbum(resourceName);
+            assertTrue(this.clientPage.wasDenied());
+
+            this.clientPage.login("alice", "alice");
+            this.clientPage.deleteAlbum(resourceName);
+            assertFalse(this.clientPage.wasDenied());
+
+            ResourcesResource resourcesResource = getAuthorizationResource().resources();
+            List<ResourceRepresentation> resources = resourcesResource.resources();
+            assertTrue(resources.stream().filter(resource -> resource.getOwner().getName().equals("alice")).collect(Collectors.toList()).isEmpty());
+        } finally {
+            this.deployer.undeploy(RESOURCE_SERVER_ID);
+        }
+    }
 
+    @Test
+    public void testInheritPermissionFromResourceParent() throws Exception {
+        try {
+            this.deployer.deploy(RESOURCE_SERVER_ID);
+
+            this.clientPage.login("alice", "alice");
+
+            String resourceName = "My Resource Instance";
+            this.clientPage.createAlbum(resourceName);
+            assertFalse(this.clientPage.wasDenied());
+
+            this.clientPage.viewAlbum(resourceName);
+            assertFalse(this.clientPage.wasDenied());
+
+            this.clientPage.navigateTo();
+            this.clientPage.deleteAlbum(resourceName);
             assertFalse(this.clientPage.wasDenied());
+
+            this.clientPage.createAlbum(resourceName);
+
+            this.clientPage.login("admin", "admin");
+
+            this.clientPage.navigateToAdminAlbum();
+            this.clientPage.viewAlbum(resourceName);
+            assertFalse(this.clientPage.wasDenied());
+
+            this.clientPage.navigateToAdminAlbum();;
+            this.clientPage.deleteAlbum(resourceName);
+            assertFalse(this.clientPage.wasDenied());
+
+            this.clientPage.login("alice", "alice");
+            this.clientPage.createAlbum(resourceName);
+            assertFalse(this.clientPage.wasDenied());
+
+            ResourcesResource resourcesResource = getAuthorizationResource().resources();
+            resourcesResource.resources().forEach(resource -> {
+                if (resource.getName().equals(resourceName)) {
+                    try {
+                        PolicyRepresentation resourceInstancePermission = new PolicyRepresentation();
+
+                        resourceInstancePermission.setName(resourceName + "Permission");
+                        resourceInstancePermission.setType("resource");
+
+                        Map<String, String> config = new HashMap<>();
+
+                        config.put("resources", JsonSerialization.writeValueAsString(Arrays.asList(resource.getId())));
+                        config.put("applyPolicies", JsonSerialization.writeValueAsString(Arrays.asList("Only Owner Policy")));
+
+                        resourceInstancePermission.setConfig(config);
+                        getAuthorizationResource().policies().create(resourceInstancePermission);
+                    } catch (Exception e) {
+                        throw new RuntimeException("Error creating policy.", e);
+                    }
+                }
+            });
+
+            this.clientPage.login("admin", "admin");
+
+            this.clientPage.navigateToAdminAlbum();
+            this.clientPage.viewAlbum(resourceName);
+            assertTrue(this.clientPage.wasDenied());
+
+            this.clientPage.navigateToAdminAlbum();
+            this.clientPage.deleteAlbum(resourceName);
+            assertTrue(this.clientPage.wasDenied());
+
+            resourcesResource.resources().forEach(resource -> {
+                if (resource.getName().equals(resourceName)) {
+                    resource.setScopes(resource.getScopes().stream().filter(scope -> !scope.getName().equals("urn:photoz.com:scopes:album:view")).collect(Collectors.toSet()));
+                    resourcesResource.resource(resource.getId()).update(resource);
+                }
+            });
+
+            this.clientPage.login("admin", "admin");
+
+            this.clientPage.navigateToAdminAlbum();
+            this.clientPage.viewAlbum(resourceName);
+            assertFalse(this.clientPage.wasDenied());
+
+            this.clientPage.navigateToAdminAlbum();
+            this.clientPage.deleteAlbum(resourceName);
+            assertTrue(this.clientPage.wasDenied());
+
+            this.clientPage.login("alice", "alice");
+            this.clientPage.deleteAlbum(resourceName);
+            assertFalse(this.clientPage.wasDenied());
+            List<ResourceRepresentation> resources = resourcesResource.resources();
+            assertTrue(resources.stream().filter(resource -> resource.getOwner().getName().equals("alice")).collect(Collectors.toList()).isEmpty());
+
+            resourcesResource.resources().forEach(resource -> {
+                if (resource.getName().equals(resourceName)) {
+                    resource.setScopes(Collections.emptySet());
+                    resourcesResource.resource(resource.getId()).update(resource);
+                }
+            });
         } finally {
             this.deployer.undeploy(RESOURCE_SERVER_ID);
         }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzAdapterTest.java
new file mode 100644
index 0000000..7f61556
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzAdapterTest.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright 2016 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.Deployer;
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.test.api.ArquillianResource;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.Test;
+import org.keycloak.admin.client.resource.AuthorizationResource;
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.admin.client.resource.ClientsResource;
+import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.admin.client.resource.UserResource;
+import org.keycloak.admin.client.resource.UsersResource;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.representations.idm.authorization.PolicyRepresentation;
+import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
+import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest;
+import org.keycloak.testsuite.util.WaitUtils;
+import org.keycloak.util.JsonSerialization;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.keycloak.testsuite.util.IOUtil.loadJson;
+import static org.keycloak.testsuite.util.IOUtil.loadRealm;
+import static org.keycloak.testsuite.util.WaitUtils.pause;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public abstract class AbstractServletAuthzAdapterTest extends AbstractExampleAdapterTest {
+
+    private static final String REALM_NAME = "servlet-authz";
+    private static final String RESOURCE_SERVER_ID = "servlet-authz-app";
+
+    @ArquillianResource
+    private Deployer deployer;
+
+    @Override
+    public void addAdapterTestRealms(List<RealmRepresentation> testRealms) {
+        testRealms.add(
+                loadRealm(new File(TEST_APPS_HOME_DIR + "/servlet-authz-app/servlet-authz-realm.json")));
+    }
+
+    @Deployment(name = RESOURCE_SERVER_ID, managed = false)
+    public static WebArchive deployment() throws IOException {
+        return exampleDeployment(RESOURCE_SERVER_ID);
+    }
+
+    @Override
+    public void beforeAbstractKeycloakTest() throws Exception {
+        super.beforeAbstractKeycloakTest();
+        importResourceServerSettings();
+    }
+
+    @Test
+    public void testRegularUserPermissions() throws Exception {
+        try {
+            this.deployer.deploy(RESOURCE_SERVER_ID);
+
+            login("alice", "alice");
+            assertFalse(wasDenied());
+            assertTrue(hasLink("User Premium"));
+            assertTrue(hasLink("Administration"));
+            assertTrue(hasText("urn:servlet-authz:page:main:actionForUser"));
+            assertFalse(hasText("urn:servlet-authz:page:main:actionForAdmin"));
+            assertFalse(hasText("urn:servlet-authz:page:main:actionForPremiumUser"));
+
+            navigateToDynamicMenuPage();
+            assertTrue(hasText("Do user thing"));
+            assertFalse(hasText("Do  user premium thing"));
+            assertFalse(hasText("Do administration thing"));
+
+            navigateToUserPremiumPage();
+            assertTrue(wasDenied());
+
+            navigateToAdminPage();
+            assertTrue(wasDenied());
+        } finally {
+            this.deployer.undeploy(RESOURCE_SERVER_ID);
+        }
+    }
+
+    @Test
+    public void testUserPremiumPermissions() throws Exception {
+        try {
+            this.deployer.deploy(RESOURCE_SERVER_ID);
+
+            login("jdoe", "jdoe");
+            assertFalse(wasDenied());
+            assertTrue(hasLink("User Premium"));
+            assertTrue(hasLink("Administration"));
+            assertTrue(hasText("urn:servlet-authz:page:main:actionForUser"));
+            assertTrue(hasText("urn:servlet-authz:page:main:actionForPremiumUser"));
+            assertFalse(hasText("urn:servlet-authz:page:main:actionForAdmin"));
+
+            navigateToDynamicMenuPage();
+            assertTrue(hasText("Do user thing"));
+            assertTrue(hasText("Do  user premium thing"));
+            assertFalse(hasText("Do administration thing"));
+
+            navigateToUserPremiumPage();
+            assertFalse(wasDenied());
+
+            navigateToAdminPage();
+            assertTrue(wasDenied());
+        } finally {
+            this.deployer.undeploy(RESOURCE_SERVER_ID);
+        }
+    }
+
+    @Test
+    public void testAdminPermissions() throws Exception {
+        try {
+            this.deployer.deploy(RESOURCE_SERVER_ID);
+
+            login("admin", "admin");
+            assertFalse(wasDenied());
+            assertTrue(hasLink("User Premium"));
+            assertTrue(hasLink("Administration"));
+            assertTrue(hasText("urn:servlet-authz:page:main:actionForUser"));
+            assertTrue(hasText("urn:servlet-authz:page:main:actionForAdmin"));
+            assertFalse(hasText("urn:servlet-authz:page:main:actionForPremiumUser"));
+
+            navigateToDynamicMenuPage();
+            assertTrue(hasText("Do user thing"));
+            assertTrue(hasText("Do administration thing"));
+            assertFalse(hasText("Do  user premium thing"));
+
+            navigateToUserPremiumPage();
+            assertTrue(wasDenied());
+
+            navigateToAdminPage();
+            assertFalse(wasDenied());
+        } finally {
+            this.deployer.undeploy(RESOURCE_SERVER_ID);
+        }
+    }
+
+    @Test
+    public void testGrantPremiumAccessToUser() throws Exception {
+        try {
+            this.deployer.deploy(RESOURCE_SERVER_ID);
+
+            login("alice", "alice");
+            assertFalse(wasDenied());
+
+            navigateToUserPremiumPage();
+            assertTrue(wasDenied());
+
+            for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
+                if ("Premium Resource Permission".equals(policy.getName())) {
+                    policy.getConfig().put("applyPolicies", "[\"Any User Policy\"]");
+                    getAuthorizationResource().policies().policy(policy.getId()).update(policy);
+                }
+            }
+
+            login("alice", "alice");
+
+            navigateToUserPremiumPage();
+            assertFalse(wasDenied());
+
+            for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
+                if ("Premium Resource Permission".equals(policy.getName())) {
+                    policy.getConfig().put("applyPolicies", "[\"Only Premium User Policy\"]");
+                    getAuthorizationResource().policies().policy(policy.getId()).update(policy);
+                }
+            }
+
+            login("alice", "alice");
+
+            navigateToUserPremiumPage();
+            assertTrue(wasDenied());
+
+            PolicyRepresentation onlyAlicePolicy = new PolicyRepresentation();
+
+            onlyAlicePolicy.setName("Temporary Premium Access Policy");
+            onlyAlicePolicy.setType("user");
+            HashMap<String, String> config = new HashMap<>();
+            UsersResource usersResource = realmsResouce().realm(REALM_NAME).users();
+            List<UserRepresentation> users = usersResource.search("alice", null, null, null, null, null);
+
+            assertFalse(users.isEmpty());
+
+            config.put("users", JsonSerialization.writeValueAsString(Arrays.asList(users.get(0).getId())));
+
+            onlyAlicePolicy.setConfig(config);
+            getAuthorizationResource().policies().create(onlyAlicePolicy);
+
+            for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
+                if ("Premium Resource Permission".equals(policy.getName())) {
+                    policy.getConfig().put("applyPolicies", "[\"Temporary Premium Access Policy\"]");
+                    getAuthorizationResource().policies().policy(policy.getId()).update(policy);
+                }
+            }
+
+            login("alice", "alice");
+
+            navigateToUserPremiumPage();
+            assertFalse(wasDenied());
+        } finally {
+            this.deployer.undeploy(RESOURCE_SERVER_ID);
+        }
+    }
+
+    @Test
+    public void testGrantAdministrativePermissions() throws Exception {
+        try {
+            this.deployer.deploy(RESOURCE_SERVER_ID);
+
+            login("jdoe", "jdoe");
+
+            navigateToAdminPage();
+            assertTrue(wasDenied());
+
+            RealmResource realmResource = realmsResouce().realm(REALM_NAME);
+            UsersResource usersResource = realmResource.users();
+            List<UserRepresentation> users = usersResource.search("jdoe", null, null, null, null, null);
+
+            assertFalse(users.isEmpty());
+
+            UserResource userResource = usersResource.get(users.get(0).getId());
+
+            RoleRepresentation adminRole = realmResource.roles().get("admin").toRepresentation();
+            userResource.roles().realmLevel().add(Arrays.asList(adminRole));
+
+            login("jdoe", "jdoe");
+
+            navigateToAdminPage();
+            assertFalse(wasDenied());
+        } finally {
+            this.deployer.undeploy(RESOURCE_SERVER_ID);
+        }
+    }
+
+    private boolean hasLink(String text) {
+        return getLink(text) != null;
+    }
+
+    private boolean hasText(String text) {
+        return this.driver.getPageSource().contains(text);
+    }
+
+    private WebElement getLink(String text) {
+        return this.driver.findElement(By.xpath("//a[text() = '" + text + "']"));
+    }
+
+    private void importResourceServerSettings() throws FileNotFoundException {
+        getAuthorizationResource().importSettings(loadJson(new FileInputStream(new File(TEST_APPS_HOME_DIR + "/servlet-authz-app/servlet-authz-app-authz-service.json")), ResourceServerRepresentation.class));
+    }
+
+    private AuthorizationResource getAuthorizationResource() throws FileNotFoundException {
+        return getClientResource(RESOURCE_SERVER_ID).authorization();
+    }
+
+    private ClientResource getClientResource(String clientId) {
+        ClientsResource clients = this.realmsResouce().realm(REALM_NAME).clients();
+        ClientRepresentation resourceServer = clients.findByClientId(clientId).get(0);
+        return clients.get(resourceServer.getId());
+    }
+
+    private void logOut() {
+        navigateTo();
+        By by = By.xpath("//a[text() = 'Sign Out']");
+        WaitUtils.waitUntilElement(by);
+        this.driver.findElement(by).click();
+        pause(500);
+    }
+
+    private void login(String username, String password) throws InterruptedException {
+        navigateTo();
+        Thread.sleep(2000);
+        if (this.driver.getCurrentUrl().startsWith(getResourceServerUrl().toString())) {
+            Thread.sleep(2000);
+            logOut();
+            navigateTo();
+        }
+
+        Thread.sleep(2000);
+
+        this.loginPage.form().login(username, password);
+    }
+
+    private void navigateTo() {
+        this.driver.navigate().to(getResourceServerUrl());
+        WaitUtils.waitUntilElement(By.xpath("//a[text() = 'Dynamic Menu']"));
+    }
+
+    private  boolean wasDenied() {
+        return this.driver.getPageSource().contains("You can not access this resource.");
+    }
+
+    private URL getResourceServerUrl() {
+        try {
+            return new URL(this.appServerContextRootPage + "/" + RESOURCE_SERVER_ID);
+        } catch (MalformedURLException e) {
+            throw new RuntimeException("Could not obtain resource server url.", e);
+        }
+    }
+
+    private void navigateToDynamicMenuPage() {
+        navigateTo();
+        getLink("Dynamic Menu").click();
+    }
+
+    private void navigateToUserPremiumPage() {
+        navigateTo();
+        getLink("User Premium").click();
+    }
+
+    private void navigateToAdminPage() {
+        navigateTo();
+        getLink("Administration").click();
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/authorization/WildflyServletAuthzAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/authorization/WildflyServletAuthzAdapterTest.java
new file mode 100644
index 0000000..d50cf29
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/authorization/WildflyServletAuthzAdapterTest.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2016 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.adapter.example.authorization.AbstractDefaultAuthzConfigAdapterTest;
+import org.keycloak.testsuite.adapter.example.authorization.AbstractServletAuthzAdapterTest;
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+@RunAsClient
+@AppServerContainer("app-server-wildfly")
+//@AdapterLibsLocationProperty("adapter.libs.wildfly")
+public class WildflyServletAuthzAdapterTest extends AbstractServletAuthzAdapterTest {
+
+}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/pom.xml
index b8c715f..7ae36b2 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/adapters/pom.xml
@@ -287,6 +287,12 @@
                                                 <version>${project.version}</version>
                                                 <type>war</type>
                                             </artifactItem>
+                                            <artifactItem>
+                                                <groupId>org.keycloak.testsuite</groupId>
+                                                <artifactId>servlet-authz-app</artifactId>
+                                                <version>${project.version}</version>
+                                                <type>war</type>
+                                            </artifactItem>
                                         </artifactItems>
                                         <outputDirectory>${examples.home}</outputDirectory>
                                         <overWriteIfNewer>true</overWriteIfNewer>