use of org.keycloak.jose.jws.JWSInput in project keycloak by keycloak.
the class FixedHostnameTest method assertTokenIssuer.
private void assertTokenIssuer(String realm, String expectedBaseUrl) throws Exception {
oauth.realm(realm);
OAuthClient.AccessTokenResponse tokenResponse = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password");
AccessToken token = new JWSInput(tokenResponse.getAccessToken()).readJsonContent(AccessToken.class);
assertEquals(expectedBaseUrl + "/auth/realms/" + realm, token.getIssuer());
String introspection = oauth.introspectAccessTokenWithClientCredential(oauth.getClientId(), "password", tokenResponse.getAccessToken());
ObjectMapper objectMapper = new ObjectMapper();
JsonNode introspectionNode = objectMapper.readTree(introspection);
assertTrue(introspectionNode.get("active").asBoolean());
assertEquals(expectedBaseUrl + "/auth/realms/" + realm, introspectionNode.get("iss").asText());
}
use of org.keycloak.jose.jws.JWSInput in project keycloak by keycloak.
the class JWTClientAuthenticator method authenticateClient.
@Override
public void authenticateClient(ClientAuthenticationFlowContext context) {
// KEYCLOAK-19461: Needed for quarkus resteasy implementation throws exception when called with mediaType authentication/json in OpenShiftTokenReviewEndpoint
if (!isFormDataRequest(context.getHttpRequest())) {
Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "invalid_client", "Parameter client_assertion_type is missing");
context.challenge(challengeResponse);
return;
}
MultivaluedMap<String, String> params = context.getHttpRequest().getDecodedFormParameters();
String clientAssertionType = params.getFirst(OAuth2Constants.CLIENT_ASSERTION_TYPE);
String clientAssertion = params.getFirst(OAuth2Constants.CLIENT_ASSERTION);
if (clientAssertionType == null) {
Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "invalid_client", "Parameter client_assertion_type is missing");
context.challenge(challengeResponse);
return;
}
if (!clientAssertionType.equals(OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT)) {
Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "invalid_client", "Parameter client_assertion_type has value '" + clientAssertionType + "' but expected is '" + OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT + "'");
context.challenge(challengeResponse);
return;
}
if (clientAssertion == null) {
Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "invalid_client", "client_assertion parameter missing");
context.failure(AuthenticationFlowError.INVALID_CLIENT_CREDENTIALS, challengeResponse);
return;
}
try {
JWSInput jws = new JWSInput(clientAssertion);
JsonWebToken token = jws.readJsonContent(JsonWebToken.class);
RealmModel realm = context.getRealm();
String clientId = token.getSubject();
if (clientId == null) {
throw new RuntimeException("Can't identify client. Subject missing on JWT token");
}
if (!clientId.equals(token.getIssuer())) {
throw new RuntimeException("Issuer mismatch. The issuer should match the subject");
}
context.getEvent().client(clientId);
ClientModel client = realm.getClientByClientId(clientId);
if (client == null) {
context.failure(AuthenticationFlowError.CLIENT_NOT_FOUND, null);
return;
} else {
context.setClient(client);
}
if (!client.isEnabled()) {
context.failure(AuthenticationFlowError.CLIENT_DISABLED, null);
return;
}
String expectedSignatureAlg = OIDCAdvancedConfigWrapper.fromClientModel(client).getTokenEndpointAuthSigningAlg();
if (jws.getHeader().getAlgorithm() == null || jws.getHeader().getAlgorithm().name() == null) {
Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "invalid_client", "invalid signature algorithm");
context.challenge(challengeResponse);
return;
}
String actualSignatureAlg = jws.getHeader().getAlgorithm().name();
if (expectedSignatureAlg != null && !expectedSignatureAlg.equals(actualSignatureAlg)) {
Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "invalid_client", "invalid signature algorithm");
context.challenge(challengeResponse);
return;
}
// Get client key and validate signature
PublicKey clientPublicKey = getSignatureValidationKey(client, context, jws);
if (clientPublicKey == null) {
// Error response already set to context
return;
}
boolean signatureValid;
try {
JsonWebToken jwt = context.getSession().tokens().decodeClientJWT(clientAssertion, client, JsonWebToken.class);
signatureValid = jwt != null;
} catch (RuntimeException e) {
Throwable cause = e.getCause() != null ? e.getCause() : e;
throw new RuntimeException("Signature on JWT token failed validation", cause);
}
if (!signatureValid) {
throw new RuntimeException("Signature on JWT token failed validation");
}
// Allow both "issuer" or "token-endpoint" as audience
List<String> expectedAudiences = getExpectedAudiences(context, realm);
if (!token.hasAnyAudience(expectedAudiences)) {
throw new RuntimeException("Token audience doesn't match domain. Expected audiences are any of " + expectedAudiences + " but audience from token is '" + Arrays.asList(token.getAudience()) + "'");
}
if (!token.isActive()) {
throw new RuntimeException("Token is not active");
}
// KEYCLOAK-2986
int currentTime = Time.currentTime();
if (token.getExpiration() == 0 && token.getIssuedAt() + 10 < currentTime) {
throw new RuntimeException("Token is not active");
}
if (token.getId() == null) {
throw new RuntimeException("Missing ID on the token");
}
SingleUseTokenStoreProvider singleUseCache = context.getSession().getProvider(SingleUseTokenStoreProvider.class);
int lifespanInSecs = Math.max(token.getExpiration() - currentTime, 10);
if (singleUseCache.putIfAbsent(token.getId(), lifespanInSecs)) {
logger.tracef("Added token '%s' to single-use cache. Lifespan: %d seconds, client: %s", token.getId(), lifespanInSecs, clientId);
} else {
logger.warnf("Token '%s' already used when authenticating client '%s'.", token.getId(), clientId);
throw new RuntimeException("Token reuse detected");
}
context.success();
} catch (Exception e) {
ServicesLogger.LOGGER.errorValidatingAssertion(e);
Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), OAuthErrorException.INVALID_CLIENT, "Client authentication with signed JWT failed: " + e.getMessage());
context.failure(AuthenticationFlowError.INVALID_CLIENT_CREDENTIALS, challengeResponse);
}
}
use of org.keycloak.jose.jws.JWSInput in project keycloak by keycloak.
the class JWTClientSecretAuthenticator method authenticateClient.
@Override
public void authenticateClient(ClientAuthenticationFlowContext context) {
// KEYCLOAK-19461: Needed for quarkus resteasy implementation throws exception when called with mediaType authentication/json in OpenShiftTokenReviewEndpoint
if (!isFormDataRequest(context.getHttpRequest())) {
Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "invalid_client", "Parameter client_assertion_type is missing");
context.challenge(challengeResponse);
return;
}
MultivaluedMap<String, String> params = context.getHttpRequest().getDecodedFormParameters();
String clientAssertionType = params.getFirst(OAuth2Constants.CLIENT_ASSERTION_TYPE);
String clientAssertion = params.getFirst(OAuth2Constants.CLIENT_ASSERTION);
if (clientAssertionType == null) {
Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "invalid_client", "Parameter client_assertion_type is missing");
context.challenge(challengeResponse);
return;
}
if (!clientAssertionType.equals(OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT)) {
Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "invalid_client", "Parameter client_assertion_type has value '" + clientAssertionType + "' but expected is '" + OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT + "'");
context.challenge(challengeResponse);
return;
}
if (clientAssertion == null) {
Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "invalid_client", "client_assertion parameter missing");
context.failure(AuthenticationFlowError.INVALID_CLIENT_CREDENTIALS, challengeResponse);
return;
}
try {
JWSInput jws = new JWSInput(clientAssertion);
JsonWebToken token = jws.readJsonContent(JsonWebToken.class);
RealmModel realm = context.getRealm();
String clientId = token.getSubject();
if (clientId == null) {
throw new RuntimeException("Can't identify client. Subject missing on JWT token");
}
if (!clientId.equals(token.getIssuer())) {
throw new RuntimeException("Issuer mismatch. The issuer should match the subject");
}
context.getEvent().client(clientId);
ClientModel client = realm.getClientByClientId(clientId);
if (client == null) {
context.failure(AuthenticationFlowError.CLIENT_NOT_FOUND, null);
return;
} else {
context.setClient(client);
}
if (!client.isEnabled()) {
context.failure(AuthenticationFlowError.CLIENT_DISABLED, null);
return;
}
String expectedSignatureAlg = OIDCAdvancedConfigWrapper.fromClientModel(client).getTokenEndpointAuthSigningAlg();
if (jws.getHeader().getAlgorithm() == null || jws.getHeader().getAlgorithm().name() == null) {
Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "invalid_client", "invalid signature algorithm");
context.challenge(challengeResponse);
return;
}
String actualSignatureAlg = jws.getHeader().getAlgorithm().name();
if (expectedSignatureAlg != null && !expectedSignatureAlg.equals(actualSignatureAlg)) {
Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "invalid_client", "invalid signature algorithm");
context.challenge(challengeResponse);
return;
}
String clientSecretString = client.getSecret();
if (clientSecretString == null) {
context.failure(AuthenticationFlowError.INVALID_CLIENT_CREDENTIALS, null);
return;
}
boolean signatureValid;
try {
JsonWebToken jwt = context.getSession().tokens().decodeClientJWT(clientAssertion, client, JsonWebToken.class);
signatureValid = jwt != null;
} catch (RuntimeException e) {
Throwable cause = e.getCause() != null ? e.getCause() : e;
throw new RuntimeException("Signature on JWT token by client secret failed validation", cause);
}
if (!signatureValid) {
throw new RuntimeException("Signature on JWT token by client secret failed validation");
}
// According to <a href="http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication">OIDC's client authentication spec</a>,
// JWT contents and verification in client_secret_jwt is the same as in private_key_jwt
// Allow both "issuer" or "token-endpoint" as audience
String issuerUrl = Urls.realmIssuer(context.getUriInfo().getBaseUri(), realm.getName());
String tokenUrl = OIDCLoginProtocolService.tokenUrl(context.getUriInfo().getBaseUriBuilder()).build(realm.getName()).toString();
if (!token.hasAudience(issuerUrl) && !token.hasAudience(tokenUrl)) {
throw new RuntimeException("Token audience doesn't match domain. Realm issuer is '" + issuerUrl + "' but audience from token is '" + Arrays.asList(token.getAudience()).toString() + "'");
}
if (!token.isActive()) {
throw new RuntimeException("Token is not active");
}
// KEYCLOAK-2986, token-timeout or token-expiration in keycloak.json might not be used
int currentTime = Time.currentTime();
if (token.getExpiration() == 0 && token.getIssuedAt() + 10 < currentTime) {
throw new RuntimeException("Token is not active");
}
if (token.getId() == null) {
throw new RuntimeException("Missing ID on the token");
}
SingleUseTokenStoreProvider singleUseCache = context.getSession().getProvider(SingleUseTokenStoreProvider.class);
int lifespanInSecs = Math.max(token.getExpiration() - currentTime, 10);
if (singleUseCache.putIfAbsent(token.getId(), lifespanInSecs)) {
logger.tracef("Added token '%s' to single-use cache. Lifespan: %d seconds, client: %s", token.getId(), lifespanInSecs, clientId);
} else {
logger.warnf("Token '%s' already used when authenticating client '%s'.", token.getId(), clientId);
throw new RuntimeException("Token reuse detected");
}
context.success();
} catch (Exception e) {
ServicesLogger.LOGGER.errorValidatingAssertion(e);
Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "unauthorized_client", "Client authentication with client secret signed JWT failed: " + e.getMessage());
context.failure(AuthenticationFlowError.INVALID_CLIENT_CREDENTIALS, challengeResponse);
}
}
use of org.keycloak.jose.jws.JWSInput in project keycloak by keycloak.
the class AuthzClientCredentialsTest method testSuccessfulAuthorizationRequest.
@Test
public void testSuccessfulAuthorizationRequest() throws Exception {
AuthzClient authzClient = getAuthzClient("keycloak-with-jwt-authentication.json");
ProtectionResource protection = authzClient.protection();
PermissionRequest request = new PermissionRequest("Default Resource");
PermissionResponse ticketResponse = protection.permission().create(request);
String ticket = ticketResponse.getTicket();
AuthorizationResponse authorizationResponse = authzClient.authorization("marta", "password").authorize(new AuthorizationRequest(ticket));
String rpt = authorizationResponse.getToken();
assertNotNull(rpt);
AccessToken accessToken = new JWSInput(rpt).readJsonContent(AccessToken.class);
AccessToken.Authorization authorization = accessToken.getAuthorization();
assertNotNull(authorization);
List<Permission> permissions = new ArrayList<>(authorization.getPermissions());
assertFalse(permissions.isEmpty());
assertEquals("Default Resource", permissions.get(0).getResourceName());
}
use of org.keycloak.jose.jws.JWSInput in project keycloak by keycloak.
the class HoKTest method expectSuccessfulResponseFromTokenEndpoint.
private void expectSuccessfulResponseFromTokenEndpoint(String sessionId, String codeId, AccessTokenResponse response) throws Exception {
assertEquals(200, response.getStatusCode());
Assert.assertThat(response.getExpiresIn(), allOf(greaterThanOrEqualTo(250), lessThanOrEqualTo(300)));
Assert.assertThat(response.getRefreshExpiresIn(), allOf(greaterThanOrEqualTo(1750), lessThanOrEqualTo(1800)));
assertEquals("Bearer", response.getTokenType());
String expectedKid = oauth.doCertsRequest("test").getKeys()[0].getKeyId();
JWSHeader header = new JWSInput(response.getAccessToken()).getHeader();
assertEquals("RS256", header.getAlgorithm().name());
assertEquals("JWT", header.getType());
assertEquals(expectedKid, header.getKeyId());
assertNull(header.getContentType());
header = new JWSInput(response.getIdToken()).getHeader();
assertEquals("RS256", header.getAlgorithm().name());
assertEquals("JWT", header.getType());
assertEquals(expectedKid, header.getKeyId());
assertNull(header.getContentType());
header = new JWSInput(response.getRefreshToken()).getHeader();
assertEquals("HS256", 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());
assertEquals(2, token.getRealmAccess().getRoles().size());
assertTrue(token.getRealmAccess().isUserInRole("user"));
assertEquals(1, token.getResourceAccess(oauth.getClientId()).getRoles().size());
assertTrue(token.getResourceAccess(oauth.getClientId()).isUserInRole("customer-user"));
EventRepresentation event = events.expectCodeToToken(codeId, sessionId).assertEvent();
assertEquals(token.getId(), event.getDetails().get(Details.TOKEN_ID));
assertEquals(oauth.parseRefreshToken(response.getRefreshToken()).getId(), event.getDetails().get(Details.REFRESH_TOKEN_ID));
assertEquals(sessionId, token.getSessionState());
}
Aggregations