You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ac...@apache.org on 2026/08/03 11:32:31 UTC
(camel) branch camel-4.18.x updated: [backport camel-4.18.x] CAMEL-24319: camel-keycloak - add optional token type (typ) and authorized party (azp) validation (#25259) (#25307)
This is an automated email from the ASF dual-hosted git repository.
oscerd pushed a commit to branch camel-4.18.x
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/camel-4.18.x by this push:
new 895b7647b9b7 [backport camel-4.18.x] CAMEL-24319: camel-keycloak - add optional token type (typ) and authorized party (azp) validation (#25259) (#25307)
895b7647b9b7 is described below
commit 895b7647b9b7516adda0b6c0095284c13f6a6554
Author: Andrea Cosentino <an...@gmail.com>
AuthorDate: Mon Aug 3 13:32:24 2026 +0200
[backport camel-4.18.x] CAMEL-24319: camel-keycloak - add optional token type (typ) and authorized party (azp) validation (#25259) (#25307)
Opt-in expectedTokenTypes + expectedAuthorizedParty on KeycloakSecurityPolicy,
applied on all three token-validation paths (authenticateToken, validateRoles,
validatePermissions) for both local JWT verification and token introspection.
Disabled by default (non-breaking). The documentation page is main-only.
(cherry picked from commit bb4922598c39fef67b39f3a56b6b17b1a0e5b0c8)
Co-authored-by: Claude Opus 4.8 <no...@anthropic.com>
---
.../keycloak/security/KeycloakSecurityHelper.java | 55 ++++++++++++
.../keycloak/security/KeycloakSecurityPolicy.java | 68 +++++++++++++++
.../security/KeycloakSecurityProcessor.java | 81 +++++++++++++++++-
.../security/KeycloakSecurityHelperTest.java | 97 ++++++++++++++++++++++
.../security/KeycloakSecurityProcessorTest.java | 79 ++++++++++++++++++
5 files changed, 379 insertions(+), 1 deletion(-)
diff --git a/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/security/KeycloakSecurityHelper.java b/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/security/KeycloakSecurityHelper.java
index 39549c1c6034..a274f9ce9406 100644
--- a/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/security/KeycloakSecurityHelper.java
+++ b/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/security/KeycloakSecurityHelper.java
@@ -89,6 +89,36 @@ public final class KeycloakSecurityHelper {
public static AccessToken parseAndVerifyAccessToken(
String tokenString, PublicKey publicKey, String expectedIssuer, List<String> expectedAudiences)
throws VerificationException {
+ return parseAndVerifyAccessToken(tokenString, publicKey, expectedIssuer, expectedAudiences, null, null);
+ }
+
+ /**
+ * Parses and fully verifies an access token including signature, issuer and, optionally, audience, token type
+ * ({@code typ}) and authorized party ({@code azp}) validation. This is the recommended method for secure token
+ * validation.
+ *
+ * @param tokenString the JWT token string
+ * @param publicKey the public key for signature verification
+ * @param expectedIssuer the expected issuer URL (e.g., "http://localhost:8080/realms/myrealm")
+ * @param expectedAudiences the expected audiences; when non-empty, the token's "aud" claim must contain
+ * every one of them (matching Keycloak's own
+ * {@link TokenVerifier#audience(String...)} check). Pass null or an empty list to
+ * skip audience validation.
+ * @param expectedTokenTypes the accepted token types; when non-empty, the token's {@code typ} claim must be
+ * one of them. Guards against token-type confusion (e.g. an ID or refresh token
+ * presented where an access token is expected). Pass null or an empty list to skip
+ * token-type validation.
+ * @param expectedAuthorizedParty the expected authorized party; when non-empty, the token's {@code azp} claim must
+ * equal it. Ensures the token was issued for the expected client. Pass null or an
+ * empty string to skip authorized-party validation.
+ * @return the verified access token
+ * @throws VerificationException if verification fails (invalid signature, wrong issuer, expired, missing/wrong
+ * audience, wrong token type, wrong authorized party, etc.)
+ */
+ public static AccessToken parseAndVerifyAccessToken(
+ String tokenString, PublicKey publicKey, String expectedIssuer, List<String> expectedAudiences,
+ List<String> expectedTokenTypes, String expectedAuthorizedParty)
+ throws VerificationException {
if (publicKey == null) {
throw new VerificationException("Public key is required for secure token verification");
}
@@ -119,6 +149,31 @@ public final class KeycloakSecurityHelper {
String.format("Token issuer mismatch: expected '%s' but got '%s'", expectedIssuer, actualIssuer));
}
+ // Optional token type (typ) allow-list — guards against token-type confusion, e.g. an ID or refresh token
+ // being presented where an access token is expected.
+ if (expectedTokenTypes != null && !expectedTokenTypes.isEmpty()) {
+ String actualType = token.getType();
+ if (actualType == null || !expectedTokenTypes.contains(actualType)) {
+ LOG.error("SECURITY: Token type mismatch - expected one of {} but got '{}'",
+ expectedTokenTypes, actualType);
+ throw new VerificationException(
+ String.format("Token type mismatch: expected one of %s but got '%s'",
+ expectedTokenTypes, actualType));
+ }
+ }
+
+ // Optional authorized party (azp) check — ensures the token was issued for the expected client.
+ if (expectedAuthorizedParty != null && !expectedAuthorizedParty.isEmpty()) {
+ String actualAzp = token.getIssuedFor();
+ if (!expectedAuthorizedParty.equals(actualAzp)) {
+ LOG.error("SECURITY: Token authorized party (azp) mismatch - expected '{}' but got '{}'",
+ expectedAuthorizedParty, actualAzp);
+ throw new VerificationException(
+ String.format("Token authorized party mismatch: expected '%s' but got '%s'",
+ expectedAuthorizedParty, actualAzp));
+ }
+ }
+
LOG.debug("Token successfully verified for issuer: {}", expectedIssuer);
return token;
}
diff --git a/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/security/KeycloakSecurityPolicy.java b/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/security/KeycloakSecurityPolicy.java
index 17781c45ec21..55d76ea17b9c 100644
--- a/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/security/KeycloakSecurityPolicy.java
+++ b/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/security/KeycloakSecurityPolicy.java
@@ -106,6 +106,21 @@ public class KeycloakSecurityPolicy implements AuthorizationPolicy {
*/
private String expectedAudience;
+ /**
+ * Comma-separated list of accepted token types ({@code typ} claim). When set, a token whose {@code typ} is not one
+ * of the configured values is rejected. This guards against token-type confusion, e.g. an ID token or refresh token
+ * being presented where an access token is expected. Disabled by default for backward compatibility. Example:
+ * "Bearer"
+ */
+ private String expectedTokenTypes;
+
+ /**
+ * Expected authorized party ({@code azp} claim). When set, a token whose {@code azp} does not equal the configured
+ * value is rejected, ensuring the token was issued for the expected client. Disabled by default for backward
+ * compatibility. Example: "my-client"
+ */
+ private String expectedAuthorizedParty;
+
public KeycloakSecurityPolicy() {
this.requiredRoles = "";
this.requiredPermissions = "";
@@ -470,4 +485,57 @@ public class KeycloakSecurityPolicy implements AuthorizationPolicy {
.filter(s -> !s.isEmpty())
.collect(Collectors.toList());
}
+
+ /**
+ * Gets the accepted token type(s) as a comma-separated string.
+ *
+ * @return comma-separated token types (e.g., "Bearer"), or null if not configured
+ */
+ public String getExpectedTokenTypes() {
+ return expectedTokenTypes;
+ }
+
+ /**
+ * Sets the accepted token type(s) as a comma-separated string. When set, a token whose "typ" claim is not one of
+ * the configured values is rejected.
+ *
+ * @param expectedTokenTypes comma-separated token types (e.g., "Bearer")
+ */
+ public void setExpectedTokenTypes(String expectedTokenTypes) {
+ this.expectedTokenTypes = expectedTokenTypes;
+ }
+
+ /**
+ * Gets the accepted token types as a list.
+ *
+ * @return list of accepted token types, or an empty list if not configured
+ */
+ public List<String> getExpectedTokenTypesAsList() {
+ if (ObjectHelper.isEmpty(expectedTokenTypes)) {
+ return Collections.emptyList();
+ }
+ return Arrays.stream(expectedTokenTypes.split(","))
+ .map(String::trim)
+ .filter(s -> !s.isEmpty())
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Gets the expected authorized party ({@code azp}).
+ *
+ * @return the expected authorized party, or null if not configured
+ */
+ public String getExpectedAuthorizedParty() {
+ return expectedAuthorizedParty;
+ }
+
+ /**
+ * Sets the expected authorized party ({@code azp}). When set, a token whose "azp" claim does not equal the
+ * configured value is rejected.
+ *
+ * @param expectedAuthorizedParty the expected authorized party (e.g., "my-client")
+ */
+ public void setExpectedAuthorizedParty(String expectedAuthorizedParty) {
+ this.expectedAuthorizedParty = expectedAuthorizedParty;
+ }
}
diff --git a/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/security/KeycloakSecurityProcessor.java b/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/security/KeycloakSecurityProcessor.java
index df9f958c4871..6ccf59c39ddd 100644
--- a/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/security/KeycloakSecurityProcessor.java
+++ b/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/security/KeycloakSecurityProcessor.java
@@ -114,6 +114,14 @@ public class KeycloakSecurityProcessor extends DelegateProcessor {
if (!policy.getExpectedAudienceAsList().isEmpty()) {
validateAudienceFromIntrospection(introspectionResult, exchange);
}
+
+ if (!policy.getExpectedTokenTypesAsList().isEmpty()) {
+ validateTokenTypeFromIntrospection(introspectionResult, exchange);
+ }
+
+ if (!ObjectHelper.isEmpty(policy.getExpectedAuthorizedParty())) {
+ validateAuthorizedPartyFromIntrospection(introspectionResult, exchange);
+ }
} else {
parseAndVerifyToken(accessToken, exchange);
}
@@ -283,6 +291,15 @@ public class KeycloakSecurityProcessor extends DelegateProcessor {
validateAudienceFromIntrospection(introspectionResult, exchange);
}
+ // Validate token type / authorized party from introspection result if configured
+ if (!policy.getExpectedTokenTypesAsList().isEmpty()) {
+ validateTokenTypeFromIntrospection(introspectionResult, exchange);
+ }
+
+ if (!ObjectHelper.isEmpty(policy.getExpectedAuthorizedParty())) {
+ validateAuthorizedPartyFromIntrospection(introspectionResult, exchange);
+ }
+
userRoles = KeycloakSecurityHelper.extractRolesFromIntrospection(
introspectionResult, policy.getRealm(), policy.getClientId());
} else {
@@ -339,7 +356,8 @@ public class KeycloakSecurityProcessor extends DelegateProcessor {
if (publicKey != null) {
try {
return KeycloakSecurityHelper.parseAndVerifyAccessToken(
- accessToken, publicKey, expectedIssuer, policy.getExpectedAudienceAsList());
+ accessToken, publicKey, expectedIssuer, policy.getExpectedAudienceAsList(),
+ policy.getExpectedTokenTypesAsList(), policy.getExpectedAuthorizedParty());
} catch (VerificationException e) {
LOG.error("Token verification failed: {}", e.getMessage());
throw new CamelAuthorizationException("Token verification failed: " + e.getMessage(), exchange, e);
@@ -415,6 +433,57 @@ public class KeycloakSecurityProcessor extends DelegateProcessor {
LOG.debug("Audience validation from introspection successful: {}", expectedAudiences);
}
+ /**
+ * Validates the token type ({@code typ}) from an introspection result. When token-type validation is configured, a
+ * token whose {@code typ} is missing or not among the accepted values is rejected — guarding against token-type
+ * confusion (e.g. an ID or refresh token presented where an access token is expected).
+ */
+ private void validateTokenTypeFromIntrospection(
+ KeycloakTokenIntrospector.IntrospectionResult introspectionResult, Exchange exchange)
+ throws CamelAuthorizationException {
+ List<String> expectedTokenTypes = policy.getExpectedTokenTypesAsList();
+ // Use the JWT "typ" claim (token category, e.g. Bearer/Refresh/ID), which Keycloak forwards on its
+ // introspection response — not the RFC 7662 "token_type" field, which is the OAuth token type ("Bearer")
+ // and does not distinguish access from refresh/ID tokens, i.e. it cannot express what this check validates.
+ Object typeClaim = introspectionResult.getClaim("typ");
+ String actualType = typeClaim instanceof String s ? s : null;
+
+ if (actualType == null || !expectedTokenTypes.contains(actualType)) {
+ LOG.error("SECURITY: Token type mismatch from introspection - expected one of {} but got '{}'",
+ expectedTokenTypes, actualType);
+ throw new CamelAuthorizationException(
+ String.format("Token type mismatch: expected one of %s but got '%s'",
+ expectedTokenTypes, actualType),
+ exchange);
+ }
+
+ LOG.debug("Token type validation from introspection successful: {}", expectedTokenTypes);
+ }
+
+ /**
+ * Validates the authorized party ({@code azp}) from an introspection result. When authorized-party validation is
+ * configured, a token whose {@code azp} does not equal the expected value is rejected — ensuring the token was
+ * issued for the expected client.
+ */
+ private void validateAuthorizedPartyFromIntrospection(
+ KeycloakTokenIntrospector.IntrospectionResult introspectionResult, Exchange exchange)
+ throws CamelAuthorizationException {
+ String expectedAuthorizedParty = policy.getExpectedAuthorizedParty();
+ Object azpClaim = introspectionResult.getClaim("azp");
+ String actualAzp = azpClaim instanceof String s ? s : null;
+
+ if (!expectedAuthorizedParty.equals(actualAzp)) {
+ LOG.error("SECURITY: Token authorized party (azp) mismatch from introspection - expected '{}' but got '{}'",
+ expectedAuthorizedParty, actualAzp);
+ throw new CamelAuthorizationException(
+ String.format("Token authorized party mismatch: expected '%s' but got '%s'",
+ expectedAuthorizedParty, actualAzp),
+ exchange);
+ }
+
+ LOG.debug("Authorized party validation from introspection successful: {}", expectedAuthorizedParty);
+ }
+
private void validatePermissions(String accessToken, Exchange exchange) throws Exception {
try {
Set<String> userPermissions;
@@ -439,6 +508,16 @@ public class KeycloakSecurityProcessor extends DelegateProcessor {
validateAudienceFromIntrospection(introspectionResult, exchange);
}
+ // Validate token type and authorized party too, otherwise these checks could be bypassed by
+ // configuring only permissions (which skips authenticateToken/validateRoles) with introspection.
+ if (!policy.getExpectedTokenTypesAsList().isEmpty()) {
+ validateTokenTypeFromIntrospection(introspectionResult, exchange);
+ }
+
+ if (!ObjectHelper.isEmpty(policy.getExpectedAuthorizedParty())) {
+ validateAuthorizedPartyFromIntrospection(introspectionResult, exchange);
+ }
+
userPermissions = KeycloakSecurityHelper.extractPermissionsFromIntrospection(introspectionResult);
} else {
// Use local JWT parsing with secure verification
diff --git a/components/camel-keycloak/src/test/java/org/apache/camel/component/keycloak/security/KeycloakSecurityHelperTest.java b/components/camel-keycloak/src/test/java/org/apache/camel/component/keycloak/security/KeycloakSecurityHelperTest.java
index 90639e8374d1..539417472e9b 100644
--- a/components/camel-keycloak/src/test/java/org/apache/camel/component/keycloak/security/KeycloakSecurityHelperTest.java
+++ b/components/camel-keycloak/src/test/java/org/apache/camel/component/keycloak/security/KeycloakSecurityHelperTest.java
@@ -224,6 +224,103 @@ public class KeycloakSecurityHelperTest {
assertEquals(expectedIssuer, verified.getIssuer());
}
+ @Test
+ void testParseAndVerifyAccessTokenAcceptsMatchingTokenType() throws Exception {
+ String expectedIssuer = "http://localhost:8080/realms/test";
+
+ KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
+ keyGen.initialize(2048);
+ KeyPair keyPair = keyGen.generateKeyPair();
+
+ AccessToken token = new AccessToken();
+ token.issuer(expectedIssuer);
+ token.subject("user-123");
+ token.exp(System.currentTimeMillis() / 1000 + 3600);
+ token.type("Bearer");
+
+ String signed = new JWSBuilder()
+ .type("JWT")
+ .jsonContent(token)
+ .rsa256(keyPair.getPrivate());
+
+ AccessToken verified = KeycloakSecurityHelper.parseAndVerifyAccessToken(
+ signed, keyPair.getPublic(), expectedIssuer, null, List.of("Bearer"), null);
+ assertEquals("user-123", verified.getSubject());
+ }
+
+ @Test
+ void testParseAndVerifyAccessTokenRejectsWrongTokenType() throws Exception {
+ String expectedIssuer = "http://localhost:8080/realms/test";
+
+ KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
+ keyGen.initialize(2048);
+ KeyPair keyPair = keyGen.generateKeyPair();
+
+ // An ID token presented where an access token (typ=Bearer) is expected.
+ AccessToken token = new AccessToken();
+ token.issuer(expectedIssuer);
+ token.subject("user-123");
+ token.exp(System.currentTimeMillis() / 1000 + 3600);
+ token.type("ID");
+
+ String signed = new JWSBuilder()
+ .type("JWT")
+ .jsonContent(token)
+ .rsa256(keyPair.getPrivate());
+
+ assertThrows(VerificationException.class,
+ () -> KeycloakSecurityHelper.parseAndVerifyAccessToken(
+ signed, keyPair.getPublic(), expectedIssuer, null, List.of("Bearer"), null));
+ }
+
+ @Test
+ void testParseAndVerifyAccessTokenAcceptsMatchingAuthorizedParty() throws Exception {
+ String expectedIssuer = "http://localhost:8080/realms/test";
+
+ KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
+ keyGen.initialize(2048);
+ KeyPair keyPair = keyGen.generateKeyPair();
+
+ AccessToken token = new AccessToken();
+ token.issuer(expectedIssuer);
+ token.subject("user-123");
+ token.exp(System.currentTimeMillis() / 1000 + 3600);
+ token.issuedFor("my-client");
+
+ String signed = new JWSBuilder()
+ .type("JWT")
+ .jsonContent(token)
+ .rsa256(keyPair.getPrivate());
+
+ AccessToken verified = KeycloakSecurityHelper.parseAndVerifyAccessToken(
+ signed, keyPair.getPublic(), expectedIssuer, null, null, "my-client");
+ assertEquals("user-123", verified.getSubject());
+ }
+
+ @Test
+ void testParseAndVerifyAccessTokenRejectsWrongAuthorizedParty() throws Exception {
+ String expectedIssuer = "http://localhost:8080/realms/test";
+
+ KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
+ keyGen.initialize(2048);
+ KeyPair keyPair = keyGen.generateKeyPair();
+
+ AccessToken token = new AccessToken();
+ token.issuer(expectedIssuer);
+ token.subject("user-123");
+ token.exp(System.currentTimeMillis() / 1000 + 3600);
+ token.issuedFor("other-client");
+
+ String signed = new JWSBuilder()
+ .type("JWT")
+ .jsonContent(token)
+ .rsa256(keyPair.getPrivate());
+
+ assertThrows(VerificationException.class,
+ () -> KeycloakSecurityHelper.parseAndVerifyAccessToken(
+ signed, keyPair.getPublic(), expectedIssuer, null, null, "my-client"));
+ }
+
@Test
void testExtractKeyIdReturnsKidFromHeader() throws Exception {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
diff --git a/components/camel-keycloak/src/test/java/org/apache/camel/component/keycloak/security/KeycloakSecurityProcessorTest.java b/components/camel-keycloak/src/test/java/org/apache/camel/component/keycloak/security/KeycloakSecurityProcessorTest.java
index c3c1c2f8f269..c13a39723e0b 100644
--- a/components/camel-keycloak/src/test/java/org/apache/camel/component/keycloak/security/KeycloakSecurityProcessorTest.java
+++ b/components/camel-keycloak/src/test/java/org/apache/camel/component/keycloak/security/KeycloakSecurityProcessorTest.java
@@ -372,4 +372,83 @@ class KeycloakSecurityProcessorTest {
assertFalse(routeReached.get(),
"Route body must not be reached when the token has the required permission but not the expected audience");
}
+
+ @Test
+ void testTokenWrongTokenTypeRejectedWithRequiredPermissionsIntrospection() throws Exception {
+ // Token satisfies the required permission but carries the wrong token type: validatePermissions() must
+ // still run the token-type check on the introspection path, otherwise it could be bypassed with a
+ // permissions-only configuration.
+ KeycloakTokenIntrospector introspector = new KeycloakTokenIntrospector(
+ "http://localhost:8080", "test-realm", "test-client", "test-secret", (TokenCache) null) {
+ @Override
+ public IntrospectionResult introspect(String token) {
+ return new IntrospectionResult(Map.of("active", true, "scope", "read", "typ", "Refresh"));
+ }
+ };
+
+ KeycloakSecurityPolicy policy = new KeycloakSecurityPolicy() {
+ @Override
+ public boolean isUseTokenIntrospection() {
+ return true;
+ }
+
+ @Override
+ public KeycloakTokenIntrospector getTokenIntrospector() {
+ return introspector;
+ }
+ };
+ policy.setServerUrl("http://localhost:8080");
+ policy.setRealm("test-realm");
+ policy.setClientId("test-client");
+ policy.setClientSecret("test-secret");
+ policy.setValidateIssuer(false);
+ policy.setExpectedTokenTypes("Bearer");
+ policy.setRequiredPermissions("read");
+
+ AtomicBoolean routeReached = new AtomicBoolean(false);
+ KeycloakSecurityProcessor processor = new KeycloakSecurityProcessor(e -> routeReached.set(true), policy);
+
+ assertThrows(CamelAuthorizationException.class, () -> processor.process(bearer("x")));
+ assertFalse(routeReached.get(),
+ "Route body must not be reached when the token has the required permission but the wrong token type");
+ }
+
+ @Test
+ void testTokenWrongAuthorizedPartyRejectedWithRequiredPermissionsIntrospection() throws Exception {
+ // Token satisfies the required permission but carries the wrong authorized party (azp): validatePermissions()
+ // must still run the azp check on the introspection path.
+ KeycloakTokenIntrospector introspector = new KeycloakTokenIntrospector(
+ "http://localhost:8080", "test-realm", "test-client", "test-secret", (TokenCache) null) {
+ @Override
+ public IntrospectionResult introspect(String token) {
+ return new IntrospectionResult(Map.of("active", true, "scope", "read", "azp", "attacker-client"));
+ }
+ };
+
+ KeycloakSecurityPolicy policy = new KeycloakSecurityPolicy() {
+ @Override
+ public boolean isUseTokenIntrospection() {
+ return true;
+ }
+
+ @Override
+ public KeycloakTokenIntrospector getTokenIntrospector() {
+ return introspector;
+ }
+ };
+ policy.setServerUrl("http://localhost:8080");
+ policy.setRealm("test-realm");
+ policy.setClientId("test-client");
+ policy.setClientSecret("test-secret");
+ policy.setValidateIssuer(false);
+ policy.setExpectedAuthorizedParty("expected-client");
+ policy.setRequiredPermissions("read");
+
+ AtomicBoolean routeReached = new AtomicBoolean(false);
+ KeycloakSecurityProcessor processor = new KeycloakSecurityProcessor(e -> routeReached.set(true), policy);
+
+ assertThrows(CamelAuthorizationException.class, () -> processor.process(bearer("x")));
+ assertFalse(routeReached.get(),
+ "Route body must not be reached when the token has the required permission but the wrong authorized party");
+ }
}