keycloak-aplcache

Changes

Details

diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java
index d7985b0..925a0db 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java
@@ -17,26 +17,21 @@
 
 package org.keycloak.adapters;
 
-import org.apache.http.HttpEntity;
 import org.apache.http.HttpResponse;
 import org.apache.http.client.HttpClient;
-import org.apache.http.client.methods.HttpGet;
 import org.jboss.logging.Logger;
 import org.keycloak.adapters.authentication.ClientCredentialsProvider;
+import org.keycloak.adapters.authorization.PolicyEnforcer;
+import org.keycloak.adapters.rotation.PublicKeyLocator;
 import org.keycloak.adapters.spi.HttpFacade;
 import org.keycloak.common.enums.RelativeUrlsUsed;
 import org.keycloak.common.enums.SslRequired;
 import org.keycloak.enums.TokenStore;
 import org.keycloak.representations.adapters.config.AdapterConfig;
-import org.keycloak.representations.idm.PublishedRealmRepresentation;
-import org.keycloak.util.JsonSerialization;
 import org.keycloak.common.util.KeycloakUriBuilder;
 
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
-import java.io.InputStream;
 import java.net.URI;
-import java.security.PublicKey;
 import java.util.Map;
 
 /**
@@ -57,7 +52,7 @@ public class AdapterDeploymentContext {
      * during the application deployment's life cycle.
      *
      * @param deployment A KeycloakConfigResolver, possibly missing the Auth
-     *                   Server URL and/or Realm Public Key
+     *                   Server URL
      */
     public AdapterDeploymentContext(KeycloakDeployment deployment) {
         this.deployment = deployment;
@@ -79,7 +74,6 @@ public class AdapterDeploymentContext {
     /**
      * For single-tenant deployments, it complements KeycloakDeployment
      * by resolving a relative Auth Server's URL based on the current request
-     * and, if needed, will lazily resolve the Realm's Public Key.
      *
      * For multi-tenant deployments, defers the resolution of KeycloakDeployment
      * to the KeycloakConfigResolver .
@@ -98,8 +92,8 @@ public class AdapterDeploymentContext {
         if (deployment.getAuthServerBaseUrl() == null) return deployment;
 
         KeycloakDeployment resolvedDeployment = resolveUrls(deployment, facade);
-        if (resolvedDeployment.getRealmKey() == null) {
-            resolveRealmKey(resolvedDeployment);
+        if (resolvedDeployment.getPublicKeyLocator() == null) {
+            throw new RuntimeException("KeycloakDeployment was never initialized through appropriate SPIs");
         }
         return resolvedDeployment;
     }
@@ -115,45 +109,6 @@ public class AdapterDeploymentContext {
         }
     }
 
-    public void resolveRealmKey(KeycloakDeployment deployment) {
-        if (deployment.getClient() == null) {
-            throw new RuntimeException("KeycloakDeployment was never initialized through appropriate SPIs");
-        }
-        HttpGet get = new HttpGet(deployment.getRealmInfoUrl());
-        try {
-            HttpResponse response = deployment.getClient().execute(get);
-            int status = response.getStatusLine().getStatusCode();
-            if (status != 200) {
-                close(response);
-                throw new RuntimeException("Unable to resolve realm public key remotely, status = " + status);
-            }
-            HttpEntity entity = response.getEntity();
-            if (entity == null) {
-                throw new RuntimeException("Unable to resolve realm public key remotely.  There was no entity.");
-            }
-            InputStream is = entity.getContent();
-            try {
-                ByteArrayOutputStream os = new ByteArrayOutputStream();
-                int c;
-                while ((c = is.read()) != -1) {
-                    os.write(c);
-                }
-                byte[] bytes = os.toByteArray();
-                String json = new String(bytes);
-                PublishedRealmRepresentation rep = JsonSerialization.readValue(json, PublishedRealmRepresentation.class);
-                deployment.setRealmKey(rep.getPublicKey());
-            } finally {
-                try {
-                    is.close();
-                } catch (IOException ignored) {
-
-                }
-            }
-        } catch (IOException e) {
-            throw new RuntimeException("Unable to resolve realm public key remotely", e);
-        }
-    }
-
     /**
      * This delegate is used to store temporary, per-request metadata like request resolved URLs.
      * Ever method is delegated except URL get methods and isConfigured()
@@ -208,6 +163,11 @@ public class AdapterDeploymentContext {
         }
 
         @Override
+        public String getJwksUrl() {
+            return (this.jwksUrl != null) ? this.jwksUrl : delegate.getJwksUrl();
+        }
+
+        @Override
         public String getResourceName() {
             return delegate.getResourceName();
         }
@@ -223,13 +183,13 @@ public class AdapterDeploymentContext {
         }
 
         @Override
-        public PublicKey getRealmKey() {
-            return delegate.getRealmKey();
+        public void setPublicKeyLocator(PublicKeyLocator publicKeyLocator) {
+            delegate.setPublicKeyLocator(publicKeyLocator);
         }
 
         @Override
-        public void setRealmKey(PublicKey realmKey) {
-            delegate.setRealmKey(realmKey);
+        public PublicKeyLocator getPublicKeyLocator() {
+            return delegate.getPublicKeyLocator();
         }
 
         @Override
@@ -466,6 +426,26 @@ public class AdapterDeploymentContext {
         public void setTokenMinimumTimeToLive(final int tokenMinimumTimeToLive) {
             delegate.setTokenMinimumTimeToLive(tokenMinimumTimeToLive);
         }
+
+        @Override
+        public PolicyEnforcer getPolicyEnforcer() {
+            return delegate.getPolicyEnforcer();
+        }
+
+        @Override
+        public void setPolicyEnforcer(PolicyEnforcer policyEnforcer) {
+            delegate.setPolicyEnforcer(policyEnforcer);
+        }
+
+        @Override
+        public void setMinTimeBetweenJwksRequests(int minTimeBetweenJwksRequests) {
+            delegate.setMinTimeBetweenJwksRequests(minTimeBetweenJwksRequests);
+        }
+
+        @Override
+        public int getMinTimeBetweenJwksRequests() {
+            return delegate.getMinTimeBetweenJwksRequests();
+        }
     }
 
     protected KeycloakUriBuilder getBaseBuilder(HttpFacade facade, String base) {
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java
index f1be944..1c900b8 100644
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java
@@ -21,6 +21,7 @@ import org.jboss.logging.Logger;
 import org.keycloak.RSATokenVerifier;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.OIDCHttpFacade;
+import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
 import org.keycloak.adapters.spi.HttpFacade;
 import org.keycloak.authorization.client.AuthorizationDeniedException;
 import org.keycloak.authorization.client.AuthzClient;
@@ -120,7 +121,7 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
                 AuthorizationResponse authzResponse = authzClient.authorization(accessToken).authorize(authzRequest);
 
                 if (authzResponse != null) {
-                    return RSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment.getRealmKey(), deployment.getRealmInfoUrl());
+                    return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
                 }
 
                 return null;
@@ -130,7 +131,7 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
 
                 if (token.getAuthorization() == null) {
                     EntitlementResponse authzResponse = authzClient.entitlement(accessToken).getAll(authzClient.getConfiguration().getClientId());
-                    return RSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment.getRealmKey(), deployment.getRealmInfoUrl());
+                    return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
                 } else {
                     EntitlementRequest request = new EntitlementRequest();
                     PermissionRequest permissionRequest = new PermissionRequest();
@@ -139,7 +140,7 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
                     permissionRequest.setScopes(new HashSet<>(pathConfig.getScopes()));
                     request.addPermission(permissionRequest);
                     EntitlementResponse authzResponse = authzClient.entitlement(accessToken).get(authzClient.getConfiguration().getClientId(), request);
-                    return RSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment.getRealmKey(), deployment.getRealmInfoUrl());
+                    return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
                 }
             }
         } catch (AuthorizationDeniedException e) {
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/BearerTokenRequestAuthenticator.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/BearerTokenRequestAuthenticator.java
index 9af6214..70b90b1 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/BearerTokenRequestAuthenticator.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/BearerTokenRequestAuthenticator.java
@@ -19,6 +19,7 @@ package org.keycloak.adapters;
 
 import org.jboss.logging.Logger;
 import org.keycloak.RSATokenVerifier;
+import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
 import org.keycloak.adapters.spi.AuthChallenge;
 import org.keycloak.adapters.spi.AuthOutcome;
 import org.keycloak.adapters.spi.HttpFacade;
@@ -84,7 +85,7 @@ public class BearerTokenRequestAuthenticator {
     
     protected AuthOutcome authenticateToken(HttpFacade exchange, String tokenString) {
         try {
-            token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
+            token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
         } catch (VerificationException e) {
             log.error("Failed to verify token", e);
             challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.INVALID_TOKEN, "invalid_token", e.getMessage());
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/CookieTokenStore.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/CookieTokenStore.java
index 7f66dbf..2093645 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/CookieTokenStore.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/CookieTokenStore.java
@@ -22,6 +22,7 @@ import java.io.IOException;
 import org.jboss.logging.Logger;
 import org.keycloak.KeycloakPrincipal;
 import org.keycloak.RSATokenVerifier;
+import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
 import org.keycloak.adapters.spi.HttpFacade;
 import org.keycloak.common.VerificationException;
 import org.keycloak.constants.AdapterConstants;
@@ -73,7 +74,7 @@ public class CookieTokenStore {
 
         try {
             // Skip check if token is active now. It's supposed to be done later by the caller
-            AccessToken accessToken = RSATokenVerifier.verifyToken(accessTokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl(), false, true);
+            AccessToken accessToken = AdapterRSATokenVerifier.verifyToken(accessTokenString, deployment, false, true);
             IDToken idToken;
             if (idTokenString != null && idTokenString.length() > 0) {
                 try {
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpAdapterUtils.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpAdapterUtils.java
new file mode 100644
index 0000000..e01f7dc
--- /dev/null
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpAdapterUtils.java
@@ -0,0 +1,79 @@
+/*
+ * 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.adapters;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpRequestBase;
+import org.keycloak.util.JsonSerialization;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class HttpAdapterUtils {
+
+
+    public static <T> T sendJsonHttpRequest(KeycloakDeployment deployment, HttpRequestBase httpRequest, Class<T> clazz) throws HttpClientAdapterException {
+        try {
+            HttpResponse response = deployment.getClient().execute(httpRequest);
+            int status = response.getStatusLine().getStatusCode();
+            if (status != 200) {
+                close(response);
+                throw new HttpClientAdapterException("Unexpected status = " + status);
+            }
+            HttpEntity entity = response.getEntity();
+            if (entity == null) {
+                throw new HttpClientAdapterException("There was no entity.");
+            }
+            InputStream is = entity.getContent();
+            try {
+                ByteArrayOutputStream os = new ByteArrayOutputStream();
+                int c;
+                while ((c = is.read()) != -1) {
+                    os.write(c);
+                }
+                byte[] bytes = os.toByteArray();
+                String json = new String(bytes);
+                return JsonSerialization.readValue(json, clazz);
+            } finally {
+                try {
+                    is.close();
+                } catch (IOException ignored) {
+
+                }
+            }
+        } catch (IOException e) {
+            throw new HttpClientAdapterException("IO error", e);
+        }
+    }
+
+
+    private static void close(HttpResponse response) {
+        if (response.getEntity() != null) {
+            try {
+                response.getEntity().getContent().close();
+            } catch (IOException e) {
+
+            }
+        }
+    }
+}
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpClientAdapterException.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpClientAdapterException.java
new file mode 100644
index 0000000..7d303e2
--- /dev/null
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpClientAdapterException.java
@@ -0,0 +1,32 @@
+/*
+ * 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.adapters;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class HttpClientAdapterException extends Exception {
+
+    public HttpClientAdapterException(String message) {
+        super(message);
+    }
+
+    public HttpClientAdapterException(String message, Throwable t) {
+        super(message, t);
+    }
+}
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/jaas/AbstractKeycloakLoginModule.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/jaas/AbstractKeycloakLoginModule.java
index 51be551..75086e7 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/jaas/AbstractKeycloakLoginModule.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/jaas/AbstractKeycloakLoginModule.java
@@ -43,6 +43,7 @@ import org.keycloak.adapters.AdapterUtils;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.KeycloakDeploymentBuilder;
 import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
 import org.keycloak.common.VerificationException;
 import org.keycloak.common.util.FindFile;
 import org.keycloak.representations.AccessToken;
@@ -88,9 +89,6 @@ public abstract class AbstractKeycloakLoginModule implements LoginModule {
         try {
             InputStream is = FindFile.findFile(keycloakConfigFile);
             KeycloakDeployment kd = KeycloakDeploymentBuilder.build(is);
-            if (kd.getRealmKey() == null) {
-                new AdapterDeploymentContext().resolveRealmKey(kd);
-            }
             return kd;
         } catch (RuntimeException e) {
             getLogger().debug("Unable to find or parse file " + keycloakConfigFile + " due to " + e.getMessage(), e);
@@ -190,7 +188,7 @@ public abstract class AbstractKeycloakLoginModule implements LoginModule {
 
 
     protected Auth bearerAuth(String tokenString) throws VerificationException {
-        AccessToken token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
+        AccessToken token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
 
         boolean verifyCaller;
         if (deployment.isUseResourceRoleMappings()) {
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
index 901b3ea..30c40c3 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
@@ -21,6 +21,7 @@ import org.apache.http.client.HttpClient;
 import org.jboss.logging.Logger;
 import org.keycloak.adapters.authentication.ClientCredentialsProvider;
 import org.keycloak.adapters.authorization.PolicyEnforcer;
+import org.keycloak.adapters.rotation.PublicKeyLocator;
 import org.keycloak.constants.ServiceUrlConstants;
 import org.keycloak.common.enums.RelativeUrlsUsed;
 import org.keycloak.common.enums.SslRequired;
@@ -29,7 +30,6 @@ import org.keycloak.representations.adapters.config.AdapterConfig;
 import org.keycloak.common.util.KeycloakUriBuilder;
 
 import java.net.URI;
-import java.security.PublicKey;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -43,7 +43,7 @@ public class KeycloakDeployment {
 
     protected RelativeUrlsUsed relativeUrls;
     protected String realm;
-    protected volatile PublicKey realmKey;
+    protected PublicKeyLocator publicKeyLocator;
     protected String authServerBaseUrl;
     protected String realmInfoUrl;
     protected KeycloakUriBuilder authUrl;
@@ -52,6 +52,7 @@ public class KeycloakDeployment {
     protected String accountUrl;
     protected String registerNodeUrl;
     protected String unregisterNodeUrl;
+    protected String jwksUrl;
     protected String principalAttribute = "sub";
 
     protected String resourceName;
@@ -79,13 +80,14 @@ public class KeycloakDeployment {
 
     protected volatile int notBefore;
     protected int tokenMinimumTimeToLive;
+    protected int minTimeBetweenJwksRequests;
     private PolicyEnforcer policyEnforcer;
 
     public KeycloakDeployment() {
     }
 
     public boolean isConfigured() {
-        return getRealm() != null && getRealmKey() != null && (isBearerOnly() || getAuthServerBaseUrl() != null);
+        return getRealm() != null && getPublicKeyLocator() != null && (isBearerOnly() || getAuthServerBaseUrl() != null);
     }
 
     public String getResourceName() {
@@ -100,12 +102,12 @@ public class KeycloakDeployment {
         this.realm = realm;
     }
 
-    public PublicKey getRealmKey() {
-        return realmKey;
+    public PublicKeyLocator getPublicKeyLocator() {
+        return publicKeyLocator;
     }
 
-    public void setRealmKey(PublicKey realmKey) {
-        this.realmKey = realmKey;
+    public void setPublicKeyLocator(PublicKeyLocator publicKeyLocator) {
+        this.publicKeyLocator = publicKeyLocator;
     }
 
     public String getAuthServerBaseUrl() {
@@ -147,6 +149,7 @@ public class KeycloakDeployment {
         accountUrl = authUrlBuilder.clone().path(ServiceUrlConstants.ACCOUNT_SERVICE_PATH).build(getRealm()).toString();
         registerNodeUrl = authUrlBuilder.clone().path(ServiceUrlConstants.CLIENTS_MANAGEMENT_REGISTER_NODE_PATH).build(getRealm()).toString();
         unregisterNodeUrl = authUrlBuilder.clone().path(ServiceUrlConstants.CLIENTS_MANAGEMENT_UNREGISTER_NODE_PATH).build(getRealm()).toString();
+        jwksUrl = authUrlBuilder.clone().path(ServiceUrlConstants.JWKS_URL).build(getRealm()).toString();
     }
 
     public RelativeUrlsUsed getRelativeUrls() {
@@ -181,6 +184,10 @@ public class KeycloakDeployment {
         return unregisterNodeUrl;
     }
 
+    public String getJwksUrl() {
+        return jwksUrl;
+    }
+
     public void setResourceName(String resourceName) {
         this.resourceName = resourceName;
     }
@@ -369,6 +376,14 @@ public class KeycloakDeployment {
         this.tokenMinimumTimeToLive = tokenMinimumTimeToLive;
     }
 
+    public int getMinTimeBetweenJwksRequests() {
+        return minTimeBetweenJwksRequests;
+    }
+
+    public void setMinTimeBetweenJwksRequests(int minTimeBetweenJwksRequests) {
+        this.minTimeBetweenJwksRequests = minTimeBetweenJwksRequests;
+    }
+
     public void setPolicyEnforcer(PolicyEnforcer policyEnforcer) {
         this.policyEnforcer = policyEnforcer;
     }
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
index 5d54df9..04e16f8 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
@@ -22,6 +22,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import org.jboss.logging.Logger;
 import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
 import org.keycloak.adapters.authorization.PolicyEnforcer;
+import org.keycloak.adapters.rotation.HardcodedPublicKeyLocator;
+import org.keycloak.adapters.rotation.JWKPublicKeyLocator;
 import org.keycloak.common.enums.SslRequired;
 import org.keycloak.common.util.PemUtils;
 import org.keycloak.enums.TokenStore;
@@ -59,11 +61,16 @@ public class KeycloakDeploymentBuilder {
             PublicKey realmKey;
             try {
                 realmKey = PemUtils.decodePublicKey(realmKeyPem);
+                HardcodedPublicKeyLocator pkLocator = new HardcodedPublicKeyLocator(realmKey);
+                deployment.setPublicKeyLocator(pkLocator);
             } catch (Exception e) {
                 throw new RuntimeException(e);
             }
-            deployment.setRealmKey(realmKey);
+        } else {
+            JWKPublicKeyLocator pkLocator = new JWKPublicKeyLocator();
+            deployment.setPublicKeyLocator(pkLocator);
         }
+
         if (adapterConfig.getSslRequired() != null) {
             deployment.setSslRequired(SslRequired.valueOf(adapterConfig.getSslRequired().toUpperCase()));
         } else {
@@ -97,6 +104,7 @@ public class KeycloakDeploymentBuilder {
         deployment.setRegisterNodeAtStartup(adapterConfig.isRegisterNodeAtStartup());
         deployment.setRegisterNodePeriod(adapterConfig.getRegisterNodePeriod());
         deployment.setTokenMinimumTimeToLive(adapterConfig.getTokenMinimumTimeToLive());
+        deployment.setMinTimeBetweenJwksRequests(adapterConfig.getMinTimeBetweenJwksRequests());
 
         if (realmKeyPem == null && adapterConfig.isBearerOnly() && adapterConfig.getAuthServerUrl() == null) {
             throw new IllegalArgumentException("For bearer auth, you must set the realm-public-key or auth-server-url");
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
index 73aa0f5..02637c0 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
@@ -20,6 +20,7 @@ package org.keycloak.adapters;
 import org.jboss.logging.Logger;
 import org.keycloak.OAuth2Constants;
 import org.keycloak.RSATokenVerifier;
+import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
 import org.keycloak.adapters.spi.AdapterSessionStore;
 import org.keycloak.adapters.spi.AuthChallenge;
 import org.keycloak.adapters.spi.AuthOutcome;
@@ -342,7 +343,7 @@ public class OAuthRequestAuthenticator {
         refreshToken = tokenResponse.getRefreshToken();
         idTokenString = tokenResponse.getIdToken();
         try {
-            token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
+            token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
             if (idTokenString != null) {
                 try {
                     JWSInput input = new JWSInput(idTokenString);
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java
index 1a8c735..5a1df8c 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java
@@ -17,7 +17,10 @@
 
 package org.keycloak.adapters;
 
+import java.security.PublicKey;
+
 import org.jboss.logging.Logger;
+import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
 import org.keycloak.adapters.spi.HttpFacade;
 import org.keycloak.adapters.spi.UserSessionManagement;
 import org.keycloak.jose.jws.JWSInputException;
@@ -198,7 +201,8 @@ public class PreAuthActionsHandler {
 
         try {
             JWSInput input = new JWSInput(token);
-            if (RSAProvider.verify(input, deployment.getRealmKey())) {
+            PublicKey publicKey = AdapterRSATokenVerifier.getPublicKey(input, deployment);
+            if (RSAProvider.verify(input, publicKey)) {
                 return input;
             }
         } catch (JWSInputException ignore) {
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java
index 7cfc7a6..75f0cb8 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java
@@ -21,6 +21,7 @@ import org.jboss.logging.Logger;
 import org.keycloak.AuthorizationContext;
 import org.keycloak.KeycloakSecurityContext;
 import org.keycloak.RSATokenVerifier;
+import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
 import org.keycloak.common.VerificationException;
 import org.keycloak.common.util.Time;
 import org.keycloak.representations.AccessToken;
@@ -130,7 +131,7 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext 
         String tokenString = response.getToken();
         AccessToken token = null;
         try {
-            token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
+            token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
             log.debug("Token Verification succeeded!");
         } catch (VerificationException e) {
             log.error("failed verification of token");
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/AdapterRSATokenVerifier.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/AdapterRSATokenVerifier.java
new file mode 100644
index 0000000..c69ee38
--- /dev/null
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/AdapterRSATokenVerifier.java
@@ -0,0 +1,64 @@
+/*
+ * 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.adapters.rotation;
+
+import java.security.PublicKey;
+
+import org.jboss.logging.Logger;
+import org.keycloak.RSATokenVerifier;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.common.VerificationException;
+import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.representations.AccessToken;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class AdapterRSATokenVerifier {
+
+    private static final Logger log = Logger.getLogger(AdapterRSATokenVerifier.class);
+
+    public static AccessToken verifyToken(String tokenString, KeycloakDeployment deployment) throws VerificationException {
+        return verifyToken(tokenString, deployment, true, true);
+    }
+
+
+    public static PublicKey getPublicKey(JWSInput input, KeycloakDeployment deployment) throws VerificationException {
+        PublicKeyLocator pkLocator = deployment.getPublicKeyLocator();
+
+        PublicKey publicKey = pkLocator.getPublicKey(input, deployment);
+        if (publicKey == null) {
+            log.errorf("Didn't find publicKey for kid: %s", input.getHeader().getKeyId());
+            throw new VerificationException("Didn't find publicKey for specified kid");
+        }
+
+        return publicKey;
+    }
+
+    public static AccessToken verifyToken(String tokenString, KeycloakDeployment deployment, boolean checkActive, boolean checkTokenType) throws VerificationException {
+        JWSInput input;
+        try {
+            input = new JWSInput(tokenString);
+        } catch (Exception e) {
+            throw new VerificationException("Couldn't parse token", e);
+        }
+
+        PublicKey publicKey = getPublicKey(input, deployment);
+        return RSATokenVerifier.verifyToken(input, publicKey, deployment.getRealmInfoUrl(), checkActive, checkTokenType);
+    }
+}
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/HardcodedPublicKeyLocator.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/HardcodedPublicKeyLocator.java
new file mode 100644
index 0000000..40fb71a
--- /dev/null
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/HardcodedPublicKeyLocator.java
@@ -0,0 +1,40 @@
+/*
+ * 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.adapters.rotation;
+
+import java.security.PublicKey;
+
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.jose.jws.JWSInput;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class HardcodedPublicKeyLocator implements PublicKeyLocator {
+
+    private PublicKey publicKey;
+
+    public HardcodedPublicKeyLocator(PublicKey publicKey) {
+        this.publicKey = publicKey;
+    }
+
+    @Override
+    public PublicKey getPublicKey(JWSInput input, KeycloakDeployment deployment) {
+        return publicKey;
+    }
+}
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/JWKPublicKeyLocator.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/JWKPublicKeyLocator.java
new file mode 100644
index 0000000..5003923
--- /dev/null
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/JWKPublicKeyLocator.java
@@ -0,0 +1,107 @@
+/*
+ * 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.adapters.rotation;
+
+import java.security.PublicKey;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.http.client.methods.HttpGet;
+import org.jboss.logging.Logger;
+import org.keycloak.adapters.HttpAdapterUtils;
+import org.keycloak.adapters.HttpClientAdapterException;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.common.util.Time;
+import org.keycloak.jose.jwk.JSONWebKeySet;
+import org.keycloak.jose.jwk.JWK;
+import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.util.JWKSUtils;
+
+/**
+ * When needed, publicKeys are downloaded by sending request to realm's jwks_url
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class JWKPublicKeyLocator implements PublicKeyLocator {
+
+    private static final Logger log = Logger.getLogger(JWKPublicKeyLocator.class);
+
+    private Map<String, PublicKey> currentKeys = new ConcurrentHashMap<>();
+
+    private volatile int lastRequestTime = 0;
+
+    @Override
+    public PublicKey getPublicKey(JWSInput input, KeycloakDeployment deployment) {
+        String kid = input.getHeader().getKeyId();
+        return getPublicKey(kid, deployment);
+    }
+
+
+    private PublicKey getPublicKey(String kid, KeycloakDeployment deployment) {
+        int minTimeBetweenRequests = deployment.getMinTimeBetweenJwksRequests();
+
+        // Check if key is in cache.
+        PublicKey publicKey = currentKeys.get(kid);
+        if (publicKey != null) {
+            return publicKey;
+        }
+
+        int currentTime = Time.currentTime();
+
+        // Check if we are allowed to send request
+        if (currentTime > lastRequestTime + minTimeBetweenRequests) {
+            synchronized (this) {
+                currentTime = Time.currentTime();
+                if (currentTime > lastRequestTime + minTimeBetweenRequests) {
+                    sendRequest(deployment);
+                    lastRequestTime = currentTime;
+                } else {
+                    // TODO: debug
+                    log.infof("Won't send request to realm jwks url. Last request time was %d", lastRequestTime);
+                }
+            }
+        }
+
+        return currentKeys.get(kid);
+
+    }
+
+
+    private void sendRequest(KeycloakDeployment deployment) {
+        // Send the request
+        // TODO: trace or remove?
+        log.infof("Going to send request to retrieve new set of realm public keys for client %s", deployment.getResourceName());
+
+        HttpGet getMethod = new HttpGet(deployment.getJwksUrl());
+        try {
+            JSONWebKeySet jwks = HttpAdapterUtils.sendJsonHttpRequest(deployment, getMethod, JSONWebKeySet.class);
+
+            Map<String, PublicKey> publicKeys = JWKSUtils.getKeysForUse(jwks, JWK.Use.SIG);
+
+            // TODO: Debug with condition
+            log.infof("Realm public keys successfully retrieved for client %s. New kids: %s", deployment.getResourceName(), publicKeys.keySet().toString());
+
+            // Update current keys
+            currentKeys.clear();
+            currentKeys.putAll(publicKeys);
+
+        } catch (HttpClientAdapterException e) {
+            log.error("Error when sending request to retrieve realm keys", e);
+        }
+    }
+}
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/PublicKeyLocator.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/PublicKeyLocator.java
new file mode 100644
index 0000000..bda80dc
--- /dev/null
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/PublicKeyLocator.java
@@ -0,0 +1,37 @@
+/*
+ * 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.adapters.rotation;
+
+import java.security.PublicKey;
+
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.jose.jws.JWSInput;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface PublicKeyLocator {
+
+    /**
+     * @param input
+     * @param deployment
+     * @return publicKey, which should be used for verify signature on given "input"
+     */
+    PublicKey getPublicKey(JWSInput input, KeycloakDeployment deployment);
+
+}
diff --git a/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java b/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java
index 4be5110..d1ce748 100644
--- a/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java
+++ b/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java
@@ -21,6 +21,8 @@ import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
 import org.junit.Test;
 import org.keycloak.adapters.authentication.ClientIdAndSecretCredentialsProvider;
 import org.keycloak.adapters.authentication.JWTClientCredentialsProvider;
+import org.keycloak.adapters.rotation.HardcodedPublicKeyLocator;
+import org.keycloak.adapters.rotation.JWKPublicKeyLocator;
 import org.keycloak.common.enums.RelativeUrlsUsed;
 import org.keycloak.common.enums.SslRequired;
 import org.keycloak.enums.TokenStore;
@@ -39,7 +41,11 @@ public class KeycloakDeploymentBuilderTest {
         KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/keycloak.json"));
         assertEquals("demo", deployment.getRealm());
         assertEquals("customer-portal", deployment.getResourceName());
-        assertEquals(PemUtils.decodePublicKey("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB"), deployment.getRealmKey());
+
+        assertTrue(deployment.getPublicKeyLocator() instanceof HardcodedPublicKeyLocator);
+        assertEquals(PemUtils.decodePublicKey("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB"),
+                deployment.getPublicKeyLocator().getPublicKey(null, deployment));
+
         assertEquals("https://localhost:8443/auth/realms/demo/protocol/openid-connect/auth", deployment.getAuthUrl().build().toString());
         assertEquals(SslRequired.EXTERNAL, deployment.getSslRequired());
         assertTrue(deployment.isUseResourceRoleMappings());
@@ -62,12 +68,16 @@ public class KeycloakDeploymentBuilderTest {
         assertEquals(TokenStore.COOKIE, deployment.getTokenStore());
         assertEquals("email", deployment.getPrincipalAttribute());
         assertEquals(10, deployment.getTokenMinimumTimeToLive());
+        assertEquals(20, deployment.getMinTimeBetweenJwksRequests());
     }
 
     @Test
     public void loadNoClientCredentials() throws Exception {
         KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/keycloak-no-credentials.json"));
         assertEquals(ClientIdAndSecretCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
+
+        assertTrue(deployment.getPublicKeyLocator() instanceof JWKPublicKeyLocator);
+        assertEquals(10, deployment.getMinTimeBetweenJwksRequests());
     }
 
     @Test
diff --git a/adapters/oidc/adapter-core/src/test/resources/keycloak.json b/adapters/oidc/adapter-core/src/test/resources/keycloak.json
index 7bf269f..a8afd22 100644
--- a/adapters/oidc/adapter-core/src/test/resources/keycloak.json
+++ b/adapters/oidc/adapter-core/src/test/resources/keycloak.json
@@ -29,5 +29,6 @@
     "register-node-period": 1000,
     "token-store": "cookie",
     "principal-attribute": "email",
-    "token-minimum-time-to-live": 10
+    "token-minimum-time-to-live": 10,
+    "min-time-between-jwks-requests": 20
 }
\ No newline at end of file
diff --git a/adapters/oidc/adapter-core/src/test/resources/keycloak-no-credentials.json b/adapters/oidc/adapter-core/src/test/resources/keycloak-no-credentials.json
index 5f223ac..a3c4026 100644
--- a/adapters/oidc/adapter-core/src/test/resources/keycloak-no-credentials.json
+++ b/adapters/oidc/adapter-core/src/test/resources/keycloak-no-credentials.json
@@ -1,7 +1,6 @@
 {
     "realm": "demo",
     "resource": "customer-portal",
-    "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
     "auth-server-url": "https://localhost:8443/auth",
     "public-client": true,
     "expose-token": true
diff --git a/adapters/oidc/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java b/adapters/oidc/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java
index fc0e47f..c52a44a 100644
--- a/adapters/oidc/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java
+++ b/adapters/oidc/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java
@@ -20,6 +20,7 @@ package org.keycloak.adapters.installed;
 import org.keycloak.OAuth2Constants;
 import org.keycloak.OAuthErrorException;
 import org.keycloak.RSATokenVerifier;
+import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
 import org.keycloak.common.VerificationException;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.KeycloakDeploymentBuilder;
@@ -213,7 +214,7 @@ public class KeycloakInstalled {
         refreshToken = tokenResponse.getRefreshToken();
         idTokenString = tokenResponse.getIdToken();
 
-        token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
+        token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
         if (idTokenString != null) {
             try {
                 JWSInput input = new JWSInput(idTokenString);
diff --git a/core/src/main/java/org/keycloak/constants/ServiceUrlConstants.java b/core/src/main/java/org/keycloak/constants/ServiceUrlConstants.java
index a21f262..36b4f1d 100755
--- a/core/src/main/java/org/keycloak/constants/ServiceUrlConstants.java
+++ b/core/src/main/java/org/keycloak/constants/ServiceUrlConstants.java
@@ -30,5 +30,6 @@ public interface ServiceUrlConstants {
     public static final String REALM_INFO_PATH = "/realms/{realm-name}";
     public static final String CLIENTS_MANAGEMENT_REGISTER_NODE_PATH = "/realms/{realm-name}/clients-managements/register-node";
     public static final String CLIENTS_MANAGEMENT_UNREGISTER_NODE_PATH = "/realms/{realm-name}/clients-managements/unregister-node";
+    public static final String JWKS_URL = "/realms/{realm-name}/protocol/openid-connect/certs";
 
 }
diff --git a/core/src/main/java/org/keycloak/jose/jwk/JWKParser.java b/core/src/main/java/org/keycloak/jose/jwk/JWKParser.java
index 7a31f72..a20d253 100755
--- a/core/src/main/java/org/keycloak/jose/jwk/JWKParser.java
+++ b/core/src/main/java/org/keycloak/jose/jwk/JWKParser.java
@@ -21,7 +21,6 @@ import com.fasterxml.jackson.core.type.TypeReference;
 import org.keycloak.common.util.Base64Url;
 import org.keycloak.util.JsonSerialization;
 
-import java.io.InputStream;
 import java.math.BigInteger;
 import java.security.KeyFactory;
 import java.security.PublicKey;
@@ -66,8 +65,8 @@ public class JWKParser {
     }
 
     public PublicKey toPublicKey() {
-        String algorithm = jwk.getKeyType();
-        if (isAlgorithmSupported(algorithm)) {
+        String keyType = jwk.getKeyType();
+        if (isKeyTypeSupported(keyType)) {
             BigInteger modulus = new BigInteger(1, Base64Url.decode(jwk.getOtherClaims().get(RSAPublicJWK.MODULUS).toString()));
             BigInteger publicExponent = new BigInteger(1, Base64Url.decode(jwk.getOtherClaims().get(RSAPublicJWK.PUBLIC_EXPONENT).toString()));
 
@@ -77,12 +76,12 @@ public class JWKParser {
                 throw new RuntimeException(e);
             }
         } else {
-            throw new RuntimeException("Unsupported algorithm " + algorithm);
+            throw new RuntimeException("Unsupported keyType " + keyType);
         }
     }
 
-    public boolean isAlgorithmSupported(String algorithm) {
-        return RSAPublicJWK.RSA.equals(algorithm);
+    public boolean isKeyTypeSupported(String keyType) {
+        return RSAPublicJWK.RSA.equals(keyType);
     }
 
 }
diff --git a/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java b/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java
index 91fb5f0..c4818b4 100755
--- a/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java
+++ b/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java
@@ -36,7 +36,7 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder;
         "client-keystore", "client-keystore-password", "client-key-password",
         "always-refresh-token",
         "register-node-at-startup", "register-node-period", "token-store", "principal-attribute",
-        "proxy-url", "turn-off-change-session-id-on-login", "token-minimum-time-to-live",
+        "proxy-url", "turn-off-change-session-id-on-login", "token-minimum-time-to-live", "min-time-between-jwks-requests",
         "policy-enforcer"
 })
 public class AdapterConfig extends BaseAdapterConfig {
@@ -71,6 +71,8 @@ public class AdapterConfig extends BaseAdapterConfig {
     protected Boolean turnOffChangeSessionIdOnLogin;
     @JsonProperty("token-minimum-time-to-live")
     protected int tokenMinimumTimeToLive = 0;
+    @JsonProperty("min-time-between-jwks-requests")
+    protected int minTimeBetweenJwksRequests = 10;
     @JsonProperty("policy-enforcer")
     protected PolicyEnforcerConfig policyEnforcerConfig;
 
@@ -216,4 +218,11 @@ public class AdapterConfig extends BaseAdapterConfig {
         this.tokenMinimumTimeToLive = tokenMinimumTimeToLive;
     }
 
+    public int getMinTimeBetweenJwksRequests() {
+        return minTimeBetweenJwksRequests;
+    }
+
+    public void setMinTimeBetweenJwksRequests(int minTimeBetweenJwksRequests) {
+        this.minTimeBetweenJwksRequests = minTimeBetweenJwksRequests;
+    }
 }
diff --git a/core/src/main/java/org/keycloak/RSATokenVerifier.java b/core/src/main/java/org/keycloak/RSATokenVerifier.java
index 27a4301..562261d 100755
--- a/core/src/main/java/org/keycloak/RSATokenVerifier.java
+++ b/core/src/main/java/org/keycloak/RSATokenVerifier.java
@@ -38,6 +38,12 @@ public class RSATokenVerifier {
     public static AccessToken verifyToken(String tokenString, PublicKey realmKey, String realmUrl, boolean checkActive, boolean checkTokenType) throws VerificationException {
         AccessToken token = toAccessToken(tokenString, realmKey);
 
+        tokenVerifications(token, realmUrl, checkActive, checkTokenType);
+
+        return token;
+    }
+
+    private static void tokenVerifications(AccessToken token, String realmUrl, boolean checkActive, boolean checkTokenType) throws VerificationException {
         String user = token.getSubject();
         if (user == null) {
             throw new VerificationException("Token user was null.");
@@ -60,9 +66,9 @@ public class RSATokenVerifier {
             throw new VerificationException("Token is not active.");
         }
 
-        return token;
     }
 
+
     public static AccessToken toAccessToken(String tokenString, PublicKey realmKey) throws VerificationException {
         JWSInput input;
         try {
@@ -81,6 +87,23 @@ public class RSATokenVerifier {
         return token;
     }
 
+
+    public static AccessToken verifyToken(JWSInput input, PublicKey realmKey, String realmUrl, boolean checkActive, boolean checkTokenType) throws VerificationException {
+        if (!isPublicKeyValid(input, realmKey)) throw new VerificationException("Invalid token signature.");
+
+        AccessToken token;
+        try {
+            token = input.readJsonContent(AccessToken.class);
+        } catch (JWSInputException e) {
+            throw new VerificationException("Couldn't parse token signature", e);
+        }
+
+        tokenVerifications(token, realmUrl, checkActive, checkTokenType);
+
+        return token;
+    }
+
+
     private static boolean isPublicKeyValid(JWSInput input, PublicKey realmKey) throws VerificationException {
         try {
             return RSAProvider.verify(input, realmKey);
diff --git a/core/src/main/java/org/keycloak/util/JWKSUtils.java b/core/src/main/java/org/keycloak/util/JWKSUtils.java
new file mode 100644
index 0000000..72ffe91
--- /dev/null
+++ b/core/src/main/java/org/keycloak/util/JWKSUtils.java
@@ -0,0 +1,45 @@
+/*
+ * 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.util;
+
+import java.security.PublicKey;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.keycloak.jose.jwk.JSONWebKeySet;
+import org.keycloak.jose.jwk.JWK;
+import org.keycloak.jose.jwk.JWKParser;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class JWKSUtils {
+
+    public static Map<String, PublicKey> getKeysForUse(JSONWebKeySet keySet, JWK.Use requestedUse) {
+        Map<String, PublicKey> result = new HashMap<>();
+
+        for (JWK jwk : keySet.getKeys()) {
+            JWKParser parser = JWKParser.create(jwk);
+            if (jwk.getPublicKeyUse().equals(requestedUse.asString()) && parser.isKeyTypeSupported(jwk.getKeyType())) {
+                result.put(jwk.getKeyId(), parser.toPublicKey());
+            }
+        }
+
+        return result;
+    }
+}
diff --git a/examples/authz/hello-world-authz-service/src/main/webapp/WEB-INF/keycloak.json b/examples/authz/hello-world-authz-service/src/main/webapp/WEB-INF/keycloak.json
index a492837..b5a1bbf 100644
--- a/examples/authz/hello-world-authz-service/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/authz/hello-world-authz-service/src/main/webapp/WEB-INF/keycloak.json
@@ -1,6 +1,5 @@
 {
   "realm": "hello-world-authz",
-  "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
   "auth-server-url": "http://localhost:8080/auth",
   "ssl-required": "external",
   "resource": "hello-world-authz-service",
diff --git a/examples/authz/photoz/photoz-html5-client/src/main/webapp/keycloak.json b/examples/authz/photoz/photoz-html5-client/src/main/webapp/keycloak.json
index c1dee24..affafdd 100644
--- a/examples/authz/photoz/photoz-html5-client/src/main/webapp/keycloak.json
+++ b/examples/authz/photoz/photoz-html5-client/src/main/webapp/keycloak.json
@@ -1,6 +1,5 @@
 {
   "realm": "photoz",
-  "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
   "auth-server-url" : "http://localhost:8080/auth",
   "ssl-required" : "external",
   "resource" : "photoz-html5-client",
diff --git a/examples/authz/photoz/photoz-restful-api/src/main/webapp/WEB-INF/keycloak.json b/examples/authz/photoz/photoz-restful-api/src/main/webapp/WEB-INF/keycloak.json
index 6849d07..9e06730 100644
--- a/examples/authz/photoz/photoz-restful-api/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/authz/photoz/photoz-restful-api/src/main/webapp/WEB-INF/keycloak.json
@@ -1,6 +1,5 @@
 {
   "realm": "photoz",
-  "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
   "auth-server-url": "http://localhost:8080/auth",
   "ssl-required": "external",
   "resource": "photoz-restful-api",
diff --git a/examples/authz/servlet-authz/src/main/webapp/WEB-INF/keycloak.json b/examples/authz/servlet-authz/src/main/webapp/WEB-INF/keycloak.json
index eaffea8..f6b9c90 100644
--- a/examples/authz/servlet-authz/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/authz/servlet-authz/src/main/webapp/WEB-INF/keycloak.json
@@ -1,6 +1,5 @@
 {
   "realm": "servlet-authz",
-  "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
   "auth-server-url" : "http://localhost:8080/auth",
   "ssl-required" : "external",
   "resource" : "servlet-authz-app",
diff --git a/examples/basic-auth/src/main/webapp/WEB-INF/keycloak.json b/examples/basic-auth/src/main/webapp/WEB-INF/keycloak.json
index 4502199..9da7ed4 100644
--- a/examples/basic-auth/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/basic-auth/src/main/webapp/WEB-INF/keycloak.json
@@ -1,7 +1,6 @@
 {
   "realm" : "basic-auth",
   "resource" : "basic-auth-service",
-  "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
   "auth-server-url": "/auth",
   "ssl-required" : "external",
   "enable-basic-auth" : "true",
diff --git a/examples/broker/facebook-authentication/src/main/webapp/keycloak.json b/examples/broker/facebook-authentication/src/main/webapp/keycloak.json
index 55446af..17743f6 100644
--- a/examples/broker/facebook-authentication/src/main/webapp/keycloak.json
+++ b/examples/broker/facebook-authentication/src/main/webapp/keycloak.json
@@ -1,7 +1,6 @@
 {
   "realm" : "facebook-identity-provider-realm",
   "resource" : "facebook-authentication",
-  "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
   "auth-server-url": "/auth",
   "ssl-required" : "external",
   "public-client" : true
diff --git a/examples/broker/google-authentication/src/main/webapp/keycloak.json b/examples/broker/google-authentication/src/main/webapp/keycloak.json
index 93c25b4..f05da2f 100644
--- a/examples/broker/google-authentication/src/main/webapp/keycloak.json
+++ b/examples/broker/google-authentication/src/main/webapp/keycloak.json
@@ -1,7 +1,6 @@
 {
   "realm" : "google-identity-provider-realm",
   "resource" : "google-authentication",
-  "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
   "auth-server-url": "/auth",
   "ssl-required" : "external",
   "public-client" : true
diff --git a/examples/broker/twitter-authentication/src/main/webapp/keycloak.json b/examples/broker/twitter-authentication/src/main/webapp/keycloak.json
index 7243636..2f74514 100644
--- a/examples/broker/twitter-authentication/src/main/webapp/keycloak.json
+++ b/examples/broker/twitter-authentication/src/main/webapp/keycloak.json
@@ -1,7 +1,6 @@
 {
   "realm" : "twitter-identity-provider-realm",
   "resource" : "twitter-authentication",
-  "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
   "auth-server-url": "/auth",
   "ssl-required" : "external",
   "public-client" : true
diff --git a/examples/cors/angular-product-app/src/main/webapp/keycloak.json b/examples/cors/angular-product-app/src/main/webapp/keycloak.json
index 40b35a1..d685459 100755
--- a/examples/cors/angular-product-app/src/main/webapp/keycloak.json
+++ b/examples/cors/angular-product-app/src/main/webapp/keycloak.json
@@ -1,6 +1,5 @@
 {
   "realm" : "cors",
-  "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
   "auth-server-url" : "http://localhost-auth:8080/auth",
   "ssl-required" : "external",
   "resource" : "angular-cors-product",
diff --git a/examples/cors/database-service/src/main/webapp/WEB-INF/keycloak.json b/examples/cors/database-service/src/main/webapp/WEB-INF/keycloak.json
index 265049f..61da408 100755
--- a/examples/cors/database-service/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/cors/database-service/src/main/webapp/WEB-INF/keycloak.json
@@ -1,7 +1,6 @@
 {
   "realm" : "cors",
   "resource" : "cors-database-service",
-  "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
   "auth-server-url": "http://localhost-auth:8080/auth",
   "bearer-only" : true,
   "ssl-required": "external",
diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/keycloak.json b/examples/demo-template/angular2-product-app/src/main/webapp/keycloak.json
index ddd1f2e..87a2ad6 100644
--- a/examples/demo-template/angular2-product-app/src/main/webapp/keycloak.json
+++ b/examples/demo-template/angular2-product-app/src/main/webapp/keycloak.json
@@ -1,6 +1,5 @@
 {
   "realm": "demo",
-  "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
   "auth-server-url": "/auth",
   "ssl-required": "external",
   "resource": "angular2-product",
diff --git a/examples/demo-template/angular-product-app/src/main/webapp/keycloak.json b/examples/demo-template/angular-product-app/src/main/webapp/keycloak.json
index 72ecb5b..174053e 100755
--- a/examples/demo-template/angular-product-app/src/main/webapp/keycloak.json
+++ b/examples/demo-template/angular-product-app/src/main/webapp/keycloak.json
@@ -1,6 +1,5 @@
 {
   "realm" : "demo",
-  "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
   "auth-server-url" : "/auth",
   "ssl-required" : "external",
   "resource" : "angular-product",
diff --git a/examples/demo-template/customer-app/src/main/webapp/WEB-INF/keycloak.json b/examples/demo-template/customer-app/src/main/webapp/WEB-INF/keycloak.json
index c2241b3..2061909 100755
--- a/examples/demo-template/customer-app/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/demo-template/customer-app/src/main/webapp/WEB-INF/keycloak.json
@@ -1,7 +1,6 @@
 {
     "realm": "demo",
     "resource": "customer-portal",
-    "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
     "auth-server-url": "/auth",
     "ssl-required" : "external",
     "expose-token": true,
diff --git a/examples/demo-template/customer-app-cli/src/main/resources/META-INF/keycloak.json b/examples/demo-template/customer-app-cli/src/main/resources/META-INF/keycloak.json
index 51c8775..1c61433 100644
--- a/examples/demo-template/customer-app-cli/src/main/resources/META-INF/keycloak.json
+++ b/examples/demo-template/customer-app-cli/src/main/resources/META-INF/keycloak.json
@@ -1,6 +1,5 @@
 {
   "realm" : "demo",
-  "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
   "auth-server-url" : "http://localhost:8080/auth",
   "ssl-required" : "external",
   "resource" : "customer-portal-cli",
diff --git a/examples/demo-template/customer-app-filter/src/main/webapp/WEB-INF/keycloak.json b/examples/demo-template/customer-app-filter/src/main/webapp/WEB-INF/keycloak.json
index 14e56f5..3766330 100755
--- a/examples/demo-template/customer-app-filter/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/demo-template/customer-app-filter/src/main/webapp/WEB-INF/keycloak.json
@@ -1,7 +1,6 @@
 {
     "realm": "demo",
     "resource": "customer-portal-filter",
-    "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
     "auth-server-url": "/auth",
     "ssl-required" : "external",
     "expose-token": true,
diff --git a/examples/demo-template/customer-app-js/src/main/webapp/keycloak.json b/examples/demo-template/customer-app-js/src/main/webapp/keycloak.json
index 224c70b..4a60ffa 100644
--- a/examples/demo-template/customer-app-js/src/main/webapp/keycloak.json
+++ b/examples/demo-template/customer-app-js/src/main/webapp/keycloak.json
@@ -1,6 +1,5 @@
 {
   "realm" : "demo",
-  "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
   "auth-server-url" : "/auth",
   "ssl-required" : "external",
   "resource" : "customer-portal-js",
diff --git a/examples/demo-template/database-service/src/main/webapp/WEB-INF/keycloak.json b/examples/demo-template/database-service/src/main/webapp/WEB-INF/keycloak.json
index cb93854..72e4903 100755
--- a/examples/demo-template/database-service/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/demo-template/database-service/src/main/webapp/WEB-INF/keycloak.json
@@ -1,7 +1,6 @@
 {
   "realm" : "demo",
   "resource" : "database-service",
-  "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
   "auth-server-url": "/auth",
   "bearer-only" : true,
   "ssl-required" : "external"
diff --git a/examples/demo-template/offline-access-app/src/main/webapp/WEB-INF/keycloak.json b/examples/demo-template/offline-access-app/src/main/webapp/WEB-INF/keycloak.json
index dff976c..600dcfa 100644
--- a/examples/demo-template/offline-access-app/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/demo-template/offline-access-app/src/main/webapp/WEB-INF/keycloak.json
@@ -1,7 +1,6 @@
 {
   "realm": "demo",
   "resource": "offline-access-portal",
-  "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
   "auth-server-url": "/auth",
   "ssl-required" : "external",
   "credentials": {
diff --git a/examples/demo-template/product-app/src/main/webapp/WEB-INF/keycloak.json b/examples/demo-template/product-app/src/main/webapp/WEB-INF/keycloak.json
index 0a86c04..1092701 100755
--- a/examples/demo-template/product-app/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/demo-template/product-app/src/main/webapp/WEB-INF/keycloak.json
@@ -1,7 +1,6 @@
 {
   "realm" : "demo",
   "resource" : "product-portal",
-  "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
   "auth-server-url" : "/auth",
   "ssl-required" : "external",
   "credentials" : {
diff --git a/examples/demo-template/service-account/src/main/java/org/keycloak/example/ProductServiceAccountServlet.java b/examples/demo-template/service-account/src/main/java/org/keycloak/example/ProductServiceAccountServlet.java
index e4d6a40..72ffd49 100644
--- a/examples/demo-template/service-account/src/main/java/org/keycloak/example/ProductServiceAccountServlet.java
+++ b/examples/demo-template/service-account/src/main/java/org/keycloak/example/ProductServiceAccountServlet.java
@@ -41,6 +41,7 @@ import org.apache.http.impl.client.DefaultHttpClient;
 import org.apache.http.message.BasicNameValuePair;
 import org.keycloak.OAuth2Constants;
 import org.keycloak.RSATokenVerifier;
+import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
 import org.keycloak.common.VerificationException;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.KeycloakDeploymentBuilder;
@@ -163,7 +164,7 @@ public abstract class ProductServiceAccountServlet extends HttpServlet {
     private void setTokens(HttpServletRequest req, KeycloakDeployment deployment, AccessTokenResponse tokenResponse) throws IOException, VerificationException {
         String token = tokenResponse.getToken();
         String refreshToken = tokenResponse.getRefreshToken();
-        AccessToken tokenParsed = RSATokenVerifier.verifyToken(token, deployment.getRealmKey(), deployment.getRealmInfoUrl());
+        AccessToken tokenParsed = AdapterRSATokenVerifier.verifyToken(token, deployment);
         req.getSession().setAttribute(TOKEN, token);
         req.getSession().setAttribute(REFRESH_TOKEN, refreshToken);
         req.getSession().setAttribute(TOKEN_PARSED, tokenParsed);
diff --git a/examples/demo-template/service-account/src/main/webapp/WEB-INF/keycloak-client-secret.json b/examples/demo-template/service-account/src/main/webapp/WEB-INF/keycloak-client-secret.json
index 7eec22a..1a9322d 100644
--- a/examples/demo-template/service-account/src/main/webapp/WEB-INF/keycloak-client-secret.json
+++ b/examples/demo-template/service-account/src/main/webapp/WEB-INF/keycloak-client-secret.json
@@ -1,6 +1,5 @@
 {
   "realm" : "demo",
-  "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
   "auth-server-url" : "http://localhost:8080/auth",
   "ssl-required" : "external",
   "resource" : "product-sa-client",
diff --git a/examples/demo-template/service-account/src/main/webapp/WEB-INF/keycloak-client-signed-jwt.json b/examples/demo-template/service-account/src/main/webapp/WEB-INF/keycloak-client-signed-jwt.json
index 3e90c34..3c99da7 100644
--- a/examples/demo-template/service-account/src/main/webapp/WEB-INF/keycloak-client-signed-jwt.json
+++ b/examples/demo-template/service-account/src/main/webapp/WEB-INF/keycloak-client-signed-jwt.json
@@ -1,6 +1,5 @@
 {
   "realm" : "demo",
-  "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
   "auth-server-url" : "http://localhost:8080/auth",
   "ssl-required" : "external",
   "resource" : "product-sa-client-jwt-auth",
diff --git a/examples/fuse/camel/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/examples/fuse/camel/src/main/resources/OSGI-INF/blueprint/blueprint.xml
index a4796cc..56550d6 100644
--- a/examples/fuse/camel/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ b/examples/fuse/camel/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -26,7 +26,6 @@
     <bean id="kcAdapterConfig" class="org.keycloak.representations.adapters.config.AdapterConfig">
         <property name="realm" value="demo"/>
         <property name="resource" value="admin-camel-endpoint"/>
-        <property name="realmKey" value="MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB"/>
         <property name="bearerOnly" value="true"/>
         <property name="authServerUrl" value="http://localhost:8080/auth" />
         <property name="sslRequired" value="EXTERNAL"/>
diff --git a/examples/fuse/customer-app-fuse/src/main/webapp/WEB-INF/keycloak.json b/examples/fuse/customer-app-fuse/src/main/webapp/WEB-INF/keycloak.json
index b5d6b30..c7f61f6 100755
--- a/examples/fuse/customer-app-fuse/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/fuse/customer-app-fuse/src/main/webapp/WEB-INF/keycloak.json
@@ -1,7 +1,6 @@
 {
     "realm": "demo",
     "resource": "customer-portal",
-    "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
     "auth-server-url": "http://localhost:8080/auth",
     "ssl-required" : "external",
     "credentials": {
diff --git a/examples/fuse/cxf-jaxrs/src/main/resources/WEB-INF/keycloak.json b/examples/fuse/cxf-jaxrs/src/main/resources/WEB-INF/keycloak.json
index f5d7e1a..a8c4b75 100644
--- a/examples/fuse/cxf-jaxrs/src/main/resources/WEB-INF/keycloak.json
+++ b/examples/fuse/cxf-jaxrs/src/main/resources/WEB-INF/keycloak.json
@@ -1,7 +1,6 @@
 {
     "realm": "demo",
     "resource": "builtin-cxf-app",
-    "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
     "auth-server-url": "http://localhost:8080/auth",
     "ssl-required" : "external",
     "credentials": {
diff --git a/examples/fuse/cxf-jaxws/src/main/resources/META-INF/spring/beans.xml b/examples/fuse/cxf-jaxws/src/main/resources/META-INF/spring/beans.xml
index 2d15ae0..8a808c6 100644
--- a/examples/fuse/cxf-jaxws/src/main/resources/META-INF/spring/beans.xml
+++ b/examples/fuse/cxf-jaxws/src/main/resources/META-INF/spring/beans.xml
@@ -32,7 +32,6 @@
     <bean id="kcAdapterConfig" class="org.keycloak.representations.adapters.config.AdapterConfig">
         <property name="realm" value="demo"/>
         <property name="resource" value="custom-cxf-endpoint"/>
-        <property name="realmKey" value="MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB"/>
         <property name="bearerOnly" value="true"/>
         <property name="authServerUrl" value="http://localhost:8080/auth" />
         <property name="sslRequired" value="EXTERNAL"/>
diff --git a/examples/fuse/external-config/external-config-keycloak.json b/examples/fuse/external-config/external-config-keycloak.json
index 920e99a..469da82 100644
--- a/examples/fuse/external-config/external-config-keycloak.json
+++ b/examples/fuse/external-config/external-config-keycloak.json
@@ -1,7 +1,6 @@
 {
     "realm": "demo",
     "resource": "external-config",
-    "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
     "auth-server-url": "http://localhost:8080/auth",
     "ssl-required" : "external",
     "credentials": {
diff --git a/examples/fuse/fuse-admin/keycloak-direct-access.json b/examples/fuse/fuse-admin/keycloak-direct-access.json
index 8e5ac0f..2441134 100644
--- a/examples/fuse/fuse-admin/keycloak-direct-access.json
+++ b/examples/fuse/fuse-admin/keycloak-direct-access.json
@@ -1,7 +1,6 @@
 {
     "realm": "demo",
     "resource": "ssh-jmx-admin-client",
-    "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
     "ssl-required" : "external",
     "auth-server-url" : "http://localhost:8080/auth",
     "credentials": {
diff --git a/examples/fuse/product-app-fuse/src/main/resources/WEB-INF/keycloak.json b/examples/fuse/product-app-fuse/src/main/resources/WEB-INF/keycloak.json
index 2a52d24..e90433a 100644
--- a/examples/fuse/product-app-fuse/src/main/resources/WEB-INF/keycloak.json
+++ b/examples/fuse/product-app-fuse/src/main/resources/WEB-INF/keycloak.json
@@ -1,7 +1,6 @@
 {
     "realm": "demo",
     "resource": "product-portal",
-    "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
     "auth-server-url": "http://localhost:8080/auth",
     "ssl-required" : "external",
     "credentials": {
diff --git a/examples/js-console/src/main/webapp/keycloak.json b/examples/js-console/src/main/webapp/keycloak.json
index c0c04d5..cc4bab3 100644
--- a/examples/js-console/src/main/webapp/keycloak.json
+++ b/examples/js-console/src/main/webapp/keycloak.json
@@ -1,6 +1,5 @@
 {
   "realm" : "example",
-  "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
   "auth-server-url" : "/auth",
   "ssl-required" : "external",
   "resource" : "js-console",
diff --git a/examples/kerberos/src/main/webapp/WEB-INF/keycloak.json b/examples/kerberos/src/main/webapp/WEB-INF/keycloak.json
index db1223c..7e9d91a 100644
--- a/examples/kerberos/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/kerberos/src/main/webapp/WEB-INF/keycloak.json
@@ -1,7 +1,6 @@
 {
   "realm" : "kerberos-demo",
   "resource" : "kerberos-app",
-  "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
   "auth-server-url": "/auth",
   "ssl-required" : "external",
   "credentials": {
diff --git a/examples/ldap/src/main/webapp/WEB-INF/keycloak.json b/examples/ldap/src/main/webapp/WEB-INF/keycloak.json
index 84e1129..f43107b 100644
--- a/examples/ldap/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/ldap/src/main/webapp/WEB-INF/keycloak.json
@@ -1,7 +1,6 @@
 {
   "realm" : "ldap-demo",
   "resource" : "ldap-app",
-  "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
   "auth-server-url": "/auth",
   "ssl-required" : "external",
   "credentials": {
diff --git a/examples/multi-tenant/src/main/resources/tenant1-keycloak.json b/examples/multi-tenant/src/main/resources/tenant1-keycloak.json
index 57be277..34c1773 100644
--- a/examples/multi-tenant/src/main/resources/tenant1-keycloak.json
+++ b/examples/multi-tenant/src/main/resources/tenant1-keycloak.json
@@ -1,7 +1,6 @@
 {
   "realm" : "tenant1",
   "resource" : "multi-tenant",
-  "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
   "auth-server-url" : "http://localhost:8080/auth",
   "ssl-required" : "external",
   "credentials" : {
diff --git a/examples/multi-tenant/src/main/resources/tenant2-keycloak.json b/examples/multi-tenant/src/main/resources/tenant2-keycloak.json
index 4f221dc..5877082 100644
--- a/examples/multi-tenant/src/main/resources/tenant2-keycloak.json
+++ b/examples/multi-tenant/src/main/resources/tenant2-keycloak.json
@@ -1,7 +1,6 @@
 {
   "realm" : "tenant2",
   "resource" : "multi-tenant",
-  "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDA0oJjgPQJhnVhOo51KauQGfLLreMFu64OJdKXRnfvAQJQTuKNwc5JrR63l/byyW1B6FgclABF818TtLvMCAkn4EuFwQZCZhg3x3+lFGiB/IzC6UAt4Bi0JQrTbdh83/U97GIPegvaDqiqEiQESEkbCZWxM6sh/34hQaAhCaFpMwIDAQAB",
   "auth-server-url" : "http://localhost:8080/auth",
   "ssl-required" : "external",
   "credentials" : {
diff --git a/server-spi/src/main/java/org/keycloak/models/Constants.java b/server-spi/src/main/java/org/keycloak/models/Constants.java
index 42982f6..edc9567 100755
--- a/server-spi/src/main/java/org/keycloak/models/Constants.java
+++ b/server-spi/src/main/java/org/keycloak/models/Constants.java
@@ -51,4 +51,7 @@ public interface Constants {
 
     // Prefix for user attributes used in various "context"data maps
     String USER_ATTRIBUTES_PREFIX = "user.attributes.";
+
+    // Indication to admin-rest-endpoint that realm keys should be re-generated
+    String GENERATE = "GENERATE";
 }
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index ac285a8..51b1f4e 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -820,7 +820,7 @@ public class RepresentationToModel {
             realm.setUserFederationProviders(providerModels);
         }
 
-        if ("GENERATE".equals(rep.getPublicKey())) {
+        if (Constants.GENERATE.equals(rep.getPublicKey())) {
             KeycloakModelUtils.generateRealmKeys(realm);
         } else {
             if (rep.getPrivateKey() != null && rep.getPublicKey() != null) {
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCClientInstallation.java b/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCClientInstallation.java
index 6fda3f0..79a3919 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCClientInstallation.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCClientInstallation.java
@@ -51,7 +51,6 @@ public class KeycloakOIDCClientInstallation implements ClientInstallationProvide
         ClientManager.InstallationAdapterConfig rep = new ClientManager.InstallationAdapterConfig();
         rep.setAuthServerUrl(baseUri.toString());
         rep.setRealm(realm.getName());
-        rep.setRealmKey(realm.getPublicKeyPem());
         rep.setSslRequired(realm.getSslRequired().name().toLowerCase());
 
         if (client.isPublicClient() && !client.isBearerOnly()) rep.setPublicClient(true);
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCJbossSubsystemClientInstallation.java b/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCJbossSubsystemClientInstallation.java
index 33ab677..d0bc939 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCJbossSubsystemClientInstallation.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCJbossSubsystemClientInstallation.java
@@ -40,7 +40,6 @@ public class KeycloakOIDCJbossSubsystemClientInstallation implements ClientInsta
         StringBuffer buffer = new StringBuffer();
         buffer.append("<secure-deployment name=\"WAR MODULE NAME.war\">\n");
         buffer.append("    <realm>").append(realm.getName()).append("</realm>\n");
-        buffer.append("    <realm-public-key>").append(realm.getPublicKeyPem()).append("</realm-public-key>\n");
         buffer.append("    <auth-server-url>").append(baseUri.toString()).append("</auth-server-url>\n");
         if (client.isBearerOnly()){
             buffer.append("    <bearer-only>true</bearer-only>\n");
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/JWKSUtils.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/JWKSUtils.java
index d8f7fe7..62d2c58 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/utils/JWKSUtils.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/JWKSUtils.java
@@ -30,6 +30,8 @@ import org.keycloak.models.KeycloakSession;
 import org.keycloak.util.JsonSerialization;
 
 /**
+ * TODO: Merge with JWKSUtils from keycloak-core?
+ *
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
 public class JWKSUtils {
@@ -44,7 +46,7 @@ public class JWKSUtils {
     public static PublicKey getKeyForUse(JSONWebKeySet keySet, JWK.Use requestedUse) {
         for (JWK jwk : keySet.getKeys()) {
             JWKParser parser = JWKParser.create(jwk);
-            if (parser.getJwk().getPublicKeyUse().equals(requestedUse.asString()) && parser.isAlgorithmSupported(jwk.getKeyType())) {
+            if (parser.getJwk().getPublicKeyUse().equals(requestedUse.asString()) && parser.isKeyTypeSupported(jwk.getKeyType())) {
                 return parser.toPublicKey();
             }
         }
diff --git a/services/src/main/java/org/keycloak/services/managers/ClientManager.java b/services/src/main/java/org/keycloak/services/managers/ClientManager.java
index 7bdf5b1..3de62db 100644
--- a/services/src/main/java/org/keycloak/services/managers/ClientManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ClientManager.java
@@ -265,7 +265,6 @@ public class ClientManager {
         InstallationAdapterConfig rep = new InstallationAdapterConfig();
         rep.setAuthServerUrl(baseUri.toString());
         rep.setRealm(realmModel.getName());
-        rep.setRealmKey(realmModel.getPublicKeyPem());
         rep.setSslRequired(realmModel.getSslRequired().name().toLowerCase());
 
         if (clientModel.isPublicClient() && !clientModel.isBearerOnly()) rep.setPublicClient(true);
@@ -286,7 +285,6 @@ public class ClientManager {
         StringBuffer buffer = new StringBuffer();
         buffer.append("<secure-deployment name=\"WAR MODULE NAME.war\">\n");
         buffer.append("    <realm>").append(realmModel.getName()).append("</realm>\n");
-        buffer.append("    <realm-public-key>").append(realmModel.getPublicKeyPem()).append("</realm-public-key>\n");
         buffer.append("    <auth-server-url>").append(baseUri.toString()).append("</auth-server-url>\n");
         if (clientModel.isBearerOnly()){
             buffer.append("    <bearer-only>true</bearer-only>\n");
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
index 7d49223..1caa5fb 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
@@ -35,6 +35,7 @@ import org.keycloak.events.admin.ResourceType;
 import org.keycloak.exportimport.ClientDescriptionConverter;
 import org.keycloak.exportimport.ClientDescriptionConverterFactory;
 import org.keycloak.models.ClientModel;
+import org.keycloak.models.Constants;
 import org.keycloak.models.GroupModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ModelDuplicateException;
@@ -281,7 +282,7 @@ public class RealmAdminResource {
 
         logger.debug("updating realm: " + realm.getName());
         try {
-            if (!"GENERATE".equals(rep.getPublicKey()) && (rep.getPrivateKey() != null && rep.getPublicKey() != null)) {
+            if (!Constants.GENERATE.equals(rep.getPublicKey()) && (rep.getPrivateKey() != null && rep.getPublicKey() != null)) {
                 try {
                     KeyPairVerifier.verify(rep.getPrivateKey(), rep.getPublicKey());
                 } catch (VerificationException e) {
@@ -289,7 +290,7 @@ public class RealmAdminResource {
                 }
             }
 
-            if (!"GENERATE".equals(rep.getPublicKey()) && (rep.getCertificate() != null)) {
+            if (!Constants.GENERATE.equals(rep.getPublicKey()) && (rep.getCertificate() != null)) {
                 try {
                     X509Certificate cert = PemUtils.decodeCertificate(rep.getCertificate());
                     if (cert == null) {
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/filter/AdapterActionsFilter.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/filter/AdapterActionsFilter.java
index c282dff..aad5bb8 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/filter/AdapterActionsFilter.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/filter/AdapterActionsFilter.java
@@ -27,8 +27,13 @@ import javax.servlet.FilterConfig;
 import javax.servlet.ServletException;
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.jboss.logging.Logger;
+import org.keycloak.adapters.AdapterDeploymentContext;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.rotation.JWKPublicKeyLocator;
 import org.keycloak.common.util.Time;
 
 /**
@@ -38,6 +43,11 @@ import org.keycloak.common.util.Time;
  */
 public class AdapterActionsFilter implements Filter {
 
+    public static final String TIME_OFFSET_PARAM = "timeOffset";
+    public static final String RESET_PUBLIC_KEY_PARAM = "resetPublicKey";
+
+    private static final Logger log = Logger.getLogger(AdapterActionsFilter.class);
+
     @Override
     public void init(FilterConfig filterConfig) throws ServletException {
 
@@ -45,17 +55,29 @@ public class AdapterActionsFilter implements Filter {
 
     @Override
     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+        HttpServletRequest servletReq = (HttpServletRequest) request;
         HttpServletResponse servletResp = (HttpServletResponse) response;
 
         //Accept timeOffset as argument to enforce timeouts
-        String timeOffsetParam = request.getParameter("timeOffset");
+        String timeOffsetParam = request.getParameter(TIME_OFFSET_PARAM);
+        String resetPublicKey = request.getParameter(RESET_PUBLIC_KEY_PARAM);
+
         if (timeOffsetParam != null && !timeOffsetParam.isEmpty()) {
-            Time.setOffset(Integer.parseInt(timeOffsetParam));
+            int timeOffset = Integer.parseInt(timeOffsetParam);
+            log.infof("Time offset updated to %d for application %s", timeOffset, servletReq.getRequestURI());
+            Time.setOffset(timeOffset);
+            writeResponse(servletResp, "Offset set successfully");
+        } else if (resetPublicKey != null && !resetPublicKey.isEmpty()) {
+            AdapterDeploymentContext deploymentContext = (AdapterDeploymentContext) request.getServletContext().getAttribute(AdapterDeploymentContext.class.getName());
+            KeycloakDeployment deployment = deploymentContext.resolveDeployment(null);
+            deployment.setPublicKeyLocator(new JWKPublicKeyLocator());
+            log.infof("Restarted publicKey locator for application %s", servletReq.getRequestURI());
+            writeResponse(servletResp, "PublicKeyLocator restarted successfully");
+        } else {
+            // Continue request
+            chain.doFilter(request, response);
         }
 
-        // Continue request
-        chain.doFilter(request, response);
-
     }
 
     @Override
@@ -64,8 +86,9 @@ public class AdapterActionsFilter implements Filter {
     }
 
     private void writeResponse(HttpServletResponse response, String responseText) throws IOException {
+        response.setContentType("text/html");
         PrintWriter writer = response.getWriter();
-        writer.println(responseText);
+        writer.println("<html><body>" + responseText + "</body></html>");
         writer.flush();
     }
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/AbstractShowTokensPage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/AbstractShowTokensPage.java
index d370dd0..6cb1f37 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/AbstractShowTokensPage.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/AbstractShowTokensPage.java
@@ -23,6 +23,7 @@ import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.RefreshToken;
 import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl;
 import org.keycloak.util.JsonSerialization;
+import org.openqa.selenium.NoSuchElementException;
 import org.openqa.selenium.WebElement;
 import org.openqa.selenium.support.FindBy;
 
@@ -43,6 +44,8 @@ public abstract class AbstractShowTokensPage extends AbstractPageWithInjectedUrl
             return JsonSerialization.readValue(accessToken.getText(), AccessToken.class);
         } catch (IOException e) {
             e.printStackTrace();
+        } catch (NoSuchElementException nsee) {
+            log.warn("No accessToken element found on the page");
         }
 
         return null;
@@ -53,7 +56,10 @@ public abstract class AbstractShowTokensPage extends AbstractPageWithInjectedUrl
             return JsonSerialization.readValue(refreshToken.getText(), RefreshToken.class);
         } catch (IOException e) {
             e.printStackTrace();
+        } catch (NoSuchElementException nsee) {
+            log.warn("No idToken element found on the page");
         }
+
         return null;
     }
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentArchiveProcessor.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentArchiveProcessor.java
index 9cd7625..4af2988 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentArchiveProcessor.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentArchiveProcessor.java
@@ -49,8 +49,6 @@ import static org.keycloak.testsuite.util.IOUtil.*;
  */
 public class DeploymentArchiveProcessor implements ApplicationArchiveProcessor {
 
-    public static final String REALM_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB";
-
     protected final Logger log = org.jboss.logging.Logger.getLogger(this.getClass());
 
     private final boolean authServerSslRequired = Boolean.parseBoolean(System.getProperty("auth.server.ssl.required"));
@@ -129,7 +127,6 @@ public class DeploymentArchiveProcessor implements ApplicationArchiveProcessor {
 //                ac.setRealmKey(null); // TODO verify if realm key is required for relative scneario
                     } else {
                         adapterConfig.setAuthServerUrl(getAuthServerContextRoot() + "/auth");
-                        adapterConfig.setRealmKey(REALM_KEY);
                     }
 
                     if ("true".equals(System.getProperty("app.server.ssl.required"))) {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractServletsAdapterTest.java
index 127c863..a40275f 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractServletsAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractServletsAdapterTest.java
@@ -22,6 +22,7 @@ import org.jboss.shrinkwrap.api.ShrinkWrap;
 import org.jboss.shrinkwrap.api.asset.StringAsset;
 import org.jboss.shrinkwrap.api.spec.WebArchive;
 import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.adapter.filter.AdapterActionsFilter;
 import org.keycloak.testsuite.util.WaitUtils;
 import org.openqa.selenium.By;
 
@@ -113,7 +114,7 @@ public abstract class AbstractServletsAdapterTest extends AbstractAdapterTest {
     protected void setAdapterAndServerTimeOffset(int timeOffset, String servletUri) {
         setTimeOffset(timeOffset);
         String timeOffsetUri = UriBuilder.fromUri(servletUri)
-                .queryParam("timeOffset", timeOffset)
+                .queryParam(AdapterActionsFilter.TIME_OFFSET_PARAM, timeOffset)
                 .build().toString();
 
         driver.navigate().to(timeOffsetUri);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java
index 083867f..02adb84 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java
@@ -28,6 +28,7 @@ import org.keycloak.OAuth2Constants;
 import org.keycloak.common.Version;
 import org.keycloak.common.util.Time;
 import org.keycloak.constants.AdapterConstants;
+import org.keycloak.models.Constants;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
 import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
 import org.keycloak.representations.AccessToken;
@@ -36,8 +37,11 @@ import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.testsuite.adapter.AbstractServletsAdapterTest;
 import org.keycloak.testsuite.adapter.filter.AdapterActionsFilter;
 import org.keycloak.testsuite.adapter.page.*;
+import org.keycloak.testsuite.util.URLAssert;
 import org.keycloak.testsuite.util.URLUtils;
+import org.keycloak.testsuite.util.WaitUtils;
 import org.keycloak.util.BasicAuthHelper;
+import org.openqa.selenium.By;
 
 import javax.ws.rs.client.Client;
 import javax.ws.rs.client.ClientBuilder;
@@ -46,6 +50,8 @@ import javax.ws.rs.client.WebTarget;
 import javax.ws.rs.core.Form;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+
 import java.net.URI;
 import java.util.List;
 import java.util.Map;
@@ -167,6 +173,59 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
     }
 
     @Test
+    public void testRealmKeyRotationWithNewKeyDownload() throws Exception {
+        // Login success first
+        tokenMinTTLPage.navigateTo();
+        testRealmLoginPage.form().waitForUsernameInputPresent();
+        assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
+        testRealmLoginPage.form().login("bburke@redhat.com", "password");
+        assertCurrentUrlEquals(tokenMinTTLPage);
+
+        AccessToken token = tokenMinTTLPage.getAccessToken();
+        Assert.assertEquals("bburke@redhat.com", token.getPreferredUsername());
+
+        // Logout
+        String logoutUri = OIDCLoginProtocolService.logoutUrl(authServerPage.createUriBuilder())
+                .queryParam(OAuth2Constants.REDIRECT_URI, tokenMinTTLPage.toString())
+                .build("demo").toString();
+        driver.navigate().to(logoutUri);
+
+        // Generate new realm key
+        RealmRepresentation realmRep = testRealmResource().toRepresentation();
+        String oldPublicKey = realmRep.getPublicKey();
+        String oldPrivateKey = realmRep.getPrivateKey();
+        realmRep.setPublicKey(Constants.GENERATE);
+        testRealmResource().update(realmRep);
+
+        // Try to login again. It should fail now
+        tokenMinTTLPage.navigateTo();
+        testRealmLoginPage.form().waitForUsernameInputPresent();
+        assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
+        testRealmLoginPage.form().login("bburke@redhat.com", "password");
+        URLAssert.assertCurrentUrlStartsWith(driver, tokenMinTTLPage.getInjectedUrl().toString());
+        assertNull(tokenMinTTLPage.getAccessToken());
+
+        String adapterActionsUrl = tokenMinTTLPage.toString() + "/unsecured/foo";
+        setAdapterAndServerTimeOffset(300, adapterActionsUrl);
+
+        // Try to login. Should work now due to realm key change
+        tokenMinTTLPage.navigateTo();
+        assertCurrentUrlEquals(tokenMinTTLPage);
+        token = tokenMinTTLPage.getAccessToken();
+        Assert.assertEquals("bburke@redhat.com", token.getPreferredUsername());
+        driver.navigate().to(logoutUri);
+
+        // Revert public keys change
+        String timeOffsetUri = UriBuilder.fromUri(adapterActionsUrl)
+                .queryParam(AdapterActionsFilter.RESET_PUBLIC_KEY_PARAM, "true")
+                .build().toString();
+        driver.navigate().to(timeOffsetUri);
+        WaitUtils.waitUntilElement(By.tagName("body")).is().visible();
+
+        setAdapterAndServerTimeOffset(0, adapterActionsUrl);
+    }
+
+    @Test
     public void testLoginSSOAndLogout() {
         // test login to customer-portal which does a bearer request to customer-db
         customerPortal.navigateTo();
@@ -444,6 +503,7 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
 
         // Sets 5 minutes offset and assert access token will be still the same
         setAdapterAndServerTimeOffset(300, tokenMinTTLPage.toString());
+        tokenMinTTLPage.navigateTo();
         token = tokenMinTTLPage.getAccessToken();
         int tokenIssued2 = token.getIssuedAt();
         Assert.assertEquals(tokenIssued1, tokenIssued2);
@@ -451,6 +511,7 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
 
         // Sets 9 minutes offset and assert access token will be refreshed (accessTokenTimeout is 10 minutes, token-min-ttl is 2 minutes. Hence 8 minutes or more should be sufficient)
         setAdapterAndServerTimeOffset(540, tokenMinTTLPage.toString());
+        tokenMinTTLPage.navigateTo();
         token = tokenMinTTLPage.getAccessToken();
         int tokenIssued3 = token.getIssuedAt();
         Assert.assertTrue(tokenIssued3 > tokenIssued1);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractOfflineServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractOfflineServletsAdapterTest.java
index fa6d0a8..b8c10b6 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractOfflineServletsAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractOfflineServletsAdapterTest.java
@@ -85,7 +85,7 @@ public abstract class AbstractOfflineServletsAdapterTest extends AbstractServlet
         String refreshTokenId = offlineTokenPage.getRefreshToken().getId();
 
         setAdapterAndServerTimeOffset(9999);
-
+        offlineTokenPage.navigateTo();
         assertCurrentUrlStartsWith(offlineTokenPage);
         Assert.assertNotEquals(offlineTokenPage.getRefreshToken().getId(), refreshTokenId);
         Assert.assertNotEquals(offlineTokenPage.getAccessToken().getId(), accessTokenId);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/InstallationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/InstallationTest.java
index fb03891..c7339ed 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/InstallationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/InstallationTest.java
@@ -21,11 +21,11 @@ import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.keycloak.admin.client.resource.ClientResource;
-import org.keycloak.events.admin.OperationType;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
 
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
 
 /**
  * Test getting the installation/configuration files for OIDC and SAML.
@@ -81,7 +81,7 @@ public class InstallationTest extends AbstractClientTest {
     private void assertOidcInstallationConfig(String config) {
         RealmRepresentation realmRep = realmRep();
         assertTrue(config.contains(realmRep.getId()));
-        assertTrue(config.contains(realmRep.getPublicKey()));
+        assertFalse(config.contains(realmRep.getPublicKey()));
         assertTrue(config.contains(authServerUrl()));
     }
 
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AdapterInstallationConfigTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AdapterInstallationConfigTest.java
index 0a4fe4b..818a84a 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AdapterInstallationConfigTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AdapterInstallationConfigTest.java
@@ -35,14 +35,11 @@ public class AdapterInstallationConfigTest extends AbstractClientRegistrationTes
     private ClientRepresentation client;
     private ClientRepresentation client2;
     private ClientRepresentation clientPublic;
-    private String publicKey;
 
     @Before
     public void before() throws Exception {
         super.before();
 
-        publicKey = adminClient.realm(REALM_NAME).toRepresentation().getPublicKey();
-
         client = new ClientRepresentation();
         client.setEnabled(true);
         client.setClientId("RegistrationAccessTokenTest");
@@ -92,7 +89,6 @@ public class AdapterInstallationConfigTest extends AbstractClientRegistrationTes
         assertEquals(1, config.getCredentials().size());
         assertEquals(client.getSecret(), config.getCredentials().get("secret"));
 
-        assertEquals(publicKey, config.getRealmKey());
         assertEquals(client.getClientId(), config.getResource());
         assertEquals(SslRequired.EXTERNAL.name().toLowerCase(), config.getSslRequired());
     }
@@ -132,7 +128,6 @@ public class AdapterInstallationConfigTest extends AbstractClientRegistrationTes
 
         assertEquals(0, config.getCredentials().size());
 
-        assertEquals(publicKey, config.getRealmKey());
         assertEquals(clientPublic.getClientId(), config.getResource());
         assertEquals(SslRequired.EXTERNAL.name().toLowerCase(), config.getSslRequired());
     }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/customer-portal/WEB-INF/keycloak.json b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/customer-portal/WEB-INF/keycloak.json
index b75c1d5..32703f9 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/customer-portal/WEB-INF/keycloak.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/customer-portal/WEB-INF/keycloak.json
@@ -1,10 +1,10 @@
 {
     "realm": "demo",
     "resource": "customer-portal",
-    "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
-    "auth-server-url": "http://localhost:8180/auth",
+    "auth-server-url": "http://localhostt:8180/auth",
     "ssl-required" : "external",
     "expose-token": true,
+    "min-time-between-jwks-requests": 120,
     "credentials": {
         "secret": "password"
     }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/input-portal/WEB-INF/keycloak.json b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/input-portal/WEB-INF/keycloak.json
index a934e97..eecc24b 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/input-portal/WEB-INF/keycloak.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/input-portal/WEB-INF/keycloak.json
@@ -1,9 +1,9 @@
 {
   "realm" : "demo",
   "resource" : "input-portal",
-  "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
   "auth-server-url" : "http://${my.host.name}:8180/auth",
   "ssl-required" : "external",
+  "min-time-between-jwks-requests": 120,
   "credentials" : {
       "secret": "password"
    }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/token-min-ttl/WEB-INF/keycloak.json b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/token-min-ttl/WEB-INF/keycloak.json
index 85eb9a7..b86e018 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/token-min-ttl/WEB-INF/keycloak.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/token-min-ttl/WEB-INF/keycloak.json
@@ -1,11 +1,11 @@
 {
   "realm": "demo",
   "resource": "token-min-ttl",
-  "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
   "auth-server-url": "http://localhost:8180/auth",
   "ssl-required" : "external",
   "credentials": {
     "secret": "password"
   },
-  "token-minimum-time-to-live": 120
+  "token-minimum-time-to-live": 120,
+  "min-time-between-jwks-requests": 120
 }
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/token-min-ttl/WEB-INF/web.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/token-min-ttl/WEB-INF/web.xml
index f8110bc..86d07f5 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/token-min-ttl/WEB-INF/web.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/token-min-ttl/WEB-INF/web.xml
@@ -68,6 +68,12 @@
             <url-pattern>/error.html</url-pattern>
         </web-resource-collection>
     </security-constraint>
+    <security-constraint>
+        <web-resource-collection>
+            <web-resource-name>Unsecured</web-resource-name>
+            <url-pattern>/unsecured/*</url-pattern>
+        </web-resource-collection>
+    </security-constraint>
 
     <login-config>
         <auth-method>KEYCLOAK</auth-method>