From 37a98fba201bcb1cf453b4775317356eaa759cde Mon Sep 17 00:00:00 2001 From: Pedro Igor Date: Mon, 22 May 2017 19:08:24 -0300 Subject: [PATCH] [KEYCLOAK-4913] - Caching more query methods --- .../authorization/ResourceAdapter.java | 5 +- .../StoreFactoryCacheManager.java | 24 +++- .../StoreFactoryCacheSession.java | 124 ++++++++++++++++-- .../authorization/entities/InScope.java | 27 ++++ .../entities/ResourceScopeListQuery.java | 25 ++++ .../events/ResourceRemovedEvent.java | 12 +- .../events/ResourceUpdatedEvent.java | 10 +- .../stream/InScopePredicate.java | 35 +++++ .../AuthorizationTokenService.java | 6 +- .../entitlement/EntitlementService.java | 7 +- 10 files changed, 249 insertions(+), 26 deletions(-) create mode 100644 model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/InScope.java create mode 100755 model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/ResourceScopeListQuery.java create mode 100755 model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/stream/InScopePredicate.java diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/ResourceAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/ResourceAdapter.java index d44cc7c58d..2706681884 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/ResourceAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/ResourceAdapter.java @@ -26,6 +26,8 @@ import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; /** * @author Bill Burke @@ -44,7 +46,7 @@ public class ResourceAdapter implements Resource, CachedModel { @Override public Resource getDelegateForUpdate() { if (updated == null) { - cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getResourceServerId()); + cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUri(), cached.getScopesIds(), cached.getResourceServerId()); updated = cacheSession.getResourceStoreDelegate().findById(cached.getId(), cached.getResourceServerId()); if (updated == null) throw new IllegalStateException("Not found in database"); } @@ -164,6 +166,7 @@ public class ResourceAdapter implements Resource, CachedModel { @Override public void updateScopes(Set scopes) { getDelegateForUpdate(); + cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUri(), scopes.stream().map(scope1 -> scope1.getId()).collect(Collectors.toSet()), cached.getResourceServerId()); updated.updateScopes(scopes); } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheManager.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheManager.java index 2c86648737..c64db03ee9 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheManager.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheManager.java @@ -22,6 +22,7 @@ import org.keycloak.models.cache.infinispan.CacheManager; import org.keycloak.models.cache.infinispan.RealmCacheManager; import org.keycloak.models.cache.infinispan.authorization.events.AuthorizationCacheInvalidationEvent; import org.keycloak.models.cache.infinispan.authorization.stream.InResourceServerPredicate; +import org.keycloak.models.cache.infinispan.authorization.stream.InScopePredicate; import org.keycloak.models.cache.infinispan.entities.Revisioned; import org.keycloak.models.cache.infinispan.events.InvalidationEvent; @@ -65,19 +66,36 @@ public class StoreFactoryCacheManager extends CacheManager { public void scopeUpdated(String id, String name, String serverId, Set invalidations) { invalidations.add(id); invalidations.add(StoreFactoryCacheSession.getScopeByNameCacheKey(name, serverId)); + invalidations.add(StoreFactoryCacheSession.getResourceByScopeCacheKey(id, serverId)); } public void scopeRemoval(String id, String name, String serverId, Set invalidations) { scopeUpdated(id, name, serverId, invalidations); + addInvalidations(InScopePredicate.create().scope(id), invalidations); } - public void resourceUpdated(String id, String name, String serverId, Set invalidations) { + public void resourceUpdated(String id, String name, String type, String uri, Set scopes, String serverId, Set invalidations) { invalidations.add(id); invalidations.add(StoreFactoryCacheSession.getResourceByNameCacheKey(name, serverId)); + + if (type != null) { + invalidations.add(StoreFactoryCacheSession.getResourceByTypeCacheKey(type, serverId)); + } + + if (uri != null) { + invalidations.add(StoreFactoryCacheSession.getResourceByUriCacheKey(uri, serverId)); + } + + if (scopes != null) { + for (String scope : scopes) { + invalidations.add(StoreFactoryCacheSession.getResourceByScopeCacheKey(scope, serverId)); + } + } } - public void resourceRemoval(String id, String name, String serverId, Set invalidations) { - resourceUpdated(id, name, serverId, invalidations); + public void resourceRemoval(String id, String name, String type, String uri, String owner, Set scopes, String serverId, Set invalidations) { + resourceUpdated(id, name, type, uri, scopes, serverId, invalidations); + invalidations.add(StoreFactoryCacheSession.getResourceByOwnerCacheKey(owner, serverId)); } public void policyUpdated(String id, String name, String serverId, Set invalidations) { diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java index 87f311b707..db39562cc8 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java @@ -35,6 +35,7 @@ import org.keycloak.models.cache.infinispan.authorization.entities.CachedResourc import org.keycloak.models.cache.infinispan.authorization.entities.CachedScope; import org.keycloak.models.cache.infinispan.authorization.entities.PolicyListQuery; import org.keycloak.models.cache.infinispan.authorization.entities.ResourceListQuery; +import org.keycloak.models.cache.infinispan.authorization.entities.ResourceScopeListQuery; import org.keycloak.models.cache.infinispan.authorization.entities.ResourceServerListQuery; import org.keycloak.models.cache.infinispan.authorization.entities.ScopeListQuery; import org.keycloak.models.cache.infinispan.authorization.events.PolicyRemovedEvent; @@ -48,11 +49,16 @@ import org.keycloak.models.cache.infinispan.authorization.events.ScopeUpdatedEve import org.keycloak.models.cache.infinispan.events.InvalidationEvent; import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; /** * @author Bill Burke @@ -233,12 +239,12 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider { invalidationEvents.add(ScopeUpdatedEvent.create(id, name, serverId)); } - public void registerResourceInvalidation(String id, String name, String serverId) { - cache.resourceUpdated(id, name, serverId, invalidations); + public void registerResourceInvalidation(String id, String name, String type, String uri, Set scopes, String serverId) { + cache.resourceUpdated(id, name, type, uri, scopes, serverId, invalidations); ResourceAdapter adapter = managedResources.get(id); if (adapter != null) adapter.invalidateFlag(); - invalidationEvents.add(ResourceUpdatedEvent.create(id, name, serverId)); + invalidationEvents.add(ResourceUpdatedEvent.create(id, name, type, uri, scopes, serverId)); } public void registerPolicyInvalidation(String id, String name, String serverId) { @@ -277,6 +283,22 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider { return "resource.name." + name + "." + serverId; } + public static String getResourceByOwnerCacheKey(String owner, String serverId) { + return "resource.owner." + owner + "." + serverId; + } + + public static String getResourceByTypeCacheKey(String type, String serverId) { + return "resource.type." + type + "." + serverId; + } + + public static String getResourceByUriCacheKey(String uri, String serverId) { + return "resource.uri." + uri + "." + serverId; + } + + public static String getResourceByScopeCacheKey(String scopeId, String serverId) { + return "resource.scope." + scopeId + "." + serverId; + } + public static String getPolicyByNameCacheKey(String name, String serverId) { return "policy.name." + name + "." + serverId; } @@ -451,7 +473,7 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider { @Override public Resource create(String name, ResourceServer resourceServer, String owner) { Resource resource = getResourceStoreDelegate().create(name, resourceServer, owner); - registerResourceInvalidation(resource.getId(), resource.getName(), resourceServer.getId()); + registerResourceInvalidation(resource.getId(), resource.getName(), resource.getType(), resource.getUri(), resource.getScopes().stream().map(scope -> scope.getId()).collect(Collectors.toSet()), resourceServer.getId()); return resource; } @@ -462,8 +484,8 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider { if (resource == null) return; cache.invalidateObject(id); - invalidationEvents.add(ResourceRemovedEvent.create(id, resource.getName(), resource.getResourceServer().getId())); - cache.resourceRemoval(id, resource.getName(), resource.getResourceServer().getId(), invalidations); + invalidationEvents.add(ResourceRemovedEvent.create(id, resource.getName(), resource.getType(), resource.getUri(), resource.getOwner(), resource.getScopes().stream().map(scope -> scope.getId()).collect(Collectors.toSet()), resource.getResourceServer().getId())); + cache.resourceRemoval(id, resource.getName(), resource.getType(), resource.getUri(), resource.getOwner(), resource.getScopes().stream().map(scope -> scope.getId()).collect(Collectors.toSet()), resource.getResourceServer().getId(), invalidations); getResourceStoreDelegate().delete(id); } @@ -523,12 +545,48 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider { @Override public List findByOwner(String ownerId, String resourceServerId) { - return getResourceStoreDelegate().findByOwner(ownerId, resourceServerId); + if (ownerId == null) return null; + String cacheKey = getResourceByOwnerCacheKey(ownerId, resourceServerId); + ResourceListQuery query = cache.get(cacheKey, ResourceListQuery.class); + if (query != null) { + logger.tracev("resource by owner cache hit: {0}", ownerId); + } + if (query == null) { + Long loaded = cache.getCurrentRevision(cacheKey); + List model = getResourceStoreDelegate().findByOwner(ownerId, resourceServerId); + if (model == null) return null; + if (invalidations.contains(cacheKey)) return model; + query = new ResourceListQuery(loaded, cacheKey, model.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId); + cache.addRevisioned(query, startupRevision); + return model; + } else if (invalidations.contains(cacheKey)) { + return getResourceStoreDelegate().findByOwner(ownerId, resourceServerId); + } else { + return query.getResources().stream().map(resourceId -> findById(resourceId, resourceServerId)).collect(Collectors.toList()); + } } @Override public List findByUri(String uri, String resourceServerId) { - return getResourceStoreDelegate().findByUri(uri, resourceServerId); + if (uri == null) return null; + String cacheKey = getResourceByUriCacheKey(uri, resourceServerId); + ResourceListQuery query = cache.get(cacheKey, ResourceListQuery.class); + if (query != null) { + logger.tracev("resource by uri cache hit: {0}", uri); + } + if (query == null) { + Long loaded = cache.getCurrentRevision(cacheKey); + List model = getResourceStoreDelegate().findByUri(uri, resourceServerId); + if (model == null) return null; + if (invalidations.contains(cacheKey)) return model; + query = new ResourceListQuery(loaded, cacheKey, model.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId); + cache.addRevisioned(query, startupRevision); + return model; + } else if (invalidations.contains(cacheKey)) { + return getResourceStoreDelegate().findByUri(uri, resourceServerId); + } else { + return query.getResources().stream().map(resourceId -> findById(resourceId, resourceServerId)).collect(Collectors.toList()); + } } @Override @@ -543,12 +601,58 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider { @Override public List findByScope(List ids, String resourceServerId) { - return getResourceStoreDelegate().findByScope(ids, resourceServerId); + if (ids == null) return null; + List result = new ArrayList<>(); + Iterator iterator = ids.iterator(); + + while (iterator.hasNext()) { + String id = iterator.next(); + String cacheKey = getResourceByScopeCacheKey(id, resourceServerId); + ResourceScopeListQuery query = cache.get(cacheKey, ResourceScopeListQuery.class); + if (query != null) { + logger.tracev("resource by scope cache hit: {0}", id); + if (invalidations.contains(cacheKey)) { + result.addAll(getResourceStoreDelegate().findByScope(ids, resourceServerId)); + } else { + result.addAll(query.getResources().stream().map(resourceId -> findById(resourceId, resourceServerId)).collect(Collectors.toList())); + } + } else if (invalidations.contains(id)) { + result.addAll(getResourceStoreDelegate().findByScope(ids, resourceServerId)); + } else { + Long loaded = cache.getCurrentRevision(cacheKey); + List model = getResourceStoreDelegate().findByScope(Arrays.asList(id), resourceServerId); + if (model == null) return null; + if (invalidations.contains(cacheKey)) return model; + query = new ResourceScopeListQuery(loaded, cacheKey, id, model.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId); + cache.addRevisioned(query, startupRevision); + result.addAll(query.getResources().stream().map(resourceId -> findById(resourceId, resourceServerId)).collect(Collectors.toList())); + } + } + + return result; } @Override public List findByType(String type, String resourceServerId) { - return getResourceStoreDelegate().findByType(type, resourceServerId); + if (type == null) return null; + String cacheKey = getResourceByTypeCacheKey(type, resourceServerId); + ResourceListQuery query = cache.get(cacheKey, ResourceListQuery.class); + if (query != null) { + logger.tracev("resource by type cache hit: {0}", type); + } + if (query == null) { + Long loaded = cache.getCurrentRevision(cacheKey); + List model = getResourceStoreDelegate().findByType(type, resourceServerId); + if (model == null) return null; + if (invalidations.contains(cacheKey)) return model; + query = new ResourceListQuery(loaded, cacheKey, model.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId); + cache.addRevisioned(query, startupRevision); + return model; + } else if (invalidations.contains(cacheKey)) { + return getResourceStoreDelegate().findByType(type, resourceServerId); + } else { + return query.getResources().stream().map(resourceId -> findById(resourceId, resourceServerId)).collect(Collectors.toList()); + } } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/InScope.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/InScope.java new file mode 100644 index 0000000000..aab2643413 --- /dev/null +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/InScope.java @@ -0,0 +1,27 @@ +/* + * 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.cache.infinispan.authorization.entities; + +import java.util.Set; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface InScope { + String getScopeId(); +} diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/ResourceScopeListQuery.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/ResourceScopeListQuery.java new file mode 100755 index 0000000000..116e1aaa3b --- /dev/null +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/ResourceScopeListQuery.java @@ -0,0 +1,25 @@ +package org.keycloak.models.cache.infinispan.authorization.entities; + +import java.util.HashSet; +import java.util.Set; + +import org.keycloak.models.cache.infinispan.entities.AbstractRevisioned; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class ResourceScopeListQuery extends ResourceListQuery implements InScope { + + private final String scopeId; + + public ResourceScopeListQuery(Long revision, String id, String scopeId, Set resources, String serverId) { + super(revision, id, resources, serverId); + this.scopeId = scopeId; + } + + @Override + public String getScopeId() { + return scopeId; + } +} \ No newline at end of file diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/ResourceRemovedEvent.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/ResourceRemovedEvent.java index 4e1a31fbd0..15964a6ce3 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/ResourceRemovedEvent.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/ResourceRemovedEvent.java @@ -29,12 +29,20 @@ public class ResourceRemovedEvent extends InvalidationEvent implements Authoriza private String id; private String name; + private String owner; private String serverId; + private String type; + private String uri; + private Set scopes; - public static ResourceRemovedEvent create(String id, String name, String serverId) { + public static ResourceRemovedEvent create(String id, String name, String type, String uri, String owner, Set scopes, String serverId) { ResourceRemovedEvent event = new ResourceRemovedEvent(); event.id = id; event.name = name; + event.type = type; + event.uri = uri; + event.owner = owner; + event.scopes = scopes; event.serverId = serverId; return event; } @@ -51,6 +59,6 @@ public class ResourceRemovedEvent extends InvalidationEvent implements Authoriza @Override public void addInvalidations(StoreFactoryCacheManager cache, Set invalidations) { - cache.resourceRemoval(id, name, serverId, invalidations); + cache.resourceRemoval(id, name, type, uri, owner, scopes, serverId, invalidations); } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/ResourceUpdatedEvent.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/ResourceUpdatedEvent.java index 113d5e0a53..cc30f230d9 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/ResourceUpdatedEvent.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/ResourceUpdatedEvent.java @@ -30,11 +30,17 @@ public class ResourceUpdatedEvent extends InvalidationEvent implements Authoriza private String id; private String name; private String serverId; + private String type; + private String uri; + private Set scopes; - public static ResourceUpdatedEvent create(String id, String name, String serverId) { + public static ResourceUpdatedEvent create(String id, String name, String type, String uri, Set scopes, String serverId) { ResourceUpdatedEvent event = new ResourceUpdatedEvent(); event.id = id; event.name = name; + event.type = type; + event.uri = uri; + event.scopes = scopes; event.serverId = serverId; return event; } @@ -51,6 +57,6 @@ public class ResourceUpdatedEvent extends InvalidationEvent implements Authoriza @Override public void addInvalidations(StoreFactoryCacheManager cache, Set invalidations) { - cache.resourceUpdated(id, name, serverId, invalidations); + cache.resourceUpdated(id, name, type, uri, scopes, serverId, invalidations); } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/stream/InScopePredicate.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/stream/InScopePredicate.java new file mode 100755 index 0000000000..3a2a197364 --- /dev/null +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/stream/InScopePredicate.java @@ -0,0 +1,35 @@ +package org.keycloak.models.cache.infinispan.authorization.stream; + +import java.io.Serializable; +import java.util.Map; +import java.util.function.Predicate; + +import org.keycloak.models.cache.infinispan.authorization.entities.InResourceServer; +import org.keycloak.models.cache.infinispan.authorization.entities.InScope; +import org.keycloak.models.cache.infinispan.entities.Revisioned; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class InScopePredicate implements Predicate>, Serializable { + private String scopeId; + + public static InScopePredicate create() { + return new InScopePredicate(); + } + + public InScopePredicate scope(String id) { + scopeId = id; + return this; + } + + @Override + public boolean test(Map.Entry entry) { + Object value = entry.getValue(); + if (value == null) return false; + if (!(value instanceof InScope)) return false; + + return scopeId.equals(((InScope)value).getScopeId()); + } +} diff --git a/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java b/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java index 3a9337e730..875a0e0dae 100644 --- a/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java +++ b/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java @@ -65,6 +65,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -238,10 +239,7 @@ public class AuthorizationTokenService { if ("$KC_SCOPE_PERMISSION".equals(key)) { ScopeStore scopeStore = authorization.getStoreFactory().getScopeStore(); - List scopes = entry.getValue().stream().map(scopeName -> { - Scope byName = scopeStore.findByName(scopeName, resourceServer.getId()); - return byName; - }).collect(Collectors.toList()); + List scopes = entry.getValue().stream().map(scopeName -> scopeStore.findByName(scopeName, resourceServer.getId())).filter(scope -> Objects.nonNull(scope)).collect(Collectors.toList()); return Arrays.asList(new ResourcePermission(null, scopes, resourceServer)).stream(); } else { Resource entryResource = storeFactory.getResourceStore().findById(key, resourceServer.getId()); diff --git a/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java b/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java index 463ff0bf19..71058ac031 100644 --- a/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java +++ b/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java @@ -23,8 +23,10 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.function.Function; +import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -319,10 +321,7 @@ public class EntitlementService { if ("$KC_SCOPE_PERMISSION".equals(key)) { ScopeStore scopeStore = authorization.getStoreFactory().getScopeStore(); - List scopes = entry.getValue().stream().map(scopeName -> { - Scope byName = scopeStore.findByName(scopeName, resourceServer.getId()); - return byName; - }).collect(Collectors.toList()); + List scopes = entry.getValue().stream().map(scopeName -> scopeStore.findByName(scopeName, resourceServer.getId())).filter(scope -> Objects.nonNull(scope)).collect(Collectors.toList()); return Arrays.asList(new ResourcePermission(null, scopes, resourceServer)).stream(); } else { Resource entryResource = storeFactory.getResourceStore().findById(key, resourceServer.getId());