keycloak-aplcache

Changes

pom.xml 4(+3 -1)

Details

diff --git a/adapters/oidc/js/src/main/resources/keycloak.d.ts b/adapters/oidc/js/src/main/resources/keycloak.d.ts
index 5579da1..2f5d220 100644
--- a/adapters/oidc/js/src/main/resources/keycloak.d.ts
+++ b/adapters/oidc/js/src/main/resources/keycloak.d.ts
@@ -1,101 +1,478 @@
 /*
- * Copyright 2017 Red Hat, Inc. and/or its affiliates
- * and other contributors as indicated by the @author tags.
+ * MIT License
  *
- * 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
+ * Copyright 2017 Brett Epps <https://github.com/eppsilon>
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
+ * following conditions:
  *
- * 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.
+ * The above copyright notice and this permission notice shall be included in all copies or substantial
+ * portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
+ * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+ * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  */
-declare module KeycloakModule {
-
-    export interface Promise {
-        success(callback: Function): Promise;
-        error(callback: Function): Promise;
-    }
-
-    export type ResponseModes = "query" | "fragment";
-    export type Flows = "standard" | "implicit" | "hybrid";
-    
-    export interface InitOptions {
-        checkLoginIframe?: boolean;
-        checkLoginIframeInterval?: number;
-        onLoad?: string;
-        adapter?: string;
-        responseMode?: ResponseModes;
-        flow?: Flows;
-        token?: string;
-        refreshToken?: string;
-        idToken?: string;
-        timeSkew?: number;
-    }
-
-    export interface LoginOptions {
-        redirectUri?: string;
-        prompt?: string;
-        maxAge?: number;
-        loginHint?: string;
-        action?: string;
-        locale?: string;
-    }
-
-    export interface RedirectUriOptions {
-        redirectUri?: string;
-    }
-
-    export interface KeycloakClient {
-        init(options?: InitOptions): Promise;
-        login(options?: LoginOptions): Promise;
-        createLoginUrl(options?: LoginOptions): string;
-        logout(options?: RedirectUriOptions): Promise;
-        createLogoutUrl(options?: RedirectUriOptions): string;
-        register(options?: LoginOptions): Promise;
-        createRegisterUrl(options?: RedirectUriOptions): string;
-        accountManagement(): Promise;
-        createAccountUrl(options?: RedirectUriOptions): string;
-        hasRealmRole(role: string): boolean;
-        hasResourceRole(role: string, resource?: string): boolean;
-        loadUserProfile(): Promise;
-        isTokenExpired(minValidity: number): boolean;
-        updateToken(minValidity: number): Promise;
-        clearToken(): any;
-
-        realm: string;
-        clientId: string;
-        authServerUrl: string;
-
-        token: string;
-        tokenParsed: any;
-        refreshToken: string;
-        refreshTokenParsed: any;
-        idToken: string;
-        idTokenParsed: any;
-        realmAccess: any;
-        resourceAccess: any;
-        authenticated: boolean;
-        subject: string;
-        timeSkew: number;
-        responseMode: ResponseModes;
-        flow: Flows;
-        responseType: string;
-
-        onReady: Function;
-        onAuthSuccess: Function;
-        onAuthError: Function;
-        onAuthRefreshSuccess: Function;
-        onAuthRefreshError: Function;
-        onAuthLogout: Function;
-        onTokenExpired: Function;
-    }
-}
+export as namespace Keycloak;
+
+export = Keycloak;
+
+/**
+ * Creates a new Keycloak client instance.
+ * @param config Path to a JSON config file or a plain config object.
+ */
+declare function Keycloak(config?: string|{}): Keycloak.KeycloakInstance;
+
+declare namespace Keycloak {
+	type KeycloakAdapterName = 'cordova'|'default';
+	type KeycloakOnLoad = 'login-required'|'check-sso';
+	type KeycloakResponseMode = 'query'|'fragment';
+	type KeycloakResponseType = 'code'|'id_token token'|'code id_token token';
+	type KeycloakFlow = 'standard'|'implicit'|'hybrid';
+
+	interface KeycloakInitOptions {
+		/**
+		 * @private Undocumented.
+		 */
+		adapter?: KeycloakAdapterName;
+
+		/**
+		 * Specifies an action to do on load.
+		 */
+		onLoad?: KeycloakOnLoad;
+
+		/**
+		 * Set an initial value for the token.
+		 */
+		token?: string;
+
+		/**
+		 * Set an initial value for the refresh token.
+		 */
+		refreshToken?: string;
+
+		/**
+		 * Set an initial value for the id token (only together with `token` or
+		 * `refreshToken`).
+		 */
+		idToken?: string;
+
+		/**
+		 * Set an initial value for skew between local time and Keycloak server in
+		 * seconds (only together with `token` or `refreshToken`).
+		 */
+		timeSkew?: number;
+
+		/**
+		 * Set to enable/disable monitoring login state.
+		 * @default true
+		 */
+		checkLoginIframe?: boolean;
+
+		/**
+		 * Set the interval to check login state (in seconds).
+		 * @default 5
+		 */
+		checkLoginIframeInterval?: boolean;
+
+		/**
+		 * Set the OpenID Connect response mode to send to Keycloak upon login.
+		 * @default fragment After successful authentication Keycloak will redirect
+		 *                   to JavaScript application with OpenID Connect parameters
+		 *                   added in URL fragment. This is generally safer and
+		 *                   recommended over query.
+		 */
+		responseMode?: KeycloakResponseMode;
+
+		/**
+		 * Set the OpenID Connect flow.
+		 * @default standard
+		 */
+		flow?: KeycloakFlow;
+	}
+
+	interface KeycloakLoginOptions {
+		/**
+		 * @private Undocumented.
+		 */
+		scope?: string;
+
+		/**
+		 * Specifies the uri to redirect to after login.
+		 */
+		redirectUri?: string;
+
+		/**
+		 * By default the login screen is displayed if the user is not logged into
+		 * Keycloak. To only authenticate to the application if the user is already
+		 * logged in and not display the login page if the user is not logged in, set
+		 * this option to `'none'`. To always require re-authentication and ignore
+		 * SSO, set this option to `'login'`.
+		 */
+		prompt?: 'none'|'login';
+
+		/**
+		 * If value is `'register'` then user is redirected to registration page,
+		 * otherwise to login page.
+		 */
+		action?: 'register';
+
+		/**
+		 * Used just if user is already authenticated. Specifies maximum time since
+		 * the authentication of user happened. If user is already authenticated for
+		 * longer time than `'maxAge'`, the SSO is ignored and he will need to
+		 * authenticate again.
+		 */
+		maxAge?: number;
+
+		/**
+		 * Used to pre-fill the username/email field on the login form.
+		 */
+		loginHint?: string;
+
+		/**
+		 * Used to tell Keycloak which IDP the user wants to authenticate with.
+		 */
+		idpHint?: string;
+
+		/**
+		 * Specifies the desired locale for the UI.
+		 */
+		locale?: string;
+	}
+
+	type KeycloakPromiseCallback<T> = (result: T) => void;
+
+	interface KeycloakPromise<TSuccess, TError> {
+		/**
+		 * Function to call if the promised action succeeds.
+		 */
+		success(callback: KeycloakPromiseCallback<TSuccess>): KeycloakPromise<TSuccess, TError>;
+
+		/**
+		 * Function to call if the promised action throws an error.
+		 */
+		error(callback: KeycloakPromiseCallback<TError>): KeycloakPromise<TSuccess, TError>;
+	}
+
+	interface KeycloakError {
+		error: string;
+		error_description: string;
+	}
+
+	interface KeycloakAdapter {
+		login(options?: KeycloakLoginOptions): KeycloakPromise<void, void>;
+		logout(options?: any): KeycloakPromise<void, void>;
+		register(options?: KeycloakLoginOptions): KeycloakPromise<void, void>;
+		accountManagement(): KeycloakPromise<void, void>;
+		redirectUri(options: { redirectUri: string; }, encodeHash: boolean): string;
+	}
+
+	interface KeycloakProfile {
+		id?: string;
+		username?: string;
+		email?: string;
+		firstName?: string;
+		lastName?: string;
+		enabled?: boolean;
+		emailVerified?: boolean;
+		totp?: boolean;
+		createdTimestamp?: number;
+	}
+
+	// export interface KeycloakUserInfo {}
+
+	/**
+	 * A client for the Keycloak authentication server.
+	 * @see {@link https://keycloak.gitbooks.io/securing-client-applications-guide/content/topics/oidc/javascript-adapter.html|Keycloak JS adapter documentation}
+	 */
+	interface KeycloakInstance {
+		/**
+		 * Is true if the user is authenticated, false otherwise.
+		 */
+		authenticated?: boolean;
+
+		/**
+		 * The user id.
+		 */
+		subject?: string;
+
+		/**
+		 * Response mode passed in init (default value is `'fragment'`).
+		 */
+		responseMode?: KeycloakResponseMode;
+
+		/**
+		 * Response type sent to Keycloak with login requests. This is determined
+		 * based on the flow value used during initialization, but can be overridden
+		 * by setting this value.
+		 */
+		responseType?: KeycloakResponseType;
+
+		/**
+		 * Flow passed in init.
+		 */
+		flow?: KeycloakFlow;
+
+		/**
+		 * The realm roles associated with the token.
+		 */
+		realmAccess?: { roles: string[] };
+
+		/**
+		 * The resource roles associated with the token.
+		 */
+		resourceAccess?: string[];
+
+		/**
+		 * The base64 encoded token that can be sent in the Authorization header in
+		 * requests to services.
+		 */
+		token?: string;
 
-declare var Keycloak: {
-    new(config?: any): KeycloakModule.KeycloakClient;
-};
\ No newline at end of file
+		/**
+		 * The parsed token as a JavaScript object.
+		 */
+		tokenParsed?: {
+			exp?: number;
+			iat?: number;
+			nonce?: string;
+			sub?: string;
+			session_state?: string;
+			realm_access?: { roles: string[] };
+			resource_access?: string[];
+		};
+
+		/**
+		 * The base64 encoded refresh token that can be used to retrieve a new token.
+		 */
+		refreshToken?: string;
+
+		/**
+		 * The parsed refresh token as a JavaScript object.
+		 */
+		refreshTokenParsed?: { nonce?: string };
+
+		/**
+		 * The base64 encoded ID token.
+		 */
+		idToken?: string;
+
+		/**
+		 * The parsed id token as a JavaScript object.
+		 */
+		idTokenParsed?: { nonce?: string };
+
+		/**
+		 * The estimated time difference between the browser time and the Keycloak
+		 * server in seconds. This value is just an estimation, but is accurate
+		 * enough when determining if a token is expired or not.
+		 */
+		timeSkew?: number;
+
+		/**
+		 * @private Undocumented.
+		 */
+		loginRequired?: boolean;
+
+		/**
+		 * @private Undocumented.
+		 */
+		authServerUrl?: string;
+
+		/**
+		 * @private Undocumented.
+		 */
+		realm?: string;
+
+		/**
+		 * @private Undocumented.
+		 */
+		clientId?: string;
+
+		/**
+		 * @private Undocumented.
+		 */
+		clientSecret?: string;
+
+		/**
+		 * @private Undocumented.
+		 */
+		redirectUri?: string;
+
+		/**
+		 * @private Undocumented.
+		 */
+		sessionId?: string;
+
+		/**
+		 * @private Undocumented.
+		 */
+		profile?: KeycloakProfile;
+
+		/**
+		 * @private Undocumented.
+		 */
+		userInfo?: {}; // KeycloakUserInfo;
+
+		/**
+		 * Called when the adapter is initialized.
+		 */
+		onReady?(authenticated?: boolean): void;
+
+		/**
+		 * Called when a user is successfully authenticated.
+		 */
+		onAuthSuccess?(): void;
+
+		/**
+		 * Called if there was an error during authentication.
+		 */
+		onAuthError?(errorData: KeycloakError): void;
+
+		/**
+		 * Called when the token is refreshed.
+		 */
+		onAuthRefreshSuccess?(): void;
+
+		/**
+		 * Called if there was an error while trying to refresh the token.
+		 */
+		onAuthRefreshError?(): void;
+
+		/**
+		 * Called if the user is logged out (will only be called if the session
+		 * status iframe is enabled, or in Cordova mode).
+		 */
+		onAuthLogout?(): void;
+
+		/**
+		 * Called when the access token is expired. If a refresh token is available
+		 * the token can be refreshed with Keycloak#updateToken, or in cases where
+		 * it's not (ie. with implicit flow) you can redirect to login screen to
+		 * obtain a new access token.
+		 */
+		onTokenExpired?(): void;
+
+		/**
+		 * Called to initialize the adapter.
+		 * @param initOptions Initialization options.
+		 * @returns A promise to set functions to be invoked on success or error.
+		 */
+		init(initOptions: KeycloakInitOptions): KeycloakPromise<boolean, KeycloakError>;
+
+		/**
+		 * Redirects to login form.
+		 * @param options Login options.
+		 */
+		login(options?: KeycloakLoginOptions): KeycloakPromise<void, void>;
+
+		/**
+		 * Redirects to logout.
+		 * @param options Logout options.
+		 * @param options.redirectUri Specifies the uri to redirect to after logout.
+		 */
+		logout(options?: any): KeycloakPromise<void, void>;
+
+		/**
+		 * Redirects to registration form.
+		 * @param options Supports same options as Keycloak#login but `action` is
+		 *                set to `'register'`.
+		 */
+		register(options?: any): KeycloakPromise<void, void>;
+
+		/**
+		 * Redirects to the Account Management Console.
+		 */
+		accountManagement(): KeycloakPromise<void, void>;
+
+		/**
+		 * Returns the URL to login form.
+		 * @param options Supports same options as Keycloak#login.
+		 */
+		createLoginUrl(options?: KeycloakLoginOptions): string;
+
+		/**
+		 * Returns the URL to logout the user.
+		 * @param options Logout options.
+		 * @param options.redirectUri Specifies the uri to redirect to after logout.
+		 */
+		createLogoutUrl(options?: any): string;
+
+		/**
+		 * Returns the URL to registration page.
+		 * @param options Supports same options as Keycloak#createLoginUrl but
+		 *                `action` is set to `'register'`.
+		 */
+		createRegisterUrl(options?: KeycloakLoginOptions): string;
+
+		/**
+		 * Returns the URL to the Account Management Console.
+		 */
+		createAccountUrl(): string;
+
+		/**
+		 * Returns true if the token has less than `minValidity` seconds left before
+		 * it expires.
+		 * @param minValidity If not specified, `0` is used.
+		 */
+		isTokenExpired(minValidity?: number): boolean;
+
+		/**
+		 * If the token expires within `minValidity` seconds, the token is refreshed.
+		 * If the session status iframe is enabled, the session status is also
+		 * checked.
+		 * @returns A promise to set functions that can be invoked if the token is
+		 *          still valid, or if the token is no longer valid.
+		 * @example
+		 * ```js
+		 * keycloak.updateToken(5).success(function(refreshed) {
+		 *   if (refreshed) {
+		 *     alert('Token was successfully refreshed');
+		 *   } else {
+		 *     alert('Token is still valid');
+		 *   }
+		 * }).error(function() {
+		 *   alert('Failed to refresh the token, or the session has expired');
+		 * });
+		 */
+		updateToken(minValidity: number): KeycloakPromise<boolean, boolean>;
+
+		/**
+		 * Clears authentication state, including tokens. This can be useful if
+		 * the application has detected the session was expired, for example if
+		 * updating token fails. Invoking this results in Keycloak#onAuthLogout
+		 * callback listener being invoked.
+		 */
+		clearToken(): void;
+
+		/**
+		 * Returns true if the token has the given realm role.
+		 * @param role A realm role name.
+		 */
+		hasRealmRole(role: string): boolean;
+
+		/**
+		 * Returns true if the token has the given role for the resource.
+		 * @param role A role name.
+		 * @param resource If not specified, `clientId` is used.
+		 */
+		hasResourceRole(role: string, resource?: string): boolean;
+
+		/**
+		 * Loads the user's profile.
+		 * @returns A promise to set functions to be invoked on success or error.
+		 */
+		loadUserProfile(): KeycloakPromise<KeycloakProfile, void>;
+
+		/**
+		 * @private Undocumented.
+		 */
+		loadUserInfo(): KeycloakPromise<{}, void>;
+	}
+}
diff --git a/adapters/oidc/js/src/main/resources/keycloak-authz.d.ts b/adapters/oidc/js/src/main/resources/keycloak-authz.d.ts
new file mode 100644
index 0000000..c7e0b2f
--- /dev/null
+++ b/adapters/oidc/js/src/main/resources/keycloak-authz.d.ts
@@ -0,0 +1,59 @@
+/*
+ * MIT License
+ *
+ * Copyright 2017 Brett Epps <https://github.com/eppsilon>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
+ * following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial
+ * portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
+ * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+ * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+import * as Keycloak from 'keycloak';
+
+export as namespace KeycloakAuthorization;
+
+export = KeycloakAuthorization;
+
+/**
+ * Creates a new Keycloak client instance.
+ * @param config Path to a JSON config file or a plain config object.
+ */
+declare function KeycloakAuthorization(keycloak: Keycloak.KeycloakInstance): KeycloakAuthorization.KeycloakAuthorizationInstance;
+
+declare namespace KeycloakAuthorization {
+	interface KeycloakAuthorizationPromise {
+		then(onGrant: (rpt: string) => void, onDeny: () => void, onError: () => void): void;
+	}
+
+	interface KeycloakAuthorizationInstance {
+		rpt: any;
+		config: { rpt_endpoint: string };
+
+		init(): void;
+
+		/**
+		 * This method enables client applications to better integrate with resource servers protected by a Keycloak
+		 * policy enforcer.
+		 *
+		 * In this case, the resource server will respond with a 401 status code and a WWW-Authenticate header holding the
+		 * necessary information to ask a Keycloak server for authorization data using both UMA and Entitlement protocol,
+		 * depending on how the policy enforcer at the resource server was configured.
+		 */
+		authorize(wwwAuthenticateHeader: string): KeycloakAuthorizationPromise;
+
+		/**
+		 * Obtains all entitlements from a Keycloak server based on a given resourceServerId.
+		 */
+		entitlement(resourceServerId: string, entitlementRequest: {}): KeycloakAuthorizationPromise;
+	}
+}
diff --git a/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronSessionTokenStore.java b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronSessionTokenStore.java
index 385a8a6..eace514 100644
--- a/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronSessionTokenStore.java
+++ b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronSessionTokenStore.java
@@ -76,7 +76,7 @@ public class ElytronSessionTokenStore implements ElytronTokeStore {
     public boolean isCached(RequestAuthenticator authenticator) {
         HttpScope session = this.httpFacade.getScope(Scope.SESSION);
 
-        if (session == null) {
+        if (session == null || !session.supportsAttachments()) {
             log.debug("session was null, returning null");
             return false;
         }
diff --git a/distribution/server-overlay/src/main/cli/keycloak-install-ha-base.cli b/distribution/server-overlay/src/main/cli/keycloak-install-ha-base.cli
index 0e209c7..ec2b56f 100644
--- a/distribution/server-overlay/src/main/cli/keycloak-install-ha-base.cli
+++ b/distribution/server-overlay/src/main/cli/keycloak-install-ha-base.cli
@@ -9,7 +9,8 @@ embed-server --server-config=standalone-ha.xml
 /subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions:add(mode="SYNC",owners="1")
 /subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineSessions:add(mode="SYNC",owners="1")
 /subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures:add(mode="SYNC",owners="1")
-/subsystem=infinispan/cache-container=keycloak/distributed-cache=authorization:add(mode="SYNC",owners="1")
+/subsystem=infinispan/cache-container=keycloak/local-cache=authorization:add()
+/subsystem=infinispan/cache-container=keycloak/local-cache=authorization/eviction=EVICTION:add(max-entries=10000,strategy=LRU)
 /subsystem=infinispan/cache-container=keycloak/replicated-cache=work:add(mode="SYNC")
 /subsystem=infinispan/cache-container=keycloak/local-cache=keys:add()
 /subsystem=infinispan/cache-container=keycloak/local-cache=keys/eviction=EVICTION:add(max-entries=1000,strategy=LRU)
diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/AbstractCachedStore.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/AbstractCachedStore.java
new file mode 100644
index 0000000..90fe8b6
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/AbstractCachedStore.java
@@ -0,0 +1,73 @@
+/*
+ * 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.models.authorization.infinispan;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.keycloak.authorization.store.StoreFactory;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public abstract class AbstractCachedStore {
+
+    private final InfinispanStoreFactoryProvider cacheStoreFactory;
+    private final StoreFactory storeFactory;
+
+    AbstractCachedStore(InfinispanStoreFactoryProvider cacheStoreFactory, StoreFactory storeFactory) {
+        this.cacheStoreFactory = cacheStoreFactory;
+        this.storeFactory = storeFactory;
+    }
+
+    protected void addInvalidation(String cacheKeyForPolicy) {
+        getCachedStoreFactory().addInvalidation(cacheKeyForPolicy);
+    }
+
+    protected <E> E putCacheEntry(String resourceServerId, String cacheKeyForPolicy, E cachedPolicy) {
+        cacheStoreFactory.putCacheEntry(resourceServerId, cacheKeyForPolicy, Arrays.asList(cachedPolicy));
+        return cachedPolicy;
+    }
+
+    protected List<Object> resolveCacheEntry(String resourceServerId, String cacheKeyForPolicy) {
+        return cacheStoreFactory.resolveCachedEntry(resourceServerId, cacheKeyForPolicy);
+    }
+
+    protected void removeCachedEntry(String resourceServerId, String key) {
+        getCachedStoreFactory().removeCachedEntry(resourceServerId, key);
+    }
+
+    protected void invalidate(String resourceServerId) {
+        cacheStoreFactory.invalidate(resourceServerId);
+    }
+
+    protected StoreFactory getStoreFactory() {
+        return this.storeFactory;
+    }
+
+    protected boolean isInvalid(String cacheKey) {
+        return cacheStoreFactory.isInvalid(cacheKey);
+    }
+
+    protected InfinispanStoreFactoryProvider.CacheTransaction getTransaction() {
+        return cacheStoreFactory.getTransaction();
+    }
+
+    protected InfinispanStoreFactoryProvider getCachedStoreFactory() {
+        return cacheStoreFactory;
+    }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedPolicyStore.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedPolicyStore.java
index b517098..73284d7 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedPolicyStore.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedPolicyStore.java
@@ -30,18 +30,13 @@ import java.util.function.Function;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
-import org.infinispan.Cache;
 import org.keycloak.authorization.model.Policy;
 import org.keycloak.authorization.model.Resource;
 import org.keycloak.authorization.model.ResourceServer;
 import org.keycloak.authorization.model.Scope;
 import org.keycloak.authorization.store.PolicyStore;
 import org.keycloak.authorization.store.StoreFactory;
-import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.authorization.infinispan.InfinispanStoreFactoryProvider.CacheTransaction;
 import org.keycloak.models.authorization.infinispan.entities.CachedPolicy;
-import org.keycloak.models.cache.authorization.CachedStoreFactoryProvider;
 import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation;
 import org.keycloak.representations.idm.authorization.DecisionStrategy;
 import org.keycloak.representations.idm.authorization.Logic;
@@ -49,28 +44,15 @@ import org.keycloak.representations.idm.authorization.Logic;
 /**
  * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
  */
-public class CachedPolicyStore implements PolicyStore {
+public class CachedPolicyStore extends AbstractCachedStore implements PolicyStore {
 
-    private static final String POLICY_ID_CACHE_PREFIX = "policy-id-";
+    private static final String POLICY_CACHE_PREFIX = "pc-";
 
-    private final Cache<String, Map<String, List<CachedPolicy>>> cache;
-    private final CachedStoreFactoryProvider cacheStoreFactory;
-    private final CacheTransaction transaction;
-    private final List<String> cacheKeys;
-    private final StoreFactory storeFactory;
     private PolicyStore delegate;
 
-    public CachedPolicyStore(KeycloakSession session, CachedStoreFactoryProvider cacheStoreFactory, CacheTransaction transaction, StoreFactory delegate) {
-        this.cacheStoreFactory = cacheStoreFactory;
-        this.transaction = transaction;
-        InfinispanConnectionProvider provider = session.getProvider(InfinispanConnectionProvider.class);
-        this.cache = provider.getCache(InfinispanConnectionProvider.AUTHORIZATION_CACHE_NAME);
-        cacheKeys = new ArrayList<>();
-        cacheKeys.add("findByResource");
-        cacheKeys.add("findByResourceType");
-        cacheKeys.add("findByScopeIds");
-        cacheKeys.add("findByType");
-        this.storeFactory = delegate;
+    public CachedPolicyStore(InfinispanStoreFactoryProvider cacheStoreFactory, StoreFactory storeFactory) {
+        super(cacheStoreFactory, storeFactory);
+        this.delegate = storeFactory.getPolicyStore();
     }
 
     @Override
@@ -78,13 +60,11 @@ public class CachedPolicyStore implements PolicyStore {
         Policy policy = getDelegate().create(representation, getStoreFactory().getResourceServerStore().findById(resourceServer.getId()));
         String id = policy.getId();
 
-        this.transaction.whenRollback(() -> {
-            resolveResourceServerCache(resourceServer.getId()).remove(getCacheKeyForPolicy(id));
-        });
+        addInvalidation(getCacheKeyForPolicy(policy.getId()));
+        addInvalidation(getCacheKeyForPolicyName(policy.getName()));
+        addInvalidation(getCacheKeyForPolicyType(policy.getType()));
 
-        this.transaction.whenCommit(() -> {
-            invalidateCache(resourceServer.getId());
-        });
+        configureTransaction(resourceServer, id);
 
         return createAdapter(new CachedPolicy(policy));
     }
@@ -95,9 +75,13 @@ public class CachedPolicyStore implements PolicyStore {
         if (policy == null) {
             return;
         }
-        ResourceServer resourceServer = policy.getResourceServer();
+
+        addInvalidation(getCacheKeyForPolicy(policy.getId()));
+        addInvalidation(getCacheKeyForPolicyName(policy.getName()));
+        addInvalidation(getCacheKeyForPolicyType(policy.getType()));
+
         getDelegate().delete(id);
-        invalidateCache(resourceServer.getId());
+        configureTransaction(policy.getResourceServer(), policy.getId());
     }
 
     @Override
@@ -106,27 +90,43 @@ public class CachedPolicyStore implements PolicyStore {
             return getDelegate().findById(id, null);
         }
 
+        if (isInvalid(getCacheKeyForPolicy(id))) {
+            return getDelegate().findById(id, resourceServerId);
+        }
+
         String cacheKeyForPolicy = getCacheKeyForPolicy(id);
-        List<CachedPolicy> cached = resolveResourceServerCache(resourceServerId).get(cacheKeyForPolicy);
+        List<Object> cached = resolveCacheEntry(resourceServerId, cacheKeyForPolicy);
 
         if (cached == null) {
             Policy policy = getDelegate().findById(id, resourceServerId);
 
             if (policy != null) {
-                CachedPolicy cachedPolicy = new CachedPolicy(policy);
-                resolveResourceServerCache(resourceServerId).put(cacheKeyForPolicy, Arrays.asList(cachedPolicy));
-                return createAdapter(cachedPolicy);
+                return createAdapter(putCacheEntry(resourceServerId, cacheKeyForPolicy, new CachedPolicy(policy)));
             }
 
             return null;
         }
 
-        return createAdapter(cached.get(0));
+        return createAdapter(CachedPolicy.class.cast(cached.get(0)));
     }
 
     @Override
     public Policy findByName(String name, String resourceServerId) {
-        return getDelegate().findByName(name, resourceServerId);
+        String cacheKey = getCacheKeyForPolicyName(name);
+
+        if (isInvalid(cacheKey)) {
+            return getDelegate().findByName(name, resourceServerId);
+        }
+
+        return cacheResult(resourceServerId, cacheKey, () -> {
+            Policy policy = getDelegate().findByName(name, resourceServerId);
+
+            if (policy == null) {
+                return Collections.emptyList();
+            }
+
+            return Arrays.asList(policy);
+        }).stream().findFirst().orElse(null);
     }
 
     @Override
@@ -141,12 +141,24 @@ public class CachedPolicyStore implements PolicyStore {
 
     @Override
     public List<Policy> findByResource(String resourceId, String resourceServerId) {
-        return cacheResult(resourceServerId, new StringBuilder("findByResource").append(resourceId).toString(), () -> getDelegate().findByResource(resourceId, resourceServerId));
+        String cacheKey = getCacheKeyForResource(resourceId);
+
+        if (isInvalid(cacheKey)) {
+            return getDelegate().findByResource(resourceId, resourceServerId);
+        }
+
+        return cacheResult(resourceServerId, cacheKey, () -> getDelegate().findByResource(resourceId, resourceServerId));
     }
 
     @Override
     public List<Policy> findByResourceType(String resourceType, String resourceServerId) {
-        return cacheResult(resourceServerId, new StringBuilder("findByResourceType").append(resourceType).toString(), () -> getDelegate().findByResourceType(resourceType, resourceServerId));
+        String cacheKey = getCacheKeyForResourceType(resourceType);
+
+        if (isInvalid(cacheKey)) {
+            return getDelegate().findByResourceType(resourceType, resourceServerId);
+        }
+
+        return cacheResult(resourceServerId, cacheKey, () -> getDelegate().findByResourceType(resourceType, resourceServerId));
     }
 
     @Override
@@ -154,7 +166,13 @@ public class CachedPolicyStore implements PolicyStore {
         List<Policy> policies = new ArrayList<>();
 
         for (String scopeId : scopeIds) {
-            policies.addAll(cacheResult(resourceServerId, new StringBuilder("findByScopeIds").append(scopeId).toString(), () -> getDelegate().findByScopeIds(Arrays.asList(scopeId), resourceServerId)));
+            String cacheKey = getCacheForScope(scopeId);
+
+            if (isInvalid(cacheKey)) {
+                policies.addAll(getDelegate().findByScopeIds(Arrays.asList(scopeId), resourceServerId));
+            } else {
+                policies.addAll(cacheResult(resourceServerId, cacheKey, () -> getDelegate().findByScopeIds(Arrays.asList(scopeId), resourceServerId)));
+            }
         }
 
         return policies;
@@ -162,7 +180,13 @@ public class CachedPolicyStore implements PolicyStore {
 
     @Override
     public List<Policy> findByType(String type, String resourceServerId) {
-        return cacheResult(resourceServerId, new StringBuilder("findByType").append(type).toString(), () -> getDelegate().findByType(type, resourceServerId));
+        String cacheKey = getCacheKeyForPolicyType(type);
+
+        if (isInvalid(cacheKey)) {
+            return getDelegate().findByType(type, resourceServerId);
+        }
+
+        return cacheResult(resourceServerId, cacheKey, () -> getDelegate().findByType(type, resourceServerId));
     }
 
     @Override
@@ -170,35 +194,28 @@ public class CachedPolicyStore implements PolicyStore {
         return getDelegate().findDependentPolicies(id, resourceServerId);
     }
 
-    @Override
-    public void notifyChange(Object cached) {
-        String resourceServerId;
-
-        if (Resource.class.isInstance(cached)) {
-            resourceServerId = ((Resource) cached).getResourceServer().getId();
-        } else if (Scope.class.isInstance(cached)){
-            resourceServerId = ((Scope) cached).getResourceServer().getId();
-        } else {
-            throw new RuntimeException("Unexpected notification [" + cached + "]");
-        }
+    private String getCacheKeyForPolicy(String id) {
+        return new StringBuilder().append(POLICY_CACHE_PREFIX).append("id-").append(id).toString();
+    }
 
-        invalidateCache(resourceServerId);
+    private String getCacheKeyForPolicyType(String type) {
+        return new StringBuilder().append(POLICY_CACHE_PREFIX).append("findByType-").append(type).toString();
     }
 
-    private String getCacheKeyForPolicy(String policyId) {
-        return POLICY_ID_CACHE_PREFIX + policyId;
+    private String getCacheKeyForPolicyName(String name) {
+        return new StringBuilder().append(POLICY_CACHE_PREFIX).append("findByName-").append(name).toString();
     }
 
-    private StoreFactory getStoreFactory() {
-        return this.storeFactory;
+    private String getCacheKeyForResourceType(String resourceType) {
+        return new StringBuilder().append(POLICY_CACHE_PREFIX).append("findByResourceType-").append(resourceType).toString();
     }
 
-    private PolicyStore getDelegate() {
-        if (this.delegate == null) {
-            this.delegate = getStoreFactory().getPolicyStore();
-        }
+    private String getCacheForScope(String scopeId) {
+        return new StringBuilder().append(POLICY_CACHE_PREFIX).append("findByScopeIds-").append(scopeId).toString();
+    }
 
-        return this.delegate;
+    private String getCacheKeyForResource(String resourceId) {
+        return new StringBuilder().append(POLICY_CACHE_PREFIX).append("findByResource-").append(resourceId).toString();
     }
 
     private Policy createAdapter(CachedPolicy cached) {
@@ -243,11 +260,21 @@ public class CachedPolicyStore implements PolicyStore {
 
             @Override
             public Map<String, String> getConfig() {
-                return cached.getConfig();
+                return new HashMap<>(cached.getConfig());
             }
 
             @Override
             public void setConfig(Map<String, String> config) {
+                String resourceType = config.get("defaultResourceType");
+
+                if (resourceType != null) {
+                    addInvalidation(getCacheKeyForResourceType(resourceType));
+                    String cachedResourceType = cached.getConfig().get("defaultResourceType");
+                    if (cachedResourceType != null && !resourceType.equals(cachedResourceType)) {
+                        addInvalidation(getCacheKeyForResourceType(cachedResourceType));
+                    }
+                }
+
                 getDelegateForUpdate().setConfig(config);
                 cached.setConfig(config);
             }
@@ -259,6 +286,8 @@ public class CachedPolicyStore implements PolicyStore {
 
             @Override
             public void setName(String name) {
+                addInvalidation(getCacheKeyForPolicyName(name));
+                addInvalidation(getCacheKeyForPolicyName(cached.getName()));
                 getDelegateForUpdate().setName(name);
                 cached.setName(name);
             }
@@ -281,13 +310,18 @@ public class CachedPolicyStore implements PolicyStore {
 
             @Override
             public void addScope(Scope scope) {
-                getDelegateForUpdate().addScope(getStoreFactory().getScopeStore().findById(scope.getId(), cached.getResourceServerId()));
+                Scope model = getStoreFactory().getScopeStore().findById(scope.getId(), cached.getResourceServerId());
+                addInvalidation(getCacheForScope(model.getId()));
+                getDelegateForUpdate().addScope(model);
                 cached.addScope(scope);
+                scopes.add(scope);
             }
 
             @Override
             public void removeScope(Scope scope) {
-                getDelegateForUpdate().removeScope(getStoreFactory().getScopeStore().findById(scope.getId(), cached.getResourceServerId()));
+                Scope model = getStoreFactory().getScopeStore().findById(scope.getId(), cached.getResourceServerId());
+                addInvalidation(getCacheForScope(scope.getId()));
+                getDelegateForUpdate().removeScope(model);
                 cached.removeScope(scope);
                 scopes.remove(scope);
             }
@@ -307,20 +341,37 @@ public class CachedPolicyStore implements PolicyStore {
 
             @Override
             public void addResource(Resource resource) {
-                getDelegateForUpdate().addResource(getStoreFactory().getResourceStore().findById(resource.getId(), cached.getResourceServerId()));
+                Resource model = getStoreFactory().getResourceStore().findById(resource.getId(), cached.getResourceServerId());
+
+                addInvalidation(getCacheKeyForResource(model.getId()));
+
+                if (model.getType() != null) {
+                    addInvalidation(getCacheKeyForResourceType(model.getType()));
+                }
+
+                getDelegateForUpdate().addResource(model);
                 cached.addResource(resource);
+                resources.add(resource);
             }
 
             @Override
             public void removeResource(Resource resource) {
-                getDelegateForUpdate().removeResource(getStoreFactory().getResourceStore().findById(resource.getId(), cached.getResourceServerId()));
+                Resource model = getStoreFactory().getResourceStore().findById(resource.getId(), cached.getResourceServerId());
+
+                addInvalidation(getCacheKeyForResource(model.getId()));
+
+                if (model.getType() != null) {
+                    addInvalidation(getCacheKeyForResourceType(model.getType()));
+                }
+
+                getDelegateForUpdate().removeResource(model);
                 cached.removeResource(resource);
                 resources.remove(resource);
             }
 
             @Override
             public Set<Policy> getAssociatedPolicies() {
-                if (associatedPolicies == null) {
+                if (associatedPolicies == null || updated != null) {
                     associatedPolicies = new HashSet<>();
 
                     for (String id : cached.getAssociatedPoliciesIds()) {
@@ -337,7 +388,7 @@ public class CachedPolicyStore implements PolicyStore {
 
             @Override
             public Set<Resource> getResources() {
-                if (resources == null) {
+                if (resources == null || updated != null) {
                     resources = new HashSet<>();
 
                     for (String id : cached.getResourcesIds()) {
@@ -354,7 +405,7 @@ public class CachedPolicyStore implements PolicyStore {
 
             @Override
             public Set<Scope> getScopes() {
-                if (scopes == null) {
+                if (scopes == null || updated != null) {
                     scopes = new HashSet<>();
 
                     for (String id : cached.getScopesIds()) {
@@ -394,12 +445,8 @@ public class CachedPolicyStore implements PolicyStore {
                 if (this.updated == null) {
                     this.updated = getDelegate().findById(getId(), cached.getResourceServerId());
                     if (this.updated == null) throw new IllegalStateException("Not found in database");
-                    transaction.whenCommit(() -> {
-                        invalidateCache(cached.getResourceServerId());
-                    });
-                    transaction.whenRollback(() -> {
-                        resolveResourceServerCache(cached.getResourceServerId()).remove(getCacheKeyForPolicy(getId()));
-                    });
+                    addInvalidation(getCacheKeyForPolicy(updated.getId()));
+                    configureTransaction(updated.getResourceServer(), updated.getId());
                 }
 
                 return this.updated;
@@ -407,33 +454,47 @@ public class CachedPolicyStore implements PolicyStore {
         };
     }
 
-    private CachedStoreFactoryProvider getCachedStoreFactory() {
-        return cacheStoreFactory;
-    }
-
-    private void invalidateCache(String resourceServerId) {
-        cache.remove(resourceServerId);
-    }
-
     private List<Policy> cacheResult(String resourceServerId, String key, Supplier<List<Policy>> provider) {
-        List<CachedPolicy> cached = resolveResourceServerCache(resourceServerId).computeIfAbsent(key, (Function<String, List<CachedPolicy>>) o -> {
+        List<Object> cached = getCachedStoreFactory().computeIfCachedEntryAbsent(resourceServerId, key, (Function<String, List<Object>>) o -> {
             List<Policy> result = provider.get();
 
             if (result.isEmpty()) {
                 return Collections.emptyList();
             }
 
-            return result.stream().map(policy -> new CachedPolicy(policy)).collect(Collectors.toList());
+            return result.stream().map(policy -> policy.getId()).collect(Collectors.toList());
         });
 
         if (cached == null) {
             return Collections.emptyList();
         }
 
-        return cached.stream().map(cachedPolicy -> createAdapter(cachedPolicy)).collect(Collectors.toList());
+        return cached.stream().map(id -> findById(id.toString(), resourceServerId)).collect(Collectors.toList());
+    }
+
+    private void configureTransaction(ResourceServer resourceServer, String id) {
+        getTransaction().whenRollback(() -> removeCachedEntry(resourceServer.getId(), getCacheKeyForPolicy(id)));
+        getTransaction().whenCommit(() -> invalidate(resourceServer.getId()));
     }
 
-    private Map<String, List<CachedPolicy>> resolveResourceServerCache(String id) {
-        return cache.computeIfAbsent(id, key -> new HashMap<>());
+    private PolicyStore getDelegate() {
+        return delegate;
+    }
+
+    void addInvalidations(Object object) {
+        if (Resource.class.isInstance(object)) {
+            Resource resource = (Resource) object;
+            addInvalidation(getCacheKeyForResource(resource.getId()));
+            String type = resource.getType();
+
+            if (type != null) {
+                addInvalidation(getCacheKeyForResourceType(type));
+            }
+        } else if (Scope.class.isInstance(object)) {
+            Scope scope = (Scope) object;
+            addInvalidation(getCacheForScope(scope.getId()));
+        } else {
+            throw new RuntimeException("Unexpected notification [" + object + "]");
+        }
     }
 }
\ No newline at end of file
diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceServerStore.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceServerStore.java
index a98a34e..6322843 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceServerStore.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceServerStore.java
@@ -18,46 +18,34 @@
 
 package org.keycloak.models.authorization.infinispan;
 
-import java.util.Arrays;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
-import org.infinispan.Cache;
 import org.keycloak.authorization.model.ResourceServer;
 import org.keycloak.authorization.store.ResourceServerStore;
 import org.keycloak.authorization.store.StoreFactory;
-import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.authorization.infinispan.InfinispanStoreFactoryProvider.CacheTransaction;
 import org.keycloak.models.authorization.infinispan.entities.CachedResourceServer;
 import org.keycloak.representations.idm.authorization.PolicyEnforcementMode;
 
 /**
  * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
  */
-public class CachedResourceServerStore implements ResourceServerStore {
+public class CachedResourceServerStore extends AbstractCachedStore implements ResourceServerStore {
 
-    private static final String RS_ID_CACHE_PREFIX = "rs-id-";
-    private static final String RS_CLIENT_ID_CACHE_PREFIX = "rs-client-id-";
+    private static final String RS_PREFIX = "rs-";
 
-    private final CacheTransaction transaction;
-    private StoreFactory storeFactory;
-    private ResourceServerStore delegate;
-    private final Cache<String, Map<String, List<CachedResourceServer>>> cache;
+    private final ResourceServerStore delegate;
 
-    public CachedResourceServerStore(KeycloakSession session, CacheTransaction transaction, StoreFactory delegate) {
-        this.transaction = transaction;
-        InfinispanConnectionProvider provider = session.getProvider(InfinispanConnectionProvider.class);
-        this.cache = provider.getCache(InfinispanConnectionProvider.AUTHORIZATION_CACHE_NAME);
-        this.storeFactory = delegate;
+    public CachedResourceServerStore(InfinispanStoreFactoryProvider cachedStoreFactory, StoreFactory storeFactory) {
+        super(cachedStoreFactory, storeFactory);
+        this.delegate = storeFactory.getResourceServerStore();
     }
 
     @Override
     public ResourceServer create(String clientId) {
         ResourceServer resourceServer = getDelegate().create(clientId);
 
-        this.transaction.whenRollback(() -> resolveResourceServerCache(resourceServer.getId()).remove(getCacheKeyForResourceServer(resourceServer.getId())));
+        getTransaction().whenCommit(() -> getCachedStoreFactory().removeEntries(resourceServer));
+        getTransaction().whenRollback(() -> removeCachedEntry(resourceServer.getId(), getCacheKeyForResourceServer(resourceServer.getId())));
 
         return createAdapter(new CachedResourceServer(resourceServer));
     }
@@ -65,71 +53,71 @@ public class CachedResourceServerStore implements ResourceServerStore {
     @Override
     public void delete(String id) {
         ResourceServer resourceServer = getDelegate().findById(id);
-        getDelegate().delete(id);
-        this.transaction.whenCommit(() -> {
-            cache.remove(id);
-            cache.remove(resourceServer.getClientId());
-        });
+
+        if (resourceServer != null) {
+            getDelegate().delete(id);
+            getTransaction().whenCommit(() -> getCachedStoreFactory().removeEntries(resourceServer));
+        }
     }
 
     @Override
     public ResourceServer findById(String id) {
-        String cacheKeyForResourceServer = getCacheKeyForResourceServer(id);
-        List<CachedResourceServer> cached = resolveResourceServerCache(id).get(cacheKeyForResourceServer);
+        String cacheKey = getCacheKeyForResourceServer(id);
+
+        if (isInvalid(cacheKey)) {
+            return getDelegate().findById(id);
+        }
+
+        List<Object> cached = resolveCacheEntry(id, cacheKey);
 
         if (cached == null) {
             ResourceServer resourceServer = getDelegate().findById(id);
 
             if (resourceServer != null) {
-                CachedResourceServer cachedResourceServer = new CachedResourceServer(resourceServer);
-                resolveResourceServerCache(id).put(cacheKeyForResourceServer, Arrays.asList(cachedResourceServer));
-                return createAdapter(cachedResourceServer);
+                return createAdapter(putCacheEntry(id, cacheKey, new CachedResourceServer(resourceServer)));
             }
 
             return null;
         }
 
-        return createAdapter(cached.get(0));
+        return createAdapter(CachedResourceServer.class.cast(cached.get(0)));
     }
 
     @Override
     public ResourceServer findByClient(String id) {
-        String cacheKeyForResourceServer = getCacheKeyForResourceServerClientId(id);
-        List<CachedResourceServer> cached = resolveResourceServerCache(id).get(cacheKeyForResourceServer);
+        String cacheKey = getCacheKeyForResourceServerClientId(id);
+
+        if (isInvalid(cacheKey)) {
+            return getDelegate().findByClient(id);
+        }
+
+        List<Object> cached = resolveCacheEntry(id, cacheKey);
 
         if (cached == null) {
             ResourceServer resourceServer = getDelegate().findByClient(id);
 
             if (resourceServer != null) {
-                resolveResourceServerCache(cacheKeyForResourceServer).put(cacheKeyForResourceServer, Arrays.asList(new CachedResourceServer(resourceServer)));
-                return findById(resourceServer.getId());
+                return findById(putCacheEntry(id, cacheKey, resourceServer.getId()));
             }
 
             return null;
         }
 
-        return createAdapter(cached.get(0));
+        return findById(cached.get(0).toString());
     }
 
     private String getCacheKeyForResourceServer(String id) {
-        return RS_ID_CACHE_PREFIX + id;
+        return new StringBuilder(RS_PREFIX).append("id-").append(id).toString();
     }
 
     private String getCacheKeyForResourceServerClientId(String id) {
-        return RS_CLIENT_ID_CACHE_PREFIX + id;
+        return new StringBuilder(RS_PREFIX).append("findByClientId-").append(id).toString();
     }
 
     private ResourceServerStore getDelegate() {
-        if (this.delegate == null) {
-            this.delegate = getStoreFactory().getResourceServerStore();
-        }
-
         return this.delegate;
     }
 
-    private StoreFactory getStoreFactory() {
-        return this.storeFactory;
-    }
     private ResourceServer createAdapter(ResourceServer cached) {
         return new ResourceServer() {
 
@@ -171,9 +159,9 @@ public class CachedResourceServerStore implements ResourceServerStore {
                 if (this.updated == null) {
                     this.updated = getDelegate().findById(getId());
                     if (this.updated == null) throw new IllegalStateException("Not found in database");
-                    transaction.whenCommit(() -> {
-                        cache.remove(getId());
-                        cache.remove(getClientId());
+                    addInvalidation(getCacheKeyForResourceServer(updated.getId()));
+                    getTransaction().whenCommit(() -> {
+                        invalidate(updated.getId());
                     });
                 }
 
@@ -181,8 +169,4 @@ public class CachedResourceServerStore implements ResourceServerStore {
             }
         };
     }
-
-    private Map<String, List<CachedResourceServer>> resolveResourceServerCache(String id) {
-        return cache.computeIfAbsent(id, key -> new HashMap<>());
-    }
 }
diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceStore.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceStore.java
index e820ab0..bc892d0 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceStore.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceStore.java
@@ -21,7 +21,6 @@ package org.keycloak.models.authorization.infinispan;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -29,56 +28,39 @@ import java.util.function.Function;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
-import org.infinispan.Cache;
 import org.keycloak.authorization.model.Resource;
 import org.keycloak.authorization.model.ResourceServer;
 import org.keycloak.authorization.model.Scope;
 import org.keycloak.authorization.store.ResourceStore;
 import org.keycloak.authorization.store.StoreFactory;
-import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.authorization.infinispan.InfinispanStoreFactoryProvider.CacheTransaction;
 import org.keycloak.models.authorization.infinispan.entities.CachedResource;
-import org.keycloak.models.cache.authorization.CachedStoreFactoryProvider;
 
 /**
  * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
  */
-public class CachedResourceStore implements ResourceStore {
+public class CachedResourceStore extends AbstractCachedStore implements ResourceStore {
 
-    private static final String RESOURCE_ID_CACHE_PREFIX = "rsc-id-";
-    private static final String RESOURCE_NAME_CACHE_PREFIX = "rsc-name-";
+    private static final String RESOURCE_CACHE_PREFIX = "rs-";
 
-    private final CachedStoreFactoryProvider cacheStoreFactory;
-    private final CacheTransaction transaction;
-    private final List<String> cacheKeys;
-    private StoreFactory delegateStoreFactory;
     private ResourceStore delegate;
-    private final Cache<String, Map<String, List<CachedResource>>> cache;
-
-    public CachedResourceStore(KeycloakSession session, CachedStoreFactoryProvider cacheStoreFactory, CacheTransaction transaction, StoreFactory delegate) {
-        this.cacheStoreFactory = cacheStoreFactory;
-        InfinispanConnectionProvider provider = session.getProvider(InfinispanConnectionProvider.class);
-        this.cache = provider.getCache(InfinispanConnectionProvider.AUTHORIZATION_CACHE_NAME);
-        this.transaction = transaction;
-        cacheKeys = new ArrayList<>();
-        cacheKeys.add("findByOwner");
-        cacheKeys.add("findByUri");
-        cacheKeys.add("findByName");
-        this.delegateStoreFactory = delegate;
+
+    public CachedResourceStore(InfinispanStoreFactoryProvider cacheStoreFactory, StoreFactory storeFactory) {
+        super(cacheStoreFactory, storeFactory);
+        delegate = storeFactory.getResourceStore();
     }
 
     @Override
     public Resource create(String name, ResourceServer resourceServer, String owner) {
-        Resource resource = getDelegate().create(name, getDelegateStoreFactory().getResourceServerStore().findById(resourceServer.getId()), owner);
+        Resource resource = getDelegate().create(name, getStoreFactory().getResourceServerStore().findById(resourceServer.getId()), owner);
 
-        this.transaction.whenRollback(() -> {
-            resolveResourceServerCache(resourceServer.getId()).remove(getCacheKeyForResource(resource.getId()));
-        });
+        addInvalidation(getCacheKeyForResource(resource.getId()));
+        addInvalidation(getCacheKeyForResourceName(resource.getName()));
+        addInvalidation(getCacheKeyForOwner(owner));
 
-        this.transaction.whenCommit(() -> {
-            invalidateCache(resourceServer.getId());
-        });
+        getCachedStoreFactory().getPolicyStore().addInvalidations(resource);
+
+        getTransaction().whenRollback(() -> removeCachedEntry(resourceServer.getId(), getCacheKeyForResource(resource.getId())));
+        getTransaction().whenCommit(() -> invalidate(resourceServer.getId()));
 
         return createAdapter(new CachedResource(resource));
     }
@@ -86,44 +68,69 @@ public class CachedResourceStore implements ResourceStore {
     @Override
     public void delete(String id) {
         Resource resource = getDelegate().findById(id, null);
+
         if (resource == null) {
             return;
         }
+
         ResourceServer resourceServer = resource.getResourceServer();
+
+        addInvalidation(getCacheKeyForResource(resource.getId()));
+        addInvalidation(getCacheKeyForResourceName(resource.getName()));
+        addInvalidation(getCacheKeyForOwner(resource.getOwner()));
+        addInvalidation(getCacheKeyForUri(resource.getUri()));
+        getCachedStoreFactory().getPolicyStore().addInvalidations(resource);
+
         getDelegate().delete(id);
-        this.transaction.whenCommit(() -> {
-            invalidateCache(resourceServer.getId());
+
+        getTransaction().whenCommit(() -> {
+            invalidate(resourceServer.getId());
         });
     }
 
     @Override
     public Resource findById(String id, String resourceServerId) {
         String cacheKeyForResource = getCacheKeyForResource(id);
-        List<CachedResource> cached = resolveResourceServerCache(resourceServerId).get(cacheKeyForResource);
+
+        if (isInvalid(cacheKeyForResource)) {
+            return getDelegate().findById(id, resourceServerId);
+        }
+
+        List<Object> cached = resolveCacheEntry(resourceServerId, cacheKeyForResource);
 
         if (cached == null) {
             Resource resource = getDelegate().findById(id, resourceServerId);
 
             if (resource != null) {
-                CachedResource cachedResource = new CachedResource(resource);
-                resolveResourceServerCache(resourceServerId).put(cacheKeyForResource, Arrays.asList(cachedResource));
-                return createAdapter(cachedResource);
+                return createAdapter(putCacheEntry(resourceServerId, cacheKeyForResource, new CachedResource(resource)));
             }
 
             return null;
         }
 
-        return createAdapter(cached.get(0));
+        return createAdapter(CachedResource.class.cast(cached.get(0)));
     }
 
     @Override
     public List<Resource> findByOwner(String ownerId, String resourceServerId) {
-        return cacheResult(resourceServerId, new StringBuilder("findByOwner").append(ownerId).toString(), () -> getDelegate().findByOwner(ownerId, resourceServerId));
+        String cacheKey = getCacheKeyForOwner(ownerId);
+
+        if (isInvalid(cacheKey)) {
+            return getDelegate().findByOwner(ownerId, resourceServerId);
+        }
+
+        return cacheResult(resourceServerId, cacheKey, () -> getDelegate().findByOwner(ownerId, resourceServerId));
     }
 
     @Override
     public List<Resource> findByUri(String uri, String resourceServerId) {
-        return cacheResult(resourceServerId, new StringBuilder("findByUri").append(uri).toString(), () -> getDelegate().findByUri(uri, resourceServerId));
+        String cacheKey = getCacheKeyForUri(uri);
+
+        if (isInvalid(cacheKey)) {
+            return getDelegate().findByUri(uri, resourceServerId);
+        }
+
+        return cacheResult(resourceServerId, cacheKey, () -> getDelegate().findByUri(uri, resourceServerId));
     }
 
     @Override
@@ -143,22 +150,21 @@ public class CachedResourceStore implements ResourceStore {
 
     @Override
     public Resource findByName(String name, String resourceServerId) {
-        String cacheKeyForResource = getCacheKeyForResourceName(name, resourceServerId);
-        List<CachedResource> cached = resolveResourceServerCache(resourceServerId).get(cacheKeyForResource);
+        String cacheKey = getCacheKeyForResourceName(name);
 
-        if (cached == null) {
+        if (isInvalid(cacheKey)) {
+            return getDelegate().findByName(name, resourceServerId);
+        }
+
+        return cacheResult(resourceServerId, cacheKey, () -> {
             Resource resource = getDelegate().findByName(name, resourceServerId);
 
-            if (resource != null) {
-                invalidateCache(resourceServerId);
-                resolveResourceServerCache(resourceServerId).put(cacheKeyForResource, Arrays.asList(new CachedResource(resource)));
-                return findById(resource.getId(), resourceServerId);
+            if (resource == null) {
+                return Collections.emptyList();
             }
 
-            return null;
-        }
-
-        return createAdapter(cached.get(0));
+            return Arrays.asList(resource);
+        }).stream().findFirst().orElse(null);
     }
 
     @Override
@@ -167,23 +173,41 @@ public class CachedResourceStore implements ResourceStore {
     }
 
     private String getCacheKeyForResource(String id) {
-        return RESOURCE_ID_CACHE_PREFIX + id;
+        return new StringBuilder(RESOURCE_CACHE_PREFIX).append("id-").append(id).toString();
     }
 
-    private String getCacheKeyForResourceName(String name, String resourceServerId) {
-        return RESOURCE_NAME_CACHE_PREFIX + name + "-" + resourceServerId;
+    private String getCacheKeyForResourceName(String name) {
+        return new StringBuilder(RESOURCE_CACHE_PREFIX).append("findByName-").append(name).toString();
     }
 
-    private ResourceStore getDelegate() {
-        if (this.delegate == null) {
-            this.delegate = getDelegateStoreFactory().getResourceStore();
-        }
+    private String getCacheKeyForOwner(String name) {
+        return new StringBuilder(RESOURCE_CACHE_PREFIX).append("findByOwner-").append(name).toString();
+    }
+
+    private String getCacheKeyForUri(String uri) {
+        return new StringBuilder(RESOURCE_CACHE_PREFIX).append("findByUri-").append(uri).toString();
+    }
 
+    private ResourceStore getDelegate() {
         return this.delegate;
     }
 
-    private StoreFactory getDelegateStoreFactory() {
-        return this.delegateStoreFactory;
+    private List<Resource> cacheResult(String resourceServerId, String key, Supplier<List<Resource>> provider) {
+        List<Object> cached = getCachedStoreFactory().computeIfCachedEntryAbsent(resourceServerId, key, (Function<String, List<Object>>) o -> {
+            List<Resource> result = provider.get();
+
+            if (result.isEmpty()) {
+                return Collections.emptyList();
+            }
+
+            return result.stream().map(policy -> policy.getId()).collect(Collectors.toList());
+        });
+
+        if (cached == null) {
+            return Collections.emptyList();
+        }
+
+        return cached.stream().map(id -> findById(id.toString(), resourceServerId)).collect(Collectors.toList());
     }
 
     private Resource createAdapter(CachedResource cached) {
@@ -204,6 +228,8 @@ public class CachedResourceStore implements ResourceStore {
 
             @Override
             public void setName(String name) {
+                addInvalidation(getCacheKeyForResourceName(name));
+                addInvalidation(getCacheKeyForResourceName(cached.getName()));
                 getDelegateForUpdate().setName(name);
                 cached.setName(name);
             }
@@ -215,6 +241,8 @@ public class CachedResourceStore implements ResourceStore {
 
             @Override
             public void setUri(String uri) {
+                addInvalidation(getCacheKeyForUri(uri));
+                addInvalidation(getCacheKeyForUri(cached.getUri()));
                 getDelegateForUpdate().setUri(uri);
                 cached.setUri(uri);
             }
@@ -226,6 +254,7 @@ public class CachedResourceStore implements ResourceStore {
 
             @Override
             public void setType(String type) {
+                getCachedStoreFactory().getPolicyStore().addInvalidations(cached);
                 getDelegateForUpdate().setType(type);
                 cached.setType(type);
             }
@@ -270,7 +299,7 @@ public class CachedResourceStore implements ResourceStore {
 
             @Override
             public void updateScopes(Set<Scope> scopes) {
-                getDelegateForUpdate().updateScopes(scopes.stream().map(scope -> getDelegateStoreFactory().getScopeStore().findById(scope.getId(), cached.getResourceServerId())).collect(Collectors.toSet()));
+                getDelegateForUpdate().updateScopes(scopes.stream().map(scope -> getStoreFactory().getScopeStore().findById(scope.getId(), cached.getResourceServerId())).collect(Collectors.toSet()));
                 cached.updateScopes(scopes);
             }
 
@@ -279,52 +308,14 @@ public class CachedResourceStore implements ResourceStore {
                     String resourceServerId = cached.getResourceServerId();
                     this.updated = getDelegate().findById(getId(), resourceServerId);
                     if (this.updated == null) throw new IllegalStateException("Not found in database");
-                    transaction.whenCommit(() -> {
-                        invalidateCache(resourceServerId);
-                    });
-                    transaction.whenRollback(() -> {
-                        resolveResourceServerCache(resourceServerId).remove(getCacheKeyForResource(cached.getId()));
-                    });
+                    addInvalidation(getCacheKeyForResource(updated.getId()));
+                    getCachedStoreFactory().getPolicyStore().addInvalidations(updated);
+                    getTransaction().whenCommit(() -> invalidate(resourceServerId));
+                    getTransaction().whenRollback(() -> removeCachedEntry(resourceServerId, getCacheKeyForResource(cached.getId())));
                 }
 
                 return this.updated;
             }
         };
     }
-
-    private CachedStoreFactoryProvider getCachedStoreFactory() {
-        return cacheStoreFactory;
-    }
-
-    private List<Resource> cacheResult(String resourceServerId, String key, Supplier<List<Resource>> provider) {
-        List<CachedResource> cached = resolveResourceServerCache(resourceServerId).computeIfAbsent(key, (Function<String, List<CachedResource>>) o -> {
-            List<Resource> result = provider.get();
-
-            if (result.isEmpty()) {
-                return null;
-            }
-
-            return result.stream().map(resource -> new CachedResource(resource)).collect(Collectors.toList());
-        });
-
-        if (cached == null) {
-            return Collections.emptyList();
-        }
-
-        List<Resource> adapters = new ArrayList<>();
-
-        for (CachedResource resource : cached) {
-            adapters.add(createAdapter(resource));
-        }
-
-        return adapters;
-    }
-
-    private void invalidateCache(String resourceServerId) {
-        cache.remove(resourceServerId);
-    }
-
-    private Map<String, List<CachedResource>> resolveResourceServerCache(String id) {
-        return cache.computeIfAbsent(id, key -> new HashMap<>());
-    }
 }
diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedScopeStore.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedScopeStore.java
index fb878d0..741f5f7 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedScopeStore.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedScopeStore.java
@@ -19,52 +19,44 @@
 package org.keycloak.models.authorization.infinispan;
 
 import java.util.Arrays;
-import java.util.HashMap;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
 
-import org.infinispan.Cache;
 import org.keycloak.authorization.model.ResourceServer;
 import org.keycloak.authorization.model.Scope;
 import org.keycloak.authorization.store.ScopeStore;
 import org.keycloak.authorization.store.StoreFactory;
-import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.authorization.infinispan.InfinispanStoreFactoryProvider.CacheTransaction;
 import org.keycloak.models.authorization.infinispan.entities.CachedScope;
-import org.keycloak.models.cache.authorization.CachedStoreFactoryProvider;
 
 /**
  * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
  */
-public class CachedScopeStore implements ScopeStore {
-
-    private static final String SCOPE_ID_CACHE_PREFIX = "scp-id-";
-    private static final String SCOPE_NAME_CACHE_PREFIX = "scp-name-";
-
-    private final Cache<String, Map<String, List<CachedScope>>> cache;
-    private final CachedStoreFactoryProvider cacheStoreFactory;
-    private final CacheTransaction transaction;
-    private ScopeStore delegate;
-    private StoreFactory storeFactory;
-
-    public CachedScopeStore(KeycloakSession session, CachedStoreFactoryProvider cacheStoreFactory, CacheTransaction transaction, StoreFactory delegate) {
-        this.cacheStoreFactory = cacheStoreFactory;
-        this.transaction = transaction;
-        InfinispanConnectionProvider provider = session.getProvider(InfinispanConnectionProvider.class);
-        this.cache = provider.getCache(InfinispanConnectionProvider.AUTHORIZATION_CACHE_NAME);
-        this.storeFactory = delegate;
+public class CachedScopeStore extends AbstractCachedStore implements ScopeStore {
+
+    private static final String SCOPE_CACHE_PREFIX = "scp-";
+
+    private final ScopeStore delegate;
+
+    public CachedScopeStore(InfinispanStoreFactoryProvider cacheStoreFactory, StoreFactory storeFactory) {
+        super(cacheStoreFactory, storeFactory);
+        this.delegate = storeFactory.getScopeStore();
     }
 
     @Override
     public Scope create(String name, ResourceServer resourceServer) {
         Scope scope = getDelegate().create(name, getStoreFactory().getResourceServerStore().findById(resourceServer.getId()));
 
-        this.transaction.whenRollback(() -> resolveResourceServerCache(resourceServer.getId()).remove(getCacheKeyForScope(scope.getId())));
-        this.transaction.whenCommit(() -> {
-            invalidateCache(resourceServer.getId());
-        });
+        addInvalidation(getCacheKeyForScope(scope.getId()));
+        addInvalidation(getCacheKeyForScopeName(scope.getName()));
+        getCachedStoreFactory().getPolicyStore().addInvalidations(scope);
+
+        getTransaction().whenRollback(() -> removeCachedEntry(resourceServer.getId(), getCacheKeyForScope(scope.getId())));
+        getTransaction().whenCommit(() -> invalidate(resourceServer.getId()));
 
         return createAdapter(new CachedScope(scope));
     }
@@ -72,53 +64,62 @@ public class CachedScopeStore implements ScopeStore {
     @Override
     public void delete(String id) {
         Scope scope = getDelegate().findById(id, null);
+
         if (scope == null) {
             return;
         }
+
         ResourceServer resourceServer = scope.getResourceServer();
+
+        addInvalidation(getCacheKeyForScope(scope.getId()));
+        addInvalidation(getCacheKeyForScopeName(scope.getName()));
+        getCachedStoreFactory().getPolicyStore().addInvalidations(scope);
+
         getDelegate().delete(id);
-        this.transaction.whenCommit(() -> {
-            invalidateCache(resourceServer.getId());
-        });
+
+        getTransaction().whenCommit(() -> invalidate(resourceServer.getId()));
     }
 
     @Override
     public Scope findById(String id, String resourceServerId) {
-        String cacheKeyForScope = getCacheKeyForScope(id);
-        List<CachedScope> cached = resolveResourceServerCache(resourceServerId).get(cacheKeyForScope);
+        String cacheKey = getCacheKeyForScope(id);
+
+        if (isInvalid(cacheKey)) {
+            return getDelegate().findById(id, resourceServerId);
+        }
+
+        List<Object> cached = resolveCacheEntry(resourceServerId, cacheKey);
 
         if (cached == null) {
             Scope scope = getDelegate().findById(id, resourceServerId);
 
             if (scope != null) {
-                CachedScope cachedScope = new CachedScope(scope);
-                resolveResourceServerCache(resourceServerId).put(cacheKeyForScope, Arrays.asList(cachedScope));
-                return createAdapter(cachedScope);
+                return createAdapter(putCacheEntry(resourceServerId, cacheKey, new CachedScope(scope)));
             }
 
             return null;
         }
 
-        return createAdapter(cached.get(0));
+        return createAdapter(CachedScope.class.cast(cached.get(0)));
     }
 
     @Override
     public Scope findByName(String name, String resourceServerId) {
-        String cacheKeyForScope = getCacheKeyForScopeName(name);
-        List<CachedScope> cached = resolveResourceServerCache(resourceServerId).get(cacheKeyForScope);
+        String cacheKey = getCacheKeyForScopeName(name);
 
-        if (cached == null) {
+        if (isInvalid(cacheKey)) {
+            return getDelegate().findByName(name, resourceServerId);
+        }
+
+        return cacheResult(resourceServerId, cacheKey, () -> {
             Scope scope = getDelegate().findByName(name, resourceServerId);
 
-            if (scope != null) {
-                resolveResourceServerCache(resourceServerId).put(cacheKeyForScope, Arrays.asList(new CachedScope(scope)));
-                return findById(scope.getId(), resourceServerId);
+            if (scope == null) {
+                return Collections.emptyList();
             }
 
-            return null;
-        }
-
-        return createAdapter(cached.get(0));
+            return Arrays.asList(scope);
+        }).stream().findFirst().orElse(null);
     }
 
     @Override
@@ -132,23 +133,33 @@ public class CachedScopeStore implements ScopeStore {
     }
 
     private String getCacheKeyForScope(String id) {
-        return SCOPE_ID_CACHE_PREFIX + id;
+        return new StringBuilder(SCOPE_CACHE_PREFIX).append("id-").append(id).toString();
     }
 
     private String getCacheKeyForScopeName(String name) {
-        return SCOPE_NAME_CACHE_PREFIX + name;
+        return new StringBuilder(SCOPE_CACHE_PREFIX).append("findByName-").append(name).toString();
     }
 
     private ScopeStore getDelegate() {
-        if (this.delegate == null) {
-            this.delegate = getStoreFactory().getScopeStore();
-        }
-
         return this.delegate;
     }
 
-    private StoreFactory getStoreFactory() {
-        return this.storeFactory;
+    private List<Scope> cacheResult(String resourceServerId, String key, Supplier<List<Scope>> provider) {
+        List<Object> cached = getCachedStoreFactory().computeIfCachedEntryAbsent(resourceServerId, key, (Function<String, List<Object>>) o -> {
+            List<Scope> result = provider.get();
+
+            if (result.isEmpty()) {
+                return Collections.emptyList();
+            }
+
+            return result.stream().map(policy -> policy.getId()).collect(Collectors.toList());
+        });
+
+        if (cached == null) {
+            return Collections.emptyList();
+        }
+
+        return cached.stream().map(id -> findById(id.toString(), resourceServerId)).collect(Collectors.toList());
     }
 
     private Scope createAdapter(CachedScope cached) {
@@ -168,6 +179,8 @@ public class CachedScopeStore implements ScopeStore {
 
             @Override
             public void setName(String name) {
+                addInvalidation(getCacheKeyForScopeName(name));
+                addInvalidation(getCacheKeyForScopeName(cached.getName()));
                 getDelegateForUpdate().setName(name);
                 cached.setName(name);
             }
@@ -192,13 +205,10 @@ public class CachedScopeStore implements ScopeStore {
                 if (this.updated == null) {
                     this.updated = getDelegate().findById(getId(), cached.getResourceServerId());
                     if (this.updated == null) throw new IllegalStateException("Not found in database");
-                    transaction.whenCommit(() -> {
-                        invalidateCache(cached.getResourceServerId());
-                    });
-                    transaction.whenRollback(() -> {
-                        resolveResourceServerCache(cached.getResourceServerId()).remove(getCacheKeyForScope(cached.getId()));
-                        resolveResourceServerCache(cached.getResourceServerId()).remove(getCacheKeyForScopeName(cached.getName()));
-                    });
+                    addInvalidation(getCacheKeyForScope(updated.getId()));
+                    getCachedStoreFactory().getPolicyStore().addInvalidations(updated);
+                    getTransaction().whenCommit(() -> invalidate(cached.getResourceServerId()));
+                    getTransaction().whenRollback(() -> removeCachedEntry(cached.getResourceServerId(), getCacheKeyForScope(cached.getId())));
                 }
 
                 return this.updated;
@@ -218,16 +228,4 @@ public class CachedScopeStore implements ScopeStore {
             }
         };
     }
-
-    private CachedStoreFactoryProvider getCachedStoreFactory() {
-        return cacheStoreFactory;
-    }
-
-    private void invalidateCache(String resourceServerId) {
-        cache.remove(resourceServerId);
-    }
-
-    private Map<String, List<CachedScope>> resolveResourceServerCache(String id) {
-        return cache.computeIfAbsent(id, key -> new HashMap<>());
-    }
 }
diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/events/AuthorizationInvalidationEvent.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/events/AuthorizationInvalidationEvent.java
new file mode 100644
index 0000000..1d44923
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/events/AuthorizationInvalidationEvent.java
@@ -0,0 +1,44 @@
+/*
+ * 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.models.authorization.infinispan.events;
+
+import java.util.Set;
+
+import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class AuthorizationInvalidationEvent extends InvalidationEvent {
+
+    private final String resourceServerId;
+    private Set<String> invalidations;
+
+    public AuthorizationInvalidationEvent(String resourceServerId, Set<String> invalidations) {
+        this.resourceServerId = resourceServerId;
+        this.invalidations = invalidations;
+    }
+
+    public Set<String> getInvalidations() {
+        return invalidations;
+    }
+
+    @Override
+    public String getId() {
+        return resourceServerId;
+    }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/events/ResourceServerRemovedEvent.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/events/ResourceServerRemovedEvent.java
new file mode 100644
index 0000000..9fde6d9
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/events/ResourceServerRemovedEvent.java
@@ -0,0 +1,36 @@
+/*
+ * 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.models.authorization.infinispan.events;
+
+import java.util.Collections;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class ResourceServerRemovedEvent extends AuthorizationInvalidationEvent {
+
+    private final String clientId;
+
+    public ResourceServerRemovedEvent(String id, String clientId) {
+        super(id, Collections.emptySet());
+        this.clientId = clientId;
+    }
+
+    public String getClientId() {
+        return clientId;
+    }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreFactoryProvider.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreFactoryProvider.java
index bbc3848..692173c 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreFactoryProvider.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreFactoryProvider.java
@@ -19,9 +19,12 @@
 package org.keycloak.models.authorization.infinispan;
 
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
 
-import org.keycloak.authorization.store.PolicyStore;
+import org.keycloak.authorization.model.ResourceServer;
 import org.keycloak.authorization.store.ResourceServerStore;
 import org.keycloak.authorization.store.ResourceStore;
 import org.keycloak.authorization.store.ScopeStore;
@@ -39,16 +42,21 @@ public class InfinispanStoreFactoryProvider implements CachedStoreFactoryProvide
     private final CachedResourceStore resourceStore;
     private final CachedScopeStore scopeStore;
     private final CachedPolicyStore policyStore;
+    private final KeycloakSession session;
+    private final StoreFactoryCacheManager cacheManager;
     private ResourceServerStore resourceServerStore;
+    private Set<String> invalidations = new HashSet<>();
 
-    public InfinispanStoreFactoryProvider(KeycloakSession session) {
+    public InfinispanStoreFactoryProvider(KeycloakSession session, StoreFactoryCacheManager cacheManager) {
+        this.session = session;
+        this.cacheManager = cacheManager;
         this.transaction = new CacheTransaction();
         session.getTransactionManager().enlistAfterCompletion(transaction);
         StoreFactory delegate = session.getProvider(StoreFactory.class);
-        resourceStore = new CachedResourceStore(session, this, this.transaction, delegate);
-        resourceServerStore = new CachedResourceServerStore(session, this.transaction, delegate);
-        scopeStore = new CachedScopeStore(session, this, this.transaction, delegate);
-        policyStore = new CachedPolicyStore(session, this, this.transaction, delegate);
+        resourceStore = new CachedResourceStore(this, delegate);
+        resourceServerStore = new CachedResourceServerStore(this, delegate);
+        scopeStore = new CachedScopeStore(this, delegate);
+        policyStore = new CachedPolicyStore(this, delegate);
     }
 
     @Override
@@ -67,7 +75,7 @@ public class InfinispanStoreFactoryProvider implements CachedStoreFactoryProvide
     }
 
     @Override
-    public PolicyStore getPolicyStore() {
+    public CachedPolicyStore getPolicyStore() {
         return policyStore;
     }
 
@@ -76,6 +84,42 @@ public class InfinispanStoreFactoryProvider implements CachedStoreFactoryProvide
 
     }
 
+    void addInvalidation(String cacheKey) {
+        invalidations.add(cacheKey);
+    }
+
+    boolean isInvalid(String cacheKeyForPolicy) {
+        return invalidations.contains(cacheKeyForPolicy);
+    }
+
+    void invalidate(String resourceServerId) {
+        cacheManager.invalidate(session, resourceServerId, invalidations);
+    }
+
+    List<Object> resolveCachedEntry(String resourceServerId, String cacheKeyForPolicy) {
+        return cacheManager.resolveResourceServerCache(resourceServerId).get(cacheKeyForPolicy);
+    }
+
+    void putCacheEntry(String resourceServerId, String key, List<Object> entry) {
+        cacheManager.resolveResourceServerCache(resourceServerId).put(key, entry);
+    }
+
+    List<Object> computeIfCachedEntryAbsent(String resourceServerId, String key, Function<String, List<Object>> function) {
+        return cacheManager.resolveResourceServerCache(resourceServerId).computeIfAbsent(key, function);
+    }
+
+    CacheTransaction getTransaction() {
+        return transaction;
+    }
+
+    void removeCachedEntry(String id, String key) {
+        cacheManager.resolveResourceServerCache(id).remove(key);
+    }
+
+    void removeEntries(ResourceServer resourceServer) {
+        cacheManager.removeAll(session, resourceServer);
+    }
+
     static class CacheTransaction implements KeycloakTransaction {
 
         private List<Runnable> completeTasks = new ArrayList<>();
diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreProviderFactory.java
index ad58890..672b565 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreProviderFactory.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreProviderFactory.java
@@ -18,11 +18,16 @@
 
 package org.keycloak.models.authorization.infinispan;
 
+import java.util.List;
+import java.util.Map;
+
+import org.infinispan.Cache;
 import org.keycloak.Config;
-import org.keycloak.authorization.AuthorizationProvider;
-import org.keycloak.authorization.store.StoreFactory;
+import org.keycloak.cluster.ClusterProvider;
+import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.authorization.infinispan.events.AuthorizationInvalidationEvent;
 import org.keycloak.models.cache.authorization.CachedStoreFactoryProvider;
 import org.keycloak.models.cache.authorization.CachedStoreProviderFactory;
 import org.keycloak.provider.EnvironmentDependentProviderFactory;
@@ -31,9 +36,12 @@ import org.keycloak.provider.EnvironmentDependentProviderFactory;
  * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
  */
 public class InfinispanStoreProviderFactory implements CachedStoreProviderFactory, EnvironmentDependentProviderFactory {
+
+    private StoreFactoryCacheManager cacheManager;
+
     @Override
     public CachedStoreFactoryProvider create(KeycloakSession session) {
-        return new InfinispanStoreFactoryProvider(session);
+        return new InfinispanStoreFactoryProvider(session, cacheManager);
     }
 
     @Override
@@ -43,7 +51,25 @@ public class InfinispanStoreProviderFactory implements CachedStoreProviderFactor
 
     @Override
     public void postInit(KeycloakSessionFactory factory) {
+        KeycloakSession session = factory.create();
+
+        try {
+            InfinispanConnectionProvider provider = session.getProvider(InfinispanConnectionProvider.class);
+            Cache<String, Map<String, List<Object>>> cache = provider.getCache(InfinispanConnectionProvider.AUTHORIZATION_CACHE_NAME);
+            ClusterProvider clusterProvider = session.getProvider(ClusterProvider.class);
+
+            cacheManager = new StoreFactoryCacheManager(cache);
 
+            clusterProvider.registerListener(ClusterProvider.ALL, event -> {
+                if (event instanceof AuthorizationInvalidationEvent) {
+                    cacheManager.invalidate(AuthorizationInvalidationEvent.class.cast(event));
+                }
+            });
+        } finally {
+            if (session != null) {
+                session.close();
+            }
+        }
     }
 
     @Override
diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/StoreFactoryCacheManager.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/StoreFactoryCacheManager.java
new file mode 100644
index 0000000..4fd5f6f
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/StoreFactoryCacheManager.java
@@ -0,0 +1,72 @@
+/*
+ * 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.models.authorization.infinispan;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.infinispan.Cache;
+import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.cluster.ClusterProvider;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.authorization.infinispan.events.AuthorizationInvalidationEvent;
+import org.keycloak.models.authorization.infinispan.events.ResourceServerRemovedEvent;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class StoreFactoryCacheManager {
+
+    private static final String AUTHORIZATION_UPDATE_TASK_KEY = "authorization-update";
+
+    private final Cache<String, Map<String, List<Object>>> cache;
+
+    StoreFactoryCacheManager(Cache<String, Map<String, List<Object>>> cache) {
+        this.cache = cache;
+    }
+
+    void invalidate(AuthorizationInvalidationEvent event) {
+        if (event instanceof ResourceServerRemovedEvent) {
+            cache.remove(event.getId());
+            cache.remove(ResourceServerRemovedEvent.class.cast(event).getClientId());
+        } else {
+            Map<String, List<Object>> resolveResourceServerCache = resolveResourceServerCache(event.getId());
+
+            for (String key : event.getInvalidations()) {
+                resolveResourceServerCache.remove(key);
+            }
+        }
+    }
+
+    public void invalidate(KeycloakSession session, String resourceServerId, Set<String> invalidations) {
+        getClusterProvider(session).notify(AUTHORIZATION_UPDATE_TASK_KEY, new AuthorizationInvalidationEvent(resourceServerId, invalidations), false);
+    }
+
+    public Map<String, List<Object>> resolveResourceServerCache(String id) {
+        return cache.computeIfAbsent(id, key -> new HashMap<>());
+    }
+
+    void removeAll(KeycloakSession session, ResourceServer id) {
+        getClusterProvider(session).notify(AUTHORIZATION_UPDATE_TASK_KEY, new ResourceServerRemovedEvent(id.getId(), id.getClientId()), false);
+    }
+
+    private ClusterProvider getClusterProvider(KeycloakSession session) {
+        return session.getProvider(ClusterProvider.class);
+    }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/PolicyEntity.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/PolicyEntity.java
index 648b1b3..0305ae5 100644
--- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/PolicyEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/PolicyEntity.java
@@ -74,7 +74,7 @@ public class PolicyEntity implements Policy {
     @Column(name = "LOGIC")
     private Logic logic = Logic.POSITIVE;
 
-    @ElementCollection
+    @ElementCollection(fetch = FetchType.LAZY)
     @MapKeyColumn(name="NAME")
     @Column(name="VALUE", columnDefinition = "TEXT")
     @CollectionTable(name="POLICY_CONFIG", joinColumns={ @JoinColumn(name="POLICY_ID") })
@@ -92,7 +92,7 @@ public class PolicyEntity implements Policy {
     @JoinTable(name = "RESOURCE_POLICY", joinColumns = @JoinColumn(name = "POLICY_ID"), inverseJoinColumns = @JoinColumn(name = "RESOURCE_ID"))
     private Set<ResourceEntity> resources = new HashSet<>();
 
-    @OneToMany(fetch = FetchType.EAGER, cascade = {})
+    @OneToMany(fetch = FetchType.LAZY, cascade = {})
     @JoinTable(name = "SCOPE_POLICY", joinColumns = @JoinColumn(name = "POLICY_ID"), inverseJoinColumns = @JoinColumn(name = "SCOPE_ID"))
     private Set<ScopeEntity> scopes = new HashSet<>();
 

pom.xml 4(+3 -1)

diff --git a/pom.xml b/pom.xml
index 4f128e2..48072ba 100755
--- a/pom.xml
+++ b/pom.xml
@@ -37,6 +37,8 @@
     <packaging>pom</packaging>
 
     <properties>
+        <product.rhsso.version>7.2.0.DR3</product.rhsso.version>
+
         <product.build-time>${timestamp}</product.build-time>
 
         <!-- WildFly -->
@@ -1533,7 +1535,7 @@
                 <product.slot>rh-sso</product.slot>
                 <product.wildfly.console.slot>eap</product.wildfly.console.slot>
                 <product.name-html>\u003Cstrong\u003ERed Hat\u003C\u002Fstrong\u003E\u003Csup\u003E\u00AE\u003C\u002Fsup\u003E Single Sign On</product.name-html>
-                <product.version>${project.version}</product.version>
+                <product.version>${product.rhsso.version}</product.version>
                 <product.default-profile>product</product.default-profile>
                 <product.filename.version>7.2</product.filename.version>
             </properties>
diff --git a/server-spi/src/main/java/org/keycloak/credential/hash/PasswordHashProvider.java b/server-spi/src/main/java/org/keycloak/credential/hash/PasswordHashProvider.java
index 0a4013e..ee555c2 100644
--- a/server-spi/src/main/java/org/keycloak/credential/hash/PasswordHashProvider.java
+++ b/server-spi/src/main/java/org/keycloak/credential/hash/PasswordHashProvider.java
@@ -27,8 +27,7 @@ import org.keycloak.provider.Provider;
 public interface PasswordHashProvider extends Provider {
     boolean policyCheck(PasswordPolicy policy, CredentialModel credentia);
 
-    void encode(String rawPassword, PasswordPolicy policy, CredentialModel credential);
+    void encode(String rawPassword, int iterations, CredentialModel credential);
 
     boolean verify(String rawPassword, CredentialModel credential);
-
 }
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/store/PolicyStore.java b/server-spi-private/src/main/java/org/keycloak/authorization/store/PolicyStore.java
index 793185e..dbf64a4 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/store/PolicyStore.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/store/PolicyStore.java
@@ -128,13 +128,4 @@ public interface PolicyStore {
      * @return a list of policies that depends on the a policy with the given identifier
      */
     List<Policy> findDependentPolicies(String id, String resourceServerId);
-
-    /**
-     * Notify this store about changes to data associated with policies. E.g.: resources and scopes..
-     *
-     * TODO: need a better strategy to handle cross-references between stores, specially in cases where the store is caching data. Use some event-based solution here.
-     *
-     * @param cached
-     */
-    default void notifyChange(Object cached) {}
 }
diff --git a/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2PasswordHashProvider.java b/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2PasswordHashProvider.java
index b3f8459..6c170e1 100644
--- a/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2PasswordHashProvider.java
+++ b/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2PasswordHashProvider.java
@@ -17,11 +17,8 @@
 
 package org.keycloak.credential.hash;
 
-import org.keycloak.Config;
 import org.keycloak.common.util.Base64;
 import org.keycloak.credential.CredentialModel;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.UserCredentialModel;
 
@@ -35,42 +32,34 @@ import java.security.spec.KeySpec;
 /**
  * @author <a href="mailto:me@tsudot.com">Kunal Kerkar</a>
  */
-public class Pbkdf2PasswordHashProvider implements PasswordHashProviderFactory, PasswordHashProvider {
+public class Pbkdf2PasswordHashProvider implements PasswordHashProvider {
 
-    public static final String ID = "pbkdf2";
+    private final String providerId;
 
-    private static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA1";
-    private static final int DERIVED_KEY_SIZE = 512;
+    private final String pbkdf2Algorithm;
 
-    public CredentialModel encode(String rawPassword, int iterations) {
-        byte[] salt = getSalt();
-        String encodedPassword = encode(rawPassword, iterations, salt);
+    public static final int DERIVED_KEY_SIZE = 512;
 
-        CredentialModel credentials = new CredentialModel();
-        credentials.setAlgorithm(ID);
-        credentials.setType(UserCredentialModel.PASSWORD);
-        credentials.setSalt(salt);
-        credentials.setHashIterations(iterations);
-        credentials.setValue(encodedPassword);
-        return credentials;
+    public Pbkdf2PasswordHashProvider(String providerId, String pbkdf2Algorithm) {
+        this.providerId = providerId;
+        this.pbkdf2Algorithm = pbkdf2Algorithm;
     }
 
     @Override
     public boolean policyCheck(PasswordPolicy policy, CredentialModel credential) {
-        return credential.getHashIterations() == policy.getHashIterations() && ID.equals(credential.getAlgorithm());
+        return credential.getHashIterations() == policy.getHashIterations() && providerId.equals(credential.getAlgorithm());
     }
 
     @Override
-    public void encode(String rawPassword, PasswordPolicy policy, CredentialModel credential) {
+    public void encode(String rawPassword, int iterations, CredentialModel credential) {
         byte[] salt = getSalt();
-        String encodedPassword = encode(rawPassword, policy.getHashIterations(), salt);
+        String encodedPassword = encode(rawPassword, iterations, salt);
 
-        credential.setAlgorithm(ID);
+        credential.setAlgorithm(providerId);
         credential.setType(UserCredentialModel.PASSWORD);
         credential.setSalt(salt);
-        credential.setHashIterations(policy.getHashIterations());
+        credential.setHashIterations(iterations);
         credential.setValue(encodedPassword);
-
     }
 
     @Override
@@ -78,27 +67,9 @@ public class Pbkdf2PasswordHashProvider implements PasswordHashProviderFactory, 
         return encode(rawPassword, credential.getHashIterations(), credential.getSalt()).equals(credential.getValue());
     }
 
-    @Override
-    public PasswordHashProvider create(KeycloakSession session) {
-        return this;
-    }
-
-    @Override
-    public void init(Config.Scope config) {
-    }
-
-    @Override
-    public void postInit(KeycloakSessionFactory factory) {
-    }
-
     public void close() {
     }
 
-    @Override
-    public String getId() {
-        return ID;
-    }
-
     private String encode(String rawPassword, int iterations, byte[] salt) {
         KeySpec spec = new PBEKeySpec(rawPassword.toCharArray(), salt, iterations, DERIVED_KEY_SIZE);
 
@@ -122,10 +93,9 @@ public class Pbkdf2PasswordHashProvider implements PasswordHashProviderFactory, 
 
     private SecretKeyFactory getSecretKeyFactory() {
         try {
-            return SecretKeyFactory.getInstance(PBKDF2_ALGORITHM);
+            return SecretKeyFactory.getInstance(pbkdf2Algorithm);
         } catch (NoSuchAlgorithmException e) {
             throw new RuntimeException("PBKDF2 algorithm not found", e);
         }
     }
-
 }
diff --git a/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2PasswordHashProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2PasswordHashProviderFactory.java
new file mode 100644
index 0000000..ecd917d
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2PasswordHashProviderFactory.java
@@ -0,0 +1,54 @@
+/*
+ * 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.credential.hash;
+
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+
+/**
+ * @author <a href="mailto:me@tsudot.com">Kunal Kerkar</a>
+ */
+public class Pbkdf2PasswordHashProviderFactory implements PasswordHashProviderFactory {
+
+    public static final String ID = "pbkdf2";
+
+    public static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA1";
+
+    @Override
+    public PasswordHashProvider create(KeycloakSession session) {
+        return new Pbkdf2PasswordHashProvider(ID, PBKDF2_ALGORITHM);
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+    }
+
+    @Override
+    public String getId() {
+        return ID;
+    }
+
+    @Override
+    public void close() {
+    }
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2Sha256PasswordHashProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2Sha256PasswordHashProviderFactory.java
new file mode 100644
index 0000000..c6453d1
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2Sha256PasswordHashProviderFactory.java
@@ -0,0 +1,39 @@
+package org.keycloak.credential.hash;
+
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+
+/**
+ * PBKDF2 Password Hash provider with HMAC using SHA256
+ *
+ * @author <a href"mailto:abkaplan07@gmail.com">Adam Kaplan</a>
+ */
+public class Pbkdf2Sha256PasswordHashProviderFactory implements PasswordHashProviderFactory {
+
+    public static final String ID = "pbkdf2-sha256";
+
+    public static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA256";
+
+    @Override
+    public PasswordHashProvider create(KeycloakSession session) {
+        return new Pbkdf2PasswordHashProvider(ID, PBKDF2_ALGORITHM);
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+    }
+
+    @Override
+    public String getId() {
+        return ID;
+    }
+
+    @Override
+    public void close() {
+    }
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2Sha512PasswordHashProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2Sha512PasswordHashProviderFactory.java
new file mode 100644
index 0000000..5f838a1
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2Sha512PasswordHashProviderFactory.java
@@ -0,0 +1,39 @@
+package org.keycloak.credential.hash;
+
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+
+/**
+ * Provider factory for SHA512 variant of the PBKDF2 password hash algorithm.
+ *
+ * @author @author <a href="mailto:abkaplan07@gmail.com">Adam Kaplan</a>
+ */
+public class Pbkdf2Sha512PasswordHashProviderFactory implements PasswordHashProviderFactory {
+
+    public static final String ID = "pbkdf2-sha512";
+
+    public static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA512";
+
+    @Override
+    public PasswordHashProvider create(KeycloakSession session) {
+        return new Pbkdf2PasswordHashProvider(ID, PBKDF2_ALGORITHM);
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+    }
+
+    @Override
+    public String getId() {
+        return ID;
+    }
+
+    @Override
+    public void close() {
+    }
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/policy/HashAlgorithmPasswordPolicyProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/policy/HashAlgorithmPasswordPolicyProviderFactory.java
index 303ba79..c1c6218 100644
--- a/server-spi-private/src/main/java/org/keycloak/policy/HashAlgorithmPasswordPolicyProviderFactory.java
+++ b/server-spi-private/src/main/java/org/keycloak/policy/HashAlgorithmPasswordPolicyProviderFactory.java
@@ -18,8 +18,10 @@
 package org.keycloak.policy;
 
 import org.keycloak.Config;
+import org.keycloak.credential.hash.PasswordHashProvider;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.ModelException;
 import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
@@ -29,8 +31,11 @@ import org.keycloak.models.UserModel;
  */
 public class HashAlgorithmPasswordPolicyProviderFactory implements PasswordPolicyProviderFactory, PasswordPolicyProvider {
 
+    private KeycloakSession session;
+
     @Override
     public PasswordPolicyProvider create(KeycloakSession session) {
+        this.session = session;
         return this;
     }
 
@@ -83,7 +88,12 @@ public class HashAlgorithmPasswordPolicyProviderFactory implements PasswordPolic
 
     @Override
     public Object parseConfig(String value) {
-        return value != null ? value : PasswordPolicy.HASH_ALGORITHM_DEFAULT;
+        String providerId = value != null && value.length() > 0 ? value : PasswordPolicy.HASH_ALGORITHM_DEFAULT;
+        PasswordHashProvider provider = session.getProvider(PasswordHashProvider.class, providerId);
+        if (provider == null) {
+            throw new ModelException("Password hashing provider not found");
+        }
+        return providerId;
     }
 
 }
diff --git a/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java b/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java
index dc6827d..a3f468e 100644
--- a/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java
+++ b/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java
@@ -95,7 +95,7 @@ public class PasswordCredentialProvider implements CredentialProvider, Credentia
         newPassword.setType(CredentialModel.PASSWORD);
         long createdDate = Time.currentTimeMillis();
         newPassword.setCreatedDate(createdDate);
-        hash.encode(cred.getValue(), policy, newPassword);
+        hash.encode(cred.getValue(), policy.getHashIterations(), newPassword);
         getCredentialStore().createCredential(realm, user, newPassword);
         UserCache userCache = session.userCache();
         if (userCache != null) {
@@ -207,7 +207,7 @@ public class PasswordCredentialProvider implements CredentialProvider, Credentia
             return true;
         }
 
-        hash.encode(cred.getValue(), policy, password);
+        hash.encode(cred.getValue(), policy.getHashIterations(), password);
         getCredentialStore().updateCredential(realm, user, password);
         UserCache userCache = session.userCache();
         if (userCache != null) {
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
index 5e1e247..4cedd6b 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
@@ -214,16 +214,22 @@ public class TokenManager {
         }
 
         UserSessionModel userSession =  session.sessions().getUserSession(realm, token.getSessionState());
-        if (!AuthenticationManager.isSessionValid(realm, userSession)) {
-            return false;
+        if (AuthenticationManager.isSessionValid(realm, userSession)) {
+            ClientSessionModel clientSession = session.sessions().getClientSession(realm, token.getClientSession());
+            if (clientSession != null) {
+                return true;
+            }
         }
 
-        ClientSessionModel clientSession = session.sessions().getClientSession(realm, token.getClientSession());
-        if (clientSession == null) {
-            return false;
+        userSession = session.sessions().getOfflineUserSession(realm, token.getSessionState());
+        if (AuthenticationManager.isOfflineSessionValid(realm, userSession)) {
+            ClientSessionModel clientSession = session.sessions().getOfflineClientSession(realm, token.getClientSession());
+            if (clientSession != null) {
+                return true;
+            }
         }
 
-        return true;
+        return false;
     }
 
     public RefreshResult refreshAccessToken(KeycloakSession session, UriInfo uriInfo, ClientConnection connection, RealmModel realm, ClientModel authorizedClient, String encodedRefreshToken, EventBuilder event, HttpHeaders headers) throws OAuthErrorException {
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
index 12053e0..510a605 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
@@ -46,6 +46,7 @@ import org.keycloak.models.GroupModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.LDAPConstants;
 import org.keycloak.models.ModelDuplicateException;
+import org.keycloak.models.ModelException;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserSessionModel;
@@ -332,6 +333,8 @@ public class RealmAdminResource {
             return ErrorResponse.error("Specified regex pattern(s) is invalid.", Response.Status.BAD_REQUEST);
         } catch (ModelDuplicateException e) {
             return ErrorResponse.exists("Realm with same name exists");
+        } catch (ModelException e) {
+            return ErrorResponse.error(e.getMessage(), Status.BAD_REQUEST);
         } catch (Exception e) {
             logger.error(e.getMessage(), e);
             return ErrorResponse.error("Failed to update realm", Response.Status.INTERNAL_SERVER_ERROR);
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.credential.hash.PasswordHashProviderFactory b/services/src/main/resources/META-INF/services/org.keycloak.credential.hash.PasswordHashProviderFactory
index e72e56d..48f56fc 100644
--- a/services/src/main/resources/META-INF/services/org.keycloak.credential.hash.PasswordHashProviderFactory
+++ b/services/src/main/resources/META-INF/services/org.keycloak.credential.hash.PasswordHashProviderFactory
@@ -1 +1,3 @@
-org.keycloak.credential.hash.Pbkdf2PasswordHashProvider
\ No newline at end of file
+org.keycloak.credential.hash.Pbkdf2PasswordHashProviderFactory
+org.keycloak.credential.hash.Pbkdf2Sha256PasswordHashProviderFactory
+org.keycloak.credential.hash.Pbkdf2Sha512PasswordHashProviderFactory
\ No newline at end of file
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adduser/AddUserTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adduser/AddUserTest.java
index 651afc5..fb23bcd 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adduser/AddUserTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adduser/AddUserTest.java
@@ -26,7 +26,7 @@ import org.junit.rules.TemporaryFolder;
 import org.keycloak.admin.client.Keycloak;
 import org.keycloak.admin.client.resource.RealmResource;
 import org.keycloak.admin.client.resource.UserResource;
-import org.keycloak.credential.hash.Pbkdf2PasswordHashProvider;
+import org.keycloak.credential.hash.Pbkdf2PasswordHashProviderFactory;
 import org.keycloak.models.Constants;
 import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.representations.idm.CredentialRepresentation;
@@ -85,7 +85,7 @@ public class AddUserTest {
 
         CredentialRepresentation credentials = user.getCredentials().get(0);
 
-        assertEquals(Pbkdf2PasswordHashProvider.ID, credentials.getAlgorithm());
+        assertEquals(Pbkdf2PasswordHashProviderFactory.ID, credentials.getAlgorithm());
         assertEquals(new Integer(100000), credentials.getHashIterations());
 
         KeycloakServer server = new KeycloakServer();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/FederatedStorageExportImportTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/FederatedStorageExportImportTest.java
index 6eccb21..8a1bf5d 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/FederatedStorageExportImportTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/FederatedStorageExportImportTest.java
@@ -103,7 +103,7 @@ public class FederatedStorageExportImportTest {
         session.userFederatedStorage().addRequiredAction(realm, userId, "UPDATE_PASSWORD");
         CredentialModel credential = new CredentialModel();
         getHashProvider(session, realm.getPasswordPolicy()).encode("password", realm.
-                getPasswordPolicy(), credential);
+                getPasswordPolicy().getHashIterations(), credential);
         session.userFederatedStorage().createCredential(realm, userId, credential);
         session.userFederatedStorage().grantRole(realm, userId, role);
         session.userFederatedStorage().joinGroup(realm, userId, group);
@@ -170,7 +170,7 @@ public class FederatedStorageExportImportTest {
         session.userFederatedStorage().addRequiredAction(realm, userId, "UPDATE_PASSWORD");
         CredentialModel credential = new CredentialModel();
         getHashProvider(session, realm.getPasswordPolicy()).encode("password", realm.
-                getPasswordPolicy(), credential);
+                getPasswordPolicy().getHashIterations(), credential);
         session.userFederatedStorage().createCredential(realm, userId, credential);
         session.userFederatedStorage().grantRole(realm, userId, role);
         session.userFederatedStorage().joinGroup(realm, userId, group);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/RolePolicyTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/RolePolicyTest.java
new file mode 100644
index 0000000..93aa5ca
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/RolePolicyTest.java
@@ -0,0 +1,234 @@
+/*
+ * 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.authz;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Predicate;
+
+import org.junit.Before;
+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.authorization.client.AuthorizationDeniedException;
+import org.keycloak.authorization.client.AuthzClient;
+import org.keycloak.authorization.client.Configuration;
+import org.keycloak.authorization.client.representation.AuthorizationRequest;
+import org.keycloak.authorization.client.representation.AuthorizationResponse;
+import org.keycloak.authorization.client.representation.PermissionRequest;
+import org.keycloak.authorization.client.util.HttpResponseException;
+import org.keycloak.representations.idm.GroupRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.representations.idm.authorization.JSPolicyRepresentation;
+import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation;
+import org.keycloak.representations.idm.authorization.ResourceRepresentation;
+import org.keycloak.representations.idm.authorization.RolePolicyRepresentation;
+import org.keycloak.testsuite.AbstractKeycloakTest;
+import org.keycloak.testsuite.util.AdminClientUtil;
+import org.keycloak.testsuite.util.ClientBuilder;
+import org.keycloak.testsuite.util.GroupBuilder;
+import org.keycloak.testsuite.util.RealmBuilder;
+import org.keycloak.testsuite.util.RoleBuilder;
+import org.keycloak.testsuite.util.RolesBuilder;
+import org.keycloak.testsuite.util.UserBuilder;
+import org.keycloak.util.JsonSerialization;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class RolePolicyTest extends AbstractKeycloakTest {
+
+    @Override
+    public void addTestRealms(List<RealmRepresentation> testRealms) {
+        testRealms.add(RealmBuilder.create().name("authz-test")
+                .roles(RolesBuilder.create()
+                        .realmRole(RoleBuilder.create().name("uma_authorization").build())
+                        .realmRole(RoleBuilder.create().name("Role A").build())
+                        .realmRole(RoleBuilder.create().name("Role B").build())
+                        .realmRole(RoleBuilder.create().name("Role C").build())
+                )
+                .group(GroupBuilder.create().name("Group A").realmRoles(Arrays.asList("Role A")).build())
+                .group(GroupBuilder.create().name("Group B").realmRoles(Arrays.asList("Role C")).build())
+                .user(UserBuilder.create().username("marta").password("password").addRoles("uma_authorization", "Role A"))
+                .user(UserBuilder.create().username("kolo").password("password").addRoles("uma_authorization", "Role B"))
+                .user(UserBuilder.create().username("alice").password("password").addRoles("uma_authorization").addGroups("Group B"))
+                .client(ClientBuilder.create().clientId("resource-server-test")
+                    .secret("secret")
+                    .authorizationServicesEnabled(true)
+                    .redirectUris("http://localhost/resource-server-test")
+                    .defaultRoles("uma_protection")
+                    .directAccessGrants())
+                .build());
+    }
+
+    @Before
+    public void configureAuthorization() throws Exception {
+        createResource("Resource A");
+        createResource("Resource B");
+        createResource("Resource C");
+
+        createRealmRolePolicy("Role A Policy", "Role A");
+        createRealmRolePolicy("Role B Policy", "Role B");
+        createRealmRolePolicy("Role C Policy", "Role C");
+
+        createResourcePermission("Resource A Permission", "Resource A", "Role A Policy");
+        createResourcePermission("Resource B Permission", "Resource B", "Role B Policy");
+        createResourcePermission("Resource C Permission", "Resource C", "Role C Policy");
+    }
+
+    @Test
+    public void testUserWithExpectedRole() {
+        AuthzClient authzClient = getAuthzClient();
+        PermissionRequest request = new PermissionRequest();
+
+        request.setResourceSetName("Resource A");
+
+        String ticket = authzClient.protection().permission().forResource(request).getTicket();
+        AuthorizationResponse response = authzClient.authorization("marta", "password").authorize(new AuthorizationRequest(ticket));
+
+        assertNotNull(response.getRpt());
+    }
+
+    @Test
+    public void testUserWithoutExpectedRole() {
+        AuthzClient authzClient = getAuthzClient();
+        PermissionRequest request = new PermissionRequest();
+
+        request.setResourceSetName("Resource A");
+
+        String ticket = authzClient.protection().permission().forResource(request).getTicket();
+
+        try {
+            authzClient.authorization("kolo", "password").authorize(new AuthorizationRequest(ticket));
+            fail("Should fail because user is not granted with expected role");
+        } catch (AuthorizationDeniedException ignore) {
+
+        }
+
+        request.setResourceSetName("Resource B");
+        ticket = authzClient.protection().permission().forResource(request).getTicket();
+        assertNotNull(authzClient.authorization("kolo", "password").authorize(new AuthorizationRequest(ticket)));
+
+        UserRepresentation user = getRealm().users().search("kolo").get(0);
+        RoleRepresentation roleA = getRealm().roles().get("Role A").toRepresentation();
+        getRealm().users().get(user.getId()).roles().realmLevel().add(Arrays.asList(roleA));
+
+        request.setResourceSetName("Resource A");
+        ticket = authzClient.protection().permission().forResource(request).getTicket();
+        assertNotNull(authzClient.authorization("kolo", "password").authorize(new AuthorizationRequest(ticket)));
+    }
+
+    @Test
+    public void testUserWithGroupRole() throws InterruptedException {
+        AuthzClient authzClient = getAuthzClient();
+        PermissionRequest request = new PermissionRequest();
+
+        request.setResourceSetName("Resource C");
+
+        String ticket = authzClient.protection().permission().forResource(request).getTicket();
+        assertNotNull(authzClient.authorization("alice", "password").authorize(new AuthorizationRequest(ticket)));
+
+        UserRepresentation user = getRealm().users().search("alice").get(0);
+        GroupRepresentation groupB = getRealm().groups().groups().stream().filter(representation -> "Group B".equals(representation.getName())).findFirst().get();
+        getRealm().users().get(user.getId()).leaveGroup(groupB.getId());
+
+        try {
+            authzClient.authorization("alice", "password").authorize(new AuthorizationRequest(ticket));
+            fail("Should fail because user is not granted with expected role");
+        } catch (AuthorizationDeniedException ignore) {
+
+        }
+
+        request.setResourceSetName("Resource A");
+        ticket = authzClient.protection().permission().forResource(request).getTicket();
+
+        try {
+            authzClient.authorization("alice", "password").authorize(new AuthorizationRequest(ticket));
+            fail("Should fail because user is not granted with expected role");
+        } catch (AuthorizationDeniedException ignore) {
+
+        }
+
+        GroupRepresentation groupA = getRealm().groups().groups().stream().filter(representation -> "Group A".equals(representation.getName())).findFirst().get();
+        getRealm().users().get(user.getId()).joinGroup(groupA.getId());
+
+        assertNotNull(authzClient.authorization("alice", "password").authorize(new AuthorizationRequest(ticket)));
+    }
+
+    private void createRealmRolePolicy(String name, String... roles) {
+        RolePolicyRepresentation policy = new RolePolicyRepresentation();
+
+        policy.setName(name);
+
+        for (String role : roles) {
+            policy.addRole(role);
+        }
+
+        getClient().authorization().policies().role().create(policy);
+    }
+
+    private void createResourcePermission(String name, String resource, String... policies) {
+        ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation();
+
+        permission.setName(name);
+        permission.addResource(resource);
+        permission.addPolicy(policies);
+
+        getClient().authorization().permissions().resource().create(permission);
+    }
+
+    private void createResource(String name) {
+        AuthorizationResource authorization = getClient().authorization();
+        ResourceRepresentation resource = new ResourceRepresentation(name);
+
+        authorization.resources().create(resource);
+    }
+
+    private RealmResource getRealm() {
+        try {
+            return AdminClientUtil.createAdminClient().realm("authz-test");
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to create admin client");
+        }
+    }
+
+    private ClientResource getClient(RealmResource realm) {
+        ClientsResource clients = realm.clients();
+        return clients.findByClientId("resource-server-test").stream().map(representation -> clients.get(representation.getId())).findFirst().orElseThrow(() -> new RuntimeException("Expected client [resource-server-test]"));
+    }
+
+    private AuthzClient getAuthzClient() {
+        try {
+            return AuthzClient.create(JsonSerialization.readValue(getClass().getResourceAsStream("/authorization-test/default-keycloak.json"), Configuration.class));
+        } catch (IOException cause) {
+            throw new RuntimeException("Failed to create authz client", cause);
+        }
+    }
+
+    private ClientResource getClient() {
+        return getClient(getRealm());
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/PasswordHashingTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/PasswordHashingTest.java
new file mode 100644
index 0000000..caaadb9
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/PasswordHashingTest.java
@@ -0,0 +1,201 @@
+/*
+ * 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.forms;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.graphene.page.Page;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.admin.client.resource.UserResource;
+import org.keycloak.common.util.Base64;
+import org.keycloak.credential.CredentialModel;
+import org.keycloak.credential.hash.Pbkdf2PasswordHashProviderFactory;
+import org.keycloak.credential.hash.Pbkdf2Sha256PasswordHashProviderFactory;
+import org.keycloak.credential.hash.Pbkdf2Sha512PasswordHashProviderFactory;
+import org.keycloak.events.Details;
+import org.keycloak.events.EventType;
+import org.keycloak.models.BrowserSecurityHeaders;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.ModelToRepresentation;
+import org.keycloak.models.utils.RepresentationToModel;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.representations.idm.ErrorRepresentation;
+import org.keycloak.representations.idm.EventRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
+import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.admin.ApiUtil;
+import org.keycloak.testsuite.client.KeycloakTestingClient;
+import org.keycloak.testsuite.pages.AppPage;
+import org.keycloak.testsuite.pages.AppPage.RequestType;
+import org.keycloak.testsuite.pages.ErrorPage;
+import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
+import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
+import org.keycloak.testsuite.util.RealmBuilder;
+import org.keycloak.testsuite.util.UserBuilder;
+import org.keycloak.util.JsonSerialization;
+
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import javax.ws.rs.BadRequestException;
+import javax.ws.rs.InternalServerErrorException;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.KeySpec;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class PasswordHashingTest extends AbstractTestRealmKeycloakTest {
+
+    @Deployment
+    public static WebArchive deploy() {
+        return RunOnServerDeployment.create(PasswordHashingTest.class, AbstractTestRealmKeycloakTest.class);
+    }
+
+    @Override
+    public void configureTestRealm(RealmRepresentation testRealm) {
+    }
+
+    @Page
+    protected LoginPage loginPage;
+
+    @Test
+    public void testSetInvalidProvider() throws Exception {
+        try {
+            setPasswordPolicy("hashAlgorithm(nosuch)");
+            fail("Expected error");
+        } catch (BadRequestException e) {
+            ErrorRepresentation error = e.getResponse().readEntity(ErrorRepresentation.class);
+            assertEquals("Password hashing provider not found", error.getErrorMessage());
+        }
+    }
+
+    @Test
+    public void testPasswordRehashedOnAlgorithmChanged() throws Exception {
+        String username = "testPasswordRehashedOnAlgorithmChanged";
+        createUser(username);
+
+        CredentialModel credential = fetchCredentials(username);
+
+        assertEquals(Pbkdf2PasswordHashProviderFactory.ID, credential.getAlgorithm());
+
+        assertEncoded(credential, "password", credential.getSalt(), "PBKDF2WithHmacSHA1", 20000);
+
+        setPasswordPolicy("hashAlgorithm(" + Pbkdf2Sha256PasswordHashProviderFactory.ID + ")");
+
+        loginPage.open();
+        loginPage.login(username, "password");
+
+        credential = fetchCredentials(username);
+
+        assertEquals(Pbkdf2Sha256PasswordHashProviderFactory.ID, credential.getAlgorithm());
+        assertEncoded(credential, "password", credential.getSalt(), "PBKDF2WithHmacSHA256", 20000);
+    }
+
+    @Test
+    public void testPasswordRehashedOnIterationsChanged() throws Exception {
+        String username = "testPasswordRehashedOnIterationsChanged";
+        createUser(username);
+
+        CredentialModel credential = fetchCredentials(username);
+
+        assertEquals(20000, credential.getHashIterations());
+
+        setPasswordPolicy("hashIterations(1)");
+
+        loginPage.open();
+        loginPage.login(username, "password");
+
+        credential = fetchCredentials(username);
+
+        assertEquals(1, credential.getHashIterations());
+        assertEncoded(credential, "password", credential.getSalt(), "PBKDF2WithHmacSHA1", 1);
+    }
+
+    @Test
+    public void testPbkdf2Sha1() throws Exception {
+        setPasswordPolicy("hashAlgorithm(" + Pbkdf2PasswordHashProviderFactory.ID + ")");
+        String username = "testPbkdf2Sha1";
+        createUser(username);
+
+        CredentialModel credential = fetchCredentials(username);
+        assertEncoded(credential, "password", credential.getSalt(), "PBKDF2WithHmacSHA1", 20000);
+    }
+
+    @Test
+    public void testPbkdf2Sha256() throws Exception {
+        setPasswordPolicy("hashAlgorithm(" + Pbkdf2Sha256PasswordHashProviderFactory.ID + ")");
+        String username = "testPbkdf2Sha256";
+        createUser(username);
+
+        CredentialModel credential = fetchCredentials(username);
+        assertEncoded(credential, "password", credential.getSalt(), "PBKDF2WithHmacSHA256", 20000);
+    }
+
+    @Test
+    public void testPbkdf2Sha512() throws Exception {
+        setPasswordPolicy("hashAlgorithm(" + Pbkdf2Sha512PasswordHashProviderFactory.ID + ")");
+        String username = "testPbkdf2Sha512";
+        createUser(username);
+
+        CredentialModel credential = fetchCredentials(username);
+        assertEncoded(credential, "password", credential.getSalt(), "PBKDF2WithHmacSHA512", 20000);
+    }
+
+
+    private void createUser(String username) {
+        ApiUtil.createUserAndResetPasswordWithAdminClient(adminClient.realm("test"), UserBuilder.create().username(username).build(), "password");
+    }
+
+    private void setPasswordPolicy(String policy) {
+        RealmRepresentation realmRep = testRealm().toRepresentation();
+        realmRep.setPasswordPolicy(policy);
+        testRealm().update(realmRep);
+    }
+
+    private CredentialModel fetchCredentials(String username) {
+        return testingClient.server("test").fetch(session -> {
+            RealmModel realm = session.getContext().getRealm();
+            UserModel user = session.users().getUserByUsername(username, realm);
+            return session.userCredentialManager().getStoredCredentialsByType(realm, user, CredentialRepresentation.PASSWORD).get(0);
+        }, CredentialModel.class);
+    }
+
+    private void assertEncoded(CredentialModel credential, String password, byte[] salt, String algorithm, int iterations) throws Exception {
+        KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, iterations, 512);
+        byte[] key = SecretKeyFactory.getInstance(algorithm).generateSecret(spec).getEncoded();
+        assertEquals(Base64.encodeBytes(key), credential.getValue());
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/TokenIntrospectionTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/TokenIntrospectionTest.java
index ee700dc..114b70e 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/TokenIntrospectionTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/TokenIntrospectionTest.java
@@ -237,6 +237,38 @@ public class TokenIntrospectionTest extends AbstractTestRealmKeycloakTest {
         assertNull(rep.getSubject());
     }
 
+    // KEYCLOAK-4829
+    @Test
+    public void testIntrospectAccessTokenOfflineAccess() throws Exception {
+        oauth.scope(OAuth2Constants.OFFLINE_ACCESS);
+        oauth.doLogin("test-user@localhost", "password");
+        String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+        AccessTokenResponse accessTokenResponse = oauth.doAccessTokenRequest(code, "password");
+
+        setTimeOffset(86400);
+
+        // "Online" session still exists, but is invalid
+        accessTokenResponse = oauth.doRefreshTokenRequest(accessTokenResponse.getRefreshToken(), "password");
+        String tokenResponse = oauth.introspectAccessTokenWithClientCredential("confidential-cli", "secret1", accessTokenResponse.getAccessToken());
+        TokenMetadataRepresentation rep = JsonSerialization.readValue(tokenResponse, TokenMetadataRepresentation.class);
+
+        assertTrue(rep.isActive());
+        assertEquals("test-user@localhost", rep.getUserName());
+        assertEquals("test-app", rep.getClientId());
+
+        // "Online" session doesn't even exists
+        testingClient.testing().removeExpired("test");
+
+        accessTokenResponse = oauth.doRefreshTokenRequest(accessTokenResponse.getRefreshToken(), "password");
+        tokenResponse = oauth.introspectAccessTokenWithClientCredential("confidential-cli", "secret1", accessTokenResponse.getAccessToken());
+        rep = JsonSerialization.readValue(tokenResponse, TokenMetadataRepresentation.class);
+
+        assertTrue(rep.isActive());
+        assertEquals("test-user@localhost", rep.getUserName());
+        assertEquals("test-app", rep.getClientId());
+    }
+
+
     @Test
     public void testIntrospectAccessTokenUserDisabled() throws Exception {
         oauth.doLogin("test-user@localhost", "password");
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/RealmBuilder.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/RealmBuilder.java
index 1a4c8be..243b648 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/RealmBuilder.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/RealmBuilder.java
@@ -18,11 +18,13 @@
 package org.keycloak.testsuite.util;
 
 import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.GroupRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.representations.idm.RolesRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
 import org.keycloak.testsuite.events.EventsListenerProviderFactory;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedList;
@@ -215,4 +217,12 @@ public class RealmBuilder {
         rep.setSsoSessionIdleTimeout(sessionIdleTimeout);
         return this;
     }
+
+    public RealmBuilder group(GroupRepresentation group) {
+        if (rep.getGroups() == null) {
+            rep.setGroups(new ArrayList<>());
+        }
+        rep.getGroups().add(group);
+        return this;
+    }
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/UserBuilder.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/UserBuilder.java
index f127d7a..27c423f 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/UserBuilder.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/UserBuilder.java
@@ -17,14 +17,15 @@
 
 package org.keycloak.testsuite.util;
 
-import org.keycloak.representations.idm.CredentialRepresentation;
-import org.keycloak.representations.idm.UserRepresentation;
-
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
 
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
@@ -161,8 +162,15 @@ public class UserBuilder {
         return this;
     }
 
+    public UserBuilder addGroups(String... group) {
+        if (rep.getGroups() == null) {
+            rep.setGroups(new ArrayList<>());
+        }
+        rep.getGroups().addAll(Arrays.asList(group));
+        return this;
+    }
+
     public UserRepresentation build() {
         return rep;
     }
-
 }
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-detail.html
index 11d01c8..88416f3 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/realm-detail.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-detail.html
@@ -34,7 +34,7 @@
             <div class="form-group">
                 <label class="col-md-2 control-label">{{:: 'endpoints' | translate}}</label>
                 <div class="col-md-6">
-                    <a class="form-control" ng-href="{{authUrl}}/realms/{{realm.id}}/.well-known/openid-configuration" target="_blank">OpenID Endpoint Configuration</a>
+                    <a class="form-control" ng-href="{{authUrl}}/realms/{{realm.realm}}/.well-known/openid-configuration" target="_blank">OpenID Endpoint Configuration</a>
                 </div>
                 <kc-tooltip>{{:: 'realm-detail.oidc-endpoints.tooltip' | translate}}</kc-tooltip>
             </div>
diff --git a/wildfly/adduser/src/main/java/org/keycloak/wildfly/adduser/AddUser.java b/wildfly/adduser/src/main/java/org/keycloak/wildfly/adduser/AddUser.java
index 79fcaaf..048b384 100644
--- a/wildfly/adduser/src/main/java/org/keycloak/wildfly/adduser/AddUser.java
+++ b/wildfly/adduser/src/main/java/org/keycloak/wildfly/adduser/AddUser.java
@@ -30,7 +30,10 @@ import org.jboss.aesh.console.command.registry.AeshCommandRegistryBuilder;
 import org.jboss.aesh.console.command.registry.CommandRegistry;
 import org.keycloak.common.util.Base64;
 import org.keycloak.credential.CredentialModel;
-import org.keycloak.credential.hash.Pbkdf2PasswordHashProvider;
+import org.keycloak.credential.hash.PasswordHashProvider;
+import org.keycloak.credential.hash.PasswordHashProviderFactory;
+import org.keycloak.credential.hash.Pbkdf2PasswordHashProviderFactory;
+import org.keycloak.models.PasswordPolicy;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
@@ -44,6 +47,8 @@ import java.lang.reflect.Method;
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
+import java.util.ServiceLoader;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -52,6 +57,7 @@ public class AddUser {
 
     private static final String COMMAND_NAME = "add-user";
     private static final int DEFAULT_HASH_ITERATIONS = 100000;
+    private static final String DEFAULT_HASH_ALGORITH = PasswordPolicy.HASH_ALGORITHM_DEFAULT;
 
     public static void main(String[] args) throws Exception {
         AddUserCommand command = new AddUserCommand();
@@ -152,14 +158,23 @@ public class AddUser {
         user.setUsername(userName);
         user.setCredentials(new LinkedList<CredentialRepresentation>());
 
-        CredentialModel credentialValueModel = new Pbkdf2PasswordHashProvider().encode(password, iterations > 0 ? iterations : DEFAULT_HASH_ITERATIONS);
+        Map<String, Object> config = new HashMap<>();
+        if (iterations > 0) {
+            config.put("hashIterations", iterations);
+        }
+
+        PasswordHashProviderFactory hashProviderFactory = getHashProviderFactory(DEFAULT_HASH_ALGORITH);
+        PasswordHashProvider hashProvider = hashProviderFactory.create(null);
+
+        CredentialModel credentialModel = new CredentialModel();
+        hashProvider.encode(password, iterations > 0 ? iterations : DEFAULT_HASH_ITERATIONS, credentialModel);
 
         CredentialRepresentation credentials = new CredentialRepresentation();
-        credentials.setType(credentialValueModel.getType());
-        credentials.setAlgorithm(credentialValueModel.getAlgorithm());
-        credentials.setHashIterations(credentialValueModel.getHashIterations());
-        credentials.setSalt(Base64.encodeBytes(credentialValueModel.getSalt()));
-        credentials.setHashedSaltedValue(credentialValueModel.getValue());
+        credentials.setType(credentialModel.getType());
+        credentials.setAlgorithm(credentialModel.getAlgorithm());
+        credentials.setHashIterations(credentialModel.getHashIterations());
+        credentials.setSalt(Base64.encodeBytes(credentialModel.getSalt()));
+        credentials.setHashedSaltedValue(credentialModel.getValue());
 
         user.getCredentials().add(credentials);
 
@@ -203,6 +218,16 @@ public class AddUser {
         System.out.println("Added '" + userName + "' to '" + addUserFile + "', restart server to load user");
     }
 
+    private static PasswordHashProviderFactory getHashProviderFactory(String providerId) {
+        ServiceLoader<PasswordHashProviderFactory> providerFactories = ServiceLoader.load(PasswordHashProviderFactory.class);
+        for (PasswordHashProviderFactory f : providerFactories) {
+            if (f.getId().equals(providerId)) {
+                return f;
+            }
+        }
+        return null;
+    }
+
     private static void checkRequired(Command command, String field) throws Exception {
         if (isEmpty(command, field)) {
             Option option = command.getClass().getDeclaredField(field).getAnnotation(Option.class);
diff --git a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan.xml b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan.xml
index f779bcf..205c1f5 100755
--- a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan.xml
+++ b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan.xml
@@ -99,7 +99,9 @@
                 <distributed-cache name="sessions" mode="SYNC" owners="1"/>
                 <distributed-cache name="offlineSessions" mode="SYNC" owners="1"/>
                 <distributed-cache name="loginFailures" mode="SYNC" owners="1"/>
-                <distributed-cache name="authorization" mode="SYNC" owners="1"/>
+                <local-cache name="authorization">
+                    <eviction max-entries="10000" strategy="LRU"/>
+                </local-cache>
                 <replicated-cache name="work" mode="SYNC" />
                 <local-cache name="keys">
                     <eviction max-entries="1000" strategy="LRU"/>
diff --git a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan2.xml b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan2.xml
index 0c96904..95bcffd 100755
--- a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan2.xml
+++ b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan2.xml
@@ -102,7 +102,9 @@
                 <distributed-cache name="sessions" mode="SYNC" owners="1"/>
                 <distributed-cache name="offlineSessions" mode="SYNC" owners="1"/>
                 <distributed-cache name="loginFailures" mode="SYNC" owners="1"/>
-                <distributed-cache name="authorization" mode="SYNC" owners="1"/>
+                <local-cache name="authorization">
+                    <eviction max-entries="10000" strategy="LRU"/>
+                </local-cache>
                 <replicated-cache name="work" mode="SYNC" />
                 <local-cache name="keys">
                     <eviction max-entries="1000" strategy="LRU"/>