mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-09 23:12:06 -03:30
Audience validation according to latest specs proposal
closes #43984 Signed-off-by: mposolda <mposolda@gmail.com>
This commit is contained in:
parent
6043027d99
commit
b8a8be33aa
@ -15,6 +15,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.broker.provider;
|
||||
import java.util.List;
|
||||
|
||||
import org.keycloak.protocol.oidc.JWTAuthorizationGrantValidationContext;
|
||||
|
||||
public interface JWTAuthorizationGrantProvider {
|
||||
@ -25,4 +27,9 @@ public interface JWTAuthorizationGrantProvider {
|
||||
|
||||
boolean isAssertionReuseAllowed();
|
||||
|
||||
/**
|
||||
* @return list of allowed audience values. JWT assertion is considered valid if it's audience is one of the audiences returned from this method
|
||||
*/
|
||||
List<String> getAllowedAudienceForJWTGrant();
|
||||
|
||||
}
|
||||
|
||||
@ -75,6 +75,7 @@ import org.keycloak.representations.IDToken;
|
||||
import org.keycloak.representations.JsonWebToken;
|
||||
import org.keycloak.services.ErrorPage;
|
||||
import org.keycloak.services.ErrorResponseException;
|
||||
import org.keycloak.services.Urls;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
import org.keycloak.services.resources.IdentityBrokerService;
|
||||
@ -86,6 +87,7 @@ import org.keycloak.util.TokenUtil;
|
||||
import org.keycloak.vault.VaultStringSecret;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@ -1096,4 +1098,14 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
|
||||
public boolean isAssertionReuseAllowed() {
|
||||
return getConfig().getJwtAuthorizationGrantAssertionReuseAllowed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAllowedAudienceForJWTGrant() {
|
||||
RealmModel realm = session.getContext().getRealm();
|
||||
|
||||
URI baseUri = session.getContext().getUri().getBaseUri();
|
||||
String issuer = Urls.realmIssuer(baseUri, realm.getName());
|
||||
String tokenEndpoint = Urls.tokenEndpoint(baseUri, realm.getName()).toString();
|
||||
return List.of(issuer, tokenEndpoint);
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,7 +44,6 @@ import org.keycloak.sessions.AuthenticationSessionModel;
|
||||
import org.keycloak.sessions.RootAuthenticationSessionModel;
|
||||
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.util.List;
|
||||
|
||||
public class JWTAuthorizationGrantType extends OAuth2GrantTypeBase {
|
||||
|
||||
@ -53,7 +52,6 @@ public class JWTAuthorizationGrantType extends OAuth2GrantTypeBase {
|
||||
setContext(context);
|
||||
|
||||
String assertion = formParams.getFirst(OAuth2Constants.ASSERTION);
|
||||
String expectedAudience = Urls.realmIssuer(session.getContext().getUri().getBaseUri(), realm.getName());
|
||||
|
||||
try {
|
||||
|
||||
@ -64,7 +62,6 @@ public class JWTAuthorizationGrantType extends OAuth2GrantTypeBase {
|
||||
authorizationGrantContext.validateClient();
|
||||
|
||||
//mandatory claims
|
||||
authorizationGrantContext.validateTokenAudience(List.of(expectedAudience), false);
|
||||
authorizationGrantContext.validateIssuer();
|
||||
authorizationGrantContext.validateSubject();
|
||||
|
||||
@ -88,6 +85,9 @@ public class JWTAuthorizationGrantType extends OAuth2GrantTypeBase {
|
||||
// assign the provider and perform validations associated to the jwt grant provider
|
||||
authorizationGrantContext.validateTokenActive(jwtAuthorizationGrantProvider.getAllowedClockSkew(), 300, jwtAuthorizationGrantProvider.isAssertionReuseAllowed());
|
||||
|
||||
// Validate audience
|
||||
authorizationGrantContext.validateTokenAudience(jwtAuthorizationGrantProvider.getAllowedAudienceForJWTGrant(), false);
|
||||
|
||||
//validate the JWT assertion and get the brokered identity from the idp
|
||||
BrokeredIdentityContext brokeredIdentityContext = jwtAuthorizationGrantProvider.validateAuthorizationGrantAssertion(authorizationGrantContext);
|
||||
if (brokeredIdentityContext == null) {
|
||||
|
||||
@ -184,6 +184,10 @@ public class Urls {
|
||||
return tokenBase(baseUri).path(OIDCLoginProtocolService.class, "logout");
|
||||
}
|
||||
|
||||
public static URI tokenEndpoint(URI baseUri, String realmName) {
|
||||
return tokenBase(baseUri).path(OIDCLoginProtocolService.class, "token").build(realmName);
|
||||
}
|
||||
|
||||
public static URI realmRegisterAction(URI baseUri, String realmName) {
|
||||
return loginActionsBase(baseUri).path(LoginActionsService.class, "processRegister").build(realmName);
|
||||
}
|
||||
|
||||
@ -125,7 +125,7 @@ public class JWTAuthorizationGrantTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBadAudience() {
|
||||
public void testAudience() {
|
||||
String jwt = getIdentityProvider().encodeToken(createAuthorizationGrantToken("basic-user-id", null, IDP_ISSUER, Time.currentTime() + 300L));
|
||||
AccessTokenResponse response = oAuthClient.jwtAuthorizationGrantRequest(jwt).send();
|
||||
assertFailure("Invalid token audience", response, events.poll());
|
||||
@ -133,6 +133,35 @@ public class JWTAuthorizationGrantTest {
|
||||
jwt = getIdentityProvider().encodeToken(createAuthorizationGrantToken("basic-user-id", "fake-audience", IDP_ISSUER));
|
||||
response = oAuthClient.jwtAuthorizationGrantRequest(jwt).send();
|
||||
assertFailure("Invalid token audience", response, events.poll());
|
||||
|
||||
// Issuer as audience works
|
||||
jwt = getIdentityProvider().encodeToken(createAuthorizationGrantToken("basic-user-id", oAuthClient.getEndpoints().getIssuer(), IDP_ISSUER));
|
||||
response = oAuthClient.jwtAuthorizationGrantRequest(jwt).send();
|
||||
assertSuccess("test-app", "basic-user", response);
|
||||
|
||||
// Token endpoint as audience works
|
||||
jwt = getIdentityProvider().encodeToken(createAuthorizationGrantToken("basic-user-id", oAuthClient.getEndpoints().getToken(), IDP_ISSUER));
|
||||
response = oAuthClient.jwtAuthorizationGrantRequest(jwt).send();
|
||||
assertSuccess("test-app", "basic-user", response);
|
||||
|
||||
// Introspection endpoint as audience does not work
|
||||
jwt = getIdentityProvider().encodeToken(createAuthorizationGrantToken("basic-user-id", oAuthClient.getEndpoints().getIntrospection(), IDP_ISSUER));
|
||||
response = oAuthClient.jwtAuthorizationGrantRequest(jwt).send();
|
||||
assertFailure("Invalid token audience", response, events.poll());
|
||||
|
||||
// Multiple audiences does not work
|
||||
JsonWebToken jwtToken = createAuthorizationGrantToken("basic-user-id", oAuthClient.getEndpoints().getIssuer(), IDP_ISSUER);
|
||||
jwtToken.addAudience("fake");
|
||||
jwt = getIdentityProvider().encodeToken(jwtToken);
|
||||
response = oAuthClient.jwtAuthorizationGrantRequest(jwt).send();
|
||||
assertFailure("Multiple audiences not allowed", response, events.poll());
|
||||
|
||||
// Multiple audiences does not work (even if both are valid)
|
||||
jwtToken = createAuthorizationGrantToken("basic-user-id", oAuthClient.getEndpoints().getIssuer(), IDP_ISSUER);
|
||||
jwtToken.addAudience(oAuthClient.getEndpoints().getToken());
|
||||
jwt = getIdentityProvider().encodeToken(jwtToken);
|
||||
response = oAuthClient.jwtAuthorizationGrantRequest(jwt).send();
|
||||
assertFailure("Multiple audiences not allowed", response, events.poll());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user