keycloak-uncached
Changes
server-spi-private/src/main/java/org/keycloak/jose/jws/TokenSignatureProviderFactory.java 11(+11 -0)
server-spi-private/src/main/java/org/keycloak/models/utils/DefaultTokenSignatureProviders.java 35(+35 -0)
services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java 9(+9 -0)
services/src/main/resources/META-INF/services/org.keycloak.jose.jws.TokenSignatureProviderFactory 4(+4 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/TokenSignatureUtil.java 165(+165 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/GeneratedEcdsaKeyProviderTest.java 162(+162 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java 141(+141 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/LogoutTest.java 52(+52 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java 87(+87 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java 190(+190 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/AbstractOIDCResponseTypeTest.java 56(+56 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/OIDCHybridResponseTypeCodeIDTokenTest.java 6(+4 -2)
Details
diff --git a/core/src/main/java/org/keycloak/jose/jws/crypto/HashProvider.java b/core/src/main/java/org/keycloak/jose/jws/crypto/HashProvider.java
index fcdc2a4..738463d 100644
--- a/core/src/main/java/org/keycloak/jose/jws/crypto/HashProvider.java
+++ b/core/src/main/java/org/keycloak/jose/jws/crypto/HashProvider.java
@@ -27,18 +27,16 @@ import java.util.Arrays;
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
 public class HashProvider {
-
-    // See "at_hash" and "c_hash" in OIDC specification
-    public static String oidcHash(Algorithm jwtAlgorithm, String input) {
-        byte[] digest = digest(jwtAlgorithm, input);
+    // KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+    public static String oidcHash(String jwtAlgorithmName, String input) {
+        byte[] digest = digest(jwtAlgorithmName, input);
 
         int hashLength = digest.length / 2;
         byte[] hashInput = Arrays.copyOf(digest, hashLength);
 
         return Base64Url.encode(hashInput);
     }
-
-    private static byte[] digest(Algorithm algorithm, String input) {
+    private static byte[] digest(String algorithm, String input) {
         String digestAlg = getJavaDigestAlgorithm(algorithm);
 
         try {
@@ -49,18 +47,22 @@ public class HashProvider {
             throw new RuntimeException(e);
         }
     }
-
-    private static String getJavaDigestAlgorithm(Algorithm alg) {
+    private static String getJavaDigestAlgorithm(String alg) {
         switch (alg) {
-            case RS256:
+            case "RS256":
                 return "SHA-256";
-            case RS384:
+            case "RS384":
                 return "SHA-384";
-            case RS512:
+            case "RS512":
                 return "SHA-512";
             default:
                 throw new IllegalArgumentException("Not an RSA Algorithm");
         }
     }
 
+    // See "at_hash" and "c_hash" in OIDC specification
+    public static String oidcHash(Algorithm jwtAlgorithm, String input) {
+        return oidcHash(jwtAlgorithm.name(), input);
+    }
+
 }
                diff --git a/core/src/main/java/org/keycloak/jose/jws/crypto/HMACProvider.java b/core/src/main/java/org/keycloak/jose/jws/crypto/HMACProvider.java
index 4a97d73..b4c1016 100755
--- a/core/src/main/java/org/keycloak/jose/jws/crypto/HMACProvider.java
+++ b/core/src/main/java/org/keycloak/jose/jws/crypto/HMACProvider.java
@@ -25,6 +25,7 @@ import org.keycloak.jose.jws.JWSInput;
 import javax.crypto.Mac;
 import javax.crypto.SecretKey;
 import javax.crypto.spec.SecretKeySpec;
+
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 
                diff --git a/core/src/main/java/org/keycloak/jose/jws/JWSBuilder.java b/core/src/main/java/org/keycloak/jose/jws/JWSBuilder.java
index a17050e..edd8ebf 100755
--- a/core/src/main/java/org/keycloak/jose/jws/JWSBuilder.java
+++ b/core/src/main/java/org/keycloak/jose/jws/JWSBuilder.java
@@ -25,6 +25,7 @@ import org.keycloak.util.JsonSerialization;
 import javax.crypto.SecretKey;
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
+import java.security.Key;
 import java.security.PrivateKey;
 
 /**
@@ -36,7 +37,7 @@ public class JWSBuilder {
     String kid;
     String contentType;
     byte[] contentBytes;
-
+    
     public JWSBuilder type(String type) {
         this.type = type;
         return this;
@@ -66,22 +67,6 @@ public class JWSBuilder {
         return new EncodingBuilder();
     }
 
-
-    protected String encodeHeader(Algorithm alg) {
-        StringBuilder builder = new StringBuilder("{");
-        builder.append("\"alg\":\"").append(alg.toString()).append("\"");
-
-        if (type != null) builder.append(",\"typ\" : \"").append(type).append("\"");
-        if (kid != null) builder.append(",\"kid\" : \"").append(kid).append("\"");
-        if (contentType != null) builder.append(",\"cty\":\"").append(contentType).append("\"");
-        builder.append("}");
-        try {
-            return Base64Url.encode(builder.toString().getBytes("UTF-8"));
-        } catch (UnsupportedEncodingException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
     protected String encodeAll(StringBuffer encoding, byte[] signature) {
         encoding.append('.');
         if (signature != null) {
@@ -91,15 +76,37 @@ public class JWSBuilder {
     }
 
     protected void encode(Algorithm alg, byte[] data, StringBuffer encoding) {
-        encoding.append(encodeHeader(alg));
-        encoding.append('.');
-        encoding.append(Base64Url.encode(data));
+        // KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+        encode(alg.name(), data, encoding);
     }
 
     protected byte[] marshalContent() {
         return contentBytes;
     }
 
+    // KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+    protected void encode(String sigAlgName, byte[] data, StringBuffer encoding) {
+        encoding.append(encodeHeader(sigAlgName));
+        encoding.append('.');
+        encoding.append(Base64Url.encode(data));
+    }
+
+    // KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+    protected String encodeHeader(String sigAlgName) {
+        StringBuilder builder = new StringBuilder("{");
+        builder.append("\"alg\":\"").append(sigAlgName).append("\"");
+
+        if (type != null) builder.append(",\"typ\" : \"").append(type).append("\"");
+        if (kid != null) builder.append(",\"kid\" : \"").append(kid).append("\"");
+        if (contentType != null) builder.append(",\"cty\":\"").append(contentType).append("\"");
+        builder.append("}");
+        try {
+            return Base64Url.encode(builder.toString().getBytes("UTF-8"));
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
     public class EncodingBuilder {
         public String none() {
             StringBuffer buffer = new StringBuffer();
@@ -108,6 +115,20 @@ public class JWSBuilder {
             return encodeAll(buffer, null);
         }
 
+        // KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI        
+        public String sign(JWSSignatureProvider signatureProvider, String sigAlgName, Key key) {
+            StringBuffer buffer = new StringBuffer();
+            byte[] data = marshalContent();
+            encode(sigAlgName, data, buffer);
+            byte[] signature = null;
+            try {
+                signature = signatureProvider.sign(buffer.toString().getBytes("UTF-8"), sigAlgName, key);
+            } catch (UnsupportedEncodingException e) {
+                throw new RuntimeException(e);
+            }
+            return encodeAll(buffer, signature);
+        }
+
         public String sign(Algorithm algorithm, PrivateKey privateKey) {
             StringBuffer buffer = new StringBuffer();
             byte[] data = marshalContent();
@@ -133,7 +154,6 @@ public class JWSBuilder {
             return sign(Algorithm.RS512, privateKey);
         }
 
-
         public String hmac256(byte[] sharedSecret) {
             StringBuffer buffer = new StringBuffer();
             byte[] data = marshalContent();
                diff --git a/core/src/main/java/org/keycloak/jose/jws/JWSSignatureProvider.java b/core/src/main/java/org/keycloak/jose/jws/JWSSignatureProvider.java
new file mode 100644
index 0000000..573d135
--- /dev/null
+++ b/core/src/main/java/org/keycloak/jose/jws/JWSSignatureProvider.java
@@ -0,0 +1,9 @@
+package org.keycloak.jose.jws;
+
+import java.security.Key;
+
+public interface JWSSignatureProvider {
+    // KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+    byte[] sign(byte[] data, String sigAlgName, Key key);
+    boolean verify(JWSInput input, Key key);
+}
                diff --git a/core/src/main/java/org/keycloak/TokenVerifier.java b/core/src/main/java/org/keycloak/TokenVerifier.java
index 1f1d54c..c575eec 100755
--- a/core/src/main/java/org/keycloak/TokenVerifier.java
+++ b/core/src/main/java/org/keycloak/TokenVerifier.java
@@ -24,12 +24,15 @@ import org.keycloak.jose.jws.AlgorithmType;
 import org.keycloak.jose.jws.JWSHeader;
 import org.keycloak.jose.jws.JWSInput;
 import org.keycloak.jose.jws.JWSInputException;
+import org.keycloak.jose.jws.JWSSignatureProvider;
 import org.keycloak.jose.jws.crypto.HMACProvider;
 import org.keycloak.jose.jws.crypto.RSAProvider;
 import org.keycloak.representations.JsonWebToken;
 import org.keycloak.util.TokenUtil;
 
 import javax.crypto.SecretKey;
+
+import java.security.Key;
 import java.security.PublicKey;
 import java.util.*;
 import java.util.logging.Level;
@@ -144,6 +147,18 @@ public class TokenVerifier<T extends JsonWebToken> {
     private JWSInput jws;
     private T token;
 
+    // KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+    private Key verifyKey = null;
+    private JWSSignatureProvider signatureProvider = null;
+    public TokenVerifier<T> verifyKey(Key verifyKey) {
+        this.verifyKey = verifyKey;
+        return this;
+    }
+    public TokenVerifier<T> signatureProvider(JWSSignatureProvider signatureProvider) {
+        this.signatureProvider = signatureProvider;
+        return this;
+    }
+
     protected TokenVerifier(String tokenString, Class<T> clazz) {
         this.tokenString = tokenString;
         this.clazz = clazz;
@@ -337,6 +352,12 @@ public class TokenVerifier<T extends JsonWebToken> {
     }
 
     public void verifySignature() throws VerificationException {
+        // KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+        if (this.signatureProvider != null && this.verify() != null) {
+            verifySignatureByProvider();
+            return;
+        }
+
         AlgorithmType algorithmType = getHeader().getAlgorithm().getType();
 
         if (null == algorithmType) {
@@ -361,6 +382,13 @@ public class TokenVerifier<T extends JsonWebToken> {
         }
     }
 
+    // KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+    private void verifySignatureByProvider() throws VerificationException {
+        if (!signatureProvider.verify(jws, verifyKey)) {
+            throw new TokenSignatureInvalidException(token, "Invalid token signature");
+        }
+    }
+
     public TokenVerifier<T> verify() throws VerificationException {
         if (getToken() == null) {
             parse();
                diff --git a/server-spi-private/src/main/java/org/keycloak/jose/jws/TokenSignatureProvider.java b/server-spi-private/src/main/java/org/keycloak/jose/jws/TokenSignatureProvider.java
new file mode 100644
index 0000000..96632d9
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/jose/jws/TokenSignatureProvider.java
@@ -0,0 +1,12 @@
+package org.keycloak.jose.jws;
+
+import java.security.Key;
+
+import org.keycloak.provider.Provider;
+
+// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+
+public interface TokenSignatureProvider extends Provider {
+    byte[] sign(byte[] data, String sigAlgName, Key key);
+    boolean verify(JWSInput jws, Key verifyKey);
+}
                diff --git a/server-spi-private/src/main/java/org/keycloak/jose/jws/TokenSignatureProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/jose/jws/TokenSignatureProviderFactory.java
new file mode 100644
index 0000000..391c444
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/jose/jws/TokenSignatureProviderFactory.java
@@ -0,0 +1,11 @@
+package org.keycloak.jose.jws;
+
+import org.keycloak.component.ComponentFactory;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.models.KeycloakSession;
+
+// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+
+public interface TokenSignatureProviderFactory<T extends TokenSignatureProvider> extends ComponentFactory<T, TokenSignatureProvider> {
+    T create(KeycloakSession session, ComponentModel model);
+}
                diff --git a/server-spi-private/src/main/java/org/keycloak/jose/jws/TokenSignatureSpi.java b/server-spi-private/src/main/java/org/keycloak/jose/jws/TokenSignatureSpi.java
new file mode 100644
index 0000000..253831d
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/jose/jws/TokenSignatureSpi.java
@@ -0,0 +1,29 @@
+package org.keycloak.jose.jws;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+
+public class TokenSignatureSpi implements Spi {
+    @Override
+    public boolean isInternal() {
+        return true;
+    }
+
+    @Override
+    public String getName() {
+        return "tokenSignature";
+    }
+
+    @Override
+    public Class<? extends Provider> getProviderClass() {
+        return TokenSignatureProvider.class;
+    }
+
+    @Override
+    public Class<? extends ProviderFactory> getProviderFactoryClass() {
+        return TokenSignatureProviderFactory.class;
+    }
+}
                diff --git a/server-spi-private/src/main/java/org/keycloak/keys/KeyProvider.java b/server-spi-private/src/main/java/org/keycloak/keys/KeyProvider.java
index 7984ea6..9870ff3 100644
--- a/server-spi-private/src/main/java/org/keycloak/keys/KeyProvider.java
+++ b/server-spi-private/src/main/java/org/keycloak/keys/KeyProvider.java
@@ -18,12 +18,8 @@
 package org.keycloak.keys;
 
 import org.keycloak.crypto.KeyWrapper;
-import org.keycloak.jose.jws.AlgorithmType;
 import org.keycloak.provider.Provider;
 
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.cert.X509Certificate;
 import java.util.List;
 
 /**
                diff --git a/server-spi-private/src/main/java/org/keycloak/keys/SignatureKeyProvider.java b/server-spi-private/src/main/java/org/keycloak/keys/SignatureKeyProvider.java
new file mode 100644
index 0000000..49930b9
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/keys/SignatureKeyProvider.java
@@ -0,0 +1,10 @@
+package org.keycloak.keys;
+
+import java.security.Key;
+
+// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+
+public interface SignatureKeyProvider {
+    Key getSignKey();
+    Key getVerifyKey(String kid);
+}
                diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/DefaultTokenSignatureProviders.java b/server-spi-private/src/main/java/org/keycloak/models/utils/DefaultTokenSignatureProviders.java
new file mode 100644
index 0000000..66caff5
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/DefaultTokenSignatureProviders.java
@@ -0,0 +1,35 @@
+package org.keycloak.models.utils;
+
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.jose.jws.TokenSignatureProvider;
+import org.keycloak.models.RealmModel;
+
+// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+
+public class DefaultTokenSignatureProviders {
+    private static final String COMPONENT_SIGNATURE_ALGORITHM_KEY = "org.keycloak.jose.jws.TokenSignatureProvider.algorithm";
+    private static final String RSASSA_PROVIDER_ID = "rsassa-signature";
+    private static final String HMAC_PROVIDER_ID = "hmac-signature";
+
+    public static void createProviders(RealmModel realm) {
+       createAndAddProvider(realm, RSASSA_PROVIDER_ID, "RS256");
+       createAndAddProvider(realm, RSASSA_PROVIDER_ID, "RS384");
+       createAndAddProvider(realm, RSASSA_PROVIDER_ID, "RS512");
+       createAndAddProvider(realm, HMAC_PROVIDER_ID, "HS256");
+       createAndAddProvider(realm, HMAC_PROVIDER_ID, "HS384");
+       createAndAddProvider(realm, HMAC_PROVIDER_ID, "HS512");
+    }
+
+    private static void createAndAddProvider(RealmModel realm, String providerId, String sigAlgName) {
+        ComponentModel generated = new ComponentModel();
+        generated.setName(providerId);
+        generated.setParentId(realm.getId());
+        generated.setProviderId(providerId);
+        generated.setProviderType(TokenSignatureProvider.class.getName());
+        MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
+        config.putSingle(COMPONENT_SIGNATURE_ALGORITHM_KEY, sigAlgName);
+        generated.setConfig(config);
+        realm.addComponentModel(generated);
+    }
+}
                diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index e0069b0..12dc27c 100755
--- a/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -53,6 +53,7 @@ import org.keycloak.common.util.MultivaluedHashMap;
 import org.keycloak.common.util.UriUtils;
 import org.keycloak.component.ComponentModel;
 import org.keycloak.credential.CredentialModel;
+import org.keycloak.jose.jws.TokenSignatureProvider;
 import org.keycloak.keys.KeyProvider;
 import org.keycloak.migration.MigrationProvider;
 import org.keycloak.migration.migrators.MigrationUtils;
@@ -420,6 +421,12 @@ public class RepresentationToModel {
                 DefaultKeyProviders.createProviders(newRealm);
             }
         }
+
+        // KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+        if (newRealm.getComponents(newRealm.getId(), TokenSignatureProvider.class.getName()).isEmpty()) {
+            DefaultTokenSignatureProviders.createProviders(newRealm);
+        }
+
     }
 
     public static void importUserFederationProvidersAndMappers(KeycloakSession session, RealmRepresentation rep, RealmModel newRealm) {
                diff --git a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi
index 9fae0fd..a517b26 100755
--- a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi
+++ b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi
@@ -70,4 +70,7 @@ org.keycloak.credential.hash.PasswordHashSpi
 org.keycloak.credential.CredentialSpi
 org.keycloak.keys.PublicKeyStorageSpi
 org.keycloak.keys.KeySpi
-org.keycloak.storage.client.ClientStorageProviderSpi
\ No newline at end of file
+org.keycloak.storage.client.ClientStorageProviderSpi
+# KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+org.keycloak.jose.jws.TokenSignatureSpi
+
                diff --git a/services/src/main/java/org/keycloak/jose/jws/AbstractTokenSignatureProvider.java b/services/src/main/java/org/keycloak/jose/jws/AbstractTokenSignatureProvider.java
new file mode 100644
index 0000000..e7b2c83
--- /dev/null
+++ b/services/src/main/java/org/keycloak/jose/jws/AbstractTokenSignatureProvider.java
@@ -0,0 +1,36 @@
+package org.keycloak.jose.jws;
+
+import java.security.Key;
+import java.security.Signature;
+
+import org.jboss.logging.Logger;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.crypto.JavaAlgorithm;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.jose.jws.JWSSignatureProvider;
+
+// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+
+public abstract class AbstractTokenSignatureProvider implements TokenSignatureProvider, JWSSignatureProvider {
+    protected static final Logger logger = Logger.getLogger(AbstractTokenSignatureProvider.class);
+
+    public AbstractTokenSignatureProvider(KeycloakSession session, ComponentModel model) {}
+
+    @Override
+    public void close() {}
+
+    @Override
+    public abstract byte[] sign(byte[] data, String sigAlgName, Key key);
+
+    @Override
+    public abstract boolean verify(JWSInput jws, Key verifyKey);
+
+    protected Signature getSignature(String sigAlgName) {
+        try {
+            return Signature.getInstance(JavaAlgorithm.getJavaAlgorithm(sigAlgName));
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+}
                diff --git a/services/src/main/java/org/keycloak/jose/jws/EcdsaTokenSignatureProvider.java b/services/src/main/java/org/keycloak/jose/jws/EcdsaTokenSignatureProvider.java
new file mode 100644
index 0000000..18bd61b
--- /dev/null
+++ b/services/src/main/java/org/keycloak/jose/jws/EcdsaTokenSignatureProvider.java
@@ -0,0 +1,69 @@
+package org.keycloak.jose.jws;
+
+import java.security.Key;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Signature;
+
+import org.keycloak.component.ComponentModel;
+import org.keycloak.models.KeycloakSession;
+
+// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+
+public class EcdsaTokenSignatureProvider extends AbstractTokenSignatureProvider {
+
+    public EcdsaTokenSignatureProvider(KeycloakSession session, ComponentModel model) {
+        super(session, model);
+    }
+
+    @Override
+    public void close() {}
+
+    @Override
+    public byte[] sign(byte[] data, String sigAlgName, Key key) {
+        try {
+            PrivateKey privateKey = (PrivateKey)key;
+            Signature signature = getSignature(sigAlgName);
+            signature.initSign(privateKey);
+            signature.update(data);
+            return signature.sign();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public boolean verify(JWSInput jws, Key verifyKey) {
+        try {
+            PublicKey publicKey = (PublicKey)verifyKey;
+            Signature verifier = getSignature(jws.getHeader().getAlgorithm().name());
+            verifier.initVerify(publicKey);
+            verifier.update(jws.getEncodedSignatureInput().getBytes("UTF-8"));
+            return verifier.verify(jws.getSignature());
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+    @Override
+    protected Signature getSignature(String sigAlgName) {
+        try {
+            return Signature.getInstance(getJavaAlgorithm(sigAlgName));
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private String getJavaAlgorithm(String sigAlgName) {
+        switch (sigAlgName) {
+        case "ES256":
+            return "SHA256withECDSA";
+        case "ES384":
+            return "SHA384withECDSA";
+        case "ES512":
+            return "SHA512withECDSA";
+        default:
+            throw new IllegalArgumentException("Not an ECDSA Algorithm");
+        }
+    }
+}
                diff --git a/services/src/main/java/org/keycloak/jose/jws/EcdsaTokenSignatureProviderFactory.java b/services/src/main/java/org/keycloak/jose/jws/EcdsaTokenSignatureProviderFactory.java
new file mode 100644
index 0000000..f5ec54d
--- /dev/null
+++ b/services/src/main/java/org/keycloak/jose/jws/EcdsaTokenSignatureProviderFactory.java
@@ -0,0 +1,54 @@
+package org.keycloak.jose.jws;
+
+import java.util.List;
+
+import org.keycloak.Config.Scope;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.provider.ProviderConfigurationBuilder;
+
+// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+
+@SuppressWarnings("rawtypes")
+public class EcdsaTokenSignatureProviderFactory implements TokenSignatureProviderFactory {
+
+    public static final String ID = "ecdsa-signature";
+
+    private static final String HELP_TEXT = "Generates token signature provider using EC key";
+
+    private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = ProviderConfigurationBuilder.create().build();
+
+    @Override
+    public void init(Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+    }
+
+    @Override
+    public void close() {
+    }
+
+    @Override
+    public String getId() {
+        return ID;
+    }
+
+    @Override
+    public String getHelpText() {
+        return HELP_TEXT;
+    }
+
+    @Override
+    public List<ProviderConfigProperty> getConfigProperties() {
+        return CONFIG_PROPERTIES;
+    }
+
+    @Override
+    public TokenSignatureProvider create(KeycloakSession session, ComponentModel model) {
+        return new EcdsaTokenSignatureProvider(session, model);
+    }
+}
                diff --git a/services/src/main/java/org/keycloak/jose/jws/HmacTokenSignatureProvider.java b/services/src/main/java/org/keycloak/jose/jws/HmacTokenSignatureProvider.java
new file mode 100644
index 0000000..7ce582f
--- /dev/null
+++ b/services/src/main/java/org/keycloak/jose/jws/HmacTokenSignatureProvider.java
@@ -0,0 +1,52 @@
+package org.keycloak.jose.jws;
+
+import java.security.Key;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.Mac;
+
+import org.keycloak.common.util.Base64Url;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.crypto.JavaAlgorithm;
+import org.keycloak.models.KeycloakSession;
+
+// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+
+public class HmacTokenSignatureProvider extends AbstractTokenSignatureProvider {
+
+    public HmacTokenSignatureProvider(KeycloakSession session, ComponentModel model) {
+        super(session, model);
+    }
+
+    private Mac getMAC(final String sigAlgName) {
+        try {
+            return Mac.getInstance(JavaAlgorithm.getJavaAlgorithm(sigAlgName));
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException("Unsupported HMAC algorithm: " + e.getMessage(), e);
+        }
+    }
+
+    @Override
+    public byte[] sign(byte[] data, String sigAlgName, Key key) {
+        try {
+            Mac mac = getMAC(sigAlgName);
+            mac.init(key);
+            mac.update(data);
+            return mac.doFinal();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public boolean verify(JWSInput jws, Key verifyKey) {
+        try {
+            byte[] signature = sign(jws.getEncodedSignatureInput().getBytes("UTF-8"), jws.getHeader().getAlgorithm().name(), verifyKey);
+            return MessageDigest.isEqual(signature, Base64Url.decode(jws.getEncodedSignature()));
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+}
                diff --git a/services/src/main/java/org/keycloak/jose/jws/HmacTokenSignatureProviderFactory.java b/services/src/main/java/org/keycloak/jose/jws/HmacTokenSignatureProviderFactory.java
new file mode 100644
index 0000000..bb4b8b0
--- /dev/null
+++ b/services/src/main/java/org/keycloak/jose/jws/HmacTokenSignatureProviderFactory.java
@@ -0,0 +1,56 @@
+package org.keycloak.jose.jws;
+
+import java.util.List;
+
+import org.keycloak.Config.Scope;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.provider.ProviderConfigurationBuilder;
+
+// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+
+@SuppressWarnings("rawtypes")
+public class HmacTokenSignatureProviderFactory implements TokenSignatureProviderFactory  {
+
+    public static final String ID = "hmac-signature";
+
+    private static final String HELP_TEXT = "Generates token signature provider using HMAC key";
+
+    private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = ProviderConfigurationBuilder.create().build();
+
+    @Override
+    public void init(Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+    }
+
+    @Override
+    public void close() {
+    }
+
+    @Override
+    public String getId() {
+        return ID;
+    }
+
+    @Override
+    public String getHelpText() {
+        return HELP_TEXT;
+    }
+
+    @Override
+    public List<ProviderConfigProperty> getConfigProperties() {
+        return CONFIG_PROPERTIES;
+    }
+
+    @Override
+    public TokenSignatureProvider create(KeycloakSession session, ComponentModel model) {
+        return new HmacTokenSignatureProvider(session, model);
+    }
+
+
+}
                diff --git a/services/src/main/java/org/keycloak/jose/jws/RsassaTokenSignatureProvider.java b/services/src/main/java/org/keycloak/jose/jws/RsassaTokenSignatureProvider.java
new file mode 100644
index 0000000..ba5b328
--- /dev/null
+++ b/services/src/main/java/org/keycloak/jose/jws/RsassaTokenSignatureProvider.java
@@ -0,0 +1,47 @@
+package org.keycloak.jose.jws;
+
+import java.security.Key;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Signature;
+
+import org.keycloak.component.ComponentModel;
+import org.keycloak.models.KeycloakSession;
+
+// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+
+public class RsassaTokenSignatureProvider extends AbstractTokenSignatureProvider {
+
+    public RsassaTokenSignatureProvider(KeycloakSession session, ComponentModel model) {
+        super(session, model);
+    }
+
+    @Override
+    public void close() {}
+
+    @Override
+    public byte[] sign(byte[] data, String sigAlgName, Key key) {
+        try {
+            PrivateKey privateKey = (PrivateKey)key;
+            Signature signature = getSignature(sigAlgName);
+            signature.initSign(privateKey);
+            signature.update(data);
+            return signature.sign();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public boolean verify(JWSInput jws, Key verifyKey) {
+        try {
+            PublicKey publicKey = (PublicKey)verifyKey;
+            Signature verifier = getSignature(jws.getHeader().getAlgorithm().name());
+            verifier.initVerify(publicKey);
+            verifier.update(jws.getEncodedSignatureInput().getBytes("UTF-8"));
+            return verifier.verify(jws.getSignature());
+        } catch (Exception e) {
+            return false;
+        }
+    }
+}
                diff --git a/services/src/main/java/org/keycloak/jose/jws/RsassaTokenSignatureProviderFactory.java b/services/src/main/java/org/keycloak/jose/jws/RsassaTokenSignatureProviderFactory.java
new file mode 100644
index 0000000..4c9a2b7
--- /dev/null
+++ b/services/src/main/java/org/keycloak/jose/jws/RsassaTokenSignatureProviderFactory.java
@@ -0,0 +1,55 @@
+package org.keycloak.jose.jws;
+
+import java.util.List;
+
+import org.keycloak.Config.Scope;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.provider.ProviderConfigurationBuilder;
+
+// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+
+@SuppressWarnings("rawtypes")
+public class RsassaTokenSignatureProviderFactory implements TokenSignatureProviderFactory {
+
+    public static final String ID = "rsassa-signature";
+
+    private static final String HELP_TEXT = "Generates token signature provider using RSA key";
+
+    private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = ProviderConfigurationBuilder.create().build();
+
+    @Override
+    public void init(Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+    }
+
+    @Override
+    public void close() {
+    }
+
+    @Override
+    public String getId() {
+        return ID;
+    }
+
+    @Override
+    public String getHelpText() {
+        return HELP_TEXT;
+    }
+
+    @Override
+    public List<ProviderConfigProperty> getConfigProperties() {
+        return CONFIG_PROPERTIES;
+    }
+
+    @Override
+    public TokenSignatureProvider create(KeycloakSession session, ComponentModel model) {
+        return new RsassaTokenSignatureProvider(session, model);
+    }
+
+}
                diff --git a/services/src/main/java/org/keycloak/jose/jws/TokenSignature.java b/services/src/main/java/org/keycloak/jose/jws/TokenSignature.java
new file mode 100644
index 0000000..0a81e64
--- /dev/null
+++ b/services/src/main/java/org/keycloak/jose/jws/TokenSignature.java
@@ -0,0 +1,97 @@
+package org.keycloak.jose.jws;
+
+import java.security.Key;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.jboss.logging.Logger;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.crypto.KeyUse;
+import org.keycloak.crypto.KeyWrapper;
+import org.keycloak.jose.jws.JWSSignatureProvider;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.representations.JsonWebToken;
+import org.keycloak.representations.RefreshToken;
+import org.keycloak.util.TokenUtil;
+
+// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+
+public class TokenSignature {
+
+    private static final Logger logger = Logger.getLogger(TokenSignature.class);
+
+    KeycloakSession session;
+    RealmModel realm;
+    String sigAlgName;
+
+    public static TokenSignature getInstance(KeycloakSession session, RealmModel realm, String sigAlgName) {
+        return new TokenSignature(session, realm, sigAlgName);
+    }
+
+    public TokenSignature(KeycloakSession session, RealmModel realm, String sigAlgName) {
+        this.session = session;
+        this.realm = realm;
+        this.sigAlgName = sigAlgName;
+    }
+
+    public String sign(JsonWebToken jwt) {
+        TokenSignatureProvider tokenSignatureProvider = getTokenSignatureProvider(sigAlgName);
+        if (tokenSignatureProvider == null) return null;
+
+        KeyWrapper keyWrapper = session.keys().getActiveKey(realm, KeyUse.SIG, sigAlgName);
+        if (keyWrapper == null) return null;
+
+        String keyId = keyWrapper.getKid();
+        Key signKey = keyWrapper.getSignKey();
+        String encodedToken = new JWSBuilder().type("JWT").kid(keyId).jsonContent(jwt).sign((JWSSignatureProvider)tokenSignatureProvider, sigAlgName, signKey);
+        return encodedToken;
+    }
+
+    public boolean verify(JWSInput jws) throws JWSInputException {
+        TokenSignatureProvider tokenSignatureProvider = getTokenSignatureProvider(sigAlgName);
+        if (tokenSignatureProvider == null) return false;
+
+        KeyWrapper keyWrapper = null;
+        // Backwards compatibility. Old offline tokens didn't have KID in the header
+        if (jws.getHeader().getKeyId() == null && isOfflineToken(jws)) {
+            logger.debugf("KID is null in offline token. Using the realm active key to verify token signature.");
+            keyWrapper = session.keys().getActiveKey(realm, KeyUse.SIG, sigAlgName);
+        } else {
+            keyWrapper = session.keys().getKey(realm, jws.getHeader().getKeyId(), KeyUse.SIG, sigAlgName);
+        }
+        if (keyWrapper == null) return false;
+
+        return tokenSignatureProvider.verify(jws, keyWrapper.getVerifyKey());
+    }
+
+    private static final String COMPONENT_SIGNATURE_ALGORITHM_KEY = "org.keycloak.jose.jws.TokenSignatureProvider.algorithm";
+
+    @SuppressWarnings("rawtypes")
+    private TokenSignatureProvider getTokenSignatureProvider(String sigAlgName) {
+        List<ComponentModel> components = new LinkedList<>(realm.getComponents(realm.getId(), TokenSignatureProvider.class.getName()));
+        ComponentModel c = null;
+        for (ComponentModel component : components) {
+            if (sigAlgName.equals(component.get(COMPONENT_SIGNATURE_ALGORITHM_KEY))) {
+                c = component;
+                break;
+            }
+        }
+        if (c == null) {
+            if (logger.isTraceEnabled()) {
+                logger.tracev("Failed to find TokenSignatureProvider algorithm={0}.", sigAlgName);
+            }
+            return null;
+        }
+        ProviderFactory<TokenSignatureProvider> f = session.getKeycloakSessionFactory().getProviderFactory(TokenSignatureProvider.class, c.getProviderId());
+        TokenSignatureProviderFactory factory = (TokenSignatureProviderFactory) f;
+        TokenSignatureProvider provider = factory.create(session, c);
+        return provider;
+    }
+
+    private boolean isOfflineToken(JWSInput jws) throws JWSInputException {
+        RefreshToken token = TokenUtil.getRefreshToken(jws.getContent());
+        return token.getType().equals(TokenUtil.TOKEN_TYPE_OFFLINE);
+    }
+}
                diff --git a/services/src/main/java/org/keycloak/jose/jws/TokenSignatureUtil.java b/services/src/main/java/org/keycloak/jose/jws/TokenSignatureUtil.java
new file mode 100644
index 0000000..c51e95a
--- /dev/null
+++ b/services/src/main/java/org/keycloak/jose/jws/TokenSignatureUtil.java
@@ -0,0 +1,22 @@
+package org.keycloak.jose.jws;
+
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
+
+// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+
+public class TokenSignatureUtil {
+    public static final String REALM_SIGNATURE_ALGORITHM_KEY = "token.signed.response.alg";
+    private static String DEFAULT_ALGORITHM_NAME = "RS256";
+
+    public static String getTokenSignatureAlgorithm(KeycloakSession session, RealmModel realm, ClientModel client) {
+        String realmSigAlgName = realm.getAttribute(REALM_SIGNATURE_ALGORITHM_KEY);
+        String clientSigAlgname = null;
+        if (client != null) clientSigAlgname = OIDCAdvancedConfigWrapper.fromClientModel(client).getIdTokenSignedResponseAlg();
+        String sigAlgName = clientSigAlgname;
+        if (sigAlgName == null) sigAlgName = (realmSigAlgName == null ? DEFAULT_ALGORITHM_NAME : realmSigAlgName);
+        return sigAlgName;
+    }
+}
                diff --git a/services/src/main/java/org/keycloak/keys/AbstractEcdsaKeyProvider.java b/services/src/main/java/org/keycloak/keys/AbstractEcdsaKeyProvider.java
new file mode 100644
index 0000000..2a7ca6e
--- /dev/null
+++ b/services/src/main/java/org/keycloak/keys/AbstractEcdsaKeyProvider.java
@@ -0,0 +1,60 @@
+package org.keycloak.keys;
+
+import java.security.KeyPair;
+import java.util.Collections;
+import java.util.List;
+
+import org.keycloak.common.util.KeyUtils;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.crypto.KeyStatus;
+import org.keycloak.crypto.KeyType;
+import org.keycloak.crypto.KeyUse;
+import org.keycloak.crypto.KeyWrapper;
+import org.keycloak.models.RealmModel;
+
+// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+
+public abstract class AbstractEcdsaKeyProvider implements KeyProvider {
+
+    private final KeyStatus status;
+
+    private final ComponentModel model;
+
+    private final KeyWrapper key;
+
+    public AbstractEcdsaKeyProvider(RealmModel realm, ComponentModel model) {
+        this.model = model;
+        this.status = KeyStatus.from(model.get(Attributes.ACTIVE_KEY, true), model.get(Attributes.ENABLED_KEY, true));
+
+        if (model.hasNote(KeyWrapper.class.getName())) {
+            key = model.getNote(KeyWrapper.class.getName());
+        } else {
+            key = loadKey(realm, model);
+            model.setNote(KeyWrapper.class.getName(), key);
+        }
+    }
+
+    protected abstract KeyWrapper loadKey(RealmModel realm, ComponentModel model);
+
+    @Override
+    public List<KeyWrapper> getKeys() {
+        return Collections.singletonList(key);
+    }
+
+    protected KeyWrapper createKeyWrapper(KeyPair keyPair, String ecInNistRep) {
+        KeyWrapper key = new KeyWrapper();
+
+        key.setProviderId(model.getId());
+        key.setProviderPriority(model.get("priority", 0l));
+
+        key.setKid(KeyUtils.createKeyId(keyPair.getPublic()));
+        key.setUse(KeyUse.SIG);
+        key.setType(KeyType.EC);
+        key.setAlgorithms(AbstractEcdsaKeyProviderFactory.convertECDomainParmNistRepToAlgorithm(ecInNistRep));
+        key.setStatus(status);
+        key.setSignKey(keyPair.getPrivate());
+        key.setVerifyKey(keyPair.getPublic());
+
+        return key;
+    }
+}
                diff --git a/services/src/main/java/org/keycloak/keys/AbstractEcdsaKeyProviderFactory.java b/services/src/main/java/org/keycloak/keys/AbstractEcdsaKeyProviderFactory.java
new file mode 100644
index 0000000..14525df
--- /dev/null
+++ b/services/src/main/java/org/keycloak/keys/AbstractEcdsaKeyProviderFactory.java
@@ -0,0 +1,98 @@
+package org.keycloak.keys;
+
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.SecureRandom;
+import java.security.spec.ECGenParameterSpec;
+
+import org.keycloak.component.ComponentModel;
+import org.keycloak.component.ComponentValidationException;
+import org.keycloak.crypto.Algorithm;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.provider.ConfigurationValidationHelper;
+import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.provider.ProviderConfigurationBuilder;
+
+import static org.keycloak.provider.ProviderConfigProperty.LIST_TYPE;
+
+// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+
+@SuppressWarnings("rawtypes")
+public abstract class AbstractEcdsaKeyProviderFactory implements KeyProviderFactory {
+
+    protected static final String ECDSA_PRIVATE_KEY_KEY = "ecdsaPrivateKey";
+    protected static final String ECDSA_PUBLIC_KEY_KEY = "ecdsaPublicKey";
+    protected static final String ECDSA_ELLIPTIC_CURVE_KEY = "ecdsaEllipticCurveKey";
+
+    // only support NIST P-256 for ES256, P-384 for ES384, P-521 for ES512
+    protected static ProviderConfigProperty ECDSA_ELLIPTIC_CURVE_PROPERTY = new ProviderConfigProperty(ECDSA_ELLIPTIC_CURVE_KEY, "Elliptic Curve", "Elliptic Curve used in ECDSA", LIST_TYPE,
+            String.valueOf(GeneratedEcdsaKeyProviderFactory.DEFAULT_ECDSA_ELLIPTIC_CURVE),
+            "P-256", "P-384", "P-521");
+ 
+    public final static ProviderConfigurationBuilder configurationBuilder() {
+        return ProviderConfigurationBuilder.create()
+                .property(Attributes.PRIORITY_PROPERTY)
+                .property(Attributes.ENABLED_PROPERTY)
+                .property(Attributes.ACTIVE_PROPERTY);
+    }
+
+    @Override
+    public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel model) throws ComponentValidationException {
+        ConfigurationValidationHelper.check(model)
+                .checkLong(Attributes.PRIORITY_PROPERTY, false)
+                .checkBoolean(Attributes.ENABLED_PROPERTY, false)
+                .checkBoolean(Attributes.ACTIVE_PROPERTY, false);
+    }
+
+    public static KeyPair generateEcdsaKeyPair(String keySpecName) {
+        try {
+            KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
+            SecureRandom randomGen = SecureRandom.getInstance("SHA1PRNG");
+            ECGenParameterSpec ecSpec = new ECGenParameterSpec(keySpecName);
+            keyGen.initialize(ecSpec, randomGen);
+            return keyGen.generateKeyPair();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static String convertECDomainParmNistRepToSecRep(String ecInNistRep) {
+        // convert Elliptic Curve Domain Parameter Name in NIST to SEC which is used to generate its EC key
+        String ecInSecRep = null;
+        switch(ecInNistRep) {
+            case "P-256" :
+            	ecInSecRep = "secp256r1";
+                break;
+            case "P-384" :
+            	ecInSecRep = "secp384r1";
+                break;
+            case "P-521" :
+            	ecInSecRep = "secp521r1";
+                break;
+            default :
+                // return null
+        }
+        return ecInSecRep;
+    }
+
+    public static String convertECDomainParmNistRepToAlgorithm(String ecInNistRep) {
+        // convert Elliptic Curve Domain Parameter Name in NIST to Algorithm (JWA) representation
+        String ecInAlgorithmRep = null;
+        switch(ecInNistRep) {
+            case "P-256" :
+                ecInAlgorithmRep = Algorithm.ES256;
+                break;
+            case "P-384" :
+                ecInAlgorithmRep = Algorithm.ES384;
+                break;
+            case "P-521" :
+                ecInAlgorithmRep = Algorithm.ES512;
+                break;
+            default :
+                // return null
+        }
+        return ecInAlgorithmRep;
+    }
+
+}
                diff --git a/services/src/main/java/org/keycloak/keys/FailsafeAesKeyProvider.java b/services/src/main/java/org/keycloak/keys/FailsafeAesKeyProvider.java
index bac9f76..1bd403b 100644
--- a/services/src/main/java/org/keycloak/keys/FailsafeAesKeyProvider.java
+++ b/services/src/main/java/org/keycloak/keys/FailsafeAesKeyProvider.java
@@ -48,4 +48,5 @@ public class FailsafeAesKeyProvider extends FailsafeSecretKeyProvider {
     protected Logger logger() {
         return logger;
     }
+
 }
                diff --git a/services/src/main/java/org/keycloak/keys/FailsafeEcdsaKeyProvider.java b/services/src/main/java/org/keycloak/keys/FailsafeEcdsaKeyProvider.java
new file mode 100644
index 0000000..96f6890
--- /dev/null
+++ b/services/src/main/java/org/keycloak/keys/FailsafeEcdsaKeyProvider.java
@@ -0,0 +1,66 @@
+package org.keycloak.keys;
+
+import java.security.KeyPair;
+import java.util.Collections;
+import java.util.List;
+
+import org.jboss.logging.Logger;
+import org.keycloak.common.util.KeyUtils;
+import org.keycloak.common.util.Time;
+import org.keycloak.crypto.Algorithm;
+import org.keycloak.crypto.KeyStatus;
+import org.keycloak.crypto.KeyType;
+import org.keycloak.crypto.KeyUse;
+import org.keycloak.crypto.KeyWrapper;
+
+// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+
+public class FailsafeEcdsaKeyProvider implements KeyProvider {
+
+    private static final Logger logger = Logger.getLogger(FailsafeEcdsaKeyProvider.class);
+
+    private static KeyWrapper KEY;
+
+    private static long EXPIRES;
+
+    private KeyWrapper key;
+
+    public FailsafeEcdsaKeyProvider() {
+        logger.errorv("No active keys found, using failsafe provider, please login to admin console to add keys. Clustering is not supported.");
+
+        synchronized (FailsafeEcdsaKeyProvider.class) {
+            if (EXPIRES < Time.currentTime()) {
+                KEY = createKeyWrapper();
+                EXPIRES = Time.currentTime() + 60 * 10;
+
+                if (EXPIRES > 0) {
+                    logger.warnv("Keys expired, re-generated kid={0}", KEY.getKid());
+                }
+            }
+
+            key = KEY;
+        }
+    }
+
+    @Override
+    public List<KeyWrapper> getKeys() {
+        return Collections.singletonList(key);
+    }
+
+    private KeyWrapper createKeyWrapper() {
+        // secp256r1,NIST P-256,X9.62 prime256v1,1.2.840.10045.3.1.7
+        KeyPair keyPair = AbstractEcdsaKeyProviderFactory.generateEcdsaKeyPair("secp256r1");
+
+        KeyWrapper key = new KeyWrapper();
+
+        key.setKid(KeyUtils.createKeyId(keyPair.getPublic()));
+        key.setUse(KeyUse.SIG);
+        key.setType(KeyType.EC);
+        key.setAlgorithms(Algorithm.ES256);
+        key.setStatus(KeyStatus.ACTIVE);
+        key.setSignKey(keyPair.getPrivate());
+        key.setVerifyKey(keyPair.getPublic());
+
+        return key;
+    }
+}
                diff --git a/services/src/main/java/org/keycloak/keys/FailsafeHmacKeyProvider.java b/services/src/main/java/org/keycloak/keys/FailsafeHmacKeyProvider.java
index 1114c24..7ca5737 100644
--- a/services/src/main/java/org/keycloak/keys/FailsafeHmacKeyProvider.java
+++ b/services/src/main/java/org/keycloak/keys/FailsafeHmacKeyProvider.java
@@ -18,17 +18,9 @@
 package org.keycloak.keys;
 
 import org.jboss.logging.Logger;
-import org.keycloak.common.util.KeyUtils;
-import org.keycloak.common.util.Time;
 import org.keycloak.crypto.Algorithm;
 import org.keycloak.crypto.KeyType;
 import org.keycloak.crypto.KeyUse;
-import org.keycloak.crypto.KeyWrapper;
-import org.keycloak.models.utils.KeycloakModelUtils;
-
-import javax.crypto.SecretKey;
-import java.util.Collections;
-import java.util.List;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
                diff --git a/services/src/main/java/org/keycloak/keys/FailsafeRsaKeyProvider.java b/services/src/main/java/org/keycloak/keys/FailsafeRsaKeyProvider.java
index 9af84a5..78a23d0 100644
--- a/services/src/main/java/org/keycloak/keys/FailsafeRsaKeyProvider.java
+++ b/services/src/main/java/org/keycloak/keys/FailsafeRsaKeyProvider.java
@@ -77,5 +77,4 @@ public class FailsafeRsaKeyProvider implements KeyProvider {
         return key;
     }
 
-
 }
                diff --git a/services/src/main/java/org/keycloak/keys/GeneratedEcdsaKeyProvider.java b/services/src/main/java/org/keycloak/keys/GeneratedEcdsaKeyProvider.java
new file mode 100644
index 0000000..251eb80
--- /dev/null
+++ b/services/src/main/java/org/keycloak/keys/GeneratedEcdsaKeyProvider.java
@@ -0,0 +1,49 @@
+package org.keycloak.keys;
+
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+import org.jboss.logging.Logger;
+import org.keycloak.common.util.Base64;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.crypto.KeyWrapper;
+import org.keycloak.models.RealmModel;
+
+// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+
+public class GeneratedEcdsaKeyProvider extends AbstractEcdsaKeyProvider {
+    private static final Logger logger = Logger.getLogger(GeneratedEcdsaKeyProvider.class);
+
+    public GeneratedEcdsaKeyProvider(RealmModel realm, ComponentModel model) {
+        super(realm, model);
+    }
+
+	@Override
+	protected KeyWrapper loadKey(RealmModel realm, ComponentModel model) {
+        String privateEcdsaKeyBase64Encoded = model.getConfig().getFirst(GeneratedEcdsaKeyProviderFactory.ECDSA_PRIVATE_KEY_KEY);
+        String publicEcdsaKeyBase64Encoded = model.getConfig().getFirst(GeneratedEcdsaKeyProviderFactory.ECDSA_PUBLIC_KEY_KEY);
+        String ecInNistRep = model.getConfig().getFirst(GeneratedEcdsaKeyProviderFactory.ECDSA_ELLIPTIC_CURVE_KEY);
+
+        try {
+            PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(Base64.decode(privateEcdsaKeyBase64Encoded));
+            KeyFactory kf = KeyFactory.getInstance("EC");
+            PrivateKey decodedPrivateKey = kf.generatePrivate(privateKeySpec);
+
+            X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(Base64.decode(publicEcdsaKeyBase64Encoded));
+            PublicKey decodedPublicKey = kf.generatePublic(publicKeySpec);
+
+            KeyPair keyPair = new KeyPair(decodedPublicKey, decodedPrivateKey);
+
+            return createKeyWrapper(keyPair, ecInNistRep);
+        } catch (Exception e) {
+            logger.warnf("Exception at decodeEcdsaPublicKey. %s", e.toString());
+            return null;
+        }
+
+    }
+
+}
                diff --git a/services/src/main/java/org/keycloak/keys/GeneratedEcdsaKeyProviderFactory.java b/services/src/main/java/org/keycloak/keys/GeneratedEcdsaKeyProviderFactory.java
new file mode 100644
index 0000000..72517fc
--- /dev/null
+++ b/services/src/main/java/org/keycloak/keys/GeneratedEcdsaKeyProviderFactory.java
@@ -0,0 +1,90 @@
+package org.keycloak.keys;
+
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.List;
+
+import org.jboss.logging.Logger;
+import org.keycloak.common.util.Base64;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.component.ComponentValidationException;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.provider.ConfigurationValidationHelper;
+import org.keycloak.provider.ProviderConfigProperty;
+
+// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+
+public class GeneratedEcdsaKeyProviderFactory extends AbstractEcdsaKeyProviderFactory {
+
+    private static final Logger logger = Logger.getLogger(GeneratedEcdsaKeyProviderFactory.class);
+
+    public static final String ID = "ecdsa-generated";
+
+    private static final String HELP_TEXT = "Generates ECDSA keys";
+
+     // secp256r1,NIST P-256,X9.62 prime256v1,1.2.840.10045.3.1.7
+    public static final String DEFAULT_ECDSA_ELLIPTIC_CURVE = "P-256";
+
+    private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = AbstractEcdsaKeyProviderFactory.configurationBuilder()
+            .property(ECDSA_ELLIPTIC_CURVE_PROPERTY)
+            .build();
+
+    @Override
+    public KeyProvider create(KeycloakSession session, ComponentModel model) {
+        return new GeneratedEcdsaKeyProvider(session.getContext().getRealm(), model);
+    }
+
+    @Override
+    public String getHelpText() {
+        return HELP_TEXT;
+    }
+
+    @Override
+    public List<ProviderConfigProperty> getConfigProperties() {
+        return CONFIG_PROPERTIES;
+    }
+
+    @Override
+    public String getId() {
+        return ID;
+    }
+
+    @Override
+    public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel model) throws ComponentValidationException {
+        super.validateConfiguration(session, realm, model);
+
+        ConfigurationValidationHelper.check(model).checkList(ECDSA_ELLIPTIC_CURVE_PROPERTY, false);
+
+        String ecInNistRep = model.get(ECDSA_ELLIPTIC_CURVE_KEY);
+        if (ecInNistRep == null) ecInNistRep = DEFAULT_ECDSA_ELLIPTIC_CURVE;
+
+        if (!(model.contains(ECDSA_PRIVATE_KEY_KEY) && model.contains(ECDSA_PUBLIC_KEY_KEY))) {
+            generateKeys(realm, model, ecInNistRep);
+            logger.debugv("Generated keys for {0}", realm.getName());
+        } else {
+            String currentEc = model.get(ECDSA_ELLIPTIC_CURVE_KEY);
+            if (!ecInNistRep.equals(currentEc)) {
+                generateKeys(realm, model, ecInNistRep);
+                logger.debugv("Elliptic Curve changed, generating new keys for {0}", realm.getName());
+            }
+        }
+    }
+
+    private void generateKeys(RealmModel realm, ComponentModel model, String ecInNistRep) {
+        KeyPair keyPair;
+        try {
+            keyPair = generateEcdsaKeyPair(convertECDomainParmNistRepToSecRep(ecInNistRep));
+            model.put(ECDSA_PRIVATE_KEY_KEY, Base64.encodeBytes(keyPair.getPrivate().getEncoded()));
+            model.put(ECDSA_PUBLIC_KEY_KEY, Base64.encodeBytes(keyPair.getPublic().getEncoded()));
+            model.put(ECDSA_ELLIPTIC_CURVE_KEY, ecInNistRep);
+        } catch (Throwable t) {
+            throw new ComponentValidationException("Failed to generate ECDSA keys", t);
+        }
+    }
+
+}
                diff --git a/services/src/main/java/org/keycloak/keys/GeneratedHmacKeyProvider.java b/services/src/main/java/org/keycloak/keys/GeneratedHmacKeyProvider.java
index 2367a16..00e74f7 100644
--- a/services/src/main/java/org/keycloak/keys/GeneratedHmacKeyProvider.java
+++ b/services/src/main/java/org/keycloak/keys/GeneratedHmacKeyProvider.java
@@ -17,6 +17,8 @@
 
 package org.keycloak.keys;
 
+import java.security.Key;
+
 import org.keycloak.component.ComponentModel;
 import org.keycloak.crypto.Algorithm;
 import org.keycloak.crypto.KeyType;
                diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java
index 143ff30..50ae58f 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java
@@ -47,6 +47,9 @@ public class OIDCAdvancedConfigWrapper {
     // https://tools.ietf.org/html/draft-ietf-oauth-mtls-08#section-6.5
     private static final String USE_MTLS_HOK_TOKEN = "tls.client.certificate.bound.access.tokens";
 
+    // KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+    private static final String ID_TOKEN_SIGNED_RESPONSE_ALG = "id.token.signed.response.alg";
+
     private final ClientModel clientModel;
     private final ClientRepresentation clientRep;
 
@@ -137,6 +140,14 @@ public class OIDCAdvancedConfigWrapper {
         setAttribute(USE_MTLS_HOK_TOKEN, val);
     }
 
+    // KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+    public String getIdTokenSignedResponseAlg() {
+        return getAttribute(ID_TOKEN_SIGNED_RESPONSE_ALG);
+    }
+    public void setIdTokenSignedResponseAlg(String algName) {
+        setAttribute(ID_TOKEN_SIGNED_RESPONSE_ALG, algName);
+    }
+
     private String getAttribute(String attrKey) {
         if (clientModel != null) {
             return clientModel.getAttribute(attrKey);
                diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
index 8b16b3f..26bf09d 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
@@ -30,8 +30,9 @@ import org.keycloak.jose.jws.Algorithm;
 import org.keycloak.jose.jws.JWSBuilder;
 import org.keycloak.jose.jws.JWSInput;
 import org.keycloak.jose.jws.JWSInputException;
+import org.keycloak.jose.jws.TokenSignature;
+import org.keycloak.jose.jws.TokenSignatureUtil;
 import org.keycloak.jose.jws.crypto.HashProvider;
-import org.keycloak.jose.jws.crypto.RSAProvider;
 import org.keycloak.migration.migrators.MigrationUtils;
 import org.keycloak.models.AuthenticatedClientSessionModel;
 import org.keycloak.models.ClientModel;
@@ -74,7 +75,6 @@ import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
 
-import java.security.PublicKey;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashSet;
@@ -376,21 +376,11 @@ public class TokenManager {
 
     public RefreshToken toRefreshToken(KeycloakSession session, RealmModel realm, String encodedRefreshToken) throws JWSInputException, OAuthErrorException {
         JWSInput jws = new JWSInput(encodedRefreshToken);
-
-        PublicKey publicKey;
-
-        // Backwards compatibility. Old offline tokens didn't have KID in the header
-        if (jws.getHeader().getKeyId() == null && TokenUtil.isOfflineToken(encodedRefreshToken)) {
-            logger.debugf("KID is null in offline token. Using the realm active key to verify token signature.");
-            publicKey = session.keys().getActiveRsaKey(realm).getPublicKey();
-        } else {
-            publicKey = session.keys().getRsaPublicKey(realm, jws.getHeader().getKeyId());
-        }
-
-        if (!RSAProvider.verify(jws, publicKey)) {
+        // KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+        TokenSignature ts = TokenSignature.getInstance(session, realm, jws.getHeader().getAlgorithm().name());
+        if (!ts.verify(jws)) {
             throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token");
         }
-
         return jws.readJsonContent(RefreshToken.class);
     }
 
@@ -398,15 +388,15 @@ public class TokenManager {
         try {
             JWSInput jws = new JWSInput(encodedIDToken);
             IDToken idToken;
-            if (!RSAProvider.verify(jws, session.keys().getRsaPublicKey(realm, jws.getHeader().getKeyId()))) {
+            // KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+            TokenSignature ts = TokenSignature.getInstance(session, realm, jws.getHeader().getAlgorithm().name());
+            if (!ts.verify(jws)) {
                 throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid IDToken");
             }
             idToken = jws.readJsonContent(IDToken.class);
-
             if (idToken.isExpired()) {
                 throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "IDToken expired");
             }
-
             if (idToken.getIssuedAt() < realm.getNotBefore()) {
                 throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale IDToken");
             }
@@ -420,11 +410,12 @@ public class TokenManager {
         try {
             JWSInput jws = new JWSInput(encodedIDToken);
             IDToken idToken;
-            if (!RSAProvider.verify(jws, session.keys().getRsaPublicKey(realm, jws.getHeader().getKeyId()))) {
+            // KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+            TokenSignature ts = TokenSignature.getInstance(session, realm, jws.getHeader().getAlgorithm().name());
+            if (!ts.verify(jws)) {
                 throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid IDToken");
             }
             idToken = jws.readJsonContent(IDToken.class);
-
             return idToken;
         } catch (JWSInputException e) {
             throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid IDToken", e);
@@ -862,20 +853,20 @@ public class TokenManager {
         }
 
         public AccessTokenResponseBuilder generateCodeHash(String code) {
-            codeHash = HashProvider.oidcHash(jwsAlgorithm, code);
+            // KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+            codeHash = HashProvider.oidcHash(TokenSignatureUtil.getTokenSignatureAlgorithm(session, realm, client), code);
             return this;
         }
 
         // Financial API - Part 2: Read and Write API Security Profile
         // http://openid.net/specs/openid-financial-api-part-2.html#authorization-server
         public AccessTokenResponseBuilder generateStateHash(String state) {
-            stateHash = HashProvider.oidcHash(jwsAlgorithm, state);
+            // KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+            stateHash = HashProvider.oidcHash(TokenSignatureUtil.getTokenSignatureAlgorithm(session, realm, client), state);
             return this;
         }
 
         public AccessTokenResponse build() {
-            KeyManager.ActiveRsaKey activeRsaKey = session.keys().getActiveRsaKey(realm);
-
             if (accessToken != null) {
                 event.detail(Details.TOKEN_ID, accessToken.getId());
             }
@@ -890,8 +881,13 @@ public class TokenManager {
             }
 
             AccessTokenResponse res = new AccessTokenResponse();
+
+            // KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+            TokenSignature ts = TokenSignature.getInstance(session, realm, TokenSignatureUtil.getTokenSignatureAlgorithm(session, realm, client));
+
             if (accessToken != null) {
-                String encodedToken = new JWSBuilder().type(JWT).kid(activeRsaKey.getKid()).jsonContent(accessToken).sign(jwsAlgorithm, activeRsaKey.getPrivateKey());
+                // KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+                String encodedToken = ts.sign(accessToken);
                 res.setToken(encodedToken);
                 res.setTokenType("bearer");
                 res.setSessionState(accessToken.getSessionState());
@@ -901,7 +897,8 @@ public class TokenManager {
             }
 
             if (generateAccessTokenHash) {
-                String atHash = HashProvider.oidcHash(jwsAlgorithm, res.getToken());
+                // KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+                String atHash = HashProvider.oidcHash(TokenSignatureUtil.getTokenSignatureAlgorithm(session, realm, client), res.getToken());
                 idToken.setAccessTokenHash(atHash);
             }
             if (codeHash != null) {
@@ -913,11 +910,13 @@ public class TokenManager {
                 idToken.setStateHash(stateHash);
             }
             if (idToken != null) {
-                String encodedToken = new JWSBuilder().type(JWT).kid(activeRsaKey.getKid()).jsonContent(idToken).sign(jwsAlgorithm, activeRsaKey.getPrivateKey());
+                // KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+                String encodedToken = ts.sign(idToken);
                 res.setIdToken(encodedToken);
             }
             if (refreshToken != null) {
-                String encodedToken = new JWSBuilder().type(JWT).kid(activeRsaKey.getKid()).jsonContent(refreshToken).sign(jwsAlgorithm, activeRsaKey.getPrivateKey());
+                // KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+                String encodedToken = ts.sign(refreshToken);
                 res.setRefreshToken(encodedToken);
                 if (refreshToken.getExpiration() != 0) {
                     res.setRefreshExpiresIn(refreshToken.getExpiration() - Time.currentTime());
                diff --git a/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java b/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java
index fd9a0ce..664714a 100644
--- a/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java
@@ -121,6 +121,11 @@ public class DescriptionConverter {
             else configWrapper.setUseMtlsHoKToken(false);
         }
 
+        // KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+        if (clientOIDC.getIdTokenSignedResponseAlg() != null) {
+            configWrapper.setIdTokenSignedResponseAlg(clientOIDC.getIdTokenSignedResponseAlg());
+        }
+
         return client;
     }
 
@@ -201,6 +206,10 @@ public class DescriptionConverter {
         } else {
             response.setTlsClientCertificateBoundAccessTokens(Boolean.FALSE);
         }
+        // KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+        if (config.getIdTokenSignedResponseAlg() != null) {
+            response.setIdTokenSignedResponseAlg(config.getIdTokenSignedResponseAlg());
+        }
 
         List<ProtocolMapperRepresentation> foundPairwiseMappers = PairwiseSubMapperUtils.getPairwiseSubMappers(client);
         SubjectType subjectType = foundPairwiseMappers.isEmpty() ? SubjectType.PUBLIC : SubjectType.PAIRWISE;
                diff --git a/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java b/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
index 344203b..7742fcb 100755
--- a/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
+++ b/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
@@ -27,6 +27,7 @@ import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.utils.DefaultKeyProviders;
+import org.keycloak.models.utils.DefaultTokenSignatureProviders;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.services.ServicesLogger;
 
@@ -89,6 +90,9 @@ public class ApplianceBootstrap {
         session.getContext().setRealm(realm);
         DefaultKeyProviders.createProviders(realm);
 
+        // KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+        DefaultTokenSignatureProviders.createProviders(realm);
+
         return true;
     }
 
                diff --git a/services/src/main/resources/META-INF/services/org.keycloak.jose.jws.TokenSignatureProviderFactory b/services/src/main/resources/META-INF/services/org.keycloak.jose.jws.TokenSignatureProviderFactory
new file mode 100644
index 0000000..ed96da4
--- /dev/null
+++ b/services/src/main/resources/META-INF/services/org.keycloak.jose.jws.TokenSignatureProviderFactory
@@ -0,0 +1,4 @@
+# KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+org.keycloak.jose.jws.RsassaTokenSignatureProviderFactory
+org.keycloak.jose.jws.HmacTokenSignatureProviderFactory
+org.keycloak.jose.jws.EcdsaTokenSignatureProviderFactory
                diff --git a/services/src/main/resources/META-INF/services/org.keycloak.keys.KeyProviderFactory b/services/src/main/resources/META-INF/services/org.keycloak.keys.KeyProviderFactory
index d46a92f..01523b1 100644
--- a/services/src/main/resources/META-INF/services/org.keycloak.keys.KeyProviderFactory
+++ b/services/src/main/resources/META-INF/services/org.keycloak.keys.KeyProviderFactory
@@ -19,4 +19,6 @@ org.keycloak.keys.GeneratedHmacKeyProviderFactory
 org.keycloak.keys.GeneratedAesKeyProviderFactory
 org.keycloak.keys.GeneratedRsaKeyProviderFactory
 org.keycloak.keys.JavaKeystoreKeyProviderFactory
-org.keycloak.keys.ImportedRsaKeyProviderFactory
\ No newline at end of file
+org.keycloak.keys.ImportedRsaKeyProviderFactory
+# KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+org.keycloak.keys.GeneratedEcdsaKeyProviderFactory
                diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/TokenSignatureUtil.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/TokenSignatureUtil.java
new file mode 100644
index 0000000..428f38a
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/TokenSignatureUtil.java
@@ -0,0 +1,165 @@
+package org.keycloak.testsuite.util;
+
+import java.io.IOException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Map;
+
+import javax.ws.rs.core.Response;
+
+import org.jboss.logging.Logger;
+import org.keycloak.admin.client.Keycloak;
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.common.util.Base64;
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.jose.jws.EcdsaTokenSignatureProviderFactory;
+import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.jose.jws.TokenSignatureProvider;
+import org.keycloak.keys.GeneratedEcdsaKeyProviderFactory;
+import org.keycloak.keys.KeyProvider;
+import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.ComponentRepresentation;
+import org.keycloak.representations.idm.KeysMetadataRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.admin.ApiUtil;
+import org.keycloak.testsuite.arquillian.TestContext;
+
+// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+
+public class TokenSignatureUtil {
+    private static Logger log = Logger.getLogger(TokenSignatureUtil.class);
+
+    private static final String COMPONENT_SIGNATURE_ALGORITHM_KEY = "token.signed.response.alg";
+    
+    private static final String ECDSA_ELLIPTIC_CURVE_KEY = "ecdsaEllipticCurveKey";
+    private static final String TEST_REALM_NAME = "test";
+
+    public static void changeRealmTokenSignatureProvider(Keycloak adminClient, String toSigAlgName) {
+        RealmRepresentation rep = adminClient.realm(TEST_REALM_NAME).toRepresentation();
+        Map<String, String> attributes = rep.getAttributes();
+        log.tracef("change realm test signature algorithm from %s to %s", attributes.get(COMPONENT_SIGNATURE_ALGORITHM_KEY), toSigAlgName);
+        attributes.put(COMPONENT_SIGNATURE_ALGORITHM_KEY, toSigAlgName);
+        rep.setAttributes(attributes);
+        adminClient.realm(TEST_REALM_NAME).update(rep);
+    }
+
+    public static void changeClientTokenSignatureProvider(ClientResource clientResource, Keycloak adminClient, String toSigAlgName) {
+        ClientRepresentation clientRep = clientResource.toRepresentation();
+        log.tracef("change client %s signature algorithm from %s to %s", clientRep.getClientId(), OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).getIdTokenSignedResponseAlg(), toSigAlgName);
+        OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setIdTokenSignedResponseAlg(toSigAlgName);
+        clientResource.update(clientRep);
+    }
+
+    public static boolean verifySignature(String sigAlgName, String token, Keycloak adminClient) throws Exception {
+        PublicKey publicKey = getRealmPublicKey(TEST_REALM_NAME, sigAlgName, adminClient);
+        JWSInput jws = new JWSInput(token);
+        Signature verifier = getSignature(sigAlgName);
+        verifier.initVerify(publicKey);
+        verifier.update(jws.getEncodedSignatureInput().getBytes("UTF-8"));
+        return verifier.verify(jws.getSignature());
+    }
+
+    public static void registerTokenSignatureProvider(String sigAlgName, Keycloak adminClient, TestContext testContext) {
+        long priority = System.currentTimeMillis();
+
+        ComponentRepresentation rep = createTokenSignatureRep("valid", EcdsaTokenSignatureProviderFactory.ID);
+        rep.setConfig(new MultivaluedHashMap<>());
+        rep.getConfig().putSingle("priority", Long.toString(priority));
+        rep.getConfig().putSingle("org.keycloak.jose.jws.TokenSignatureProvider.algorithm", sigAlgName);
+
+        Response response = adminClient.realm(TEST_REALM_NAME).components().add(rep);
+        String id = ApiUtil.getCreatedId(response);
+        testContext.getOrCreateCleanup(TEST_REALM_NAME).addComponentId(id);
+        response.close();
+    }
+
+    public static void registerKeyProvider(String ecNistRep, Keycloak adminClient, TestContext testContext) {
+        long priority = System.currentTimeMillis();
+
+        ComponentRepresentation rep = createKeyRep("valid", GeneratedEcdsaKeyProviderFactory.ID);
+        rep.setConfig(new MultivaluedHashMap<>());
+        rep.getConfig().putSingle("priority", Long.toString(priority));
+        rep.getConfig().putSingle(ECDSA_ELLIPTIC_CURVE_KEY, ecNistRep);
+
+        Response response = adminClient.realm(TEST_REALM_NAME).components().add(rep);
+        String id = ApiUtil.getCreatedId(response);
+        testContext.getOrCreateCleanup(TEST_REALM_NAME).addComponentId(id);
+        response.close();
+    }
+
+    private static ComponentRepresentation createTokenSignatureRep(String name, String providerId) {
+        ComponentRepresentation rep = new ComponentRepresentation();
+        rep.setName(name);
+        rep.setParentId(TEST_REALM_NAME);
+        rep.setProviderId(providerId);
+        rep.setProviderType(TokenSignatureProvider.class.getName());
+        rep.setConfig(new MultivaluedHashMap<>());
+        return rep;
+    }
+
+    private static ComponentRepresentation createKeyRep(String name, String providerId) {
+        ComponentRepresentation rep = new ComponentRepresentation();
+        rep.setName(name);
+        rep.setParentId(TEST_REALM_NAME);
+        rep.setProviderId(providerId);
+        rep.setProviderType(KeyProvider.class.getName());
+        rep.setConfig(new MultivaluedHashMap<>());
+        return rep;
+    }
+
+    private static PublicKey getRealmPublicKey(String realm, String sigAlgName, Keycloak adminClient) {
+        KeysMetadataRepresentation keyMetadata = adminClient.realms().realm(realm).keys().getKeyMetadata();
+        String activeKid = keyMetadata.getActive().get(sigAlgName);
+        PublicKey publicKey = null;
+        for (KeysMetadataRepresentation.KeyMetadataRepresentation rep : keyMetadata.getKeys()) {
+            if (rep.getKid().equals(activeKid)) {
+                X509EncodedKeySpec publicKeySpec = null;
+                try {
+                    publicKeySpec = new X509EncodedKeySpec(Base64.decode(rep.getPublicKey()));
+                } catch (IOException e1) {
+                    e1.printStackTrace();
+                }
+                KeyFactory kf = null;
+                try {
+                    kf = KeyFactory.getInstance("EC");
+                } catch (NoSuchAlgorithmException e) {
+                    e.printStackTrace();
+                }
+                try {
+                    publicKey = kf.generatePublic(publicKeySpec);
+                } catch (InvalidKeySpecException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return publicKey;
+    }
+
+    private static String getJavaAlgorithm(String sigAlgName) {
+        switch (sigAlgName) {
+        case "ES256":
+            return "SHA256withECDSA";
+        case "ES384":
+            return "SHA384withECDSA";
+        case "ES512":
+            return "SHA512withECDSA";
+        default:
+            throw new IllegalArgumentException("Not an ECDSA Algorithm");
+        }
+    }
+
+    private static Signature getSignature(String sigAlgName) {
+        try {
+            // use Bouncy Castle for signature verification intentionally
+            Signature signature = Signature.getInstance(getJavaAlgorithm(sigAlgName), "BC");
+            return signature;
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
                diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/GeneratedEcdsaKeyProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/GeneratedEcdsaKeyProviderTest.java
new file mode 100644
index 0000000..6276b5d
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/GeneratedEcdsaKeyProviderTest.java
@@ -0,0 +1,162 @@
+package org.keycloak.testsuite.keys;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
+
+import java.util.List;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+
+import org.jboss.arquillian.graphene.page.Page;
+import org.junit.Rule;
+import org.junit.Test;
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.crypto.KeyType;
+import org.keycloak.keys.GeneratedEcdsaKeyProviderFactory;
+import org.keycloak.keys.KeyProvider;
+import org.keycloak.representations.idm.ComponentRepresentation;
+import org.keycloak.representations.idm.ErrorRepresentation;
+import org.keycloak.representations.idm.KeysMetadataRepresentation;
+import org.keycloak.representations.idm.KeysMetadataRepresentation.KeyMetadataRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.AbstractKeycloakTest;
+import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.admin.ApiUtil;
+import org.keycloak.testsuite.pages.AppPage;
+import org.keycloak.testsuite.pages.LoginPage;
+
+// KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+
+public class GeneratedEcdsaKeyProviderTest extends AbstractKeycloakTest {
+    private static final String DEFAULT_EC = "P-256";
+    private static final String ECDSA_ELLIPTIC_CURVE_KEY = "ecdsaEllipticCurveKey";
+    private static final String TEST_REALM_NAME = "test";
+
+    @Rule
+    public AssertEvents events = new AssertEvents(this);
+
+    @Page
+    protected AppPage appPage;
+
+    @Page
+    protected LoginPage loginPage;
+
+    @Override
+    public void addTestRealms(List<RealmRepresentation> testRealms) {
+        RealmRepresentation realm = loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class);
+        testRealms.add(realm);
+    }
+
+    @Test
+    public void defaultEc() throws Exception {
+        supportedEc(null);
+    }
+
+    @Test
+    public void supportedEcP521() throws Exception {
+        supportedEc("P-521");
+    }
+
+    @Test
+    public void supportedEcP384() throws Exception {
+        supportedEc("P-384");
+    }
+
+    @Test
+    public void supportedEcP256() throws Exception {
+        supportedEc("P-256");
+    }
+
+    @Test
+    public void unsupportedEcK163() throws Exception {
+        // NIST.FIPS.186-4 Koblitz Curve over Binary Field
+        unsupportedEc("K-163");
+    }
+    
+    private void supportedEc(String ecInNistRep) {
+        long priority = System.currentTimeMillis();
+
+        ComponentRepresentation rep = createRep("valid", GeneratedEcdsaKeyProviderFactory.ID);
+        rep.setConfig(new MultivaluedHashMap<>());
+        rep.getConfig().putSingle("priority", Long.toString(priority));
+        if (ecInNistRep != null) {
+            rep.getConfig().putSingle(ECDSA_ELLIPTIC_CURVE_KEY, ecInNistRep);
+        } else {
+            ecInNistRep = DEFAULT_EC;
+        }
+
+        Response response = adminClient.realm(TEST_REALM_NAME).components().add(rep);
+        String id = ApiUtil.getCreatedId(response);
+        getCleanup().addComponentId(id);
+        response.close();
+
+        ComponentRepresentation createdRep = adminClient.realm(TEST_REALM_NAME).components().component(id).toRepresentation();
+
+        // stands for the number of properties in the key provider config
+        assertEquals(2, createdRep.getConfig().size());
+        assertEquals(Long.toString(priority), createdRep.getConfig().getFirst("priority"));
+        assertEquals(ecInNistRep, createdRep.getConfig().getFirst(ECDSA_ELLIPTIC_CURVE_KEY));
+
+        KeysMetadataRepresentation keys = adminClient.realm(TEST_REALM_NAME).keys().getKeyMetadata();
+
+        KeysMetadataRepresentation.KeyMetadataRepresentation key = null;
+
+        for (KeyMetadataRepresentation k : keys.getKeys()) {
+           if (KeyType.EC.equals(k.getType()) && id.equals(k.getProviderId())) {
+                key = k;
+                break;
+           }
+        }
+        assertNotNull(key);
+
+        assertEquals(id, key.getProviderId());
+        assertEquals(KeyType.EC, key.getType());
+        assertEquals(priority, key.getProviderPriority());
+    }
+
+    private void unsupportedEc(String ecInNistRep) {
+        long priority = System.currentTimeMillis();
+
+        ComponentRepresentation rep = createRep("valid", GeneratedEcdsaKeyProviderFactory.ID);
+        rep.setConfig(new MultivaluedHashMap<>());
+        rep.getConfig().putSingle("priority", Long.toString(priority));
+        rep.getConfig().putSingle(ECDSA_ELLIPTIC_CURVE_KEY, ecInNistRep);
+        boolean isEcAccepted = true;
+
+        Response response = null;
+        try {
+            response = adminClient.realm(TEST_REALM_NAME).components().add(rep);
+            String id = ApiUtil.getCreatedId(response);
+            getCleanup().addComponentId(id);
+            response.close();
+        } catch (WebApplicationException e) {
+            isEcAccepted = false;
+        } finally {
+            response.close();
+        }
+        assertEquals(isEcAccepted, false);
+    }
+    
+    protected void assertErrror(Response response, String error) {
+        if (!response.hasEntity()) {
+            fail("No error message set");
+        }
+
+        ErrorRepresentation errorRepresentation = response.readEntity(ErrorRepresentation.class);
+        assertEquals(error, errorRepresentation.getErrorMessage());
+        response.close();
+    }
+
+    protected ComponentRepresentation createRep(String name, String providerId) {
+        ComponentRepresentation rep = new ComponentRepresentation();
+        rep.setName(name);
+        rep.setParentId(TEST_REALM_NAME);
+        rep.setProviderId(providerId);
+        rep.setProviderType(KeyProvider.class.getName());
+        rep.setConfig(new MultivaluedHashMap<>());
+        return rep;
+    }
+}
                diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
index 27b895a..6e1b1b3 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
@@ -34,6 +34,7 @@ import org.keycloak.admin.client.resource.ClientScopeResource;
 import org.keycloak.admin.client.resource.RealmResource;
 import org.keycloak.admin.client.resource.UserResource;
 import org.keycloak.common.enums.SslRequired;
+import org.keycloak.crypto.Algorithm;
 import org.keycloak.events.Details;
 import org.keycloak.events.Errors;
 import org.keycloak.jose.jws.JWSHeader;
@@ -65,6 +66,7 @@ import org.keycloak.testsuite.util.ClientManager;
 import org.keycloak.testsuite.util.OAuthClient;
 import org.keycloak.testsuite.util.RealmManager;
 import org.keycloak.testsuite.util.RoleBuilder;
+import org.keycloak.testsuite.util.TokenSignatureUtil;
 import org.keycloak.testsuite.util.UserBuilder;
 import org.keycloak.testsuite.util.UserInfoClientUtil;
 import org.keycloak.testsuite.util.UserManager;
@@ -1026,4 +1028,143 @@ public class AccessTokenTest extends AbstractKeycloakTest {
                 .header(HttpHeaders.AUTHORIZATION, header)
                 .post(Entity.form(form));
     }
+
+    // KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+
+    @Test
+    public void accessTokenRequest_RealmRS256_ClientRS384_EffectiveRS384() throws Exception {
+        try {
+            TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.RS256);
+            TokenSignatureUtil.changeClientTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), adminClient, Algorithm.RS384);
+            tokenRequest(Algorithm.RS384);
+        } finally {
+            TokenSignatureUtil.changeClientTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), adminClient, Algorithm.RS256);
+        }
+    }
+
+    @Test
+    public void accessTokenRequest_RealmRS512_ClientRS512_EffectiveRS512() throws Exception {
+        try {
+            TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.RS512);
+            TokenSignatureUtil.changeClientTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), adminClient, Algorithm.RS512);
+            tokenRequest(Algorithm.RS512);
+        } finally {
+            TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.RS256);
+            TokenSignatureUtil.changeClientTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), adminClient, Algorithm.RS256);
+        }
+    }
+
+    @Test
+    public void accessTokenRequest_RealmRS256_ClientES256_EffectiveES256() throws Exception {
+        try {
+            TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.RS256);
+            TokenSignatureUtil.changeClientTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), adminClient, Algorithm.ES256);
+            TokenSignatureUtil.registerKeyProvider("P-256", adminClient, testContext);
+            TokenSignatureUtil.registerTokenSignatureProvider(Algorithm.ES256, adminClient, testContext);
+            tokenRequestSignatureVerifyOnly(Algorithm.ES256);
+        } finally {
+            TokenSignatureUtil.changeClientTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), adminClient, Algorithm.RS256);
+        }
+    }
+
+    @Test
+    public void accessTokenRequest_RealmES384_ClientES384_EffectiveES384() throws Exception {
+        try {
+            TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.ES384);
+            TokenSignatureUtil.changeClientTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), adminClient, Algorithm.ES384);
+            TokenSignatureUtil.registerKeyProvider("P-384", adminClient, testContext);
+            TokenSignatureUtil.registerTokenSignatureProvider(Algorithm.ES384, adminClient, testContext);
+            tokenRequestSignatureVerifyOnly(Algorithm.ES384);
+        } finally {
+            TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.RS256);
+            TokenSignatureUtil.changeClientTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), adminClient, Algorithm.RS256);
+        }
+    }
+
+    @Test
+    public void accessTokenRequest_RealmRS256_ClientES512_EffectiveES512() throws Exception {
+        try {
+            TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.RS256);
+            TokenSignatureUtil.changeClientTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), adminClient, Algorithm.ES512);
+            TokenSignatureUtil.registerKeyProvider("P-521", adminClient, testContext);
+            TokenSignatureUtil.registerTokenSignatureProvider(Algorithm.ES512, adminClient, testContext);
+            tokenRequestSignatureVerifyOnly(Algorithm.ES512);
+        } finally {
+            TokenSignatureUtil.changeClientTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), adminClient, Algorithm.RS256);
+        }
+    }
+
+    private void tokenRequest(String sigAlgName) throws Exception {
+        oauth.doLogin("test-user@localhost", "password");
+
+        EventRepresentation loginEvent = events.expectLogin().assertEvent();
+
+        String sessionId = loginEvent.getSessionId();
+        String codeId = loginEvent.getDetails().get(Details.CODE_ID);
+
+        String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+        OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password");
+
+        assertEquals(200, response.getStatusCode());
+
+        assertEquals("bearer", response.getTokenType());
+
+        JWSHeader header = new JWSInput(response.getAccessToken()).getHeader();
+        assertEquals(sigAlgName, header.getAlgorithm().name());
+        assertEquals("JWT", header.getType());
+        assertNull(header.getContentType());
+
+        header = new JWSInput(response.getIdToken()).getHeader();
+        assertEquals(sigAlgName, header.getAlgorithm().name());
+        assertEquals("JWT", header.getType());
+        assertNull(header.getContentType());
+
+        header = new JWSInput(response.getRefreshToken()).getHeader();
+        assertEquals(sigAlgName, header.getAlgorithm().name());
+        assertEquals("JWT", header.getType());
+        assertNull(header.getContentType());
+
+        AccessToken token = oauth.verifyToken(response.getAccessToken());
+
+        assertEquals(findUserByUsername(adminClient.realm("test"), "test-user@localhost").getId(), token.getSubject());
+        Assert.assertNotEquals("test-user@localhost", token.getSubject());
+
+        assertEquals(sessionId, token.getSessionState());
+
+        EventRepresentation event = events.expectCodeToToken(codeId, sessionId).assertEvent();
+        assertEquals(token.getId(), event.getDetails().get(Details.TOKEN_ID));
+        assertEquals(oauth.verifyRefreshToken(response.getRefreshToken()).getId(), event.getDetails().get(Details.REFRESH_TOKEN_ID));
+        assertEquals(sessionId, token.getSessionState());
+    }
+ 
+    private void tokenRequestSignatureVerifyOnly(String sigAlgName) throws Exception {
+        oauth.doLogin("test-user@localhost", "password");
+
+        String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+        OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password");
+
+        assertEquals(200, response.getStatusCode());
+
+        assertEquals("bearer", response.getTokenType());
+
+        JWSHeader header = new JWSInput(response.getAccessToken()).getHeader();
+        assertEquals(sigAlgName, header.getAlgorithm().name());
+        assertEquals("JWT", header.getType());
+        assertNull(header.getContentType());
+
+        header = new JWSInput(response.getIdToken()).getHeader();
+        assertEquals(sigAlgName, header.getAlgorithm().name());
+        assertEquals("JWT", header.getType());
+        assertNull(header.getContentType());
+
+        header = new JWSInput(response.getRefreshToken()).getHeader();
+        assertEquals(sigAlgName, header.getAlgorithm().name());
+        assertEquals("JWT", header.getType());
+        assertNull(header.getContentType());
+
+        assertEquals(TokenSignatureUtil.verifySignature(sigAlgName, response.getAccessToken(), adminClient), true);
+        assertEquals(TokenSignatureUtil.verifySignature(sigAlgName, response.getIdToken(), adminClient), true);
+        assertEquals(TokenSignatureUtil.verifySignature(sigAlgName, response.getRefreshToken(), adminClient), true);
+    }
+
 }
                diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/LogoutTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/LogoutTest.java
index e7e72c0..11d8820 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/LogoutTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/LogoutTest.java
@@ -23,9 +23,13 @@ import org.junit.Test;
 
 import org.keycloak.OAuth2Constants;
 import org.keycloak.common.util.Time;
+import org.keycloak.jose.jws.Algorithm;
+import org.keycloak.jose.jws.JWSHeader;
+import org.keycloak.jose.jws.JWSInput;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.testsuite.AbstractKeycloakTest;
 import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.admin.ApiUtil;
 import org.keycloak.testsuite.pages.AppPage;
 import org.keycloak.testsuite.util.*;
 
@@ -176,4 +180,52 @@ public class LogoutTest extends AbstractKeycloakTest {
         }
     }
 
+    // KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+    private void backchannelLogoutRequest(String sigAlgName) throws Exception {
+        oauth.doLogin("test-user@localhost", "password");
+
+        String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+
+        oauth.clientSessionState("client-session");
+        OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
+        String idTokenString = tokenResponse.getIdToken();
+
+        JWSHeader header = new JWSInput(tokenResponse.getAccessToken()).getHeader();
+        assertEquals(sigAlgName, header.getAlgorithm().name());
+        assertEquals("JWT", header.getType());
+        assertNull(header.getContentType());
+
+        header = new JWSInput(tokenResponse.getIdToken()).getHeader();
+        assertEquals(sigAlgName, header.getAlgorithm().name());
+        assertEquals("JWT", header.getType());
+        assertNull(header.getContentType());
+
+        header = new JWSInput(tokenResponse.getRefreshToken()).getHeader();
+        assertEquals(sigAlgName, header.getAlgorithm().name());
+        assertEquals("JWT", header.getType());
+        assertNull(header.getContentType());
+
+        String logoutUrl = oauth.getLogoutUrl()
+          .idTokenHint(idTokenString)
+          .postLogoutRedirectUri(AppPage.baseUrl)
+          .build();
+        
+        try (CloseableHttpClient c = HttpClientBuilder.create().disableRedirectHandling().build();
+          CloseableHttpResponse response = c.execute(new HttpGet(logoutUrl))) {
+            assertThat(response, Matchers.statusCodeIsHC(Status.FOUND));
+            assertThat(response.getFirstHeader(HttpHeaders.LOCATION).getValue(), is(AppPage.baseUrl));
+        }
+    }
+    @Test
+    public void backchannelLogoutRequest_RealmRS384_ClientRS512_EffectiveRS512() throws Exception {
+        try {
+            TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, "RS384");
+            TokenSignatureUtil.changeClientTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), adminClient, "RS512");
+            backchannelLogoutRequest("RS512");
+        } finally {
+            TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, "RS256");
+            TokenSignatureUtil.changeClientTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), adminClient, "RS256");
+        }
+    }
+
 }
                diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java
index 1ffc4ed..2832ebf 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java
@@ -33,6 +33,9 @@ import org.keycloak.common.constants.ServiceAccountConstants;
 import org.keycloak.common.util.Time;
 import org.keycloak.events.Details;
 import org.keycloak.events.Errors;
+import org.keycloak.jose.jws.Algorithm;
+import org.keycloak.jose.jws.JWSHeader;
+import org.keycloak.jose.jws.JWSInput;
 import org.keycloak.models.AdminRoles;
 import org.keycloak.models.Constants;
 import org.keycloak.models.utils.KeycloakModelUtils;
@@ -60,6 +63,7 @@ import org.keycloak.testsuite.util.OAuthClient;
 import org.keycloak.testsuite.util.RealmBuilder;
 import org.keycloak.testsuite.util.RealmManager;
 import org.keycloak.testsuite.util.RoleBuilder;
+import org.keycloak.testsuite.util.TokenSignatureUtil;
 import org.keycloak.testsuite.util.UserBuilder;
 import org.keycloak.util.TokenUtil;
 
@@ -72,6 +76,7 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertNull;
 import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
 import static org.keycloak.testsuite.admin.ApiUtil.findRealmRoleByName;
 import static org.keycloak.testsuite.admin.ApiUtil.findUserByUsername;
@@ -747,4 +752,86 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
             changeOfflineSessionSettings(false, prev[0], prev[1]);
         }
     }
+
+    // KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+    private void offlineTokenRequest(String sigAlgName) throws Exception {
+        oauth.scope(OAuth2Constants.OFFLINE_ACCESS);
+        oauth.clientId("offline-client");
+        OAuthClient.AccessTokenResponse tokenResponse = oauth.doClientCredentialsGrantAccessTokenRequest("secret1");
+
+       JWSHeader header = null;
+       String idToken = tokenResponse.getIdToken();
+       String accessToken = tokenResponse.getAccessToken();
+       String refreshToken = tokenResponse.getRefreshToken();
+       if (idToken != null) {
+           header = new JWSInput(idToken).getHeader();
+           assertEquals(sigAlgName, header.getAlgorithm().name());
+           assertEquals("JWT", header.getType());
+           assertNull(header.getContentType());
+       }
+       if (accessToken != null) {
+           header = new JWSInput(accessToken).getHeader();
+           assertEquals(sigAlgName, header.getAlgorithm().name());
+           assertEquals("JWT", header.getType());
+           assertNull(header.getContentType());
+       }
+       if (refreshToken != null) {
+           header = new JWSInput(refreshToken).getHeader();
+           assertEquals(sigAlgName, header.getAlgorithm().name());
+           assertEquals("JWT", header.getType());
+           assertNull(header.getContentType());
+       }
+
+        AccessToken token = oauth.verifyToken(tokenResponse.getAccessToken());
+        String offlineTokenString = tokenResponse.getRefreshToken();
+        RefreshToken offlineToken = oauth.verifyRefreshToken(offlineTokenString);
+
+        events.expectClientLogin()
+                .client("offline-client")
+                .user(serviceAccountUserId)
+                .session(token.getSessionState())
+                .detail(Details.TOKEN_ID, token.getId())
+                .detail(Details.REFRESH_TOKEN_ID, offlineToken.getId())
+                .detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_OFFLINE)
+                .detail(Details.USERNAME, ServiceAccountConstants.SERVICE_ACCOUNT_USER_PREFIX + "offline-client")
+                .assertEvent();
+
+        Assert.assertEquals(TokenUtil.TOKEN_TYPE_OFFLINE, offlineToken.getType());
+        Assert.assertEquals(0, offlineToken.getExpiration());
+
+        testRefreshWithOfflineToken(token, offlineToken, offlineTokenString, token.getSessionState(), serviceAccountUserId);
+
+        // Now retrieve another offline token and verify that previous offline token is still valid
+        tokenResponse = oauth.doClientCredentialsGrantAccessTokenRequest("secret1");
+
+        AccessToken token2 = oauth.verifyToken(tokenResponse.getAccessToken());
+        String offlineTokenString2 = tokenResponse.getRefreshToken();
+        RefreshToken offlineToken2 = oauth.verifyRefreshToken(offlineTokenString2);
+
+        events.expectClientLogin()
+                .client("offline-client")
+                .user(serviceAccountUserId)
+                .session(token2.getSessionState())
+                .detail(Details.TOKEN_ID, token2.getId())
+                .detail(Details.REFRESH_TOKEN_ID, offlineToken2.getId())
+                .detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_OFFLINE)
+                .detail(Details.USERNAME, ServiceAccountConstants.SERVICE_ACCOUNT_USER_PREFIX + "offline-client")
+                .assertEvent();
+
+        // Refresh with both offline tokens is fine
+        testRefreshWithOfflineToken(token, offlineToken, offlineTokenString, token.getSessionState(), serviceAccountUserId);
+        testRefreshWithOfflineToken(token2, offlineToken2, offlineTokenString2, token2.getSessionState(), serviceAccountUserId);
+
+    }
+    @Test
+    public void offlineTokenRequest_RealmRS512_ClientRS384_EffectiveRS384() throws Exception {
+        try {
+            TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, "RS512");
+            TokenSignatureUtil.changeClientTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "offline-client"), adminClient, "RS384");
+            offlineTokenRequest("RS384");
+        } finally {
+            TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, "RS256");
+            TokenSignatureUtil.changeClientTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "offline-client"), adminClient, "RS256");
+        }
+    }
 }
                diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java
index 681e67b..1aaef7e 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java
@@ -23,8 +23,11 @@ import org.junit.Test;
 import org.keycloak.OAuth2Constants;
 import org.keycloak.admin.client.resource.RealmResource;
 import org.keycloak.common.enums.SslRequired;
+import org.keycloak.crypto.Algorithm;
 import org.keycloak.events.Details;
 import org.keycloak.events.Errors;
+import org.keycloak.jose.jws.JWSHeader;
+import org.keycloak.jose.jws.JWSInput;
 import org.keycloak.models.utils.SessionTimeoutHelper;
 import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
 import org.keycloak.representations.AccessToken;
@@ -33,10 +36,12 @@ import org.keycloak.representations.idm.EventRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.testsuite.AbstractKeycloakTest;
 import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.admin.ApiUtil;
 import org.keycloak.testsuite.util.ClientManager;
 import org.keycloak.testsuite.util.OAuthClient;
 import org.keycloak.testsuite.util.RealmBuilder;
 import org.keycloak.testsuite.util.RealmManager;
+import org.keycloak.testsuite.util.TokenSignatureUtil;
 import org.keycloak.testsuite.util.UserManager;
 import org.keycloak.util.BasicAuthHelper;
 
@@ -48,6 +53,7 @@ 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;
 
@@ -751,5 +757,189 @@ public class RefreshTokenTest extends AbstractKeycloakTest {
                 .post(Entity.form(form));
     }
 
+    // KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+
+    private void refreshToken(String sigAlgName) throws Exception {
+        oauth.doLogin("test-user@localhost", "password");
+
+        EventRepresentation loginEvent = events.expectLogin().assertEvent();
+
+        String sessionId = loginEvent.getSessionId();
+        String codeId = loginEvent.getDetails().get(Details.CODE_ID);
+
+        String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+
+        OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
+
+        JWSHeader header = new JWSInput(tokenResponse.getAccessToken()).getHeader();
+        assertEquals(sigAlgName, header.getAlgorithm().name());
+        assertEquals("JWT", header.getType());
+        assertNull(header.getContentType());
+
+        header = new JWSInput(tokenResponse.getIdToken()).getHeader();
+        assertEquals(sigAlgName, header.getAlgorithm().name());
+        assertEquals("JWT", header.getType());
+        assertNull(header.getContentType());
+
+        header = new JWSInput(tokenResponse.getRefreshToken()).getHeader();
+        assertEquals(sigAlgName, header.getAlgorithm().name());
+        assertEquals("JWT", header.getType());
+        assertNull(header.getContentType());
+
+        AccessToken token = oauth.verifyToken(tokenResponse.getAccessToken());
+        String refreshTokenString = tokenResponse.getRefreshToken();
+        RefreshToken refreshToken = oauth.verifyRefreshToken(refreshTokenString);
+
+        EventRepresentation tokenEvent = events.expectCodeToToken(codeId, sessionId).assertEvent();
+
+        Assert.assertNotNull(refreshTokenString);
+
+        assertEquals("bearer", tokenResponse.getTokenType());
+
+        assertEquals(sessionId, refreshToken.getSessionState());
+
+        setTimeOffset(2);
+
+        OAuthClient.AccessTokenResponse response = oauth.doRefreshTokenRequest(refreshTokenString, "password");
+        AccessToken refreshedToken = oauth.verifyToken(response.getAccessToken());
+        RefreshToken refreshedRefreshToken = oauth.verifyRefreshToken(response.getRefreshToken());
+
+        assertEquals(200, response.getStatusCode());
+
+        assertEquals(sessionId, refreshedToken.getSessionState());
+        assertEquals(sessionId, refreshedRefreshToken.getSessionState());
+
+        Assert.assertNotEquals(token.getId(), refreshedToken.getId());
+        Assert.assertNotEquals(refreshToken.getId(), refreshedRefreshToken.getId());
+
+        assertEquals("bearer", response.getTokenType());
+
+        assertEquals(findUserByUsername(adminClient.realm("test"), "test-user@localhost").getId(), refreshedToken.getSubject());
+        Assert.assertNotEquals("test-user@localhost", refreshedToken.getSubject());
+
+        EventRepresentation refreshEvent = events.expectRefresh(tokenEvent.getDetails().get(Details.REFRESH_TOKEN_ID), sessionId).assertEvent();
+        Assert.assertNotEquals(tokenEvent.getDetails().get(Details.TOKEN_ID), refreshEvent.getDetails().get(Details.TOKEN_ID));
+        Assert.assertNotEquals(tokenEvent.getDetails().get(Details.REFRESH_TOKEN_ID), refreshEvent.getDetails().get(Details.UPDATED_REFRESH_TOKEN_ID));
+
+        setTimeOffset(0);
+    }
+
+    @Test
+    public void tokenRefreshRequest_RealmRS384_ClientRS384_EffectiveRS384() throws Exception {
+        try {
+            TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.RS384);
+            TokenSignatureUtil.changeClientTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), adminClient, Algorithm.RS384);
+            refreshToken(Algorithm.RS384);
+        } finally {
+            TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.RS256);
+            TokenSignatureUtil.changeClientTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), adminClient, Algorithm.RS256);
+        }
+    }
+
+    @Test
+    public void tokenRefreshRequest_RealmRS256_ClientRS512_EffectiveRS512() throws Exception {
+        try {
+            TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.RS256);
+            TokenSignatureUtil.changeClientTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), adminClient, Algorithm.RS512);
+            refreshToken(Algorithm.RS512);
+        } finally {
+            TokenSignatureUtil.changeClientTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), adminClient, Algorithm.RS256);
+        }
+    }
+
+    @Test
+    public void tokenRefreshRequest_RealmRS256_ClientES256_EffectiveES256() throws Exception {
+        try {
+            TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.RS256);
+            TokenSignatureUtil.changeClientTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), adminClient, Algorithm.ES256);
+            TokenSignatureUtil.registerKeyProvider("P-256", adminClient, testContext);
+            TokenSignatureUtil.registerTokenSignatureProvider(Algorithm.ES256, adminClient, testContext);
+            refreshTokenSignatureVerifyOnly(Algorithm.ES256);
+        } finally {
+            TokenSignatureUtil.changeClientTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), adminClient, Algorithm.RS256);
+        }
+    }
+
+    @Test
+    public void tokenRefreshRequest_RealmES384_ClientES384_EffectiveES384() throws Exception {
+        try {
+            TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.ES384);
+            TokenSignatureUtil.changeClientTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), adminClient, Algorithm.ES384);
+            TokenSignatureUtil.registerKeyProvider("P-384", adminClient, testContext);
+            TokenSignatureUtil.registerTokenSignatureProvider(Algorithm.ES384, adminClient, testContext);
+            refreshTokenSignatureVerifyOnly(Algorithm.ES384);
+        } finally {
+            TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.RS256);
+            TokenSignatureUtil.changeClientTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), adminClient, Algorithm.RS256);
+        }
+    }
+
+    @Test
+    public void tokenRefreshRequest_RealmRS256_ClientES512_EffectiveES512() throws Exception {
+        try {
+            TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.RS256);
+            TokenSignatureUtil.changeClientTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), adminClient, Algorithm.ES512);
+            TokenSignatureUtil.registerKeyProvider("P-521", adminClient, testContext);
+            TokenSignatureUtil.registerTokenSignatureProvider(Algorithm.ES512, adminClient, testContext);
+            refreshTokenSignatureVerifyOnly(Algorithm.ES512);
+        } finally {
+            TokenSignatureUtil.changeClientTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), adminClient, Algorithm.RS256);
+        }
+    }
+
+    private void refreshTokenSignatureVerifyOnly(String sigAlgName) throws Exception {
+        oauth.doLogin("test-user@localhost", "password");
+
+        EventRepresentation loginEvent = events.expectLogin().assertEvent();
+
+        String sessionId = loginEvent.getSessionId();
+        String codeId = loginEvent.getDetails().get(Details.CODE_ID);
+
+        String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+
+        OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
+
+        JWSHeader header = new JWSInput(tokenResponse.getAccessToken()).getHeader();
+        assertEquals(sigAlgName, header.getAlgorithm().name());
+        assertEquals("JWT", header.getType());
+        assertNull(header.getContentType());
+
+        header = new JWSInput(tokenResponse.getIdToken()).getHeader();
+        assertEquals(sigAlgName, header.getAlgorithm().name());
+        assertEquals("JWT", header.getType());
+        assertNull(header.getContentType());
+
+        header = new JWSInput(tokenResponse.getRefreshToken()).getHeader();
+        assertEquals(sigAlgName, header.getAlgorithm().name());
+        assertEquals("JWT", header.getType());
+        assertNull(header.getContentType());
+
+        String refreshTokenString = tokenResponse.getRefreshToken();
+
+        EventRepresentation tokenEvent = events.expectCodeToToken(codeId, sessionId).assertEvent();
+
+        Assert.assertNotNull(refreshTokenString);
+
+        assertEquals("bearer", tokenResponse.getTokenType());
+
+        setTimeOffset(2);
+
+        OAuthClient.AccessTokenResponse response = oauth.doRefreshTokenRequest(refreshTokenString, "password");
+
+        assertEquals(200, response.getStatusCode());
+
+        assertEquals("bearer", response.getTokenType());
+
+        // verify JWS for refreshed access token and refresh token
+        assertEquals(TokenSignatureUtil.verifySignature(sigAlgName, response.getAccessToken(), adminClient), true);
+        assertEquals(TokenSignatureUtil.verifySignature(sigAlgName, response.getIdToken(), adminClient), true);
+        assertEquals(TokenSignatureUtil.verifySignature(sigAlgName, response.getRefreshToken(), adminClient), true);
+
+        EventRepresentation refreshEvent = events.expectRefresh(tokenEvent.getDetails().get(Details.REFRESH_TOKEN_ID), sessionId).assertEvent();
+        Assert.assertNotEquals(tokenEvent.getDetails().get(Details.TOKEN_ID), refreshEvent.getDetails().get(Details.TOKEN_ID));
+        Assert.assertNotEquals(tokenEvent.getDetails().get(Details.REFRESH_TOKEN_ID), refreshEvent.getDetails().get(Details.UPDATED_REFRESH_TOKEN_ID));
+
+        setTimeOffset(0);
+    }
 
 }
                diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/AbstractOIDCResponseTypeTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/AbstractOIDCResponseTypeTest.java
index 1a097de..c54061a 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/AbstractOIDCResponseTypeTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/AbstractOIDCResponseTypeTest.java
@@ -24,6 +24,8 @@ import org.keycloak.OAuthErrorException;
 import org.keycloak.events.Details;
 import org.keycloak.events.Errors;
 import org.keycloak.jose.jws.Algorithm;
+import org.keycloak.jose.jws.JWSHeader;
+import org.keycloak.jose.jws.JWSInput;
 import org.keycloak.representations.IDToken;
 import org.keycloak.representations.idm.EventRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
@@ -31,10 +33,12 @@ import org.keycloak.testsuite.Assert;
 import org.keycloak.testsuite.AssertEvents;
 import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
 import org.keycloak.testsuite.admin.AbstractAdminTest;
+import org.keycloak.testsuite.admin.ApiUtil;
 import org.keycloak.testsuite.pages.AppPage;
 import org.keycloak.testsuite.pages.LoginPage;
 import org.keycloak.testsuite.util.ClientManager;
 import org.keycloak.testsuite.util.OAuthClient;
+import org.keycloak.testsuite.util.TokenSignatureUtil;
 
 import javax.ws.rs.core.UriBuilder;
 import java.io.IOException;
@@ -42,6 +46,8 @@ import java.util.List;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 
 /**
  * Abstract test for various values of response_type
@@ -214,4 +220,54 @@ public abstract class AbstractOIDCResponseTypeTest extends AbstractTestRealmKeyc
     protected ClientManager.ClientManagerBuilder clientManagerBuilder() {
         return ClientManager.realm(adminClient.realm("test")).clientId("test-app");
     }
+
+    // KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+    private void oidcFlow(String sigAlgName) throws Exception {
+        EventRepresentation loginEvent = loginUser("abcdef123456");
+
+        OAuthClient.AuthorizationEndpointResponse authzResponse = new OAuthClient.AuthorizationEndpointResponse(oauth, isFragment());
+        Assert.assertNotNull(authzResponse.getSessionState());
+
+        JWSHeader header = null;
+        String idToken = authzResponse.getIdToken();
+        String accessToken = authzResponse.getAccessToken();
+        if (idToken != null) {
+            header = new JWSInput(idToken).getHeader();
+            assertEquals(sigAlgName, header.getAlgorithm().name());
+            assertEquals("JWT", header.getType());
+            assertNull(header.getContentType());
+        }
+        if (accessToken != null) {
+            header = new JWSInput(accessToken).getHeader();
+            assertEquals(sigAlgName, header.getAlgorithm().name());
+            assertEquals("JWT", header.getType());
+            assertNull(header.getContentType());
+        }
+
+        List<IDToken> idTokens = testAuthzResponseAndRetrieveIDTokens(authzResponse, loginEvent);
+
+        for (IDToken idt : idTokens) {
+            Assert.assertEquals("abcdef123456", idt.getNonce());
+            Assert.assertEquals(authzResponse.getSessionState(), idt.getSessionState());
+        }
+    }
+    @Test
+    public void oidcFlow_RealmRS256_ClientRS384_EffectiveRS384() throws Exception {
+        try {
+            setSignatureAlgorithm("RS384");
+            TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, "RS256");
+            TokenSignatureUtil.changeClientTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), adminClient, "RS384");
+            oidcFlow("RS384");
+        } finally {
+            setSignatureAlgorithm("RS256");
+            TokenSignatureUtil.changeClientTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), adminClient, "RS256");
+        }
+    }
+    private String sigAlgName = "RS256";
+    private void setSignatureAlgorithm(String sigAlgName) {
+        this.sigAlgName = sigAlgName;
+    }
+    protected String getSignatureAlgorithm() {
+        return this.sigAlgName;
+    }
 }
                diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/OIDCHybridResponseTypeCodeIDTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/OIDCHybridResponseTypeCodeIDTokenTest.java
index 154d659..9d948fe 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/OIDCHybridResponseTypeCodeIDTokenTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/OIDCHybridResponseTypeCodeIDTokenTest.java
@@ -63,13 +63,15 @@ public class OIDCHybridResponseTypeCodeIDTokenTest extends AbstractOIDCResponseT
         // Validate "c_hash"
         Assert.assertNull(idToken.getAccessTokenHash());
         Assert.assertNotNull(idToken.getCodeHash());
-        Assert.assertEquals(idToken.getCodeHash(), HashProvider.oidcHash(jwsAlgorithm, authzResponse.getCode()));
+        // KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+        Assert.assertEquals(idToken.getCodeHash(), HashProvider.oidcHash(getSignatureAlgorithm(), authzResponse.getCode()));
 
         // Financial API - Part 2: Read and Write API Security Profile
         // http://openid.net/specs/openid-financial-api-part-2.html#authorization-server
         // Validate "s_hash"
         Assert.assertNotNull(idToken.getStateHash());
-        Assert.assertEquals(idToken.getStateHash(), HashProvider.oidcHash(jwsAlgorithm, authzResponse.getState())); 
+        // KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+        Assert.assertEquals(idToken.getStateHash(), HashProvider.oidcHash(getSignatureAlgorithm(), authzResponse.getState())); 
 
         // IDToken exchanged for the code
         IDToken idToken2 = sendTokenRequestAndGetIDToken(loginEvent);
                diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/OIDCHybridResponseTypeCodeIDTokenTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/OIDCHybridResponseTypeCodeIDTokenTokenTest.java
index 4ceb049..8c934e1 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/OIDCHybridResponseTypeCodeIDTokenTokenTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/OIDCHybridResponseTypeCodeIDTokenTokenTest.java
@@ -62,16 +62,19 @@ public class OIDCHybridResponseTypeCodeIDTokenTokenTest extends AbstractOIDCResp
 
         // Validate "at_hash" and "c_hash"
         Assert.assertNotNull(idToken.getAccessTokenHash());
-        Assert.assertEquals(idToken.getAccessTokenHash(), HashProvider.oidcHash(jwsAlgorithm, authzResponse.getAccessToken()));
+        // KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+        Assert.assertEquals(idToken.getAccessTokenHash(), HashProvider.oidcHash(getSignatureAlgorithm(), authzResponse.getAccessToken()));
         Assert.assertNotNull(idToken.getCodeHash());
-        Assert.assertEquals(idToken.getCodeHash(), HashProvider.oidcHash(jwsAlgorithm, authzResponse.getCode()));
+        // KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+        Assert.assertEquals(idToken.getCodeHash(), HashProvider.oidcHash(getSignatureAlgorithm(), authzResponse.getCode()));
 
         // Financial API - Part 2: Read and Write API Security Profile
         // http://openid.net/specs/openid-financial-api-part-2.html#authorization-server
         // Validate "s_hash"
         Assert.assertNotNull(idToken.getStateHash());
-        Assert.assertEquals(idToken.getStateHash(), HashProvider.oidcHash(jwsAlgorithm, authzResponse.getState())); 
-        
+        // KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+        Assert.assertEquals(idToken.getStateHash(), HashProvider.oidcHash(getSignatureAlgorithm(), authzResponse.getState())); 
+
         // IDToken exchanged for the code
         IDToken idToken2 = sendTokenRequestAndGetIDToken(loginEvent);
 
                diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/OIDCImplicitResponseTypeIDTokenTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/OIDCImplicitResponseTypeIDTokenTokenTest.java
index cd45908..8a52480 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/OIDCImplicitResponseTypeIDTokenTokenTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/OIDCImplicitResponseTypeIDTokenTokenTest.java
@@ -61,7 +61,8 @@ public class OIDCImplicitResponseTypeIDTokenTokenTest extends AbstractOIDCRespon
 
         // Validate "at_hash"
         Assert.assertNotNull(idToken.getAccessTokenHash());
-        Assert.assertEquals(idToken.getAccessTokenHash(), HashProvider.oidcHash(jwsAlgorithm, authzResponse.getAccessToken()));
+        // KEYCLOAK-7560 Refactoring Token Signing and Verifying by Token Signature SPI
+        Assert.assertEquals(idToken.getAccessTokenHash(), HashProvider.oidcHash(getSignatureAlgorithm(), authzResponse.getAccessToken()));
         Assert.assertNull(idToken.getCodeHash());
 
         return Collections.singletonList(idToken);