further refinement of context handling (#28182)

* fully removing providers and moving the keycloaksession creation / final
cleanup

also deprecated Resteasy utility methods

closes: #29223

Signed-off-by: Steve Hawkins <shawkins@redhat.com>

Co-authored-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
Steven Hawkins 2024-05-02 11:21:01 -04:00 committed by GitHub
parent 3b1ca46be2
commit 4697cc956b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 163 additions and 289 deletions

View file

@ -18,26 +18,18 @@
package org.keycloak.common.util;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.ServiceLoader;
/**
* <p>Provides a layer of indirection to abstract invocations to Resteasy internal APIs for obtaining the KeycloakSession
* <p>Provides a way for obtaining the KeycloakSession
*
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*
* @deprecated use org.keycloak.util.KeycloakSessionUtil instead
*/
@Deprecated
public final class Resteasy {
private static ResteasyProvider provider;
static {
Iterator<ResteasyProvider> iter = ServiceLoader.load(ResteasyProvider.class, Resteasy.class.getClassLoader()).iterator();
if (iter.hasNext()) {
provider = iter.next();
}
}
private static final ThreadLocal<Map<Class<?>, Object>> contextualData = new ThreadLocal<Map<Class<?>, Object>>() {
@Override
protected Map<Class<?>, Object> initialValue() {
@ -45,12 +37,8 @@ public final class Resteasy {
};
};
public static ResteasyProvider getProvider() {
return provider;
}
/**
* Push the given {@code instance} with type/key {@code type} to the Resteasy context associated with the current thread.
* Push the given {@code instance} with type/key {@code type} to the context associated with the current thread.
* <br>Should not be called directly
*
* @param type the type/key to associate the {@code instance} with
@ -61,7 +49,7 @@ public final class Resteasy {
}
/**
* Clear the Resteasy context associated with the current thread.
* Clear the context associated with the current thread.
* <br>Should not be called directly
*/
public static void clearContextData() {
@ -69,18 +57,14 @@ public final class Resteasy {
}
/**
* Lookup the instance associated with the given type/key {@code type} from the Resteasy context associated with the current thread, or from the provider.
* Lookup the instance associated with the given type/key {@code type} from the context associated with the current thread.
* <br> Should only be used to obtain the KeycloakSession
*
* @param type the type/key to lookup
* @return the instance associated with the given {@code type} or null if non-existent.
*/
public static <R> R getContextData(Class<R> type) {
R result = (R) contextualData.get().get(type);
if (result != null) {
return result;
}
return provider.getContextData(type);
return (R) contextualData.get().get(type);
}
/**
@ -88,7 +72,6 @@ public final class Resteasy {
*
* @param type the type/key to associate the {@code instance} with
* @param instance the instance
* @deprecated use {@link #pushContext(Class, Object)}
*/
@Deprecated
public static void pushDefaultContextObject(Class type, Object instance) {

View file

@ -1,7 +0,0 @@
package org.keycloak.common.util;
public interface ResteasyProvider {
<R> R getContextData(Class<R> type);
}

View file

@ -301,3 +301,9 @@ the `SingleUseObjectKeyModel` also changed to keep consistency with the method n
The previous `getExpiration` method is now deprecated and you should prefer using new newly introduced `getExp` method
to avoid overflow after 2038.
= Resteasy util class is deprecated
`org.keycloak.common.util.Resteasy` has been deprecated. You should use the `org.keycloak.util.KeycloakSessionUtil` to obtain the `KeycloakSession` instead.
It is highly recommended to avoid obtaining the `KeycloakSession` by means other than when creating your custom provider.

View file

@ -40,7 +40,6 @@ import org.keycloak.Config;
import org.keycloak.common.Profile;
import org.keycloak.common.Profile.Feature;
import org.keycloak.common.enums.SslRequired;
import org.keycloak.common.util.Resteasy;
import org.keycloak.config.HostnameV1Options;
import org.keycloak.config.ProxyOptions;
import org.keycloak.config.ProxyOptions.Mode;
@ -50,6 +49,7 @@ import org.keycloak.provider.EnvironmentDependentProviderFactory;
import org.keycloak.urls.HostnameProvider;
import org.keycloak.urls.HostnameProviderFactory;
import org.keycloak.urls.UrlType;
import org.keycloak.utils.KeycloakSessionUtil;
public final class DefaultHostnameProvider implements HostnameProvider, HostnameProviderFactory, EnvironmentDependentProviderFactory {
@ -195,7 +195,7 @@ public final class DefaultHostnameProvider implements HostnameProvider, Hostname
}
protected URI getRealmFrontEndUrl() {
KeycloakSession session = Resteasy.getContextData(KeycloakSession.class);
KeycloakSession session = KeycloakSessionUtil.getKeycloakSession();
if (session == null) {
return null;

View file

@ -17,21 +17,27 @@
package org.keycloak.quarkus.runtime.integration.cdi;
import io.quarkus.arc.Unremovable;
import org.keycloak.models.KeycloakSession;
import org.keycloak.quarkus.runtime.transaction.TransactionalSessionHandler;
import org.keycloak.utils.KeycloakSessionUtil;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.context.RequestScoped;
import jakarta.enterprise.inject.Produces;
import org.keycloak.common.util.Resteasy;
import org.keycloak.models.KeycloakSession;
import io.quarkus.arc.Unremovable;
import jakarta.enterprise.inject.Disposes;
@ApplicationScoped
@Unremovable
public class KeycloakBeanProducer {
public class KeycloakBeanProducer implements TransactionalSessionHandler {
@Produces
@RequestScoped
public KeycloakSession getKeycloakSession() {
return Resteasy.getContextData(KeycloakSession.class);
return create();
}
void dispose(@Disposes KeycloakSession session) {
KeycloakSessionUtil.setKeycloakSession(null);
close(session);
}
}

View file

@ -28,14 +28,12 @@ import jakarta.ws.rs.container.ContainerResponseFilter;
import jakarta.ws.rs.container.PreMatching;
import jakarta.ws.rs.core.StreamingOutput;
import jakarta.ws.rs.ext.Provider;
import org.keycloak.common.util.Resteasy;
import org.keycloak.models.KeycloakSession;
import org.keycloak.quarkus.runtime.transaction.TransactionalSessionHandler;
import org.keycloak.utils.KeycloakSessionUtil;
@Provider
@PreMatching
@Priority(1)
public class CloseSessionHandler implements ContainerResponseFilter, TransactionalSessionHandler {
public class CloseSessionFilter implements ContainerResponseFilter, org.keycloak.quarkus.runtime.transaction.TransactionalSessionHandler {
@Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
@ -66,6 +64,6 @@ public class CloseSessionHandler implements ContainerResponseFilter, Transaction
}
private void closeSession() {
close(Resteasy.getContextData(KeycloakSession.class));
close(KeycloakSessionUtil.getKeycloakSession());
}
}

View file

@ -17,108 +17,13 @@
package org.keycloak.quarkus.runtime.integration.jaxrs;
import static java.util.Collections.emptySet;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import jakarta.ws.rs.core.MultivaluedMap;
public final class EmptyMultivaluedMap<K, V> implements MultivaluedMap<K, V> {
import jakarta.ws.rs.core.AbstractMultivaluedMap;
@Override
public void putSingle(K key, V value) {
throw new UnsupportedOperationException();
}
public final class EmptyMultivaluedMap<K, V> extends AbstractMultivaluedMap<K, V> {
@Override
public void add(K key, V value) {
throw new UnsupportedOperationException();
}
@Override
public V getFirst(K key) {
return null;
}
@Override
public void addAll(K key, V... newValues) {
throw new UnsupportedOperationException();
}
@Override
public void addAll(K key, List<V> valueList) {
throw new UnsupportedOperationException();
}
@Override
public void addFirst(K key, V value) {
throw new UnsupportedOperationException();
}
@Override
public boolean equalsIgnoreValueOrder(MultivaluedMap<K, V> otherMap) {
return false;
}
@Override
public int size() {
return 0;
}
@Override
public boolean isEmpty() {
return true;
}
@Override
public boolean containsKey(Object key) {
return false;
}
@Override
public boolean containsValue(Object value) {
return false;
}
@Override
public List<V> get(Object key) {
return null;
}
@Override
public List<V> put(K key, List<V> value) {
throw new UnsupportedOperationException();
}
@Override
public List<V> remove(Object key) {
throw new UnsupportedOperationException();
}
@Override
public void putAll(Map<? extends K, ? extends List<V>> m) {
throw new UnsupportedOperationException();
}
@Override
public void clear() {
throw new UnsupportedOperationException();
}
@Override
public Set<K> keySet() {
return emptySet();
}
@Override
public Collection<List<V>> values() {
return emptySet();
}
@Override
public Set<Entry<K, List<V>>> entrySet() {
return emptySet();
public EmptyMultivaluedMap() {
super(Map.of());
}
}

View file

@ -1,56 +0,0 @@
/*
* 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.quarkus.runtime.integration.resteasy;
import io.quarkus.resteasy.reactive.server.runtime.QuarkusResteasyReactiveRequestContext;
import io.vertx.ext.web.RoutingContext;
import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext;
import org.jboss.resteasy.reactive.server.spi.ServerRestHandler;
import org.keycloak.models.KeycloakSession;
import org.keycloak.quarkus.runtime.transaction.TransactionalSessionHandler;
import static org.keycloak.common.util.Resteasy.clearContextData;
public final class CreateSessionHandler implements ServerRestHandler, TransactionalSessionHandler {
@Override
public void handle(ResteasyReactiveRequestContext requestContext) {
QuarkusResteasyReactiveRequestContext context = (QuarkusResteasyReactiveRequestContext) requestContext;
RoutingContext routingContext = context.getContext();
KeycloakSession currentSession = routingContext.get(KeycloakSession.class.getName());
if (currentSession == null) {
// this handler might be invoked multiple times when resolving sub-resources
// make sure the session is created once
KeycloakSession session = create();
routingContext.put(KeycloakSession.class.getName(), session);
// the CloseSessionFilter is needed because it runs sooner than this callback
// this is just a catch-all if the CloseSessionFilter doesn't get a chance to run
context.registerCompletionCallback(ignored -> {
try {
close(session);
} catch (Exception e) {
}
clearContextData();
});
}
}
}

View file

@ -32,7 +32,7 @@ import org.jboss.resteasy.reactive.server.spi.ServerRestHandler;
public final class KeycloakHandlerChainCustomizer implements HandlerChainCustomizer {
private final CreateSessionHandler TRANSACTIONAL_SESSION_HANDLER = new CreateSessionHandler();
private final TransactionalSessionHandler TRANSACTIONAL_SESSION_HANDLER = new TransactionalSessionHandler();
private final FormBodyHandler formBodyHandler = new FormBodyHandler(true, () -> Runnable::run, Set.of());

View file

@ -1,45 +0,0 @@
/*
* 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.quarkus.runtime.integration.resteasy;
import io.quarkus.arc.Arc;
import io.quarkus.vertx.http.runtime.CurrentVertxRequest;
import io.vertx.ext.web.RoutingContext;
import org.keycloak.common.util.ResteasyProvider;
import java.util.Optional;
import jakarta.enterprise.context.ContextNotActiveException;
public class ResteasyVertxProvider implements ResteasyProvider {
@Override
public <R> R getContextData(Class<R> type) {
return (R) getRoutingContext().map(c -> c.get(type.getName())).orElse(null);
}
private static Optional<RoutingContext> getRoutingContext() {
try {
return Optional.ofNullable(Arc.container())
.map(c -> c.instance(CurrentVertxRequest.class).get()).map(CurrentVertxRequest::getCurrent);
} catch (ContextNotActiveException e) {
return Optional.empty();
}
}
}

View file

@ -0,0 +1,39 @@
/*
* 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.quarkus.runtime.integration.resteasy;
import io.quarkus.arc.Arc;
import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext;
import org.jboss.resteasy.reactive.server.spi.ServerRestHandler;
import org.keycloak.models.KeycloakSession;
import org.keycloak.utils.KeycloakSessionUtil;
public final class TransactionalSessionHandler implements ServerRestHandler, org.keycloak.quarkus.runtime.transaction.TransactionalSessionHandler {
@Override
public void handle(ResteasyReactiveRequestContext requestContext) {
requestContext.requireCDIRequestScope();
KeycloakSession currentSession = Arc.container().instance(KeycloakSession.class).get();
// this handler might be invoked multiple times when resolving sub-resources
// make sure the transaction is began once when the session is first associated with the thread
if (KeycloakSessionUtil.setKeycloakSession(currentSession) == null) {
beginTransaction(currentSession);
}
}
}

View file

@ -19,9 +19,7 @@ package org.keycloak.quarkus.runtime.transaction;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.KeycloakTransactionManager;
import org.keycloak.quarkus.runtime.integration.QuarkusKeycloakSessionFactory;
import org.keycloak.services.DefaultKeycloakSession;
/**
* <p>A {@link TransactionalSessionHandler} is responsible for managing transaction sessions and its lifecycle. Its subtypes
@ -31,24 +29,31 @@ import org.keycloak.services.DefaultKeycloakSession;
public interface TransactionalSessionHandler {
/**
* Creates a transactional {@link KeycloakSession}.
* Creates a {@link KeycloakSession}.
*
* @return a transactional keycloak session
* @return a keycloak session
*/
default KeycloakSession create() {
KeycloakSessionFactory sessionFactory = QuarkusKeycloakSessionFactory.getInstance();
KeycloakSession session = sessionFactory.create();
session.getTransactionManager().begin();
return session;
return sessionFactory.create();
}
/**
* Closes a transactional {@link KeycloakSession}.
* begin a transaction if possible
*
* @param session a transactional session
* @param session a session
*/
default void beginTransaction(KeycloakSession session) {
session.getTransactionManager().begin();
}
/**
* Closes a {@link KeycloakSession}.
*
* @param session a session
*/
default void close(KeycloakSession session) {
if (session == null || DefaultKeycloakSession.class.cast(session).isClosed()) {
if (session == null || session.isClosed()) {
return;
}

View file

@ -1 +0,0 @@
org.keycloak.quarkus.runtime.integration.resteasy.ResteasyVertxProvider

View file

@ -45,6 +45,6 @@ public class JaxRsDistTest {
assertEquals(200, when().get("/").getStatusCode());
Awaitility.await().atMost(5, TimeUnit.SECONDS).untilAsserted(
() -> cliResult.assertMessage("Request GET / has context request true has keycloaksession false"));
() -> cliResult.assertMessage("Request GET / has context request true has keycloaksession true"));
}
}

View file

@ -25,7 +25,6 @@ import org.keycloak.broker.social.SocialIdentityProviderFactory;
import org.keycloak.common.util.CertificateUtils;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.common.util.PemUtils;
import org.keycloak.common.util.Resteasy;
import org.keycloak.common.util.SecretGenerator;
import org.keycloak.common.util.Time;
import org.keycloak.component.ComponentModel;
@ -53,6 +52,7 @@ import org.keycloak.models.UserModel;
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
import org.keycloak.representations.idm.CertificateRepresentation;
import org.keycloak.transaction.JtaTransactionManagerLookup;
import org.keycloak.utils.KeycloakSessionUtil;
import javax.crypto.spec.SecretKeySpec;
import jakarta.transaction.InvalidTransactionException;
@ -380,7 +380,7 @@ public final class KeycloakModelUtils {
V result;
try (KeycloakSession session = factory.create()) {
session.getTransactionManager().begin();
KeycloakSession old = Resteasy.pushContext(KeycloakSession.class, session);
KeycloakSession old = KeycloakSessionUtil.setKeycloakSession(session);
try {
cloneContextRealmClientToSession(context, session);
result = callable.run(session);
@ -388,7 +388,7 @@ public final class KeycloakModelUtils {
session.getTransactionManager().setRollbackOnly();
throw t;
} finally {
Resteasy.pushContext(KeycloakSession.class, old);
KeycloakSessionUtil.setKeycloakSession(old);
}
}
return result;

View file

@ -23,13 +23,13 @@ import java.util.function.BiConsumer;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.ResponseBuilder;
import org.keycloak.common.util.Resteasy;
import org.keycloak.http.HttpRequest;
import org.keycloak.http.HttpResponse;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.provider.Provider;
import org.keycloak.representations.AccessToken;
import org.keycloak.utils.KeycloakSessionUtil;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -54,12 +54,12 @@ public interface Cors extends Provider {
public static final String INCLUDE_REDIRECTS = "+";
public static Cors add(HttpRequest request, ResponseBuilder response) {
KeycloakSession session = Resteasy.getContextData(KeycloakSession.class);
KeycloakSession session = KeycloakSessionUtil.getKeycloakSession();
return session.getProvider(Cors.class).request(request).builder(response);
}
public static Cors add(HttpRequest request) {
KeycloakSession session = Resteasy.getContextData(KeycloakSession.class);
KeycloakSession session = KeycloakSessionUtil.getKeycloakSession();
return session.getProvider(Cors.class).request(request);
}

View file

@ -94,6 +94,7 @@ public interface KeycloakSession extends AutoCloseable {
* @return
* @deprecated Deprecated in favor of {@link #getComponentProvider)
*/
@Deprecated
<T extends Provider> T getProvider(Class<T> clazz, ComponentModel componentModel);
/**
@ -203,6 +204,7 @@ public interface KeycloakSession extends AutoCloseable {
SingleUseObjectProvider singleUseObjects();
@Override
void close();
/**
@ -243,4 +245,6 @@ public interface KeycloakSession extends AutoCloseable {
*/
ClientPolicyManager clientPolicy();
boolean isClosed();
}

View file

@ -0,0 +1,49 @@
/*
* Copyright 2024 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.utils;
import org.keycloak.common.util.Resteasy;
import org.keycloak.models.KeycloakSession;
public class KeycloakSessionUtil {
private KeycloakSessionUtil() {
}
/**
* Get the {@link KeycloakSession} currently associated with the thread.
*
* @return the current session
*/
public static KeycloakSession getKeycloakSession() {
return Resteasy.getContextData(KeycloakSession.class);
}
/**
* Associate the {@link KeycloakSession} with the current thread.
* <br>Warning: should not be called directly. Keycloak will manage this.
*
* @param session
* @return the existing {@link KeycloakSession} or null
*/
public static KeycloakSession setKeycloakSession(KeycloakSession session) {
return Resteasy.pushContext(KeycloakSession.class, session);
}
}

View file

@ -119,13 +119,9 @@ public abstract class DefaultKeycloakContext implements KeycloakContext {
@Override
public ClientConnection getConnection() {
if (clientConnection == null) {
synchronized (this) {
if (clientConnection == null) {
clientConnection = createClientConnection();
}
}
}
return clientConnection;
}
@ -147,26 +143,18 @@ public abstract class DefaultKeycloakContext implements KeycloakContext {
@Override
public HttpRequest getHttpRequest() {
if (request == null) {
synchronized (this) {
if (request == null) {
request = createHttpRequest();
}
}
}
return request;
}
@Override
public HttpResponse getHttpResponse() {
if (response == null) {
synchronized (this) {
if (response == null) {
response = createHttpResponse();
}
}
}
return response;
}

View file

@ -17,9 +17,9 @@
package org.keycloak.services;
import org.keycloak.common.util.Resteasy;
import org.keycloak.models.KeycloakSession;
import org.keycloak.representations.idm.OAuth2ErrorRepresentation;
import org.keycloak.utils.KeycloakSessionUtil;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.MediaType;
@ -55,7 +55,7 @@ public class ErrorResponseException extends WebApplicationException {
@Override
public Response getResponse() {
KeycloakSession session = Resteasy.getContextData(KeycloakSession.class);
KeycloakSession session = KeycloakSessionUtil.getKeycloakSession();
if (session != null) {
// This has to happen, since calling getResponse() with non-null result leads to
// directly returning the result instead of

View file

@ -1,6 +1,5 @@
package org.keycloak.services.error;
import static org.keycloak.common.util.Resteasy.getContextData;
import static org.keycloak.services.resources.KeycloakApplication.getSessionFactory;
import com.fasterxml.jackson.core.JsonParseException;
@ -26,6 +25,7 @@ import org.keycloak.theme.beans.MessageBean;
import org.keycloak.theme.beans.MessageFormatterMethod;
import org.keycloak.forms.login.MessageType;
import org.keycloak.theme.freemarker.FreeMarkerProvider;
import org.keycloak.utils.KeycloakSessionUtil;
import org.keycloak.utils.MediaType;
import org.keycloak.utils.MediaTypeMatcher;
@ -55,7 +55,7 @@ public class KeycloakErrorHandler implements ExceptionMapper<Throwable> {
@Override
public Response toResponse(Throwable throwable) {
KeycloakSession session = getContextData(KeycloakSession.class);
KeycloakSession session = KeycloakSessionUtil.getKeycloakSession();
if (session == null) {
// errors might be thrown when handling errors from JAX-RS before the session is available

View file

@ -16,9 +16,9 @@
*/
package org.keycloak.services.filters;
import org.keycloak.common.util.Resteasy;
import org.keycloak.headers.SecurityHeadersProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.utils.KeycloakSessionUtil;
import jakarta.ws.rs.container.ContainerRequestContext;
@ -38,7 +38,7 @@ public class KeycloakSecurityHeadersFilter implements ContainerResponseFilter {
@Override
public void filter(ContainerRequestContext containerRequestContext, ContainerResponseContext containerResponseContext) {
KeycloakSession session = Resteasy.getContextData(KeycloakSession.class);
KeycloakSession session = KeycloakSessionUtil.getKeycloakSession();
if (session != null) {
SecurityHeadersProvider securityHeadersProvider = session.getProvider(SecurityHeadersProvider.class);