From ed58fb0e5295f7606954c180906e17911b64709e Mon Sep 17 00:00:00 2001 From: "Teddy J. Torres" Date: Thu, 31 Mar 2022 15:35:49 -0400 Subject: [PATCH] Create mp.jwt.verify.token.age config property for issue #195 Signed-off-by: Teddy J. Torres --- spec/src/main/asciidoc/configuration.asciidoc | 13 ++++- .../tck/container/jaxrs/InvalidTokenTest.java | 53 +++++++------------ ...ofile-config-publickey-location.properties | 3 +- 3 files changed, 33 insertions(+), 36 deletions(-) diff --git a/spec/src/main/asciidoc/configuration.asciidoc b/spec/src/main/asciidoc/configuration.asciidoc index b46cc824..bced0d6d 100644 --- a/spec/src/main/asciidoc/configuration.asciidoc +++ b/spec/src/main/asciidoc/configuration.asciidoc @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2020 Eclipse Microprofile Contributors: +// Copyright (c) 2016-2022 Eclipse Microprofile Contributors: // Red Hat, IBM, Tomitribe // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -412,7 +412,7 @@ Please see <> for al [[claims-verification]] ## Verification of JWT token claims -MP JWT specification currently supports the verification of the token `iss` issuer and `aud` audience claims which is done after the token signature has been verified or the token has been decrypted. +MP JWT specification currently supports the verification of the token `iss` issuer, `aud` audience, and `iat` issued at claims which is done after the token signature has been verified or the token has been decrypted. [[verify-issuer]] ### `mp.jwt.verify.issuer` @@ -426,6 +426,15 @@ Note that since this property verifies the `iss` claim value, it will be effecti The `mp.jwt.verify.audiences` config property is a comma delimited list of allowable values for the `aud` claim. If specified, a MicroProfile JWT implementation must verify the `aud` claim of incoming JWTs is present and at least one value in the claim matches one of the configured values of `mp.jwt.verify.audiences`. +[[verify-token-age]] +### `mp.jwt.verify.token.age` + +The `mp.jwt.verify.token.age` config property allows for the number of seconds since `iat` to be specified. A MicroProfile JWT implementation must verify the `iat` claim of incoming JWTs is present and the configured value of `mp.jwt.verify.token.age` since `iat` has not elapsed. Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew. + +[[verify-clock-skew]] +### `mp.jwt.verify.clock.skew` +The `mp.jwt.verify.clock.skew` config property allows for the clock skew in seconds used during the token expiry and age verification to be specified. Default value is 60 seconds. + ## Requirements for accepting signed and encrypted tokens MP JWT specification currently requires that an MP JWT application accepts only signed or only encrypted or only signed and encrypted tokens as it expected that many endpoints will have the requirements to accept a single token type only. diff --git a/tck/src/test/java/org/eclipse/microprofile/jwt/tck/container/jaxrs/InvalidTokenTest.java b/tck/src/test/java/org/eclipse/microprofile/jwt/tck/container/jaxrs/InvalidTokenTest.java index 6c23e216..f8a034b0 100644 --- a/tck/src/test/java/org/eclipse/microprofile/jwt/tck/container/jaxrs/InvalidTokenTest.java +++ b/tck/src/test/java/org/eclipse/microprofile/jwt/tck/container/jaxrs/InvalidTokenTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2020 Contributors to the Eclipse Foundation + * Copyright (c) 2016-2022 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -84,17 +84,17 @@ public void callEchoExpiredToken() throws Exception { HashSet invalidFields = new HashSet<>(); invalidFields.add(TokenUtils.InvalidClaims.EXP); String token = TokenUtils.generateTokenString("/Token1.json", invalidFields); - System.out.printf("jwt: %s\n", token); - String uri = baseURL.toExternalForm() + "endp/echo"; - WebTarget echoEndpointTarget = ClientBuilder.newClient() - .target(uri) - .queryParam("input", "hello"); - Response response = - echoEndpointTarget.request(TEXT_PLAIN).header(HttpHeaders.AUTHORIZATION, "Bearer " + token).get(); - Assert.assertEquals(response.getStatus(), HttpURLConnection.HTTP_UNAUTHORIZED); - String reply = response.readEntity(String.class); - System.out.printf("Reply: %s\n", reply); + callEchoAndExpectUnauthorized(token); + } + + @RunAsClient + @Test(groups = TEST_GROUP_JAXRS, description = "Validate a request with aged token fails with HTTP_UNAUTHORIZED") + public void callEchoAgedToken() throws Exception { + String token = TokenUtils.generateTokenString("/Token1.json"); + Thread.sleep(5000); + + callEchoAndExpectUnauthorized(token); } @RunAsClient @@ -103,17 +103,8 @@ public void callEchoBadIssuer() throws Exception { HashSet invalidFields = new HashSet<>(); invalidFields.add(TokenUtils.InvalidClaims.ISSUER); String token = TokenUtils.generateTokenString("/Token1.json", invalidFields); - System.out.printf("jwt: %s\n", token); - String uri = baseURL.toExternalForm() + "endp/echo"; - WebTarget echoEndpointTarget = ClientBuilder.newClient() - .target(uri) - .queryParam("input", "hello"); - Response response = - echoEndpointTarget.request(TEXT_PLAIN).header(HttpHeaders.AUTHORIZATION, "Bearer " + token).get(); - Assert.assertEquals(response.getStatus(), HttpURLConnection.HTTP_UNAUTHORIZED); - String reply = response.readEntity(String.class); - System.out.printf("Reply: %s\n", reply); + callEchoAndExpectUnauthorized(token); } @RunAsClient @@ -122,17 +113,8 @@ public void callEchoBadSigner() throws Exception { HashSet invalidFields = new HashSet<>(); invalidFields.add(TokenUtils.InvalidClaims.SIGNER); String token = TokenUtils.generateTokenString("/Token1.json", invalidFields); - System.out.printf("jwt: %s\n", token); - String uri = baseURL.toExternalForm() + "endp/echo"; - WebTarget echoEndpointTarget = ClientBuilder.newClient() - .target(uri) - .queryParam("input", "hello"); - Response response = - echoEndpointTarget.request(TEXT_PLAIN).header(HttpHeaders.AUTHORIZATION, "Bearer " + token).get(); - Assert.assertEquals(response.getStatus(), HttpURLConnection.HTTP_UNAUTHORIZED); - String reply = response.readEntity(String.class); - System.out.printf("Reply: %s\n", reply); + callEchoAndExpectUnauthorized(token); } @RunAsClient @@ -141,14 +123,19 @@ public void callEchoBadSignerAlg() throws Exception { HashSet invalidFields = new HashSet<>(); invalidFields.add(TokenUtils.InvalidClaims.ALG); String token = TokenUtils.generateTokenString("/Token1.json", invalidFields); + + callEchoAndExpectUnauthorized(token); + } + + private void callEchoAndExpectUnauthorized(String token) throws Exception { System.out.printf("jwt: %s\n", token); String uri = baseURL.toExternalForm() + "endp/echo"; WebTarget echoEndpointTarget = ClientBuilder.newClient() .target(uri) .queryParam("input", "hello"); - Response response = - echoEndpointTarget.request(TEXT_PLAIN).header(HttpHeaders.AUTHORIZATION, "Bearer " + token).get(); + Response response = echoEndpointTarget.request(TEXT_PLAIN) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + token).get(); Assert.assertEquals(response.getStatus(), HttpURLConnection.HTTP_UNAUTHORIZED); String reply = response.readEntity(String.class); System.out.printf("Reply: %s\n", reply); diff --git a/tck/src/test/resources/META-INF/microprofile-config-publickey-location.properties b/tck/src/test/resources/META-INF/microprofile-config-publickey-location.properties index 9bcf1272..b46e586b 100644 --- a/tck/src/test/resources/META-INF/microprofile-config-publickey-location.properties +++ b/tck/src/test/resources/META-INF/microprofile-config-publickey-location.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2011-2017 Contributors to the Eclipse Foundation +# Copyright (c) 2011-2022 Contributors to the Eclipse Foundation # # See the NOTICE file(s) distributed with this work for additional # information regarding copyright ownership. @@ -20,3 +20,4 @@ # A reference to the publicKey4k.pem contents embedded location mp.jwt.verify.publickey.location=/publicKey.pem mp.jwt.verify.issuer=https://server.example.com +mp.jwt.verify.token.age=4