mirror of
https://github.com/keycloak/keycloak.git
synced 2026-06-09 00:52:07 -04:00
Move verify and parse tokens to AbstractOAuthClient (#37663)
Closes #37660 Signed-off-by: stianst <stianst@gmail.com>
This commit is contained in:
parent
69721ba1b5
commit
83ef1e3de0
11 changed files with 154 additions and 111 deletions
|
|
@ -2,6 +2,7 @@ package org.keycloak.test.examples;
|
|||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.testframework.annotations.InjectRealm;
|
||||
import org.keycloak.testframework.annotations.InjectUser;
|
||||
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
|
||||
|
|
@ -85,6 +86,22 @@ public class OAuthClientTest {
|
|||
Assertions.assertFalse(refreshResponse.isSuccess());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseToken() {
|
||||
AccessTokenResponse accessTokenResponse = oauth.doPasswordGrantRequest(user.getUsername(), user.getPassword());
|
||||
|
||||
AccessToken accessToken = oauth.parseToken(accessTokenResponse.getAccessToken(), AccessToken.class);
|
||||
Assertions.assertEquals(user.getUsername(), accessToken.getPreferredUsername());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVerifyToken() {
|
||||
AccessTokenResponse accessTokenResponse = oauth.doPasswordGrantRequest(user.getUsername(), user.getPassword());
|
||||
|
||||
AccessToken accessToken = oauth.verifyToken(accessTokenResponse.getAccessToken(), AccessToken.class);
|
||||
Assertions.assertEquals(user.getUsername(), accessToken.getPreferredUsername());
|
||||
}
|
||||
|
||||
public static class OAuthUserConfig implements UserConfig {
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
package org.keycloak.testsuite.util.oauth;
|
||||
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.AuthorizationResponseToken;
|
||||
import org.keycloak.representations.IDToken;
|
||||
import org.keycloak.representations.JsonWebToken;
|
||||
import org.keycloak.representations.RefreshToken;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
|
||||
import java.util.Map;
|
||||
|
|
@ -28,6 +33,8 @@ public abstract class AbstractOAuthClient<T> {
|
|||
protected StateParamProvider state;
|
||||
protected String nonce;
|
||||
|
||||
private final KeyManager keyManager = new KeyManager(this);
|
||||
private final TokensManager tokensManager = new TokensManager(keyManager);
|
||||
protected HttpClientManager httpClientManager;
|
||||
protected WebDriver driver;
|
||||
|
||||
|
|
@ -123,6 +130,30 @@ public abstract class AbstractOAuthClient<T> {
|
|||
return tokenRevocationRequest(token).send();
|
||||
}
|
||||
|
||||
public <J extends JsonWebToken> J parseToken(String token, Class<J> clazz) {
|
||||
return tokensManager.parseToken(token, clazz);
|
||||
}
|
||||
|
||||
public RefreshToken parseRefreshToken(String refreshToken) {
|
||||
return tokensManager.parseToken(refreshToken, RefreshToken.class);
|
||||
}
|
||||
|
||||
public AccessToken verifyToken(String token) {
|
||||
return tokensManager.verifyToken(token, AccessToken.class);
|
||||
}
|
||||
|
||||
public IDToken verifyIDToken(String token) {
|
||||
return tokensManager.verifyToken(token, IDToken.class);
|
||||
}
|
||||
|
||||
public AuthorizationResponseToken verifyAuthorizationResponseToken(String token) {
|
||||
return tokensManager.verifyToken(token, AuthorizationResponseToken.class);
|
||||
}
|
||||
|
||||
public <J extends JsonWebToken> J verifyToken(String token, Class<J> clazz) {
|
||||
return tokensManager.verifyToken(token, clazz);
|
||||
}
|
||||
|
||||
public T baseUrl(String baseUrl) {
|
||||
this.baseUrl = baseUrl;
|
||||
return (T) this;
|
||||
|
|
@ -141,10 +172,18 @@ public abstract class AbstractOAuthClient<T> {
|
|||
return httpClientManager;
|
||||
}
|
||||
|
||||
public KeyManager keys() {
|
||||
return keyManager;
|
||||
}
|
||||
|
||||
public Endpoints getEndpoints() {
|
||||
return new Endpoints(baseUrl, config.getRealm());
|
||||
}
|
||||
|
||||
public String getRealm() {
|
||||
return config.getRealm();
|
||||
}
|
||||
|
||||
public String getRedirectUri() {
|
||||
return config.getRedirectUri();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ import java.io.IOException;
|
|||
|
||||
public class JwksRequest {
|
||||
|
||||
private final OAuthClient client;
|
||||
private final AbstractOAuthClient<?> client;
|
||||
|
||||
public JwksRequest(OAuthClient client) {
|
||||
JwksRequest(AbstractOAuthClient<?> client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
|
|
@ -9,7 +9,7 @@ public class JwksResponse extends AbstractHttpResponse {
|
|||
|
||||
private JSONWebKeySet jwks;
|
||||
|
||||
public JwksResponse(CloseableHttpResponse response) throws IOException {
|
||||
JwksResponse(CloseableHttpResponse response) throws IOException {
|
||||
super(response);
|
||||
}
|
||||
|
||||
|
|
@ -14,10 +14,10 @@ import java.util.Map;
|
|||
|
||||
public class KeyManager {
|
||||
|
||||
private final OAuthClient client;
|
||||
private final AbstractOAuthClient<?> client;
|
||||
private final Map<String, JSONWebKeySet> publicKeys = new HashMap<>();
|
||||
|
||||
public KeyManager(OAuthClient client) {
|
||||
KeyManager(AbstractOAuthClient<?> client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package org.keycloak.testsuite.util.oauth;
|
||||
|
||||
import org.keycloak.TokenVerifier;
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.crypto.Algorithm;
|
||||
import org.keycloak.crypto.AsymmetricSignatureVerifierContext;
|
||||
import org.keycloak.crypto.KeyWrapper;
|
||||
import org.keycloak.crypto.ServerECDSASignatureVerifierContext;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.representations.JsonWebToken;
|
||||
|
||||
public class TokensManager {
|
||||
|
||||
private final KeyManager keyManager;
|
||||
|
||||
TokensManager(KeyManager keyManager) {
|
||||
this.keyManager = keyManager;
|
||||
}
|
||||
|
||||
public <T extends JsonWebToken> T verifyToken(String token, Class<T> clazz) {
|
||||
try {
|
||||
TokenVerifier<T> verifier = TokenVerifier.create(token, clazz);
|
||||
String kid = verifier.getHeader().getKeyId();
|
||||
String algorithm = verifier.getHeader().getAlgorithm().name();
|
||||
KeyWrapper key = keyManager.getPublicKey(algorithm, kid);
|
||||
AsymmetricSignatureVerifierContext verifierContext;
|
||||
switch (algorithm) {
|
||||
case Algorithm.ES256:
|
||||
case Algorithm.ES384:
|
||||
case Algorithm.ES512:
|
||||
verifierContext = new ServerECDSASignatureVerifierContext(key);
|
||||
break;
|
||||
default:
|
||||
verifierContext = new AsymmetricSignatureVerifierContext(key);
|
||||
}
|
||||
verifier.verifierContext(verifierContext);
|
||||
verifier.verify();
|
||||
return verifier.getToken();
|
||||
} catch (VerificationException e) {
|
||||
throw new RuntimeException("Failed to decode token", e);
|
||||
}
|
||||
}
|
||||
|
||||
public <T extends JsonWebToken> T parseToken(String token, Class<T> clazz) {
|
||||
try {
|
||||
return new JWSInput(token).readJsonContent(clazz);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package org.keycloak.testsuite.util;
|
||||
|
||||
import org.keycloak.crypto.Algorithm;
|
||||
import org.keycloak.crypto.AsymmetricSignatureSignerContext;
|
||||
import org.keycloak.crypto.KeyWrapper;
|
||||
import org.keycloak.crypto.ServerECDSASignatureSignerContext;
|
||||
import org.keycloak.crypto.SignatureSignerContext;
|
||||
|
||||
import java.security.PrivateKey;
|
||||
|
||||
public class SignatureSignerUtil {
|
||||
|
||||
public static SignatureSignerContext createSigner(PrivateKey privateKey, String kid, String algorithm) {
|
||||
return createSigner(privateKey, kid, algorithm, null);
|
||||
}
|
||||
|
||||
public static SignatureSignerContext createSigner(PrivateKey privateKey, String kid, String algorithm, String curve) {
|
||||
KeyWrapper keyWrapper = new KeyWrapper();
|
||||
keyWrapper.setAlgorithm(algorithm);
|
||||
keyWrapper.setKid(kid);
|
||||
keyWrapper.setPrivateKey(privateKey);
|
||||
keyWrapper.setCurve(curve);
|
||||
SignatureSignerContext signer;
|
||||
switch (algorithm) {
|
||||
case Algorithm.ES256:
|
||||
case Algorithm.ES384:
|
||||
case Algorithm.ES512:
|
||||
signer = new ServerECDSASignatureSignerContext(keyWrapper);
|
||||
break;
|
||||
default:
|
||||
signer = new AsymmetricSignatureSignerContext(keyWrapper);
|
||||
}
|
||||
return signer;
|
||||
}
|
||||
}
|
||||
|
|
@ -27,32 +27,16 @@ import org.apache.http.entity.StringEntity;
|
|||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.TokenVerifier;
|
||||
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.crypto.Algorithm;
|
||||
import org.keycloak.crypto.AsymmetricSignatureSignerContext;
|
||||
import org.keycloak.crypto.AsymmetricSignatureVerifierContext;
|
||||
import org.keycloak.crypto.KeyWrapper;
|
||||
import org.keycloak.crypto.ServerECDSASignatureSignerContext;
|
||||
import org.keycloak.crypto.ServerECDSASignatureVerifierContext;
|
||||
import org.keycloak.crypto.SignatureSignerContext;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
||||
import org.keycloak.protocol.oidc.grants.ciba.channel.AuthenticationChannelResponse;
|
||||
import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.AuthorizationResponseToken;
|
||||
import org.keycloak.representations.ClaimsRepresentation;
|
||||
import org.keycloak.representations.IDToken;
|
||||
import org.keycloak.representations.JsonWebToken;
|
||||
import org.keycloak.representations.RefreshToken;
|
||||
import org.keycloak.testsuite.broker.util.SimpleHttpDefault;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
import org.keycloak.testsuite.runonserver.RunOnServerException;
|
||||
import org.keycloak.testsuite.util.DroneUtils;
|
||||
import org.keycloak.testsuite.util.WaitUtils;
|
||||
import org.keycloak.util.BasicAuthHelper;
|
||||
|
|
@ -62,13 +46,10 @@ import org.openqa.selenium.By;
|
|||
import org.openqa.selenium.WebDriver;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.PageFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.PrivateKey;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
|
@ -84,17 +65,11 @@ import static org.keycloak.testsuite.util.UIUtils.clickLink;
|
|||
*/
|
||||
public class OAuthClient extends AbstractOAuthClient<OAuthClient> {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(OAuthClient.class);
|
||||
|
||||
public static String SERVER_ROOT;
|
||||
public static String AUTH_SERVER_ROOT;
|
||||
public static String APP_ROOT;
|
||||
public static String APP_AUTH_ROOT;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
static {
|
||||
updateURLs(getAuthServerContextRoot());
|
||||
}
|
||||
|
|
@ -115,7 +90,6 @@ public class OAuthClient extends AbstractOAuthClient<OAuthClient> {
|
|||
updateAppRootRealm("master");
|
||||
}
|
||||
|
||||
private KeyManager keyManager = new KeyManager(this);
|
||||
|
||||
private String idTokenHint;
|
||||
|
||||
|
|
@ -530,74 +504,6 @@ public class OAuthClient extends AbstractOAuthClient<OAuthClient> {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO Extract into separate util to verify tokens
|
||||
public AccessToken verifyToken(String token) {
|
||||
return verifyToken(token, AccessToken.class);
|
||||
}
|
||||
|
||||
public IDToken verifyIDToken(String token) {
|
||||
return verifyToken(token, IDToken.class);
|
||||
}
|
||||
|
||||
public AuthorizationResponseToken verifyAuthorizationResponseToken(String token) {
|
||||
return verifyToken(token, AuthorizationResponseToken.class);
|
||||
}
|
||||
|
||||
public RefreshToken parseRefreshToken(String refreshToken) {
|
||||
try {
|
||||
return new JWSInput(refreshToken).readJsonContent(RefreshToken.class);
|
||||
} catch (Exception e) {
|
||||
throw new RunOnServerException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public <T extends JsonWebToken> T verifyToken(String token, Class<T> clazz) {
|
||||
try {
|
||||
TokenVerifier<T> verifier = TokenVerifier.create(token, clazz);
|
||||
String kid = verifier.getHeader().getKeyId();
|
||||
String algorithm = verifier.getHeader().getAlgorithm().name();
|
||||
KeyWrapper key = keyManager.getPublicKey(algorithm, kid);
|
||||
AsymmetricSignatureVerifierContext verifierContext;
|
||||
switch (algorithm) {
|
||||
case Algorithm.ES256:
|
||||
case Algorithm.ES384:
|
||||
case Algorithm.ES512:
|
||||
verifierContext = new ServerECDSASignatureVerifierContext(key);
|
||||
break;
|
||||
default:
|
||||
verifierContext = new AsymmetricSignatureVerifierContext(key);
|
||||
}
|
||||
verifier.verifierContext(verifierContext);
|
||||
verifier.verify();
|
||||
return verifier.getToken();
|
||||
} catch (VerificationException e) {
|
||||
throw new RuntimeException("Failed to decode token", e);
|
||||
}
|
||||
}
|
||||
|
||||
public SignatureSignerContext createSigner(PrivateKey privateKey, String kid, String algorithm) {
|
||||
return createSigner(privateKey, kid, algorithm, null);
|
||||
}
|
||||
|
||||
public SignatureSignerContext createSigner(PrivateKey privateKey, String kid, String algorithm, String curve) {
|
||||
KeyWrapper keyWrapper = new KeyWrapper();
|
||||
keyWrapper.setAlgorithm(algorithm);
|
||||
keyWrapper.setKid(kid);
|
||||
keyWrapper.setPrivateKey(privateKey);
|
||||
keyWrapper.setCurve(curve);
|
||||
SignatureSignerContext signer;
|
||||
switch (algorithm) {
|
||||
case Algorithm.ES256:
|
||||
case Algorithm.ES384:
|
||||
case Algorithm.ES512:
|
||||
signer = new ServerECDSASignatureSignerContext(keyWrapper);
|
||||
break;
|
||||
default:
|
||||
signer = new AsymmetricSignatureSignerContext(keyWrapper);
|
||||
}
|
||||
return signer;
|
||||
}
|
||||
|
||||
public String getClientId() {
|
||||
return config.getClientId();
|
||||
}
|
||||
|
|
@ -739,15 +645,6 @@ public class OAuthClient extends AbstractOAuthClient<OAuthClient> {
|
|||
return this;
|
||||
}
|
||||
|
||||
|
||||
public KeyManager keys() {
|
||||
return keyManager;
|
||||
}
|
||||
|
||||
public String getRealm() {
|
||||
return config.getRealm();
|
||||
}
|
||||
|
||||
public OAuthClient codeVerifier(String codeVerifier) {
|
||||
this.codeVerifier = codeVerifier;
|
||||
return this;
|
||||
|
|
|
|||
|
|
@ -175,6 +175,7 @@ import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientPolicyBuilder;
|
|||
import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientProfileBuilder;
|
||||
import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientProfilesBuilder;
|
||||
import org.keycloak.testsuite.util.MutualTLSUtils;
|
||||
import org.keycloak.testsuite.util.SignatureSignerUtil;
|
||||
import org.keycloak.testsuite.util.oauth.AccessTokenResponse;
|
||||
import org.keycloak.testsuite.util.oauth.AuthorizationEndpointResponse;
|
||||
import org.keycloak.testsuite.util.ServerURLs;
|
||||
|
|
@ -494,7 +495,7 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest {
|
|||
protected String createSignedRequestToken(String clientId, PrivateKey privateKey, PublicKey publicKey, String algorithm) {
|
||||
JsonWebToken jwt = createRequestToken(clientId, getRealmInfoUrl());
|
||||
String kid = KeyUtils.createKeyId(publicKey);
|
||||
SignatureSignerContext signer = oauth.createSigner(privateKey, kid, algorithm);
|
||||
SignatureSignerContext signer = SignatureSignerUtil.createSigner(privateKey, kid, algorithm);
|
||||
return new JWSBuilder().kid(kid).jsonContent(jwt).sign(signer);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResou
|
|||
import org.keycloak.testsuite.util.ClientBuilder;
|
||||
import org.keycloak.testsuite.util.ClientManager;
|
||||
import org.keycloak.testsuite.util.KeystoreUtils;
|
||||
import org.keycloak.testsuite.util.SignatureSignerUtil;
|
||||
import org.keycloak.testsuite.util.oauth.AccessTokenResponse;
|
||||
import org.keycloak.testsuite.util.oauth.OAuthClient;
|
||||
import org.keycloak.testsuite.util.RealmBuilder;
|
||||
|
|
@ -925,7 +926,7 @@ public abstract class AbstractClientAuthSignedJWTTest extends AbstractKeycloakTe
|
|||
if (kid == null) {
|
||||
kid = KeyUtils.createKeyId(publicKey);
|
||||
}
|
||||
SignatureSignerContext signer = oauth.createSigner(privateKey, kid, algorithm, curve);
|
||||
SignatureSignerContext signer = SignatureSignerUtil.createSigner(privateKey, kid, algorithm, curve);
|
||||
String ret = new JWSBuilder().kid(kid).jsonContent(jwt).sign(signer);
|
||||
return ret;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ import org.keycloak.testsuite.Assert;
|
|||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.util.ClientManager;
|
||||
import org.keycloak.testsuite.util.KeystoreUtils;
|
||||
import org.keycloak.testsuite.util.SignatureSignerUtil;
|
||||
import org.keycloak.testsuite.util.oauth.AccessTokenResponse;
|
||||
|
||||
import java.security.KeyPair;
|
||||
|
|
@ -619,7 +620,7 @@ public class ClientAuthSignedJWTTest extends AbstractClientAuthSignedJWTTest {
|
|||
PrivateKey privateKey = keyPair.getPrivate();
|
||||
JsonWebToken assertion = createRequestToken(app2.getClientId(), getRealmInfoUrl());
|
||||
|
||||
SignatureSignerContext signer = oauth.createSigner(privateKey, null, Algorithm.ES512);
|
||||
SignatureSignerContext signer = SignatureSignerUtil.createSigner(privateKey, null, Algorithm.ES512);
|
||||
String jws = new JWSBuilder().jsonContent(assertion).sign(signer);
|
||||
|
||||
List<NameValuePair> parameters = new LinkedList<>();
|
||||
|
|
|
|||
Loading…
Reference in a new issue