mirror of
https://github.com/keycloak/keycloak.git
synced 2026-02-03 20:39:33 -05:00
Client policy executor to allow extra audiences for JWT authorization grant
Closes #45180 Signed-off-by: rmartinc <rmartinc@redhat.com>
This commit is contained in:
parent
072f547b71
commit
c63f54ba3a
9 changed files with 364 additions and 3 deletions
|
|
@ -153,6 +153,7 @@ One of several purposes for this executor is to realize the security requirement
|
||||||
* Enforce SAML Redirect binding cannot be used or SAML requests and assertions are signed
|
* Enforce SAML Redirect binding cannot be used or SAML requests and assertions are signed
|
||||||
* Enforce scopes granted in link:{securing_apps_token_exchange_link}#_standard-token-exchange[Standard token exchange] or in JWT Authorization Grant are restricted to the ones present in the initial `subject_token` or `assertion` JWT. This executor only allows downscoping of the presented assertion. An error is returned if any extra scope, not originally granted to the JWT, is requested.
|
* Enforce scopes granted in link:{securing_apps_token_exchange_link}#_standard-token-exchange[Standard token exchange] or in JWT Authorization Grant are restricted to the ones present in the initial `subject_token` or `assertion` JWT. This executor only allows downscoping of the presented assertion. An error is returned if any extra scope, not originally granted to the JWT, is requested.
|
||||||
* Enforce claims for assertion grants (`subject_token` in Token Exchange and `assertion` in JWT Authorization Grant). The executor enforces the presence and specific values of a claim in a JWT. It uses a Java regex so it is quite versatile.
|
* Enforce claims for assertion grants (`subject_token` in Token Exchange and `assertion` in JWT Authorization Grant). The executor enforces the presence and specific values of a claim in a JWT. It uses a Java regex so it is quite versatile.
|
||||||
|
* Allow different valid audiences for the JWT Authorization Grant. This executor configures one or more valid audiences for the assertion instead of the ones defined in the standard (issuer or token endpoint URL) or the client ID. This behavior is not covered by the standard and can have major security implications.
|
||||||
|
|
||||||
Another available executor is the `auth-flow-enforce`, which can be used to enforce an authentication flow during an authentication request. For instance, it can be used to select a flow based on certain conditions, such as a specific scope or an ACR value. For more details, see the <<_client-policy-auth-flow, related documentation>>.
|
Another available executor is the `auth-flow-enforce`, which can be used to enforce an authentication flow during an authentication request. For instance, it can be used to select a flow based on certain conditions, such as a specific scope or an ACR value. For more details, see the <<_client-policy-auth-flow, related documentation>>.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -155,5 +155,9 @@ The enforcer can be used for any request that uses an assertion parameter. Curre
|
||||||
* Executor **jwt-claim-enforcer**. This executor allows to configure extra requirements for claims in the JWT assertion token. For example, if we want the assertion to contain an `iat` claim or a custom claim with a specific value. The configuration allows us to set any claim name and any claim value (using a java regex). If the claim in the JWT assertion does not match the regex, the request does not proceed and an error is returned.
|
* Executor **jwt-claim-enforcer**. This executor allows to configure extra requirements for claims in the JWT assertion token. For example, if we want the assertion to contain an `iat` claim or a custom claim with a specific value. The configuration allows us to set any claim name and any claim value (using a java regex). If the claim in the JWT assertion does not match the regex, the request does not proceed and an error is returned.
|
||||||
+
|
+
|
||||||
As the previous executor, for the moment this enforcer can be used for JWT Authorization Grant and the Standard Token exchange.
|
As the previous executor, for the moment this enforcer can be used for JWT Authorization Grant and the Standard Token exchange.
|
||||||
|
* Executor **jwt-authorization-grant-audience**. The executor allows to configure different and exclusive valid audiences for the assertion. The JWT Authorization Grant processing ensures, by specification, that the audience of the assertion is the {project_name} issuer or token endpoint URL. You can also use the Client ID as valid audience (option **Allows Client ID as audience for assertions**) for the OpenID Connect identity providers. Using this executor, you can configure one or more valid audiences instead of the default ones for the requests processed by the client policy.
|
||||||
|
+
|
||||||
|
WARNING: The **jwt-authorization-grant-audience** executor changes the validation of the assertion audience out of the standard compliance. This behavior can have major security implications.
|
||||||
|
|
||||||
|
|
||||||
</@tmpl.guide>
|
</@tmpl.guide>
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,10 @@ public interface JWTAuthorizationGrantValidationContext {
|
||||||
|
|
||||||
void setRestrictedScopes(Set<String> restrictedScopes);
|
void setRestrictedScopes(Set<String> restrictedScopes);
|
||||||
|
|
||||||
|
void setAudienceAlreadyValidated();
|
||||||
|
|
||||||
|
boolean isAudienceAlreadyValidated();
|
||||||
|
|
||||||
default String getIssuer() {
|
default String getIssuer() {
|
||||||
return getJWT().getIssuer();
|
return getJWT().getIssuer();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -92,9 +92,6 @@ public class JWTAuthorizationGrantType extends OAuth2GrantTypeBase {
|
||||||
// assign the signature alg and validate
|
// assign the signature alg and validate
|
||||||
authorizationGrantContext.validateSignatureAlgorithm(jwtAuthorizationGrantProvider.getAssertionSignatureAlg());
|
authorizationGrantContext.validateSignatureAlgorithm(jwtAuthorizationGrantProvider.getAssertionSignatureAlg());
|
||||||
|
|
||||||
// Validate audience
|
|
||||||
authorizationGrantContext.validateTokenAudience(jwtAuthorizationGrantProvider.getAllowedAudienceForJWTGrant(), false);
|
|
||||||
|
|
||||||
//validate the JWT assertion and get the brokered identity from the idp
|
//validate the JWT assertion and get the brokered identity from the idp
|
||||||
BrokeredIdentityContext brokeredIdentityContext = jwtAuthorizationGrantProvider.validateAuthorizationGrantAssertion(authorizationGrantContext);
|
BrokeredIdentityContext brokeredIdentityContext = jwtAuthorizationGrantProvider.validateAuthorizationGrantAssertion(authorizationGrantContext);
|
||||||
if (brokeredIdentityContext == null) {
|
if (brokeredIdentityContext == null) {
|
||||||
|
|
@ -122,6 +119,11 @@ public class JWTAuthorizationGrantType extends OAuth2GrantTypeBase {
|
||||||
throw new CorsErrorResponseException(cors, cpe.getError(), cpe.getErrorDetail(), cpe.getErrorStatus());
|
throw new CorsErrorResponseException(cors, cpe.getError(), cpe.getErrorDetail(), cpe.getErrorStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate audience if not validated previously by client policies
|
||||||
|
if (!authorizationGrantContext.isAudienceAlreadyValidated()) {
|
||||||
|
authorizationGrantContext.validateTokenAudience(jwtAuthorizationGrantProvider.getAllowedAudienceForJWTGrant(), false);
|
||||||
|
}
|
||||||
|
|
||||||
RootAuthenticationSessionModel rootAuthSession = new AuthenticationSessionManager(session).createAuthenticationSession(realm, false);
|
RootAuthenticationSessionModel rootAuthSession = new AuthenticationSessionManager(session).createAuthenticationSession(realm, false);
|
||||||
AuthenticationSessionModel authSession = createSessionModel(rootAuthSession, user, client, scopeParam);
|
AuthenticationSessionModel authSession = createSessionModel(rootAuthSession, user, client, scopeParam);
|
||||||
UserSessionModel userSession = new UserSessionManager(session).createUserSession(authSession.getParentSession().getId(), realm, user, user.getUsername(),
|
UserSessionModel userSession = new UserSessionManager(session).createUserSession(authSession.getParentSession().getId(), realm, user, user.getUsername(),
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ public class JWTAuthorizationGrantValidator extends AbstractBaseJWTValidator imp
|
||||||
|
|
||||||
private final String scope;
|
private final String scope;
|
||||||
private Set<String> restrictedScopes;
|
private Set<String> restrictedScopes;
|
||||||
|
private boolean audienceAlreadyValidated;
|
||||||
|
|
||||||
public static JWTAuthorizationGrantValidator createValidator(KeycloakSession session, ClientModel client, String assertion, String scope) {
|
public static JWTAuthorizationGrantValidator createValidator(KeycloakSession session, ClientModel client, String assertion, String scope) {
|
||||||
if (assertion == null) {
|
if (assertion == null) {
|
||||||
|
|
@ -62,6 +63,7 @@ public class JWTAuthorizationGrantValidator extends AbstractBaseJWTValidator imp
|
||||||
private JWTAuthorizationGrantValidator(KeycloakSession session, String scope, ClientAssertionState clientAssertionState) {
|
private JWTAuthorizationGrantValidator(KeycloakSession session, String scope, ClientAssertionState clientAssertionState) {
|
||||||
super(session, clientAssertionState);
|
super(session, clientAssertionState);
|
||||||
this.scope = scope;
|
this.scope = scope;
|
||||||
|
this.audienceAlreadyValidated = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void validateClient() {
|
public void validateClient() {
|
||||||
|
|
@ -102,14 +104,26 @@ public class JWTAuthorizationGrantValidator extends AbstractBaseJWTValidator imp
|
||||||
return scope;
|
return scope;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Set<String> getRestrictedScopes() {
|
public Set<String> getRestrictedScopes() {
|
||||||
return restrictedScopes;
|
return restrictedScopes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void setRestrictedScopes(Set<String> restrictedScopes) {
|
public void setRestrictedScopes(Set<String> restrictedScopes) {
|
||||||
this.restrictedScopes = restrictedScopes;
|
this.restrictedScopes = restrictedScopes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAudienceAlreadyValidated() {
|
||||||
|
this.audienceAlreadyValidated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAudienceAlreadyValidated() {
|
||||||
|
return audienceAlreadyValidated;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void failureCallback(String errorDescription) {
|
protected void failureCallback(String errorDescription) {
|
||||||
throw new RuntimeException(errorDescription);
|
throw new RuntimeException(errorDescription);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,108 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2026 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.keycloak.services.clientpolicy.executor;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.keycloak.OAuthErrorException;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.representations.JsonWebToken;
|
||||||
|
import org.keycloak.representations.idm.ClientPolicyExecutorConfigurationRepresentation;
|
||||||
|
import org.keycloak.services.clientpolicy.ClientPolicyContext;
|
||||||
|
import org.keycloak.services.clientpolicy.ClientPolicyException;
|
||||||
|
import org.keycloak.services.clientpolicy.context.JWTAuthorizationGrantContext;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author rmartinc
|
||||||
|
*/
|
||||||
|
public class JWTAuthorizationGrantAudienceExecutor implements ClientPolicyExecutorProvider<JWTAuthorizationGrantAudienceExecutor.Configuration> {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(JWTAuthorizationGrantAudienceExecutor.class);
|
||||||
|
private final KeycloakSession session;
|
||||||
|
private Configuration configuration;
|
||||||
|
|
||||||
|
public static class Configuration extends ClientPolicyExecutorConfigurationRepresentation {
|
||||||
|
|
||||||
|
@JsonProperty(JWTAuthorizationGrantAudienceExecutorFactory.ALLOWED_AUDIENCE)
|
||||||
|
protected Set<String> allowedAudience;
|
||||||
|
|
||||||
|
public Set<String> getAllowedAudience() {
|
||||||
|
return allowedAudience;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAllowedAudience(Set<String> allowedAudience) {
|
||||||
|
this.allowedAudience = allowedAudience;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public JWTAuthorizationGrantAudienceExecutor(KeycloakSession session) {
|
||||||
|
this.session = session;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getProviderId() {
|
||||||
|
return JWTAuthorizationGrantAudienceExecutorFactory.PROVIDER_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setupConfiguration(Configuration config) {
|
||||||
|
this.configuration = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<Configuration> getExecutorConfigurationClass() {
|
||||||
|
return Configuration.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Configuration getConfiguration() {
|
||||||
|
return configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void executeOnEvent(ClientPolicyContext context) throws ClientPolicyException {
|
||||||
|
switch (context.getEvent()) {
|
||||||
|
case JWT_AUTHORIZATION_GRANT -> {
|
||||||
|
JWTAuthorizationGrantContext jwtAuthnGrantContext = ((JWTAuthorizationGrantContext) context);
|
||||||
|
validateAudience(jwtAuthnGrantContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateAudience(JWTAuthorizationGrantContext jwtAuthnGrantContext) throws ClientPolicyException {
|
||||||
|
final JsonWebToken jwt = jwtAuthnGrantContext.getAuthorizationGrantContext().getJWT();
|
||||||
|
final String[] audience = jwt.getAudience();
|
||||||
|
if (audience == null || audience.length != 1 || configuration == null || configuration.getAllowedAudience() == null) {
|
||||||
|
// just continue with normal processing in this situations
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configuration.getAllowedAudience().contains(audience[0])) {
|
||||||
|
// set the audience as validated by this executor
|
||||||
|
logger.tracef("Allowing extra audience '%s' for the jwt authorization grant request.", audience[0]);
|
||||||
|
jwtAuthnGrantContext.getAuthorizationGrantContext().setAudienceAlreadyValidated();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the audience is not the ones defined in this executor, throw error
|
||||||
|
logger.tracef("Rejecting invalid audience '%s' for the jwt authorization grant request.", audience[0]);
|
||||||
|
throw new ClientPolicyException(OAuthErrorException.INVALID_GRANT, "Invalid token audience");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2026 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.keycloak.services.clientpolicy.executor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.keycloak.Config.Scope;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
import org.keycloak.provider.ProviderConfigurationBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Factory that allows to configure different audiences as valid for the JWT Authorization Grant.
|
||||||
|
* This behavior breaks the specification and can have security implications.</p>
|
||||||
|
*
|
||||||
|
* @author rmartinc
|
||||||
|
*/
|
||||||
|
public class JWTAuthorizationGrantAudienceExecutorFactory implements ClientPolicyExecutorProviderFactory {
|
||||||
|
|
||||||
|
public static final String PROVIDER_ID = "jwt-authorization-grant-audience";
|
||||||
|
public static final String ALLOWED_AUDIENCE = "allowed-audience";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientPolicyExecutorProvider create(KeycloakSession session) {
|
||||||
|
return new JWTAuthorizationGrantAudienceExecutor(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Scope config) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return PROVIDER_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHelpText() {
|
||||||
|
return """
|
||||||
|
Executor that configures new and exclusive valid audiences for the JWT Authorization Grant type.
|
||||||
|
The default audiences valid for the grant are not valid anymore.
|
||||||
|
Note this behavior breaks the standard and can have major security implications.
|
||||||
|
""";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ProviderConfigProperty> getConfigProperties() {
|
||||||
|
return ProviderConfigurationBuilder.create()
|
||||||
|
.property()
|
||||||
|
.name(ALLOWED_AUDIENCE)
|
||||||
|
.type(ProviderConfigProperty.MULTIVALUED_STRING_TYPE)
|
||||||
|
.label("Allowed audience")
|
||||||
|
.helpText("List of new and exclusive valid audiences for the JWT Authhorization Grant")
|
||||||
|
.add()
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -32,3 +32,4 @@ org.keycloak.services.clientpolicy.executor.AuthenticationFlowSelectorExecutorFa
|
||||||
org.keycloak.services.clientpolicy.executor.SecureClientAuthenticationAssertionExecutorFactory
|
org.keycloak.services.clientpolicy.executor.SecureClientAuthenticationAssertionExecutorFactory
|
||||||
org.keycloak.services.clientpolicy.executor.DownscopeAssertionGrantEnforcerExecutorFactory
|
org.keycloak.services.clientpolicy.executor.DownscopeAssertionGrantEnforcerExecutorFactory
|
||||||
org.keycloak.services.clientpolicy.executor.JWTClaimEnforcerExecutorFactory
|
org.keycloak.services.clientpolicy.executor.JWTClaimEnforcerExecutorFactory
|
||||||
|
org.keycloak.services.clientpolicy.executor.JWTAuthorizationGrantAudienceExecutorFactory
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,147 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2026 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.keycloak.tests.oauth;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.keycloak.broker.oidc.OIDCIdentityProviderConfig;
|
||||||
|
import org.keycloak.representations.JsonWebToken;
|
||||||
|
import org.keycloak.services.clientpolicy.condition.IdentityProviderConditionFactory;
|
||||||
|
import org.keycloak.services.clientpolicy.executor.JWTAuthorizationGrantAudienceExecutor;
|
||||||
|
import org.keycloak.services.clientpolicy.executor.JWTAuthorizationGrantAudienceExecutorFactory;
|
||||||
|
import org.keycloak.testframework.annotations.InjectRealm;
|
||||||
|
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
|
||||||
|
import org.keycloak.testframework.realm.ClientPolicyBuilder;
|
||||||
|
import org.keycloak.testframework.realm.ClientProfileBuilder;
|
||||||
|
import org.keycloak.testframework.realm.ManagedRealm;
|
||||||
|
import org.keycloak.testframework.realm.RealmConfigBuilder;
|
||||||
|
import org.keycloak.testsuite.util.oauth.AccessTokenResponse;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author rmartinc
|
||||||
|
*/
|
||||||
|
@KeycloakIntegrationTest(config = JWTAuthorizationGrantTest.JWTAuthorizationGrantServerConfig.class)
|
||||||
|
public class JWTAuthorizationGrantAudienceClientPoliciesTest extends BaseAbstractJWTAuthorizationGrantTest {
|
||||||
|
|
||||||
|
@InjectRealm(config = JWTAuthorizationGrantAudienceClientPoliciesTest.JWTAuthorizationGranthRealmConfig.class)
|
||||||
|
protected ManagedRealm realm;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAudiences() {
|
||||||
|
// test normal issuer audience is not valid
|
||||||
|
String jwt = identityProvider.encodeToken(createDefaultAuthorizationGrantToken());
|
||||||
|
AccessTokenResponse response = oAuthClient.jwtAuthorizationGrantRequest(jwt).send();
|
||||||
|
assertFailurePolicy("invalid_grant", "Invalid token audience", response, events.poll());
|
||||||
|
|
||||||
|
// test allowed-aud1 is valid
|
||||||
|
jwt = identityProvider.encodeToken(createAuthorizationGrantToken("basic-user-id", "allowed-aud1", IDP_ISSUER));
|
||||||
|
response = oAuthClient.jwtAuthorizationGrantRequest(jwt).send();
|
||||||
|
assertSuccess("test-app", response);
|
||||||
|
|
||||||
|
// test allowed-aud2 is valid
|
||||||
|
jwt = identityProvider.encodeToken(createAuthorizationGrantToken("basic-user-id", "allowed-aud2", IDP_ISSUER));
|
||||||
|
response = oAuthClient.jwtAuthorizationGrantRequest(jwt).send();
|
||||||
|
assertSuccess("test-app", response);
|
||||||
|
|
||||||
|
// test any other audience is wrong
|
||||||
|
jwt = identityProvider.encodeToken(createAuthorizationGrantToken("basic-user-id", "other-aud", IDP_ISSUER));
|
||||||
|
response = oAuthClient.jwtAuthorizationGrantRequest(jwt).send();
|
||||||
|
assertFailurePolicy("invalid_grant", "Invalid token audience", response, events.poll());
|
||||||
|
|
||||||
|
// test client-id audience is wrong
|
||||||
|
jwt = getIdentityProvider().encodeToken(createAuthorizationGrantToken("basic-user-id", "test-client", IDP_ISSUER));
|
||||||
|
response = oAuthClient.jwtAuthorizationGrantRequest(jwt).send();
|
||||||
|
assertFailurePolicy("invalid_grant", "Invalid token audience", response, events.poll());
|
||||||
|
|
||||||
|
// test two audiences are always wrong
|
||||||
|
JsonWebToken jwtToken = createDefaultAuthorizationGrantToken();
|
||||||
|
jwtToken.addAudience("allowed-aud2");
|
||||||
|
jwt = getIdentityProvider().encodeToken(jwtToken);
|
||||||
|
response = oAuthClient.jwtAuthorizationGrantRequest(jwt).send();
|
||||||
|
assertFailure("Multiple audiences not allowed", response, events.poll());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAudiencesWithClientId() {
|
||||||
|
// update to use client-id
|
||||||
|
realm.updateIdentityProviderWithCleanup(IDP_ALIAS, rep -> {
|
||||||
|
rep.getConfig().put(OIDCIdentityProviderConfig.ALLOW_CLIENT_ID_AS_AUDIENCE, Boolean.TRUE.toString());
|
||||||
|
});
|
||||||
|
|
||||||
|
// test normal client-id is not working anymore
|
||||||
|
String jwt = getIdentityProvider().encodeToken(createAuthorizationGrantToken("basic-user-id", "test-client", IDP_ISSUER));
|
||||||
|
AccessTokenResponse response = oAuthClient.jwtAuthorizationGrantRequest(jwt).send();
|
||||||
|
assertFailurePolicy("invalid_grant", "Invalid token audience", response, events.poll());
|
||||||
|
|
||||||
|
// test allowed-aud1 is valid
|
||||||
|
jwt = getIdentityProvider().encodeToken(createAuthorizationGrantToken("basic-user-id", "allowed-aud1", IDP_ISSUER));
|
||||||
|
response = oAuthClient.jwtAuthorizationGrantRequest(jwt).send();
|
||||||
|
assertSuccess("test-app", response);
|
||||||
|
|
||||||
|
// test allowed-aud2 is valid
|
||||||
|
jwt = identityProvider.encodeToken(createAuthorizationGrantToken("basic-user-id", "allowed-aud2", IDP_ISSUER));
|
||||||
|
response = oAuthClient.jwtAuthorizationGrantRequest(jwt).send();
|
||||||
|
assertSuccess("test-app", response);
|
||||||
|
|
||||||
|
// test any other audience is wrong
|
||||||
|
jwt = identityProvider.encodeToken(createAuthorizationGrantToken("basic-user-id", "other-aud", IDP_ISSUER));
|
||||||
|
response = oAuthClient.jwtAuthorizationGrantRequest(jwt).send();
|
||||||
|
assertFailurePolicy("invalid_grant", "Invalid token audience", response, events.poll());
|
||||||
|
|
||||||
|
// test issuer audience is wrong
|
||||||
|
jwt = getIdentityProvider().encodeToken(createDefaultAuthorizationGrantToken());
|
||||||
|
response = oAuthClient.jwtAuthorizationGrantRequest(jwt).send();
|
||||||
|
assertFailurePolicy("invalid_grant", "Invalid token audience", response, events.poll());
|
||||||
|
|
||||||
|
// test two audiences are always wrong
|
||||||
|
JsonWebToken jwtToken = createAuthorizationGrantToken("basic-user-id", "test-client", IDP_ISSUER);
|
||||||
|
jwtToken.addAudience("allowed-aud2");
|
||||||
|
jwt = getIdentityProvider().encodeToken(jwtToken);
|
||||||
|
response = oAuthClient.jwtAuthorizationGrantRequest(jwt).send();
|
||||||
|
assertFailure("Multiple audiences not allowed", response, events.poll());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class JWTAuthorizationGranthRealmConfig extends OIDCIdentityProviderJWTAuthorizationGrantTest.JWTAuthorizationGrantRealmConfig {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RealmConfigBuilder configure(RealmConfigBuilder realm) {
|
||||||
|
super.configure(realm);
|
||||||
|
JWTAuthorizationGrantAudienceExecutor.Configuration config =
|
||||||
|
new JWTAuthorizationGrantAudienceExecutor.Configuration();
|
||||||
|
config.setAllowedAudience(Set.of("allowed-aud1", "allowed-aud2"));
|
||||||
|
|
||||||
|
realm.clientProfile(ClientProfileBuilder.create()
|
||||||
|
.name("executor")
|
||||||
|
.description("executor description")
|
||||||
|
.executor(JWTAuthorizationGrantAudienceExecutorFactory.PROVIDER_ID, config)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
realm.clientPolicy(ClientPolicyBuilder.create()
|
||||||
|
.name("policy")
|
||||||
|
.description("description of policy")
|
||||||
|
.condition(IdentityProviderConditionFactory.PROVIDER_ID, ClientPolicyBuilder
|
||||||
|
.identityProviderConditionConfiguration(false, IDP_ALIAS))
|
||||||
|
.profile("executor")
|
||||||
|
.build());
|
||||||
|
|
||||||
|
return realm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue