Split PublicKeyStorageProvider (#12897)
Split PublicKeyStorageProvider - Extract clearCache() method to separate interface and move it to the legacy module - Make PublicKeyProvider factories environment dependent - Simple map storage for public keys that just delegates Resolves #12763 Co-authored-by: Martin Kanis <mkanis@redhat.com>
This commit is contained in:
parent
63614b1240
commit
098d4dda0e
29 changed files with 668 additions and 56 deletions
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright 2022 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.keys.infinispan;
|
||||
|
||||
import org.infinispan.Cache;
|
||||
import org.keycloak.cluster.ClusterProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.cache.CachePublicKeyProvider;
|
||||
import org.keycloak.models.cache.infinispan.ClearCacheEvent;
|
||||
|
||||
public class InfinispanCachePublicKeyProvider implements CachePublicKeyProvider {
|
||||
|
||||
private final KeycloakSession session;
|
||||
|
||||
private final Cache<String, PublicKeysEntry> keys;
|
||||
|
||||
public InfinispanCachePublicKeyProvider(KeycloakSession session, Cache<String, PublicKeysEntry> keys) {
|
||||
this.session = session;
|
||||
this.keys = keys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearCache() {
|
||||
keys.clear();
|
||||
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
|
||||
cluster.notify(InfinispanCachePublicKeyProviderFactory.KEYS_CLEAR_CACHE_EVENTS, new ClearCacheEvent(), true, ClusterProvider.DCNotify.ALL_DCS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Copyright 2022 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.keys.infinispan;
|
||||
|
||||
import org.infinispan.Cache;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.cluster.ClusterEvent;
|
||||
import org.keycloak.cluster.ClusterProvider;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.cache.CachePublicKeyProvider;
|
||||
import org.keycloak.models.cache.CachePublicKeyProviderFactory;
|
||||
import org.keycloak.provider.EnvironmentDependentProviderFactory;
|
||||
|
||||
public class InfinispanCachePublicKeyProviderFactory implements CachePublicKeyProviderFactory, EnvironmentDependentProviderFactory {
|
||||
|
||||
public static final String PROVIDER_ID = "infinispan";
|
||||
|
||||
public static final String PUBLIC_KEY_STORAGE_INVALIDATION_EVENT = "PUBLIC_KEY_STORAGE_INVALIDATION_EVENT";
|
||||
|
||||
public static final String KEYS_CLEAR_CACHE_EVENTS = "KEYS_CLEAR_CACHE_EVENTS";
|
||||
|
||||
private volatile Cache<String, PublicKeysEntry> keysCache;
|
||||
|
||||
@Override
|
||||
public CachePublicKeyProvider create(KeycloakSession session) {
|
||||
lazyInit(session);
|
||||
return new InfinispanCachePublicKeyProvider(session, keysCache);
|
||||
}
|
||||
|
||||
private void lazyInit(KeycloakSession session) {
|
||||
if (keysCache == null) {
|
||||
synchronized (this) {
|
||||
if (keysCache == null) {
|
||||
this.keysCache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.KEYS_CACHE_NAME);
|
||||
|
||||
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
|
||||
cluster.registerListener(PUBLIC_KEY_STORAGE_INVALIDATION_EVENT, (ClusterEvent event) -> {
|
||||
|
||||
PublicKeyStorageInvalidationEvent invalidationEvent = (PublicKeyStorageInvalidationEvent) event;
|
||||
keysCache.remove(invalidationEvent.getCacheKey());
|
||||
|
||||
});
|
||||
|
||||
cluster.registerListener(KEYS_CLEAR_CACHE_EVENTS, (ClusterEvent event) -> {
|
||||
|
||||
keysCache.clear();
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
return !Profile.isFeatureEnabled(Profile.Feature.MAP_STORAGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
}
|
|
@ -34,7 +34,6 @@ import org.keycloak.keys.PublicKeyLoader;
|
|||
import org.keycloak.keys.PublicKeyStorageProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakTransaction;
|
||||
import org.keycloak.models.cache.infinispan.ClearCacheEvent;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -63,15 +62,6 @@ public class InfinispanPublicKeyStorageProvider implements PublicKeyStorageProvi
|
|||
this.minTimeBetweenRequests = minTimeBetweenRequests;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void clearCache() {
|
||||
keys.clear();
|
||||
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
|
||||
cluster.notify(InfinispanPublicKeyStorageProviderFactory.KEYS_CLEAR_CACHE_EVENTS, new ClearCacheEvent(), true, ClusterProvider.DCNotify.ALL_DCS);
|
||||
}
|
||||
|
||||
|
||||
void addInvalidation(String cacheKey) {
|
||||
if (!transactionEnlisted) {
|
||||
session.getTransactionManager().enlistAfterCompletion(getAfterTransaction());
|
||||
|
@ -121,7 +111,7 @@ public class InfinispanPublicKeyStorageProvider implements PublicKeyStorageProvi
|
|||
|
||||
for (String cacheKey : invalidations) {
|
||||
keys.remove(cacheKey);
|
||||
cluster.notify(InfinispanPublicKeyStorageProviderFactory.PUBLIC_KEY_STORAGE_INVALIDATION_EVENT, PublicKeyStorageInvalidationEvent.create(cacheKey), true, ClusterProvider.DCNotify.ALL_DCS);
|
||||
cluster.notify(InfinispanCachePublicKeyProviderFactory.PUBLIC_KEY_STORAGE_INVALIDATION_EVENT, PublicKeyStorageInvalidationEvent.create(cacheKey), true, ClusterProvider.DCNotify.ALL_DCS);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,8 +25,7 @@ import java.util.concurrent.FutureTask;
|
|||
import org.infinispan.Cache;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.cluster.ClusterEvent;
|
||||
import org.keycloak.cluster.ClusterProvider;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||
import org.keycloak.jose.jwk.JWK;
|
||||
import org.keycloak.keys.PublicKeyStorageProvider;
|
||||
|
@ -36,22 +35,19 @@ import org.keycloak.models.ClientModel;
|
|||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.provider.EnvironmentDependentProviderFactory;
|
||||
import org.keycloak.provider.ProviderEvent;
|
||||
import org.keycloak.provider.ProviderEventListener;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class InfinispanPublicKeyStorageProviderFactory implements PublicKeyStorageProviderFactory {
|
||||
public class InfinispanPublicKeyStorageProviderFactory implements PublicKeyStorageProviderFactory, EnvironmentDependentProviderFactory {
|
||||
|
||||
private static final Logger log = Logger.getLogger(InfinispanPublicKeyStorageProviderFactory.class);
|
||||
|
||||
public static final String PROVIDER_ID = "infinispan";
|
||||
|
||||
public static final String KEYS_CLEAR_CACHE_EVENTS = "KEYS_CLEAR_CACHE_EVENTS";
|
||||
|
||||
public static final String PUBLIC_KEY_STORAGE_INVALIDATION_EVENT = "PUBLIC_KEY_STORAGE_INVALIDATION_EVENT";
|
||||
|
||||
private volatile Cache<String, PublicKeysEntry> keysCache;
|
||||
|
||||
private final Map<String, FutureTask<PublicKeysEntry>> tasksInProgress = new ConcurrentHashMap<>();
|
||||
|
@ -69,20 +65,6 @@ public class InfinispanPublicKeyStorageProviderFactory implements PublicKeyStora
|
|||
synchronized (this) {
|
||||
if (keysCache == null) {
|
||||
this.keysCache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.KEYS_CACHE_NAME);
|
||||
|
||||
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
|
||||
cluster.registerListener(PUBLIC_KEY_STORAGE_INVALIDATION_EVENT, (ClusterEvent event) -> {
|
||||
|
||||
PublicKeyStorageInvalidationEvent invalidationEvent = (PublicKeyStorageInvalidationEvent) event;
|
||||
keysCache.remove(invalidationEvent.getCacheKey());
|
||||
|
||||
});
|
||||
|
||||
cluster.registerListener(KEYS_CLEAR_CACHE_EVENTS, (ClusterEvent event) -> {
|
||||
|
||||
keysCache.clear();
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -147,6 +129,11 @@ public class InfinispanPublicKeyStorageProviderFactory implements PublicKeyStora
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
return !Profile.isFeatureEnabled(Profile.Feature.MAP_STORAGE);
|
||||
}
|
||||
|
||||
private static class SessionAndKeyHolder {
|
||||
private final KeycloakSession session;
|
||||
private final ArrayList<String> cacheKeys;
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
org.keycloak.keys.infinispan.InfinispanCachePublicKeyProviderFactory
|
29
model/legacy-private/src/main/java/org/keycloak/models/cache/CachePublicKeyProvider.java
vendored
Normal file
29
model/legacy-private/src/main/java/org/keycloak/models/cache/CachePublicKeyProvider.java
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright 2022 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.cache;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
public interface CachePublicKeyProvider extends Provider {
|
||||
|
||||
/**
|
||||
* Clears all the cached public keys, so they need to be loaded again
|
||||
*/
|
||||
void clearCache();
|
||||
|
||||
}
|
23
model/legacy-private/src/main/java/org/keycloak/models/cache/CachePublicKeyProviderFactory.java
vendored
Normal file
23
model/legacy-private/src/main/java/org/keycloak/models/cache/CachePublicKeyProviderFactory.java
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright 2022 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.cache;
|
||||
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
public interface CachePublicKeyProviderFactory extends ProviderFactory<CachePublicKeyProvider> {
|
||||
}
|
45
model/legacy-private/src/main/java/org/keycloak/models/cache/CachePublicKeyProviderSpi.java
vendored
Normal file
45
model/legacy-private/src/main/java/org/keycloak/models/cache/CachePublicKeyProviderSpi.java
vendored
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright 2022 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.cache;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.provider.Spi;
|
||||
|
||||
public class CachePublicKeyProviderSpi implements Spi {
|
||||
|
||||
@Override
|
||||
public boolean isInternal() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "publicKeyCache";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Provider> getProviderClass() {
|
||||
return CachePublicKeyProvider.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||
return CachePublicKeyProviderFactory.class;
|
||||
}
|
||||
}
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
org.keycloak.models.cache.CacheUserProviderSpi
|
||||
org.keycloak.models.cache.CacheRealmProviderSpi
|
||||
org.keycloak.models.cache.CachePublicKeyProviderSpi
|
||||
org.keycloak.storage.client.ClientStorageProviderSpi
|
||||
org.keycloak.storage.group.GroupStorageProviderSpi
|
||||
org.keycloak.storage.clientscope.ClientScopeStorageProviderSpi
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright 2022 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.services.resources.admin;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.services.resources.admin.ext.AdminRealmResourceProvider;
|
||||
import org.keycloak.services.resources.admin.ext.AdminRealmResourceProviderFactory;
|
||||
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
|
||||
|
||||
public class ClearKeysCacheRealmAdminProvider implements AdminRealmResourceProviderFactory, AdminRealmResourceProvider {
|
||||
|
||||
@Override
|
||||
public AdminRealmResourceProvider create(KeycloakSession session) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "clear-keys-cache";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getResource(KeycloakSession session, RealmModel realm, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
|
||||
return new ClearKeysCacheResource(session, realm, auth, adminEvent);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright 2022 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.services.resources.admin;
|
||||
|
||||
import org.keycloak.events.admin.OperationType;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.cache.CachePublicKeyProvider;
|
||||
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
|
||||
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.core.Context;
|
||||
|
||||
public class ClearKeysCacheResource {
|
||||
|
||||
protected AdminPermissionEvaluator auth;
|
||||
protected RealmModel realm;
|
||||
private AdminEventBuilder adminEvent;
|
||||
|
||||
@Context
|
||||
protected KeycloakSession session;
|
||||
|
||||
public ClearKeysCacheResource(KeycloakSession session, RealmModel realm, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
|
||||
this.auth = auth;
|
||||
this.realm = realm;
|
||||
this.adminEvent = adminEvent;
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear cache of external public keys (Public keys of clients or Identity providers)
|
||||
*
|
||||
*/
|
||||
@POST
|
||||
public void clearKeysCache() {
|
||||
auth.realm().requireManageRealm();
|
||||
|
||||
CachePublicKeyProvider cache = session.getProvider(CachePublicKeyProvider.class);
|
||||
if (cache != null) {
|
||||
cache.clearCache();
|
||||
}
|
||||
|
||||
adminEvent.operation(OperationType.ACTION).resourcePath(session.getContext().getUri()).success();
|
||||
}
|
||||
}
|
|
@ -18,3 +18,4 @@
|
|||
org.keycloak.services.resources.admin.UserStorageProviderRealmAdminProvider
|
||||
org.keycloak.services.resources.admin.ClearUserCacheRealmAdminProvider
|
||||
org.keycloak.services.resources.admin.ClearRealmCacheRealmAdminProvider
|
||||
org.keycloak.services.resources.admin.ClearKeysCacheRealmAdminProvider
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* Copyright 2022 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.map.keys;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.crypto.KeyWrapper;
|
||||
import org.keycloak.keys.PublicKeyLoader;
|
||||
import org.keycloak.keys.PublicKeyStorageProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.FutureTask;
|
||||
|
||||
public class MapPublicKeyStorageProvider implements PublicKeyStorageProvider {
|
||||
|
||||
private static final Logger log = Logger.getLogger(MapPublicKeyStorageProvider.class);
|
||||
|
||||
private final KeycloakSession session;
|
||||
|
||||
private final Map<String, FutureTask<Map<String, KeyWrapper>>> tasksInProgress;
|
||||
|
||||
public MapPublicKeyStorageProvider(KeycloakSession session, Map<String, FutureTask<Map<String, KeyWrapper>>> tasksInProgress) {
|
||||
this.session = session;
|
||||
this.tasksInProgress = tasksInProgress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyWrapper getPublicKey(String modelKey, String kid, PublicKeyLoader loader) {
|
||||
return getPublicKey(modelKey, kid, null, loader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyWrapper getFirstPublicKey(String modelKey, String algorithm, PublicKeyLoader loader) {
|
||||
return getPublicKey(modelKey, null, algorithm, loader);
|
||||
}
|
||||
|
||||
private KeyWrapper getPublicKey(String modelKey, String kid, String algorithm, PublicKeyLoader loader) {
|
||||
WrapperCallable wrapperCallable = new WrapperCallable(modelKey, loader);
|
||||
FutureTask<Map<String, KeyWrapper>> task = new FutureTask<>(wrapperCallable);
|
||||
FutureTask<Map<String, KeyWrapper>> existing = tasksInProgress.putIfAbsent(modelKey, task);
|
||||
Map<String, KeyWrapper> currentKeys;
|
||||
|
||||
if (existing == null) {
|
||||
task.run();
|
||||
} else {
|
||||
task = existing;
|
||||
}
|
||||
|
||||
try {
|
||||
currentKeys = task.get();
|
||||
|
||||
// Computation finished. Let's see if key is available
|
||||
KeyWrapper publicKey = algorithm != null ? getPublicKeyByAlg(currentKeys, algorithm) : getPublicKey(currentKeys, kid);
|
||||
if (publicKey != null) {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
} catch (ExecutionException ee) {
|
||||
throw new RuntimeException("Error when loading public keys: " + ee.getMessage(), ee);
|
||||
} catch (InterruptedException ie) {
|
||||
throw new RuntimeException("Error. Interrupted when loading public keys", ie);
|
||||
} finally {
|
||||
// Our thread inserted the task. Let's clean
|
||||
if (existing == null) {
|
||||
tasksInProgress.remove(modelKey);
|
||||
}
|
||||
}
|
||||
|
||||
Set<String> availableKids = currentKeys == null ? Collections.emptySet() : currentKeys.keySet();
|
||||
log.warnf("PublicKey wasn't found in the storage. Requested kid: '%s' . Available kids: '%s'", kid, availableKids);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private KeyWrapper getPublicKey(Map<String, KeyWrapper> publicKeys, String kid) {
|
||||
// Backwards compatibility
|
||||
if (kid == null && !publicKeys.isEmpty()) {
|
||||
return publicKeys.values().iterator().next();
|
||||
} else {
|
||||
return publicKeys.get(kid);
|
||||
}
|
||||
}
|
||||
|
||||
private KeyWrapper getPublicKeyByAlg(Map<String, KeyWrapper> publicKeys, String algorithm) {
|
||||
if (algorithm == null) return null;
|
||||
for (KeyWrapper keyWrapper : publicKeys.values())
|
||||
if (algorithm.equals(keyWrapper.getAlgorithmOrDefault())) return keyWrapper;
|
||||
return null;
|
||||
}
|
||||
|
||||
private class WrapperCallable implements Callable<Map<String, KeyWrapper>> {
|
||||
|
||||
private final String modelKey;
|
||||
private final PublicKeyLoader delegate;
|
||||
|
||||
public WrapperCallable(String modelKey, PublicKeyLoader delegate) {
|
||||
this.modelKey = modelKey;
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, KeyWrapper> call() throws Exception {
|
||||
Map<String, KeyWrapper> publicKeys = delegate.loadKeys();
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debugf("Public keys retrieved successfully for model %s. New kids: %s", modelKey, publicKeys.keySet().toString());
|
||||
}
|
||||
|
||||
return publicKeys;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright 2022 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.map.keys;
|
||||
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.crypto.KeyWrapper;
|
||||
import org.keycloak.keys.PublicKeyStorageProviderFactory;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.map.common.AbstractEntity;
|
||||
import org.keycloak.models.map.common.AbstractMapProviderFactory;
|
||||
import org.keycloak.provider.EnvironmentDependentProviderFactory;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.FutureTask;
|
||||
|
||||
public class MapPublicKeyStorageProviderFactory extends AbstractMapProviderFactory<MapPublicKeyStorageProvider, AbstractEntity, Object>
|
||||
implements PublicKeyStorageProviderFactory<MapPublicKeyStorageProvider>, EnvironmentDependentProviderFactory {
|
||||
|
||||
private final Map<String, FutureTask<Map<String, KeyWrapper>>> tasksInProgress = new ConcurrentHashMap<>();
|
||||
|
||||
public MapPublicKeyStorageProviderFactory() {
|
||||
super(Object.class, MapPublicKeyStorageProvider.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapPublicKeyStorageProvider createNew(KeycloakSession session) {
|
||||
return new MapPublicKeyStorageProvider(session, tasksInProgress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
return Profile.isFeatureEnabled(Profile.Feature.MAP_STORAGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpText() {
|
||||
return "Public key storage provider";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
#
|
||||
# Copyright 2022 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.
|
||||
#
|
||||
|
||||
org.keycloak.models.map.keys.MapPublicKeyStorageProviderFactory
|
|
@ -47,9 +47,4 @@ public interface PublicKeyStorageProvider extends Provider {
|
|||
*/
|
||||
KeyWrapper getFirstPublicKey(String modelKey, String algorithm, PublicKeyLoader loader);
|
||||
|
||||
/**
|
||||
* Clears all the cached public keys, so they need to be loaded again
|
||||
*/
|
||||
void clearCache();
|
||||
|
||||
}
|
||||
|
|
|
@ -22,5 +22,5 @@ import org.keycloak.provider.ProviderFactory;
|
|||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public interface PublicKeyStorageProviderFactory extends ProviderFactory<PublicKeyStorageProvider> {
|
||||
public interface PublicKeyStorageProviderFactory<T extends PublicKeyStorageProvider> extends ProviderFactory<T> {
|
||||
}
|
||||
|
|
|
@ -1079,23 +1079,6 @@ public class RealmAdminResource {
|
|||
return stripForExport(session, rep);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear cache of external public keys (Public keys of clients or Identity providers)
|
||||
*
|
||||
*/
|
||||
@Path("clear-keys-cache")
|
||||
@POST
|
||||
public void clearKeysCache() {
|
||||
auth.realm().requireManageRealm();
|
||||
|
||||
PublicKeyStorageProvider cache = session.getProvider(PublicKeyStorageProvider.class);
|
||||
if (cache != null) {
|
||||
cache.clearCache();
|
||||
}
|
||||
|
||||
adminEvent.operation(OperationType.ACTION).resourcePath(session.getContext().getUri()).success();
|
||||
}
|
||||
|
||||
@Path("keys")
|
||||
public KeyResource keys() {
|
||||
KeyResource resource = new KeyResource(realm, session, this.auth);
|
||||
|
|
|
@ -849,9 +849,11 @@
|
|||
<keycloak.eventsStore.provider>map</keycloak.eventsStore.provider>
|
||||
<keycloak.actionToken.provider>map</keycloak.actionToken.provider>
|
||||
<keycloak.singleUseObject.provider>map</keycloak.singleUseObject.provider>
|
||||
<keycloak.publicKeyStorage.provider>map</keycloak.publicKeyStorage.provider>
|
||||
<keycloak.authorizationCache.enabled>false</keycloak.authorizationCache.enabled>
|
||||
<keycloak.realmCache.enabled>false</keycloak.realmCache.enabled>
|
||||
<keycloak.userCache.enabled>false</keycloak.userCache.enabled>
|
||||
<keycloak.publicKeyCache.enabled>false</keycloak.publicKeyCache.enabled>
|
||||
<keycloak.userSessionPersister.provider></keycloak.userSessionPersister.provider>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
|
|
|
@ -26,6 +26,7 @@ import org.junit.Before;
|
|||
import org.junit.Test;
|
||||
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.common.util.*;
|
||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||
import org.keycloak.crypto.Algorithm;
|
||||
|
@ -39,6 +40,7 @@ import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
|||
import org.keycloak.representations.idm.KeysMetadataRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.Assert;
|
||||
import org.keycloak.testsuite.ProfileAssume;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.client.resources.TestingCacheResource;
|
||||
import org.keycloak.testsuite.updaters.ClientAttributeUpdater;
|
||||
|
@ -95,6 +97,8 @@ public class KcOIDCBrokerWithSignatureTest extends AbstractBaseBrokerTest {
|
|||
|
||||
@Test
|
||||
public void testSignatureVerificationJwksUrl() throws Exception {
|
||||
ProfileAssume.assumeFeatureDisabled(Profile.Feature.MAP_STORAGE);
|
||||
|
||||
// Configure OIDC identity provider with JWKS URL
|
||||
updateIdentityProviderWithJwksUrl();
|
||||
|
||||
|
@ -303,6 +307,8 @@ public class KcOIDCBrokerWithSignatureTest extends AbstractBaseBrokerTest {
|
|||
|
||||
@Test
|
||||
public void testClearKeysCache() throws Exception {
|
||||
ProfileAssume.assumeFeatureDisabled(Profile.Feature.MAP_STORAGE);
|
||||
|
||||
// Configure OIDC identity provider with JWKS URL
|
||||
updateIdentityProviderWithJwksUrl();
|
||||
|
||||
|
@ -328,6 +334,8 @@ public class KcOIDCBrokerWithSignatureTest extends AbstractBaseBrokerTest {
|
|||
// Test that when I update identityProvier, then the record in publicKey cache is cleared and it's not possible to authenticate with it anymore
|
||||
@Test
|
||||
public void testPublicKeyCacheInvalidatedWhenProviderUpdated() throws Exception {
|
||||
ProfileAssume.assumeFeatureDisabled(Profile.Feature.MAP_STORAGE);
|
||||
|
||||
// Configure OIDC identity provider with JWKS URL
|
||||
updateIdentityProviderWithJwksUrl();
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@ import org.junit.Test;
|
|||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.adapters.authentication.JWTClientCredentialsProvider;
|
||||
import org.keycloak.client.registration.Auth;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||
import org.keycloak.constants.ServiceUrlConstants;
|
||||
|
@ -58,6 +59,7 @@ import org.keycloak.representations.idm.ClientInitialAccessPresentation;
|
|||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.oidc.OIDCClientRepresentation;
|
||||
import org.keycloak.testsuite.Assert;
|
||||
import org.keycloak.testsuite.ProfileAssume;
|
||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
|
||||
import org.keycloak.testsuite.client.resources.TestApplicationResourceUrls;
|
||||
|
@ -179,6 +181,8 @@ public class OIDCJwksClientRegistrationTest extends AbstractClientRegistrationTe
|
|||
|
||||
@Test
|
||||
public void testTwoClientsWithSameKid() throws Exception {
|
||||
ProfileAssume.assumeFeatureDisabled(Profile.Feature.MAP_STORAGE);
|
||||
|
||||
// Create client with manually set "kid"
|
||||
OIDCClientRepresentation response = createClientWithManuallySetKid("a1");
|
||||
|
||||
|
@ -218,6 +222,8 @@ public class OIDCJwksClientRegistrationTest extends AbstractClientRegistrationTe
|
|||
|
||||
@Test
|
||||
public void testPublicKeyCacheInvalidatedWhenUpdatingClient() throws Exception {
|
||||
ProfileAssume.assumeFeatureDisabled(Profile.Feature.MAP_STORAGE);
|
||||
|
||||
OIDCClientRepresentation response = createClientWithManuallySetKid("a1");
|
||||
|
||||
Map<String, String> generatedKeys = testingClient.testApp().oidcClientEndpoints().getKeysAsPem();
|
||||
|
@ -271,6 +277,8 @@ public class OIDCJwksClientRegistrationTest extends AbstractClientRegistrationTe
|
|||
|
||||
@Test
|
||||
public void createClientWithJWKSURI_rotateClientKeys() throws Exception {
|
||||
ProfileAssume.assumeFeatureDisabled(Profile.Feature.MAP_STORAGE);
|
||||
|
||||
OIDCClientRepresentation clientRep = createRep();
|
||||
|
||||
clientRep.setGrantTypes(Collections.singletonList(OAuth2Constants.CLIENT_CREDENTIALS));
|
||||
|
|
|
@ -160,6 +160,15 @@
|
|||
}
|
||||
},
|
||||
|
||||
"publicKeyStorage": {
|
||||
"provider": "${keycloak.publicKeyStorage.provider:infinispan}",
|
||||
"map": {
|
||||
"storage": {
|
||||
"provider": "${keycloak.publicKeyStorage.map.storage.provider:concurrenthashmap}"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"mapStorage": {
|
||||
"concurrenthashmap": {
|
||||
"dir": "${project.build.directory:target}",
|
||||
|
@ -209,6 +218,12 @@
|
|||
}
|
||||
},
|
||||
|
||||
"publicKeyCache": {
|
||||
"default" : {
|
||||
"enabled": "${keycloak.publicKeyCache.enabled:true}"
|
||||
}
|
||||
},
|
||||
|
||||
"timer": {
|
||||
"provider": "basic"
|
||||
},
|
||||
|
|
|
@ -32,6 +32,7 @@ import org.keycloak.models.map.authorization.MapAuthorizationStoreFactory;
|
|||
import org.keycloak.models.map.client.MapClientProviderFactory;
|
||||
import org.keycloak.models.map.clientscope.MapClientScopeProviderFactory;
|
||||
import org.keycloak.models.map.events.MapEventStoreProviderFactory;
|
||||
import org.keycloak.models.map.keys.MapPublicKeyStorageProviderFactory;
|
||||
import org.keycloak.models.map.singleUseObject.MapSingleUseObjectProviderFactory;
|
||||
import org.keycloak.models.map.storage.hotRod.connections.DefaultHotRodConnectionProviderFactory;
|
||||
import org.keycloak.models.map.storage.hotRod.connections.HotRodConnectionProviderFactory;
|
||||
|
@ -78,6 +79,7 @@ public class HotRodMapStorage extends KeycloakModelParameters {
|
|||
cf.spi(AuthenticationSessionSpi.PROVIDER_ID).provider(MapRootAuthenticationSessionProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi(ActionTokenStoreSpi.NAME).provider(MapSingleUseObjectProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi(SingleUseObjectSpi.NAME).provider(MapSingleUseObjectProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi("publicKeyStorage").provider(MapPublicKeyStorageProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi("client").provider(MapClientProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi("clientScope").provider(MapClientScopeProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi("group").provider(MapGroupProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID)
|
||||
|
|
|
@ -19,8 +19,12 @@ package org.keycloak.testsuite.model.parameters;
|
|||
import org.keycloak.cluster.infinispan.InfinispanClusterProviderFactory;
|
||||
import org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory;
|
||||
import org.keycloak.connections.infinispan.InfinispanConnectionSpi;
|
||||
import org.keycloak.keys.PublicKeyStorageSpi;
|
||||
import org.keycloak.keys.infinispan.InfinispanCachePublicKeyProviderFactory;
|
||||
import org.keycloak.keys.infinispan.InfinispanPublicKeyStorageProviderFactory;
|
||||
import org.keycloak.models.ActionTokenStoreSpi;
|
||||
import org.keycloak.models.SingleUseObjectSpi;
|
||||
import org.keycloak.models.cache.CachePublicKeyProviderSpi;
|
||||
import org.keycloak.models.session.UserSessionPersisterSpi;
|
||||
import org.keycloak.models.sessions.infinispan.InfinispanActionTokenStoreProviderFactory;
|
||||
import org.keycloak.models.sessions.infinispan.InfinispanAuthenticationSessionProviderFactory;
|
||||
|
@ -63,6 +67,8 @@ public class Infinispan extends KeycloakModelParameters {
|
|||
.add(UserSessionPersisterSpi.class)
|
||||
.add(ActionTokenStoreSpi.class)
|
||||
.add(SingleUseObjectSpi.class)
|
||||
.add(PublicKeyStorageSpi.class)
|
||||
.add(CachePublicKeyProviderSpi.class)
|
||||
|
||||
.add(LegacySessionSupportSpi.class) // necessary as it will call session.userCredentialManager().onCache()
|
||||
|
||||
|
@ -80,6 +86,8 @@ public class Infinispan extends KeycloakModelParameters {
|
|||
.add(InfinispanSingleUseObjectProviderFactory.class)
|
||||
.add(StickySessionEncoderProviderFactory.class)
|
||||
.add(TimerProviderFactory.class)
|
||||
.add(InfinispanPublicKeyStorageProviderFactory.class)
|
||||
.add(InfinispanCachePublicKeyProviderFactory.class)
|
||||
.add(LegacySessionSupportProviderFactory.class)
|
||||
.build();
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ import org.keycloak.models.map.client.MapClientProviderFactory;
|
|||
import org.keycloak.models.map.clientscope.MapClientScopeProviderFactory;
|
||||
import org.keycloak.models.map.deploymentState.MapDeploymentStateProviderFactory;
|
||||
import org.keycloak.models.map.group.MapGroupProviderFactory;
|
||||
import org.keycloak.models.map.keys.MapPublicKeyStorageProviderFactory;
|
||||
import org.keycloak.models.map.loginFailure.MapUserLoginFailureProviderFactory;
|
||||
import org.keycloak.models.map.realm.MapRealmProviderFactory;
|
||||
import org.keycloak.models.map.role.MapRoleProviderFactory;
|
||||
|
@ -99,6 +100,7 @@ public class JpaMapStorage extends KeycloakModelParameters {
|
|||
.spi("dblock").provider(NoLockingDBLockProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi(ActionTokenStoreSpi.NAME).provider(MapSingleUseObjectProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi(SingleUseObjectSpi.NAME).provider(MapSingleUseObjectProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi("publicKeyStorage").provider(MapPublicKeyStorageProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi(UserSessionSpi.NAME).provider(MapUserSessionProviderFactory.PROVIDER_ID) .config("storage-user-sessions.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||
.config("storage-client-sessions.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi(EventStoreSpi.NAME).provider(MapUserSessionProviderFactory.PROVIDER_ID) .config("storage-admin-events.provider", JpaMapStorageProviderFactory.PROVIDER_ID)
|
||||
|
|
|
@ -105,7 +105,8 @@ public class LdapMapStorage extends KeycloakModelParameters {
|
|||
.spi(EventStoreSpi.NAME).config("map.storage-admin-events.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi(EventStoreSpi.NAME).config("map.storage-auth-events.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi(ActionTokenStoreSpi.NAME).config("map.storage.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi(SingleUseObjectSpi.NAME).config("map.storage.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID);
|
||||
.spi(SingleUseObjectSpi.NAME).config("map.storage.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi("publicKeyStorage").config("map.storage.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ package org.keycloak.testsuite.model.parameters;
|
|||
|
||||
import org.keycloak.authorization.store.StoreFactorySpi;
|
||||
import org.keycloak.events.EventStoreSpi;
|
||||
import org.keycloak.keys.PublicKeyStorageSpi;
|
||||
import org.keycloak.models.ActionTokenStoreProviderFactory;
|
||||
import org.keycloak.models.ActionTokenStoreSpi;
|
||||
import org.keycloak.models.DeploymentStateSpi;
|
||||
|
@ -29,6 +30,7 @@ import org.keycloak.models.dblock.NoLockingDBLockProviderFactory;
|
|||
import org.keycloak.models.map.authSession.MapRootAuthenticationSessionProviderFactory;
|
||||
import org.keycloak.models.map.authorization.MapAuthorizationStoreFactory;
|
||||
import org.keycloak.models.map.events.MapEventStoreProviderFactory;
|
||||
import org.keycloak.models.map.keys.MapPublicKeyStorageProviderFactory;
|
||||
import org.keycloak.models.map.loginFailure.MapUserLoginFailureProviderFactory;
|
||||
import org.keycloak.models.map.singleUseObject.MapSingleUseObjectProviderFactory;
|
||||
import org.keycloak.models.map.storage.chm.ConcurrentHashMapStorageProviderFactory;
|
||||
|
@ -59,6 +61,7 @@ public class Map extends KeycloakModelParameters {
|
|||
.add(AuthenticationSessionSpi.class)
|
||||
.add(ActionTokenStoreSpi.class)
|
||||
.add(SingleUseObjectSpi.class)
|
||||
.add(PublicKeyStorageSpi.class)
|
||||
.add(MapStorageSpi.class)
|
||||
|
||||
.build();
|
||||
|
@ -79,6 +82,7 @@ public class Map extends KeycloakModelParameters {
|
|||
.add(MapEventStoreProviderFactory.class)
|
||||
.add(ActionTokenStoreProviderFactory.class)
|
||||
.add(SingleUseObjectProviderFactory.class)
|
||||
.add(MapPublicKeyStorageProviderFactory.class)
|
||||
.build();
|
||||
|
||||
public Map() {
|
||||
|
@ -102,6 +106,7 @@ public class Map extends KeycloakModelParameters {
|
|||
.spi(UserLoginFailureSpi.NAME).defaultProvider(MapUserLoginFailureProviderFactory.PROVIDER_ID)
|
||||
.spi("dblock").defaultProvider(NoLockingDBLockProviderFactory.PROVIDER_ID)
|
||||
.spi(EventStoreSpi.NAME).defaultProvider(MapEventStoreProviderFactory.PROVIDER_ID)
|
||||
.spi("publicKeyStorage").defaultProvider(MapPublicKeyStorageProviderFactory.PROVIDER_ID)
|
||||
;
|
||||
cf.spi(MapStorageSpi.NAME).provider(ConcurrentHashMapStorageProviderFactory.PROVIDER_ID).config("keyType.single-use-objects", "string");
|
||||
}
|
||||
|
|
|
@ -321,9 +321,11 @@
|
|||
<systemProperty><key>keycloak.actionToken.provider</key><value>map</value></systemProperty>
|
||||
<systemProperty><key>keycloak.singleUseObject.provider</key><value>map</value></systemProperty>
|
||||
<systemProperty><key>keycloak.eventsStore.provider</key><value>map</value></systemProperty>
|
||||
<systemProperty><key>keycloak.publicKeyStorage.provider</key><value>map</value></systemProperty>
|
||||
<systemProperty><key>keycloak.authorizationCache.enabled</key><value>false</value></systemProperty>
|
||||
<systemProperty><key>keycloak.realmCache.enabled</key><value>false</value></systemProperty>
|
||||
<systemProperty><key>keycloak.userCache.enabled</key><value>false</value></systemProperty>
|
||||
<systemProperty><key>keycloak.publicKeyCache.enabled</key><value>false</value></systemProperty>
|
||||
</systemProperties>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
|
|
@ -132,6 +132,15 @@
|
|||
}
|
||||
},
|
||||
|
||||
"publicKeyStorage": {
|
||||
"provider": "${keycloak.publicKeyStorage.provider:infinispan}",
|
||||
"map": {
|
||||
"storage": {
|
||||
"provider": "${keycloak.publicKeyStorage.map.storage.provider:concurrenthashmap}"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"mapStorage": {
|
||||
"provider": "${keycloak.mapStorage.provider:}",
|
||||
"concurrenthashmap": {
|
||||
|
@ -234,6 +243,12 @@
|
|||
}
|
||||
},
|
||||
|
||||
"publicKeyCache": {
|
||||
"default" : {
|
||||
"enabled": "${keycloak.publicKeyCache.enabled:true}"
|
||||
}
|
||||
},
|
||||
|
||||
"authorizationCache": {
|
||||
"default": {
|
||||
"enabled": "${keycloak.authorizationCache.enabled:true}"
|
||||
|
|
Loading…
Reference in a new issue