KEYCLOAK-14289 OAuth Authorization Server Metadata for Token Revocation

This commit is contained in:
Yoshiyuki Tabata 2020-05-26 09:14:17 +09:00 committed by Marek Posolda
parent 97400827d2
commit cd76ed0d74
3 changed files with 79 additions and 8 deletions

View file

@ -118,6 +118,15 @@ public class OIDCConfigurationRepresentation {
@JsonProperty("tls_client_certificate_bound_access_tokens")
private Boolean tlsClientCertificateBoundAccessTokens;
@JsonProperty("revocation_endpoint")
private String revocationEndpoint;
@JsonProperty("revocation_endpoint_auth_methods_supported")
private List<String> revocationEndpointAuthMethodsSupported;
@JsonProperty("revocation_endpoint_auth_signing_alg_values_supported")
private List<String> revocationEndpointAuthSigningAlgValuesSupported;
protected Map<String, Object> otherClaims = new HashMap<String, Object>();
public String getIssuer() {
@ -347,6 +356,30 @@ public class OIDCConfigurationRepresentation {
this.tlsClientCertificateBoundAccessTokens = tlsClientCertificateBoundAccessTokens;
}
public String getRevocationEndpoint() {
return revocationEndpoint;
}
public void setRevocationEndpoint(String revocationEndpoint) {
this.revocationEndpoint = revocationEndpoint;
}
public List<String> getRevocationEndpointAuthMethodsSupported() {
return revocationEndpointAuthMethodsSupported;
}
public void setRevocationEndpointAuthMethodsSupported(List<String> revocationEndpointAuthMethodsSupported) {
this.revocationEndpointAuthMethodsSupported = revocationEndpointAuthMethodsSupported;
}
public List<String> getRevocationEndpointAuthSigningAlgValuesSupported() {
return revocationEndpointAuthSigningAlgValuesSupported;
}
public void setRevocationEndpointAuthSigningAlgValuesSupported(List<String> revocationEndpointAuthSigningAlgValuesSupported) {
this.revocationEndpointAuthSigningAlgValuesSupported = revocationEndpointAuthSigningAlgValuesSupported;
}
@JsonAnyGetter
public Map<String, Object> getOtherClaims() {
return otherClaims;

View file

@ -43,6 +43,7 @@ import org.keycloak.wellknown.WellKnownProvider;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import java.net.URI;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
@ -134,6 +135,14 @@ public class OIDCWellKnownProvider implements WellKnownProvider {
// https://tools.ietf.org/html/draft-ietf-oauth-mtls-08#section-6.2
config.setTlsClientCertificateBoundAccessTokens(true);
URI revocationEndpoint = frontendUriBuilder.clone().path(OIDCLoginProtocolService.class, "revoke")
.build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL);
if (isHttps(revocationEndpoint)) {
config.setRevocationEndpoint(revocationEndpoint.toString());
config.setRevocationEndpointAuthMethodsSupported(getClientAuthMethodsSupported());
config.setRevocationEndpointAuthSigningAlgValuesSupported(getSupportedClientSigningAlgorithms(false));
}
return config;
}
@ -200,4 +209,8 @@ public class OIDCWellKnownProvider implements WellKnownProvider {
}
return result;
}
private boolean isHttps(URI uri) {
return uri.getScheme().equals("https");
}
}

View file

