Merge pull request #2068 from stianst/KEYCLOAK-2185

KEYCLOAK-2185 Add support to disable realm and user cache, and added …
This commit is contained in:
Bill Burke 2016-01-20 10:54:00 -05:00
commit 94fa1b2c89
19 changed files with 142 additions and 175 deletions

View file

@ -34,7 +34,9 @@ public class RealmRepresentation {
protected Boolean resetPasswordAllowed;
protected Boolean editUsernameAllowed;
@Deprecated
protected Boolean userCacheEnabled;
@Deprecated
protected Boolean realmCacheEnabled;
//--- brute force settings
@ -367,22 +369,6 @@ public class RealmRepresentation {
this.rememberMe = rememberMe;
}
public Boolean isRealmCacheEnabled() {
return realmCacheEnabled;
}
public void setRealmCacheEnabled(Boolean realmCacheEnabled) {
this.realmCacheEnabled = realmCacheEnabled;
}
public Boolean isUserCacheEnabled() {
return userCacheEnabled;
}
public void setUserCacheEnabled(Boolean userCacheEnabled) {
this.userCacheEnabled = userCacheEnabled;
}
public Boolean isVerifyEmail() {
return verifyEmail;
}

View file

@ -22,6 +22,18 @@
"provider": "jpa"
},
"userCache": {
"infinispan" : {
"enabled": true
}
},
"realmCache": {
"infinispan" : {
"enabled": true
}
},
"userSessionPersister": {
"provider": "jpa"
},

View file

@ -215,6 +215,21 @@ public class MyEventListenerProviderFactory implements EventListenerProviderFact
"max": 100
}
}
}]]></programlisting>
</para>
</section>
<section>
<title>Disabling a provider</title>
<para>
You can disable a provider by setting the enabled field for the provider to false in <literal>keycloak-server.json</literal>.
For example to disable the Infinispan user cache provider add:
<programlisting><![CDATA[{
"userCache": {
"infinispan" : {
"enabled": false
}
}
}]]></programlisting>
</para>
</section>

View file

@ -66,10 +66,10 @@ i18n-enabled=Internationalization Enabled
supported-locales=Supported Locales
supported-locales.placeholder=Type a locale and enter
default-locale=Default Locale
realm-cache-enabled=Realm Cache Enabled
realm-cache-enabled.tooltip=Enable/disable cache for realms, clients and roles.
user-cache-enabled=User Cache Enabled
user-cache-enabled.tooltip=Enable/disable cache for users and user role mappings.
realm-cache-clear=Realm Cache
realm-cache-clear.tooltip=Clears all entries from the realm cache (this will clear entries for all realms)
user-cache-clear=User Cache
user-cache-clear.tooltip=Clears all entries from the user cache (this will clear entries for all realms)
revoke-refresh-token=Revoke Refresh Token
revoke-refresh-token.tooltip=If enabled refresh tokens can only be used once. Otherwise refresh tokens are not revoked when used and can be used multiple times.
sso-session-idle=SSO Session Idle

View file

