mirror of
https://github.com/keycloak/keycloak.git
synced 2026-05-26 13:50:48 +00:00
Return invalid_client for introspection client auth failures
Closes #48721 Signed-off-by: Palash Thakur <117917450+palasht75@users.noreply.github.com>
This commit is contained in:
committed by
Marek Posolda
parent
fc667a827a
commit
6d3dd321e7
+15
@@ -20,11 +20,13 @@ import java.util.List;
|
||||
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.WebApplicationException;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.MultivaluedMap;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.core.Response.Status;
|
||||
|
||||
import org.keycloak.OAuthErrorException;
|
||||
import org.keycloak.common.ClientConnection;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.Errors;
|
||||
@@ -37,6 +39,7 @@ import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.protocol.oidc.AccessTokenIntrospectionProviderFactory;
|
||||
import org.keycloak.protocol.oidc.TokenIntrospectionProvider;
|
||||
import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
|
||||
import org.keycloak.representations.idm.OAuth2ErrorRepresentation;
|
||||
import org.keycloak.services.ErrorResponseException;
|
||||
import org.keycloak.services.clientpolicy.ClientPolicyException;
|
||||
import org.keycloak.services.clientpolicy.context.TokenIntrospectContext;
|
||||
@@ -136,11 +139,23 @@ public class TokenIntrospectionEndpoint {
|
||||
|
||||
} catch (ErrorResponseException ere) {
|
||||
throw ere;
|
||||
} catch (WebApplicationException wae) {
|
||||
throw convertClientAuthenticationException(wae);
|
||||
} catch (Exception e) {
|
||||
throw throwErrorResponseException(Errors.INVALID_REQUEST, "Authentication failed.", Status.UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
|
||||
private WebApplicationException convertClientAuthenticationException(WebApplicationException wae) {
|
||||
Response response = wae.getResponse();
|
||||
if (response != null && response.getStatus() == Status.UNAUTHORIZED.getStatusCode()
|
||||
&& response.getEntity() instanceof OAuth2ErrorRepresentation error
|
||||
&& OAuthErrorException.UNAUTHORIZED_CLIENT.equals(error.getError())) {
|
||||
return throwErrorResponseException(OAuthErrorException.INVALID_CLIENT, "Client authentication failed.", Status.UNAUTHORIZED);
|
||||
}
|
||||
return wae;
|
||||
}
|
||||
|
||||
private void checkSsl() {
|
||||
if (!session.getContext().getUri().getBaseUri().getScheme().equals("https") && realm.getSslRequired().isRequired(clientConnection)) {
|
||||
throw new ErrorResponseException("invalid_request", "HTTPS required", Status.FORBIDDEN);
|
||||
|
||||
+1
-1
@@ -1815,7 +1815,7 @@ public class ClientPoliciesExecutorTest extends AbstractClientPoliciesTest {
|
||||
// Send a token introspection request with invalid 'aud' . Should fail
|
||||
signedJwt = createSignedRequestToken(clientId, privateKey, publicKey, Algorithm.RS256, getRealmInfoUrl() + "/protocol/openid-connect/introspect");
|
||||
HttpResponse tokenIntrospectionResponse = doTokenIntrospectionWithSignedJWT("access_token", refreshedResponse.getAccessToken(), signedJwt);
|
||||
assertEquals(401, tokenIntrospectionResponse.getStatusLine().getStatusCode());
|
||||
assertEquals(400, tokenIntrospectionResponse.getStatusLine().getStatusCode());
|
||||
|
||||
// Send a token introspection request with valid 'aud' . Should succeed
|
||||
signedJwt = createSignedRequestToken(clientId, privateKey, publicKey, Algorithm.RS256, getRealmInfoUrl());
|
||||
|
||||
+39
-7
@@ -200,15 +200,27 @@ public class TokenIntrospectionTest extends AbstractTestRealmKeycloakTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidClientCredentials() throws Exception {
|
||||
oauth.doLogin("test-user@localhost", "password");
|
||||
String code = oauth.parseLoginResponse().getCode();
|
||||
AccessTokenResponse accessTokenResponse = oauth.doAccessTokenRequest(code);
|
||||
public void testInvalidClientCredentials() {
|
||||
oauth.client("confidential-cli", "bad_credential");
|
||||
IntrospectionResponse tokenResponse = oauth.doIntrospectionAccessTokenRequest(accessTokenResponse.getAccessToken());
|
||||
IntrospectionResponse tokenResponse = oauth.doIntrospectionAccessTokenRequest("any-token");
|
||||
|
||||
Assertions.assertEquals("Authentication failed.", tokenResponse.getErrorDescription());
|
||||
Assertions.assertEquals(OAuthErrorException.INVALID_REQUEST, tokenResponse.getError());
|
||||
Assertions.assertEquals(401, tokenResponse.getStatusCode());
|
||||
Assertions.assertEquals(OAuthErrorException.INVALID_CLIENT, tokenResponse.getError());
|
||||
Assertions.assertEquals("Client authentication failed.", tokenResponse.getErrorDescription());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidClientCredentialsPostAuthentication() throws Exception {
|
||||
try (CloseableHttpResponse response = introspectAccessTokenWithClientSecretPost("confidential-cli", "bad_credential", "any-token")) {
|
||||
Assertions.assertEquals(401, response.getStatusLine().getStatusCode());
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
response.getEntity().writeTo(out);
|
||||
OAuth2ErrorRepresentation errorRep = JsonSerialization.readValue(
|
||||
new String(out.toByteArray(), StandardCharsets.UTF_8), OAuth2ErrorRepresentation.class);
|
||||
Assertions.assertEquals(OAuthErrorException.INVALID_CLIENT, errorRep.getError());
|
||||
Assertions.assertEquals("Client authentication failed.", errorRep.getErrorDescription());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -709,6 +721,26 @@ public class TokenIntrospectionTest extends AbstractTestRealmKeycloakTest {
|
||||
}
|
||||
}
|
||||
|
||||
private CloseableHttpResponse introspectAccessTokenWithClientSecretPost(String clientId, String clientSecret, String tokenToIntrospect) {
|
||||
HttpPost post = new HttpPost(oauth.getEndpoints().getIntrospection());
|
||||
|
||||
List<NameValuePair> parameters = new LinkedList<>();
|
||||
|
||||
parameters.add(new BasicNameValuePair("client_id", clientId));
|
||||
parameters.add(new BasicNameValuePair("client_secret", clientSecret));
|
||||
parameters.add(new BasicNameValuePair("token", tokenToIntrospect));
|
||||
parameters.add(new BasicNameValuePair("token_type_hint", "access_token"));
|
||||
|
||||
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(parameters, StandardCharsets.UTF_8);
|
||||
post.setEntity(formEntity);
|
||||
|
||||
try {
|
||||
return HttpClientBuilder.create().build().execute(post);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to introspect access token", e);
|
||||
}
|
||||
}
|
||||
|
||||
private JsonNode introspectRevokedToken() throws Exception {
|
||||
oauth.doLogin("test-user@localhost", "password");
|
||||
String code = oauth.parseLoginResponse().getCode();
|
||||
|
||||
Reference in New Issue
Block a user