@ -58,6 +58,7 @@ import java.net.URI;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -102,7 +103,7 @@ public class OIDCWellKnownProviderTest extends AbstractKeycloakTest {
public void testDiscovery() {
Client client = ClientBuilder.newClient();
try {
OIDCConfigurationRepresentation oidcConfig = getOIDCDiscoveryRepresentation(client);
OIDCConfigurationRepresentation oidcConfig = getOIDCDiscoveryRepresentation(client, OAuthClient.AUTH_SERVER_ROOT);
// URIs are filled
assertEquals(oidcConfig.getAuthorizationEndpoint(), OIDCLoginProtocolService.authUrl(UriBuilder.fromUri(OAuthClient.AUTH_SERVER_ROOT)).build("test").toString());
@ -161,6 +162,28 @@ public class OIDCWellKnownProviderTest extends AbstractKeycloakTest {
// https://tools.ietf.org/html/draft-ietf-oauth-mtls-08#section-6.2
Assert.assertTrue(oidcConfig.getTlsClientCertificateBoundAccessTokens());
// Token Revocation
assertEquals(oidcConfig.getRevocationEndpoint(), oauth.getTokenRevocationUrl());
Assert.assertNames(oidcConfig.getRevocationEndpointAuthMethodsSupported(), "client_secret_basic",
"client_secret_post", "private_key_jwt", "client_secret_jwt", "tls_client_auth");
Assert.assertNames(oidcConfig.getRevocationEndpointAuthSigningAlgValuesSupported(), Algorithm.PS256,
Algorithm.PS384, Algorithm.PS512, Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256,
Algorithm.ES384, Algorithm.ES512, Algorithm.HS256, Algorithm.HS384, Algorithm.HS512);
} finally {
client.close();
}
}
@Test
public void testHttpDiscovery() {
Client client = ClientBuilder.newClient();
try {
OIDCConfigurationRepresentation oidcConfig = getOIDCDiscoveryRepresentation(client, "http://localhost:8180/auth");
// Token Revocation
assertNull(oidcConfig.getRevocationEndpoint());
Assert.assertNull(oidcConfig.getRevocationEndpointAuthMethodsSupported());
Assert.assertNull(oidcConfig.getRevocationEndpointAuthSigningAlgValuesSupported());
} finally {
client.close();
}
@ -175,7 +198,7 @@ public class OIDCWellKnownProviderTest extends AbstractKeycloakTest {
Client client = ClientBuilder.newClient();
try {
OIDCConfigurationRepresentation oidcConfig = getOIDCDiscoveryRepresentation(client);
OIDCConfigurationRepresentation oidcConfig = getOIDCDiscoveryRepresentation(client, OAuthClient.AUTH_SERVER_ROOT);
// assert issuer matches
assertEquals(idToken.getIssuer(), oidcConfig.getIssuer());
@ -214,23 +237,25 @@ public class OIDCWellKnownProviderTest extends AbstractKeycloakTest {
public void testIntrospectionEndpointClaim() throws IOException {
Client client = ClientBuilder.newClient();
try {
ObjectNode oidcConfig = JsonSerialization.readValue(getOIDCDiscoveryConfiguration(client), ObjectNode.class);
assertEquals(oidcConfig.get("introspection_endpoint").asText(), getOIDCDiscoveryRepresentation(client).getIntrospectionEndpoint());
ObjectNode oidcConfig = JsonSerialization
.readValue(getOIDCDiscoveryConfiguration(client, OAuthClient.AUTH_SERVER_ROOT), ObjectNode.class);
assertEquals(oidcConfig.get("introspection_endpoint").asText(),
getOIDCDiscoveryRepresentation(client, OAuthClient.AUTH_SERVER_ROOT).getIntrospectionEndpoint());
} finally {
client.close();
}
}
private OIDCConfigurationRepresentation getOIDCDiscoveryRepresentation(Client client) {
private OIDCConfigurationRepresentation getOIDCDiscoveryRepresentation(Client client, String uriTemplate) {
try {
return JsonSerialization.readValue(getOIDCDiscoveryConfiguration(client), OIDCConfigurationRepresentation.class);
return JsonSerialization.readValue(getOIDCDiscoveryConfiguration(client, uriTemplate), OIDCConfigurationRepresentation.class);
} catch (IOException cause) {
throw new RuntimeException("Failed to parse OIDC configuration", cause);
}
}
private String getOIDCDiscoveryConfiguration(Client client) {
UriBuilder builder = UriBuilder.fromUri(OAuthClient.AUTH_SERVER_ROOT);
private String getOIDCDiscoveryConfiguration(Client client, String uriTemplate) {
UriBuilder builder = UriBuilder.fromUri(uriTemplate);
URI oidcDiscoveryUri = RealmsResource.wellKnownProviderUrl(builder).build("test", OIDCWellKnownProviderFactory.PROVIDER_ID);
WebTarget oidcDiscoveryTarget = client.target(oidcDiscoveryUri);