Skip to content

Commit

Permalink
feat: support custom token type header
Browse files Browse the repository at this point in the history
  • Loading branch information
dev-marek committed Nov 7, 2024
1 parent a5ecba2 commit 47e1918
Show file tree
Hide file tree
Showing 8 changed files with 404 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import io.gravitee.policy.api.PolicyConfiguration;
import io.gravitee.policy.jwt.alg.Signature;
import io.gravitee.policy.v3.jwt.resolver.KeyResolver;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
Expand Down Expand Up @@ -46,6 +47,7 @@ public class JWTPolicyConfiguration implements PolicyConfiguration {
private Integer connectTimeout = 2000;
private Long requestTimeout = 2000L;
private ConfirmationMethodValidation confirmationMethodValidation = new ConfirmationMethodValidation();
private TokenTypValidation tokenTypValidation = new TokenTypValidation();

@NoArgsConstructor
@AllArgsConstructor
Expand All @@ -67,4 +69,16 @@ public static class CertificateBoundThumbprint {
private boolean extractCertificateFromHeader = false;
private String headerName = "ssl-client-cert";
}

@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
public static class TokenTypValidation {

private boolean enabled = false;
private boolean ignoreMissing = false;
private List<String> expectedValues = List.of("JWT");
private boolean ignoreCase = false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import io.gravitee.policy.jwt.jwk.selector.IssuerAwareJWSKeySelector;
import io.gravitee.policy.jwt.jwk.selector.NoKidJWSVerificationKeySelector;
import io.gravitee.policy.jwt.utils.JWKBuilder;
import io.gravitee.policy.jwt.utils.TokenTypeVerifierFactory;
import io.reactivex.rxjava3.core.Maybe;
import java.security.KeyException;
import java.util.AbstractMap.SimpleEntry;
Expand Down Expand Up @@ -81,6 +82,8 @@ private JWTProcessor<SecurityContext> buildJWTProcessor(HttpExecutionContext ctx
DefaultJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
jwtProcessor.setJWTClaimsSetAwareJWSKeySelector(new IssuerAwareJWSKeySelector(DEFAULT_KID, selectors));

jwtProcessor.setJWSTypeVerifier(TokenTypeVerifierFactory.build(configuration.getTokenTypValidation()));

return jwtProcessor;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import io.gravitee.policy.jwt.configuration.JWTPolicyConfiguration;
import io.gravitee.policy.jwt.jwk.selector.NoKidJWSVerificationKeySelector;
import io.gravitee.policy.jwt.utils.JWKBuilder;
import io.gravitee.policy.jwt.utils.TokenTypeVerifierFactory;
import io.reactivex.rxjava3.core.Maybe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -71,6 +72,8 @@ private JWTProcessor<SecurityContext> buildJWTProcessor(String keyValue) {
log.warn("Error occurred when loading key. Key will be ignored.", throwable);
}

jwtProcessor.setJWSTypeVerifier(TokenTypeVerifierFactory.build(configuration.getTokenTypValidation()));

return jwtProcessor;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import io.gravitee.policy.jwt.jwk.source.JWKSUrlJWKSourceResolver;
import io.gravitee.policy.jwt.jwk.source.ResourceRetriever;
import io.gravitee.policy.jwt.jwk.source.VertxResourceRetriever;
import io.gravitee.policy.jwt.utils.TokenTypeVerifierFactory;
import io.gravitee.policy.v3.jwt.jwks.retriever.RetrieveOptions;
import io.reactivex.rxjava3.core.Maybe;
import io.vertx.rxjava3.core.Vertx;
Expand Down Expand Up @@ -81,6 +82,8 @@ private Maybe<JWTProcessor<SecurityContext>> buildJWTProcessor(HttpExecutionCont
final DefaultJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
jwtProcessor.setJWSKeySelector(selector);

jwtProcessor.setJWSTypeVerifier(TokenTypeVerifierFactory.build(configuration.getTokenTypValidation()));

// Initialize the Json Web Keystore before returning the jwt processor.
return sourceResolver.initialize().andThen(Maybe.just(jwtProcessor));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright © 2015 The Gravitee team (http://gravitee.io)
*
* 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 io.gravitee.policy.jwt.utils;

import com.nimbusds.jose.JOSEObjectType;
import com.nimbusds.jose.proc.BadJOSEException;
import com.nimbusds.jose.proc.DefaultJOSEObjectTypeVerifier;
import com.nimbusds.jose.proc.JOSEObjectTypeVerifier;
import com.nimbusds.jose.proc.SecurityContext;
import io.gravitee.policy.jwt.configuration.JWTPolicyConfiguration;
import lombok.experimental.UtilityClass;

@UtilityClass
public class TokenTypeVerifierFactory {

public JOSEObjectTypeVerifier<SecurityContext> buildCustom(JWTPolicyConfiguration.TokenTypValidation tokenTypValidation) {
return (header, context) -> {
String typ = header.getType() != null ? header.getType() : null;
if (typ == null) {
if (tokenTypValidation.isIgnoreMissing()) {
return;
} else {
throw new BadJOSEException("Missing typ header");
}
}
tokenTypValidation
.getExpectedValues()
.stream()
.filter(expected -> tokenTypValidation.isIgnoreCase() ? expected.equalsIgnoreCase(typ) : expected.equals(typ))
.findAny()
.orElseThrow(() -> new BadJOSEException("Unexpected typ header"));
};
}

public DefaultJOSEObjectTypeVerifier<SecurityContext> buildDefault() {
return new DefaultJOSEObjectTypeVerifier<>(
JOSEObjectType.JWT,
new JOSEObjectType("at+jwt"),
new JOSEObjectType("application/at+jwt"),
null
);
}

public JOSEObjectTypeVerifier<SecurityContext> build(JWTPolicyConfiguration.TokenTypValidation tokenTypValidation) {
if (tokenTypValidation == null || !tokenTypValidation.isEnabled()) {
return buildDefault();
} else {
return buildCustom(tokenTypValidation);
}
}
}
35 changes: 35 additions & 0 deletions src/main/resources/schemas/schema-form.json
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,41 @@
}
},
"additionalProperties": false
},
"tokenTypValidation": {
"title": "Token Type Validation",
"description": "Define the token type to validate",
"type": "object",
"properties": {
"enabled": {
"title": "Enable token type validation",
"description": "Will validate the token type extracted from the access_token with the one provided by the client. The default is false.",
"type": "boolean",
"default": false
},
"ignoreMissing": {
"title": "Ignore missing token type",
"description": "Will ignore token type validation if the token doesn't contain any token type information. Default is false.",
"type": "boolean",
"default": false
},
"expectedValues": {
"title": "Expected values",
"description": "List of expected token types. If the token type is not in the list, the validation will fail.",
"type": "array",
"items": {
"type": "string"
},
"default": ["JWT"]
},
"ignoreCase": {
"title": "Ignore case",
"description": "Will ignore the case of the token type when comparing the expected values. Default is false.",
"type": "boolean",
"default": false
}
},
"additionalProperties": false
}
},
"required": ["signature", "publicKeyResolver"],
Expand Down
Loading

0 comments on commit 47e1918

Please sign in to comment.