[KEYCLOAK-10949] - Proper error messages when failing to authenticate the request

This commit is contained in:
Pedro Igor 2019-07-26 16:48:03 -03:00 committed by Bruno Oliveira da Silva
parent 967d21dbb5
commit 8b203d48ce
2 changed files with 98 additions and 22 deletions

View file

@ -38,6 +38,7 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.Response.Status;
import org.apache.http.HttpStatus;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.jboss.resteasy.spi.HttpRequest; import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.OAuthErrorException; import org.keycloak.OAuthErrorException;
@ -99,39 +100,64 @@ public class AuthorizationTokenService {
private static final String RESPONSE_MODE_DECISION = "decision"; private static final String RESPONSE_MODE_DECISION = "decision";
private static final String RESPONSE_MODE_PERMISSIONS = "permissions"; private static final String RESPONSE_MODE_PERMISSIONS = "permissions";
private static final String RESPONSE_MODE_DECISION_RESULT = "result"; private static final String RESPONSE_MODE_DECISION_RESULT = "result";
private static Map<String, BiFunction<AuthorizationRequest, AuthorizationProvider, EvaluationContext>> SUPPORTED_CLAIM_TOKEN_FORMATS; private static Map<String, BiFunction<KeycloakAuthorizationRequest, AuthorizationProvider, EvaluationContext>> SUPPORTED_CLAIM_TOKEN_FORMATS;
static { static {
SUPPORTED_CLAIM_TOKEN_FORMATS = new HashMap<>(); SUPPORTED_CLAIM_TOKEN_FORMATS = new HashMap<>();
SUPPORTED_CLAIM_TOKEN_FORMATS.put("urn:ietf:params:oauth:token-type:jwt", (authorizationRequest, authorization) -> { SUPPORTED_CLAIM_TOKEN_FORMATS.put("urn:ietf:params:oauth:token-type:jwt", (request, authorization) -> {
String claimToken = authorizationRequest.getClaimToken(); String claimToken = request.getClaimToken();
if (claimToken != null) { if (claimToken != null) {
Map claims;
try { try {
Map claims = JsonSerialization.readValue(Base64Url.decode(authorizationRequest.getClaimToken()), Map.class); claims = JsonSerialization.readValue(Base64Url.decode(request.getClaimToken()), Map.class);
authorizationRequest.setClaims(claims); request.setClaims(claims);
return new DefaultEvaluationContext(new KeycloakIdentity(authorization.getKeycloakSession(), Tokens.getAccessToken(authorizationRequest.getSubjectToken(), authorization.getKeycloakSession())), claims, authorization.getKeycloakSession()); } catch (Exception cause) {
} catch (IOException cause) { throw new CorsErrorResponseException(request.getCors(), "invalid_request", "Invalid claims",
throw new RuntimeException("Failed to map claims from claim token [" + claimToken + "]", cause); Status.BAD_REQUEST);
} }
KeycloakIdentity identity;
try {
identity = new KeycloakIdentity(authorization.getKeycloakSession(),
Tokens.getAccessToken(request.getSubjectToken(), authorization.getKeycloakSession()));
} catch (Exception cause) {
throw new CorsErrorResponseException(request.getCors(), "unauthorized_client", "Invalid identity", Status.BAD_REQUEST);
}
return new DefaultEvaluationContext(identity, claims, authorization.getKeycloakSession());
} }
throw new RuntimeException("Claim token can not be null"); throw new CorsErrorResponseException(request.getCors(), "invalid_request", "Claim token can not be null", Status.BAD_REQUEST);
}); });
SUPPORTED_CLAIM_TOKEN_FORMATS.put(CLAIM_TOKEN_FORMAT_ID_TOKEN, (authorizationRequest, authorization) -> { SUPPORTED_CLAIM_TOKEN_FORMATS.put(CLAIM_TOKEN_FORMAT_ID_TOKEN, (request, authorization) -> {
try { KeycloakSession keycloakSession = authorization.getKeycloakSession();
KeycloakSession keycloakSession = authorization.getKeycloakSession(); String subjectToken = request.getSubjectToken();
String accessToken = authorizationRequest.getSubjectToken();
if (accessToken == null) { if (subjectToken == null) {
throw new RuntimeException("Claim token can not be null and must be a valid IDToken"); throw new CorsErrorResponseException(request.getCors(), "invalid_request", "Subject token can not be null and must be a valid ID or Access Token",
} Status.BAD_REQUEST);
IDToken idToken = new TokenManager().verifyIDTokenSignature(keycloakSession, accessToken);
return new DefaultEvaluationContext(new KeycloakIdentity(keycloakSession, idToken), authorizationRequest.getClaims(), keycloakSession);
} catch (OAuthErrorException cause) {
throw new RuntimeException("Failed to verify ID token", cause);
} }
IDToken idToken;
try {
idToken = new TokenManager().verifyIDTokenSignature(keycloakSession, subjectToken);
} catch (Exception cause) {
throw new CorsErrorResponseException(request.getCors(), "unauthorized_client", "Invalid signature", Status.BAD_REQUEST);
}
KeycloakIdentity identity;
try {
identity = new KeycloakIdentity(keycloakSession, idToken);
} catch (Exception cause) {
throw new CorsErrorResponseException(request.getCors(), "unauthorized_client", "Invalid identity", Status.BAD_REQUEST);
}
return new DefaultEvaluationContext(identity, request.getClaims(), keycloakSession);
}); });
} }
@ -370,7 +396,7 @@ public class AuthorizationTokenService {
claimTokenFormat = CLAIM_TOKEN_FORMAT_ID_TOKEN; claimTokenFormat = CLAIM_TOKEN_FORMAT_ID_TOKEN;
} }
BiFunction<AuthorizationRequest, AuthorizationProvider, EvaluationContext> evaluationContextProvider = SUPPORTED_CLAIM_TOKEN_FORMATS.get(claimTokenFormat); BiFunction<KeycloakAuthorizationRequest, AuthorizationProvider, EvaluationContext> evaluationContextProvider = SUPPORTED_CLAIM_TOKEN_FORMATS.get(claimTokenFormat);
if (evaluationContextProvider == null) { if (evaluationContextProvider == null) {
throw new CorsErrorResponseException(request.getCors(), OAuthErrorException.INVALID_REQUEST, "Claim token format [" + claimTokenFormat + "] not supported", Status.BAD_REQUEST); throw new CorsErrorResponseException(request.getCors(), OAuthErrorException.INVALID_REQUEST, "Claim token format [" + claimTokenFormat + "] not supported", Status.BAD_REQUEST);

View file

@ -58,6 +58,7 @@ import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.authorization.client.Configuration; import org.keycloak.authorization.client.Configuration;
import org.keycloak.authorization.client.representation.TokenIntrospectionResponse; import org.keycloak.authorization.client.representation.TokenIntrospectionResponse;
import org.keycloak.authorization.client.util.HttpResponseException; import org.keycloak.authorization.client.util.HttpResponseException;
import org.keycloak.authorization.permission.ResourcePermission;
import org.keycloak.common.util.Base64Url; import org.keycloak.common.util.Base64Url;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocol;
@ -1872,6 +1873,55 @@ public class EntitlementAPITest extends AbstractAuthzTest {
assertEquals(PUBLIC_TEST_CLIENT, token.getIssuedFor()); assertEquals(PUBLIC_TEST_CLIENT, token.getIssuedFor());
} }
@Test
public void testUsingExpiredToken() throws Exception {
ClientResource client = getClient(getRealm(), RESOURCE_SERVER_TEST);
AuthorizationResource authorization = client.authorization();
JSPolicyRepresentation policy = new JSPolicyRepresentation();
policy.setName(KeycloakModelUtils.generateId());
policy.setCode("$evaluation.grant();");
authorization.policies().js().create(policy).close();
ResourceRepresentation resource = new ResourceRepresentation();
resource.setName("Sensors");
try (Response response = authorization.resources().create(resource)) {
resource = response.readEntity(ResourceRepresentation.class);
}
ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation();
permission.setName("View Sensor");
permission.addPolicy(policy.getName());
authorization.permissions().resource().create(permission).close();
String accessToken = new OAuthClient().realm("authz-test").clientId(RESOURCE_SERVER_TEST).doGrantAccessTokenRequest("secret", "marta", "password").getAccessToken();
AuthzClient authzClient = getAuthzClient(AUTHZ_CLIENT_CONFIG);
AccessTokenResponse response = authzClient.authorization(accessToken).authorize();
assertNotNull(response.getToken());
getRealm().logoutAll();
AuthorizationRequest request = new AuthorizationRequest();
request.addPermission("Sensors");
request.setSubjectToken(accessToken);
try {
authzClient.authorization().authorize(request);
fail("should fail, session invalidated");
} catch (Exception e) {
Throwable expected = e.getCause();
assertEquals(400, HttpResponseException.class.cast(expected).getStatusCode());
assertTrue(HttpResponseException.class.cast(expected).toString().contains("unauthorized_client"));
}
}
private void testRptRequestWithResourceName(String configFile) { private void testRptRequestWithResourceName(String configFile) {
Metadata metadata = new Metadata(); Metadata metadata = new Metadata();