@ -349,8 +349,20 @@ module.controller('RealmThemeCtrl', function($scope, Current, Realm, realm, serv
}, true);
});
module.controller('RealmCacheCtrl', function($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications) {
genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications, "/realms/" + realm.realm + "/cache-settings");
module.controller('RealmCacheCtrl', function($scope, realm, RealmClearUserCache, RealmClearRealmCache, Notifications) {
$scope.clearUserCache = function() {
RealmClearUserCache.save({ realm: realm.realm}, function () {
Notifications.success("User cache cleared");
});
}
$scope.clearRealmCache = function() {
RealmClearRealmCache.save({ realm: realm.realm}, function () {
Notifications.success("Realm cache cleared");
});
}
});
module.controller('RealmPasswordPolicyCtrl', function($scope, Realm, realm, $http, $location, Dialog, Notifications, PasswordPolicy) {

View file

@ -595,6 +595,18 @@ module.factory('RealmPushRevocation', function($resource) {
});
});
module.factory('RealmClearUserCache', function($resource) {
return $resource(authUrl + '/admin/realms/:realm/clear-user-cache', {
realm : '@realm'
});
});
module.factory('RealmClearRealmCache', function($resource) {
return $resource(authUrl + '/admin/realms/:realm/clear-realm-cache', {
realm : '@realm'
});
});
module.factory('RealmSessionStats', function($resource) {
return $resource(authUrl + '/admin/realms/:realm/session-stats', {
realm : '@realm'

View file

@ -3,25 +3,19 @@
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
<div class="form-group">
<label class="col-md-2 control-label" for="realmCacheEnabled">{{:: 'realm-cache-enabled' | translate}}</label>
<label class="col-md-2 control-label">{{:: 'realm-cache-clear' | translate}}</label>
<div class="col-md-6">
<input ng-model="realm.realmCacheEnabled" name="realmCacheEnabled" id="realmCacheEnabled" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
<button type="submit" data-ng-click="clearRealmCache()" class="btn btn-default">{{:: 'clear' | translate}}</button>
</div>
<kc-tooltip>{{:: 'realm-cache-enabled.tooltip' | translate}}</kc-tooltip>
<kc-tooltip>{{:: 'realm-cache-clear.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="userCacheEnabled">{{:: 'user-cache-enabled' | translate}}</label>
<label class="col-md-2 control-label">{{:: 'user-cache-clear' | translate}}</label>
<div class="col-md-6">
<input ng-model="realm.userCacheEnabled" name="userCacheEnabled" id="userCacheEnabled" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
</div>
<kc-tooltip>{{:: 'user-cache-enabled.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group" data-ng-show="access.manageRealm">
<div class="col-md-10 col-md-offset-2">
<button kc-save data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
<button kc-reset data-ng-disabled="!changed">{{:: 'cancel' | translate}}</button>
<button type="submit" data-ng-click="clearUserCache()" class="btn btn-default">{{:: 'clear' | translate}}</button>
</div>
<kc-tooltip>{{:: 'user-cache-clear.tooltip' | translate}}</kc-tooltip>
</div>
</form>
</div>

View file

@ -38,22 +38,16 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
session.getTransaction().enlistAfterCompletion(getTransaction());
}
@Override
public void clear() {
cache.clear();
}
@Override
public MigrationModel getMigrationModel() {
return getDelegate().getMigrationModel();
}
@Override
public boolean isEnabled() {
return cache.isEnabled();
}
@Override
public void setEnabled(boolean enabled) {
cache.setEnabled(enabled);
}
@Override
public RealmProvider getDelegate() {
if (!transactionActive) throw new IllegalStateException("Cannot access delegate without a transaction");
@ -149,7 +143,6 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
@Override
public RealmModel createRealm(String name) {
RealmModel realm = getDelegate().createRealm(name);
if (!cache.isEnabled()) return realm;
registerRealmInvalidation(realm.getId());
return realm;
}
@ -157,14 +150,12 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
@Override
public RealmModel createRealm(String id, String name) {
RealmModel realm = getDelegate().createRealm(id, name);
if (!cache.isEnabled()) return realm;
registerRealmInvalidation(realm.getId());
return realm;
}
@Override
public RealmModel getRealm(String id) {
if (!cache.isEnabled()) return getDelegate().getRealm(id);
CachedRealm cached = cache.getCachedRealm(id);
if (cached == null) {
RealmModel model = getDelegate().getRealm(id);
@ -184,7 +175,6 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
@Override
public RealmModel getRealmByName(String name) {
if (!cache.isEnabled()) return getDelegate().getRealmByName(name);
CachedRealm cached = cache.getCachedRealmByName(name);
if (cached == null) {
RealmModel model = getDelegate().getRealmByName(name);
@ -218,7 +208,6 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
@Override
public boolean removeRealm(String id) {
if (!cache.isEnabled()) return getDelegate().removeRealm(id);
cache.invalidateCachedRealmById(id);
RealmModel realm = getDelegate().getRealm(id);
@ -247,7 +236,6 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
@Override
public RoleModel getRoleById(String id, RealmModel realm) {
if (!cache.isEnabled()) return getDelegate().getRoleById(id, realm);
CachedRole cached = cache.getRole(id);
if (cached != null && !cached.getRealm().equals(realm.getId())) {
cached = null;
@ -276,7 +264,6 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
@Override
public GroupModel getGroupById(String id, RealmModel realm) {
if (!cache.isEnabled()) return getDelegate().getGroupById(id, realm);
CachedGroup cached = cache.getGroup(id);
if (cached != null && !cached.getRealm().equals(realm.getId())) {
cached = null;
@ -301,7 +288,6 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
@Override
public ClientModel getClientById(String id, RealmModel realm) {
if (!cache.isEnabled()) return getDelegate().getClientById(id, realm);
CachedClient cached = cache.getApplication(id);
if (cached != null && !cached.getRealm().equals(realm.getId())) {
cached = null;
@ -324,7 +310,6 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
}
@Override
public ClientTemplateModel getClientTemplateById(String id, RealmModel realm) {
if (!cache.isEnabled()) return getDelegate().getClientTemplateById(id, realm);
CachedClientTemplate cached = cache.getClientTemplate(id);
if (cached != null && !cached.getRealm().equals(realm.getId())) {
cached = null;

View file

@ -22,8 +22,6 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
protected Set<String> realmInvalidations = new HashSet<>();
protected Map<String, UserModel> managedUsers = new HashMap<>();
protected boolean clearAll;
public DefaultCacheUserProvider(UserCache cache, KeycloakSession session) {
this.cache = cache;
this.session = session;
@ -32,13 +30,8 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
}
@Override
public boolean isEnabled() {
return cache.isEnabled();
}
@Override
public void setEnabled(boolean enabled) {
cache.setEnabled(enabled);
public void clear() {
cache.clear();
}
@Override
@ -73,9 +66,6 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
@Override
public void commit() {
if (delegate == null) return;
if (clearAll) {
cache.clear();
}
runInvalidations();
transactionActive = false;
}
@ -110,7 +100,6 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
@Override
public UserModel getUserById(String id, RealmModel realm) {
if (!cache.isEnabled()) return getDelegate().getUserById(id, realm);
if (isRegisteredForInvalidation(realm, id)) {
return getDelegate().getUserById(id, realm);
}
@ -136,7 +125,6 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
username = username.toLowerCase();
if (!cache.isEnabled()) return getDelegate().getUserByUsername(username, realm);
if (realmInvalidations.contains(realm.getId())) {
return getDelegate().getUserByUsername(username, realm);
}
@ -164,7 +152,6 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
email = email.toLowerCase();
if (!cache.isEnabled()) return getDelegate().getUserByEmail(email, realm);
if (realmInvalidations.contains(realm.getId())) {
return getDelegate().getUserByEmail(email, realm);
}
@ -276,7 +263,6 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
@Override
public boolean removeUser(RealmModel realm, UserModel user) {
if (!cache.isEnabled()) return getDelegate().removeUser(realm, user);
registerUserInvalidation(realm, user.getId());
return getDelegate().removeUser(realm, user);
}

View file

@ -20,34 +20,23 @@ public class InfinispanRealmCache implements RealmCache {
protected final Cache<String, Object> cache;
protected final ConcurrentHashMap<String, String> realmLookup;
protected volatile boolean enabled = true;
public InfinispanRealmCache(Cache<String, Object> cache, ConcurrentHashMap<String, String> realmLookup) {
this.cache = cache;
this.realmLookup = realmLookup;
}
public Cache<String, Object> getCache() {
return cache;
}
@Override
public void clear() {
cache.clear();
}
@Override
public boolean isEnabled() {
return enabled;
}
@Override
public void setEnabled(boolean enabled) {
if (this.enabled && !enabled) {
clear();
}
this.enabled = enabled;
}
@Override
public CachedRealm getCachedRealm(String id) {
if (!enabled) return null;
return get(id, CachedRealm.class);
}
@ -66,7 +55,6 @@ public class InfinispanRealmCache implements RealmCache {
@Override
public void addCachedRealm(CachedRealm realm) {
if (!enabled) return;
logger.tracev("Adding realm {0}", realm.getId());
cache.putForExternalRead(realm.getId(), realm);
realmLookup.put(realm.getName(), realm.getId());
@ -74,14 +62,12 @@ public class InfinispanRealmCache implements RealmCache {
@Override
public CachedRealm getCachedRealmByName(String name) {
if (!enabled) return null;
String id = realmLookup.get(name);
return id != null ? getCachedRealm(id) : null;
}
@Override
public CachedClient getApplication(String id) {
if (!enabled) return null;
return get(id, CachedClient.class);
}
@ -93,7 +79,6 @@ public class InfinispanRealmCache implements RealmCache {
@Override
public void addCachedClient(CachedClient app) {
if (!enabled) return;
logger.tracev("Adding application {0}", app.getId());
cache.putForExternalRead(app.getId(), app);
}
@ -112,7 +97,6 @@ public class InfinispanRealmCache implements RealmCache {
@Override
public CachedGroup getGroup(String id) {
if (!enabled) return null;
return get(id, CachedGroup.class);
}
@ -124,7 +108,6 @@ public class InfinispanRealmCache implements RealmCache {
@Override
public void addCachedGroup(CachedGroup role) {
if (!enabled) return;
logger.tracev("Adding group {0}", role.getId());
cache.putForExternalRead(role.getId(), role);
}
@ -144,7 +127,6 @@ public class InfinispanRealmCache implements RealmCache {
@Override
public CachedRole getRole(String id) {
if (!enabled) return null;
return get(id, CachedRole.class);
}
@ -168,7 +150,6 @@ public class InfinispanRealmCache implements RealmCache {
@Override
public void addCachedRole(CachedRole role) {
if (!enabled) return;
logger.tracev("Adding role {0}", role.getId());
cache.putForExternalRead(role.getId(), role);
}
@ -186,7 +167,6 @@ public class InfinispanRealmCache implements RealmCache {
@Override
public CachedClientTemplate getClientTemplate(String id) {
if (!enabled) return null;
return get(id, CachedClientTemplate.class);
}
@ -198,7 +178,6 @@ public class InfinispanRealmCache implements RealmCache {
@Override
public void addCachedClientTemplate(CachedClientTemplate app) {
if (!enabled) return;
logger.tracev("Adding client template {0}", app.getId());
cache.putForExternalRead(app.getId(), app);
}

View file

@ -28,19 +28,6 @@ public class InfinispanUserCache implements UserCache {
this.emailLookup = emailLookup;
}
@Override
public boolean isEnabled() {
return enabled;
}
@Override
public void setEnabled(boolean enabled) {
if (this.enabled && !enabled) {
clear();
}
this.enabled = enabled;
}
@Override
public CachedUser getCachedUser(String realmId, String id) {
if (realmId == null || id == null) return null;
@ -48,12 +35,6 @@ public class InfinispanUserCache implements UserCache {
return user != null && realmId.equals(user.getRealm()) ? user : null;
}
@Override
public void invalidateCachedUser(String realmId, CachedUser user) {
logger.tracev("Invalidating user {0}", user.getId());
cache.remove(user.getId());
}
@Override
public void invalidateCachedUserById(String realmId, String id) {
logger.tracev("Invalidating user {0}", id);

View file

@ -7,11 +7,9 @@ import org.keycloak.models.RealmProvider;
* @version $Revision: 1 $
*/
public interface CacheRealmProvider extends RealmProvider {
void clear();
RealmProvider getDelegate();
boolean isEnabled();
void setEnabled(boolean enabled);
void registerRealmInvalidation(String id);
void registerApplicationInvalidation(String id);

View file

@ -8,8 +8,7 @@ import org.keycloak.models.UserProvider;
* @version $Revision: 1 $
*/
public interface CacheUserProvider extends UserProvider {
void clear();
UserProvider getDelegate();
boolean isEnabled();
void setEnabled(boolean enabled);
void registerUserInvalidation(RealmModel realm, String id);
}

View file

@ -55,10 +55,6 @@ public interface RealmCache {
void invalidateGroupById(String id);
boolean isEnabled();
void setEnabled(boolean enabled);
CachedClientTemplate getClientTemplate(String id);
void invalidateClientTemplate(CachedClientTemplate app);

View file

@ -7,12 +7,11 @@ import org.keycloak.models.cache.entities.CachedUser;
* @version $Revision: 1 $
*/
public interface UserCache {
void clear();
CachedUser getCachedUser(String realmId, String id);
void invalidateCachedUser(String realmId, CachedUser user);
void addCachedUser(String realmId, CachedUser user);
CachedUser getCachedUserByUsername(String realmId, String name);
@ -23,7 +22,4 @@ public interface UserCache {
void invalidateRealmUsers(String realmId);
boolean isEnabled();
void setEnabled(boolean enabled);
}

View file

@ -1,25 +1,12 @@
package org.keycloak.services;
import org.jboss.logging.Logger;
import org.keycloak.models.KeycloakContext;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.KeycloakTransactionManager;
import org.keycloak.models.RealmProvider;
import org.keycloak.models.UserFederationManager;
import org.keycloak.models.UserProvider;
import org.keycloak.models.UserSessionProvider;
import org.keycloak.models.*;
import org.keycloak.models.cache.CacheRealmProvider;
import org.keycloak.models.cache.CacheUserProvider;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -35,8 +22,6 @@ public class DefaultKeycloakSession implements KeycloakSession {
private UserSessionProvider sessionProvider;
private UserFederationManager federationManager;
private KeycloakContext context;
private static final Logger logger = Logger.getLogger(DefaultKeycloakSession.class);
public DefaultKeycloakSession(DefaultKeycloakSessionFactory factory) {
this.factory = factory;
@ -51,16 +36,18 @@ public class DefaultKeycloakSession implements KeycloakSession {
}
private RealmProvider getRealmProvider() {
if (factory.getDefaultProvider(CacheRealmProvider.class) != null) {
return getProvider(CacheRealmProvider.class);
CacheRealmProvider cache = getProvider(CacheRealmProvider.class);
if (cache != null) {
return cache;
} else {
return getProvider(RealmProvider.class);
}
}
private UserProvider getUserProvider() {
if (factory.getDefaultProvider(CacheUserProvider.class) != null) {
return getProvider(CacheUserProvider.class);
CacheUserProvider cache = getProvider(CacheUserProvider.class);
if (cache != null) {
return cache;
} else {
return getProvider(UserProvider.class);
}
@ -87,7 +74,6 @@ public class DefaultKeycloakSession implements KeycloakSession {
userModel = getUserProvider();
}
return userModel;
}
public <T extends Provider> T getProvider(Class<T> clazz) {

View file

@ -79,20 +79,24 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory {
} else {
for (ProviderFactory factory : pm.load(spi)) {
Config.Scope scope = Config.scope(spi.getName(), factory.getId());
factory.init(scope);
if (scope.getBoolean("enabled", true)) {
factory.init(scope);
if (spi.isInternal() && !isInternal(factory)) {
log.warnv("{0} ({1}) is implementing the internal SPI {2}. This SPI is internal and may change without notice", factory.getId(), factory.getClass().getName(), spi.getName());
if (spi.isInternal() && !isInternal(factory)) {
log.warnv("{0} ({1}) is implementing the internal SPI {2}. This SPI is internal and may change without notice", factory.getId(), factory.getClass().getName(), spi.getName());
}
factories.put(factory.getId(), factory);
} else {
log.debugv("SPI {0} provider {1} disabled", spi.getName(), factory.getId());
}
factories.put(factory.getId(), factory);
}
if (factories.size() == 1) {
provider = factories.values().iterator().next().getId();
this.provider.put(spi.getProviderClass(), provider);
log.debugv("Loaded SPI {0} (provider = {1})", spi.getName(), provider);
log.debugv("Loaded SPI {0} (provider = {1})", spi.getName(), provider);
} else {
log.debugv("Loaded SPI {0} (providers = {1})", spi.getName(), factories.keySet());
}

View file

@ -191,16 +191,7 @@ public class RealmAdminResource {
@Produces(MediaType.APPLICATION_JSON)
public RealmRepresentation getRealm() {
if (auth.hasView()) {
RealmRepresentation rep = ModelToRepresentation.toRepresentation(realm, false);
if (session.realms() instanceof CacheRealmProvider) {
CacheRealmProvider cacheRealmProvider = (CacheRealmProvider)session.realms();
rep.setRealmCacheEnabled(cacheRealmProvider.isEnabled());
}
if (session.userStorage() instanceof CacheUserProvider) {
CacheUserProvider cache = (CacheUserProvider)session.userStorage();
rep.setUserCacheEnabled(cache.isEnabled());
}
return rep;
return ModelToRepresentation.toRepresentation(realm, false);
} else {
auth.requireAny();
@ -227,14 +218,6 @@ public class RealmAdminResource {
logger.debug("updating realm: " + realm.getName());
try {
RepresentationToModel.updateRealm(rep, realm);
if (rep.isRealmCacheEnabled() != null && session.realms() instanceof CacheRealmProvider) {
CacheRealmProvider cacheRealmProvider = (CacheRealmProvider)session.realms();
cacheRealmProvider.setEnabled(rep.isRealmCacheEnabled());
}
if (rep.isUserCacheEnabled() != null && session.userStorage() instanceof CacheUserProvider) {
CacheUserProvider cache = (CacheUserProvider)session.userStorage();
cache.setEnabled(rep.isUserCacheEnabled());
}
// Refresh periodic sync tasks for configured federationProviders
List<UserFederationProviderModel> federationProviders = realm.getUserFederationProviders();
@ -724,4 +707,35 @@ public class RealmAdminResource {
PartialImportManager partialImport = new PartialImportManager(rep, session, realm, adminEvent);
return partialImport.saveResources();
}
/**
* Clear realm cache
*
*/
@Path("clear-realm-cache")
@POST
public void clearRealmCache() {
auth.requireManage();
adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
CacheRealmProvider cache = session.getProvider(CacheRealmProvider.class);
if (cache != null) {
cache.clear();
}
}
/**
* Clear user cache
*
*/
@Path("clear-user-cache")
@POST
public void clearUserCache() {
auth.requireManage();
adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
CacheUserProvider cache = session.getProvider(CacheUserProvider.class);
if (cache != null) {
cache.clear();
}
}
}

View file

@ -26,6 +26,18 @@
"provider": "${keycloak.userSessionPersister.provider:jpa}"
},
"userCache": {
"infinispan" : {
"enabled": true
}
},
"realmCache": {
"infinispan" : {
"enabled": true
}
},
"timer": {
"provider": "basic"
},