From 3ec689af363e312d04f059e06c61c76cbade4492 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 6 Sep 2023 10:25:24 +0800 Subject: [PATCH] Implement access token validator --- docs/docs/development.md | 2 - docs/docs/img/oauth2-filtering.drawio | 54 +++++ docs/docs/img/oauth2-filtering.png | Bin 0 -> 142646 bytes docs/docs/security.md | 219 ++++++++++++++++++ pom.xml | 10 +- .../template/application/BinderFactory.java | 9 +- .../template/application/ResourceConfig.java | 4 +- .../template/web/filters/OAuthFilter.java | 116 ++++++++++ .../filters/oauth/AccessTokenValidator.java | 38 +++ .../jersey/template/DataServletITSpec.groovy | 8 + .../template/JettyServerFactorySpec.groovy | 10 +- .../web/endpoints/DataServletSpec.groovy | 7 + .../web/filters/OAuthFilterSpec.groovy | 85 +++++++ 13 files changed, 547 insertions(+), 15 deletions(-) create mode 100644 docs/docs/img/oauth2-filtering.drawio create mode 100644 docs/docs/img/oauth2-filtering.png create mode 100644 docs/docs/security.md create mode 100644 src/main/java/com/qubitpi/ws/jersey/template/web/filters/OAuthFilter.java create mode 100644 src/main/java/com/qubitpi/ws/jersey/template/web/filters/oauth/AccessTokenValidator.java create mode 100644 src/test/groovy/com/qubitpi/ws/jersey/template/web/filters/OAuthFilterSpec.groovy diff --git a/docs/docs/development.md b/docs/docs/development.md index d3e3691a..a36344a1 100644 --- a/docs/docs/development.md +++ b/docs/docs/development.md @@ -79,8 +79,6 @@ java -jar $JETTY_HOME/start.jar The webservice will run on port **8080**, and you will see the data you inserted -[Docker Compose]: https://docs.docker.com/compose/ - [jcabi-mysql]: https://mysql.jcabi.com/ [Testcontainers]: https://qubitpi.github.io/testcontainers-java/ diff --git a/docs/docs/img/oauth2-filtering.drawio b/docs/docs/img/oauth2-filtering.drawio new file mode 100644 index 00000000..4655cdc2 --- /dev/null +++ b/docs/docs/img/oauth2-filtering.drawio @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/docs/img/oauth2-filtering.png b/docs/docs/img/oauth2-filtering.png new file mode 100644 index 0000000000000000000000000000000000000000..1d2eadf42465eb95d058611c916029c1ebc1beb5 GIT binary patch literal 142646 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%0AXIZ> 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
+     * {@code
+     * {
+     *     "keys": [
+     *         {
+     *             "kty": "EC",
+     *             "use": "sig",
+     *             "kid": "eTERknhur9q8gisdaf_dfrqrgdfsg",
+     *             "alg": "ES384",
+     *             "crv": "P-384",
+     *             "x": "sdfrgHGYF...",
+     *             "y": "sdfuUIG&8..."
+     *         }
+     *     ]
+     * }
+     * }
+     * 
+ * + * @throws NullPointerException if {@code jwksUrl} is {@code null} + */ + public ES384JwtTokenValidator(final String jwksUrl) { + this.jwksUrl = Objects.requireNonNull(jwksUrl); + } + + @Override + public boolean validate(@NotNull final String accessToken) { + final JwkProvider jwkProvider = getJwkProvider(getJwksUrl()); + final Jwk jwk = getJwk(jwkProvider, accessToken); + final Algorithm algorithm = getVerificationAlgorithm(jwk); + final Verification verifier = JWT.require(algorithm); + + verifier.build().verify(accessToken); + + return true; + } + + /** + * Returns a one-time built instance of {@link JwkProvider} to save performance. + * + * @param jwksUrl The JWKS URL that, on GET, returns a json object such as + *
+     * {@code
+     * {
+     *     "keys": [
+     *         {
+     *             "kty": "EC",
+     *             "use": "sig",
+     *             "kid": "eTERknhur9q8gisdaf_dfrqrgdfsg",
+     *             "alg": "ES384",
+     *             "crv": "P-384",
+     *             "x": "sdfrgHGYF...",
+     *             "y": "sdfuUIG&8..."
+     *         }
+     *     ]
+     * }
+     * }
+     * 
+ * + * @return a new instance + * + * @throws IllegalStateException if the {@code jwksUrl} is an invalid URL + */ + @NotNull + private static JwkProvider getJwkProvider(@NotNull final String jwksUrl) { + try { + return new JwkProviderBuilder(new URL(jwksUrl)).build(); + } catch (final MalformedURLException exception) { + final String message = String.format("Invalid JWKS URL: '%s'", jwksUrl); + LOG.error(message, exception); + throw new IllegalStateException(message, exception); + } + } + + /** + * Returns a JWK key set that will be used to verify a given JWT access token. + * + * @param jwkProvider An object that contains the JWK; cannot be {@code null} + * @param accessToken A "key" that indexes the JWK from the {@code jwkProvider}; cannot be {@code null} + * + * @return a JWK key set + * + * @throws IllegalStateException if no JWK set associated with the provided token is found from {@code jwkProvider} + */ + private static Jwk getJwk(@NotNull final JwkProvider jwkProvider, @NotNull final String accessToken) { + try { + return jwkProvider.get(JWT.decode(accessToken).getKeyId()); + } catch (final JwkException exception) { + final String message = "The key ID in the access token does not match any JWK"; + LOG.error(message, exception); + throw new IllegalStateException(message, exception); + } + } + + /** + * Returns the verifying algorithm associated with a specified JWK. + * + * @param jwk The JWK contains the public key for decrypting the token signature, cannot be {@code null} + * + * @return a decrypting algorithm with public key and without private key enclosed + * + * @throws IllegalStateException if public key cannot be retrieved from the JWK + */ + private static Algorithm getVerificationAlgorithm(@NotNull final Jwk jwk) { + try { + return Algorithm.ECDSA384((ECPublicKey) jwk.getPublicKey(), null); + } catch (final InvalidPublicKeyException exception) { + final String message = "The public key cannot be build from JWK"; + LOG.error(message, exception); + throw new IllegalStateException(message, exception); + } + } + + @NotNull + private String getJwksUrl() { + return jwksUrl; + } +} +``` + +[BinderFactory]: https://github.com/QubitPi/jersey-ws-template/blob/master/src/main/java/com/qubitpi/ws/jersey/template/application/BinderFactory.java + +[OAuth 2 access token]: https://www.oauth.com/oauth2-servers/access-tokens/ diff --git a/pom.xml b/pom.xml index b6aed46f..39396b93 100644 --- a/pom.xml +++ b/pom.xml @@ -41,7 +41,7 @@ 1.7.25 1.2.3 2.13.3 - 1.1.2 + 1.0.4 6.0.0 3.1.1 4.0.6 @@ -509,12 +509,4 @@ - - - - paion - Paion Data Official Release Repository - https://nexus.paion-data.dev/repository/maven-oss - - diff --git a/src/main/java/com/qubitpi/ws/jersey/template/application/BinderFactory.java b/src/main/java/com/qubitpi/ws/jersey/template/application/BinderFactory.java index d80c1121..f2f9fa9d 100644 --- a/src/main/java/com/qubitpi/ws/jersey/template/application/BinderFactory.java +++ b/src/main/java/com/qubitpi/ws/jersey/template/application/BinderFactory.java @@ -15,6 +15,8 @@ */ package com.qubitpi.ws.jersey.template.application; +import com.qubitpi.ws.jersey.template.web.filters.oauth.AccessTokenValidator; + import org.glassfish.hk2.utilities.Binder; import org.glassfish.hk2.utilities.binding.AbstractBinder; @@ -45,7 +47,12 @@ public Binder buildBinder() { return new AbstractBinder() { @Override protected void configure() { - // intentionally left blank + bind(new AccessTokenValidator() { + @Override + public boolean validate(final String accessToken) { + return true; + } + }).to(AccessTokenValidator.class); } }; } diff --git a/src/main/java/com/qubitpi/ws/jersey/template/application/ResourceConfig.java b/src/main/java/com/qubitpi/ws/jersey/template/application/ResourceConfig.java index d6f2ee43..5889ac4f 100644 --- a/src/main/java/com/qubitpi/ws/jersey/template/application/ResourceConfig.java +++ b/src/main/java/com/qubitpi/ws/jersey/template/application/ResourceConfig.java @@ -16,9 +16,9 @@ package com.qubitpi.ws.jersey.template.application; import com.qubitpi.ws.jersey.template.web.filters.CorsFilter; +import com.qubitpi.ws.jersey.template.web.filters.OAuthFilter; import org.glassfish.hk2.utilities.Binder; -import org.glassfish.jersey.media.multipart.MultiPartFeature; import jakarta.inject.Inject; import jakarta.ws.rs.ApplicationPath; @@ -45,6 +45,6 @@ public ResourceConfig() { packages(ENDPOINT_PACKAGE); register(new CorsFilter()); register(binder); - register(MultiPartFeature.class); + register(OAuthFilter.class); } } diff --git a/src/main/java/com/qubitpi/ws/jersey/template/web/filters/OAuthFilter.java b/src/main/java/com/qubitpi/ws/jersey/template/web/filters/OAuthFilter.java new file mode 100644 index 00000000..8567a8e2 --- /dev/null +++ b/src/main/java/com/qubitpi/ws/jersey/template/web/filters/OAuthFilter.java @@ -0,0 +1,116 @@ +/* + * Copyright Jiaqi Liu + * + * 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 com.qubitpi.ws.jersey.template.web.filters; + +import com.qubitpi.ws.jersey.template.web.filters.oauth.AccessTokenValidator; + +import jakarta.annotation.Priority; +import jakarta.inject.Inject; +import jakarta.validation.constraints.NotNull; +import jakarta.ws.rs.Priorities; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerRequestFilter; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.Provider; +import net.jcip.annotations.Immutable; +import net.jcip.annotations.ThreadSafe; + +import java.io.IOException; +import java.util.Objects; + +@Provider +@Immutable +@ThreadSafe +@Priority(Priorities.AUTHENTICATION) +public class OAuthFilter implements ContainerRequestFilter { + + /** + * The header key for OAuth 2 access token. + */ + public static final String AUTHORIZATION_HEADER = "Authorization"; + + /** + * The token scheme. + */ + public static final String AUTHORIZATION_SCHEME = "Bearer"; + + private final AccessTokenValidator accessTokenValidator; + + /** + * DI constructor. + * + * @param accessTokenValidator An abstraction layer responsible for validating an OAuth2 access token + */ + @Inject + public OAuthFilter(final AccessTokenValidator accessTokenValidator) { + this.accessTokenValidator = Objects.requireNonNull(accessTokenValidator); + } + + @Override + public void filter(final ContainerRequestContext containerRequestContext) throws IOException { + if (!containerRequestContext.getHeaders().containsKey(AUTHORIZATION_HEADER)) { + containerRequestContext.abortWith( + Response.status(Response.Status.UNAUTHORIZED).entity("Authorization header is missing").build() + ); + return; + } + + final String accessToken = getAccessToken(containerRequestContext); + + if (!isValidToken(accessToken)) { + containerRequestContext.abortWith( + Response.status(Response.Status.UNAUTHORIZED).entity("Invalid access token").build() + ); + } + } + + /** + * Retrieves the access token from container request context. + * + * For example, when an HTTP request comes with header "Authorization": "Bearer 43rgfgef43ewfg4gergeg43g34g", this + * method returns "43rgfgef43ewfg4gergeg43g34g". + * + * @param containerRequestContext The request context that is ASSUMED to contain the "Authorization" header + * + * @return the bare access token + */ + @NotNull + private static String getAccessToken(@NotNull final ContainerRequestContext containerRequestContext) { + return containerRequestContext + .getHeaders() + .get(AUTHORIZATION_HEADER) + .get(0) + .replaceFirst(AUTHORIZATION_SCHEME + " ", ""); + } + + /** + * Returns whether or not a specified access token is valid. + * + * @param accessToken The token to validate + * + * @return {@code true} if the token is valid or {@code false} otherwise + * + * @throws NullPointerException if {@code accessToken} is {@code null} + */ + private boolean isValidToken(@NotNull final String accessToken) { + return getTokenValidator().validate(accessToken); + } + + @NotNull + private AccessTokenValidator getTokenValidator() { + return accessTokenValidator; + } +} diff --git a/src/main/java/com/qubitpi/ws/jersey/template/web/filters/oauth/AccessTokenValidator.java b/src/main/java/com/qubitpi/ws/jersey/template/web/filters/oauth/AccessTokenValidator.java new file mode 100644 index 00000000..5c7d208d --- /dev/null +++ b/src/main/java/com/qubitpi/ws/jersey/template/web/filters/oauth/AccessTokenValidator.java @@ -0,0 +1,38 @@ +/* + * Copyright Jiaqi Liu + * + * 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 com.qubitpi.ws.jersey.template.web.filters.oauth; + +import jakarta.validation.constraints.NotNull; + +/** + * {@link AccessTokenValidator} validates an OAuth 2 access token. + * + * This is a {@link java.util.function functional interface} whose functional method is {@link #validate(String)}. + */ +@FunctionalInterface +public interface AccessTokenValidator { + + /** + * Returns whether or not a specified access token is valid. + * + * @param accessToken The token to validate + * + * @return {@code true} if the token is valid or {@code false} otherwise + * + * @throws NullPointerException if {@code accessToken} is {@code null} + */ + boolean validate(@NotNull String accessToken); +} diff --git a/src/test/groovy/com/qubitpi/ws/jersey/template/DataServletITSpec.groovy b/src/test/groovy/com/qubitpi/ws/jersey/template/DataServletITSpec.groovy index d4fd9305..38ea574d 100644 --- a/src/test/groovy/com/qubitpi/ws/jersey/template/DataServletITSpec.groovy +++ b/src/test/groovy/com/qubitpi/ws/jersey/template/DataServletITSpec.groovy @@ -15,12 +15,15 @@ */ package com.qubitpi.ws.jersey.template +import com.qubitpi.ws.jersey.template.web.filters.OAuthFilter + import org.testcontainers.containers.GenericContainer import org.testcontainers.images.PullPolicy import org.testcontainers.images.builder.ImageFromDockerfile import org.testcontainers.spock.Testcontainers import io.restassured.RestAssured +import io.restassured.builder.RequestSpecBuilder import spock.lang.IgnoreIf import spock.lang.Shared import spock.lang.Specification @@ -72,6 +75,11 @@ class DataServletITSpec extends Specification { RestAssured.baseURI = "http://" + container.host RestAssured.port = container.firstMappedPort RestAssured.basePath = "/v1" + RestAssured.requestSpecification = new RequestSpecBuilder() + .addHeader( + OAuthFilter.AUTHORIZATION_HEADER, + OAuthFilter.AUTHORIZATION_SCHEME + " " + "someAccessToken") + .build() } @Unroll diff --git a/src/test/groovy/com/qubitpi/ws/jersey/template/JettyServerFactorySpec.groovy b/src/test/groovy/com/qubitpi/ws/jersey/template/JettyServerFactorySpec.groovy index 32bb551b..fc7d2664 100755 --- a/src/test/groovy/com/qubitpi/ws/jersey/template/JettyServerFactorySpec.groovy +++ b/src/test/groovy/com/qubitpi/ws/jersey/template/JettyServerFactorySpec.groovy @@ -15,17 +15,20 @@ */ package com.qubitpi.ws.jersey.template +import com.qubitpi.ws.jersey.template.web.filters.OAuthFilter + import org.eclipse.jetty.server.Server import org.glassfish.jersey.server.ResourceConfig import io.restassured.RestAssured +import io.restassured.builder.RequestSpecBuilder import jakarta.inject.Inject import jakarta.ws.rs.ApplicationPath import spock.lang.Specification class JettyServerFactorySpec extends Specification { - static final int PORT = 8235 + static final int PORT = 8080 static final String ENDPOINT_RESOURCE_PACKAGE = "com.qubitpi.ws.jersey.template.resource" /** @@ -47,6 +50,11 @@ class JettyServerFactorySpec extends Specification { RestAssured.baseURI = "http://localhost" RestAssured.port = PORT RestAssured.basePath = "/v1" + RestAssured.requestSpecification = new RequestSpecBuilder() + .addHeader( + OAuthFilter.AUTHORIZATION_HEADER, + OAuthFilter.AUTHORIZATION_SCHEME + " " + "someAccessToken") + .build() } def "Factory produces Jsersey-Jetty applications"() { diff --git a/src/test/groovy/com/qubitpi/ws/jersey/template/web/endpoints/DataServletSpec.groovy b/src/test/groovy/com/qubitpi/ws/jersey/template/web/endpoints/DataServletSpec.groovy index 482aee16..2e0f21f5 100644 --- a/src/test/groovy/com/qubitpi/ws/jersey/template/web/endpoints/DataServletSpec.groovy +++ b/src/test/groovy/com/qubitpi/ws/jersey/template/web/endpoints/DataServletSpec.groovy @@ -17,10 +17,12 @@ package com.qubitpi.ws.jersey.template.web.endpoints import com.qubitpi.ws.jersey.template.JettyServerFactory import com.qubitpi.ws.jersey.template.application.ResourceConfig +import com.qubitpi.ws.jersey.template.web.filters.OAuthFilter import org.eclipse.jetty.server.Server import io.restassured.RestAssured +import io.restassured.builder.RequestSpecBuilder import spock.lang.Specification class DataServletSpec extends Specification { @@ -31,6 +33,11 @@ class DataServletSpec extends Specification { RestAssured.baseURI = "http://localhost" RestAssured.port = PORT RestAssured.basePath = "/v1" + RestAssured.requestSpecification = new RequestSpecBuilder() + .addHeader( + OAuthFilter.AUTHORIZATION_HEADER, + OAuthFilter.AUTHORIZATION_SCHEME + " " + "someAccessToken") + .build() } def "Healthchecking endpoints returns 200"() { diff --git a/src/test/groovy/com/qubitpi/ws/jersey/template/web/filters/OAuthFilterSpec.groovy b/src/test/groovy/com/qubitpi/ws/jersey/template/web/filters/OAuthFilterSpec.groovy new file mode 100644 index 00000000..2ab1d686 --- /dev/null +++ b/src/test/groovy/com/qubitpi/ws/jersey/template/web/filters/OAuthFilterSpec.groovy @@ -0,0 +1,85 @@ +/* + * Copyright Jiaqi Liu + * + * 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 com.qubitpi.ws.jersey.template.web.filters + +import com.qubitpi.ws.jersey.template.web.filters.oauth.AccessTokenValidator + +import jakarta.ws.rs.container.ContainerRequestContext +import jakarta.ws.rs.core.MultivaluedHashMap +import jakarta.ws.rs.core.MultivaluedMap +import jakarta.ws.rs.core.Response +import spock.lang.Specification +import spock.lang.Unroll + +class OAuthFilterSpec extends Specification { + + def "When request is missing 'Authorization' header, request is aborted"() { + given: "incoming request is missing auth header" + ContainerRequestContext requestContext = Mock(ContainerRequestContext) + requestContext.getHeaders() >> new MultivaluedHashMap() + + when: "filter is trying to validate some token" + new OAuthFilter(Mock(AccessTokenValidator)).filter(requestContext) + + then: "non-existing token header aborts the request" + 1 * requestContext.abortWith(_ as Response) + } + + @Unroll + def "When request comes with #tokenKind access token, request is #abort"() { + given: "a mocked token validator that declares both a valid and invalid token in 2 sequential calls" + AccessTokenValidator validator = Mock(AccessTokenValidator) + validator.validate(_ as String) >> validToken + + and: "request always comes with a access token in header" + MultivaluedMap headers = new MultivaluedHashMap<>() + headers.put(OAuthFilter.AUTHORIZATION_HEADER, [OAuthFilter.AUTHORIZATION_SCHEME + " " + "some_token"]) + + and: "the header is in request context" + ContainerRequestContext requestContext = Mock(ContainerRequestContext) + requestContext.getHeaders() >> headers + + when: "filter validates token with the mocked validator" + new OAuthFilter(validator).filter(requestContext) + + then: "invalid token aborts the request while valid token does not" + (validToken? 0 : 1) * requestContext.abortWith(_ as Response) + + where: + tokenKind | validToken + "valid" | true + "invalid" | false + + abort = validToken ? "not aborted" : "aborted" + } + + @SuppressWarnings('GroovyAccessibility') + def "Filter can extract access token from request header"() { + given: "a header set with a access token in it" + MultivaluedMap headers = new MultivaluedHashMap<>() + headers.put( + OAuthFilter.AUTHORIZATION_HEADER, + [OAuthFilter.AUTHORIZATION_SCHEME + " " + "43rgfgef43ewfg4gergeg43g34g"] + ) + + and: "the header is in request context" + ContainerRequestContext requestContext = Mock(ContainerRequestContext) + requestContext.getHeaders() >> headers + + expect: "filter retrieves the token" + OAuthFilter.getAccessToken(requestContext) == "43rgfgef43ewfg4gergeg43g34g" + } +}

Umc&LWxiSjXU9M6ft}E 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�+C1rqCQL@gD(W6MK; z@}=cIiN;Yi?@~yHqWuPR@uU`-z)m1PZpIulPW2>BEL8>5oTYVVHhqCNJ-PHVDF*y| z8z5h}z2kccNek@8T-mJt8k!W`O@C3djw{f-r84?nB#^zg9xI`Fk9U;koE_`|CeZ&k z`++={7c5a7F=3E=fu>QpB1^y-Oz3k|A@sR2)IU-)rmT1p|I;x;_x2|qP=XtR8}G8~ zoUR(;mC!1C*9>N4muI%3K637ZjXhqQND5#pLUU}xup^v?-3wlTtZv6gaNg{~p-GS{I9##bKU_(C z_^Qd9^Ldev6&N^*Y!w&!b>NiojxvO<Xl^!W9iYSJ;-Vlrn8cmsG86AO+qq?HMTVUFOw zL2q=#w&IYH!eQfOeh5CygvEU##;W@nZxyxJ${v(DwDq%9IpinROIh`^?wILXDzL|O z$PMV%mbobv-Myi}L~fNFJy@bkd%sb*gg^xmI>JLK+LR^>NEQdGg4L@J5pa2h; zKraA2yj+j*Eim*$1Q8;Q86d%DoC&a7eF-}dr{y{;dA0_IA@?IL9{2be6zG|y-CMq= z-q30X@PK!WII(aLmM@iP!2_|f7~4MH`sAO;Tre;r-V|Qhkc8gQf$xNS_%?V22d$r_% zUqczjcK|9yFpQw4`e<1g#^_nVuchuQZ-@Y*{%-aO;0d|or-6KZeXy^?4qUCN&ST=Z zu_Is;`P_ZbgU^8Q!z%u#9Mu5qxPjiiC%~{JFp0l5eJbmuXX{D!2!tV%x(Dgfen!HHO9>F7fyf-3P8rf zw6Pr^<0T*?usm7@hGB#U$kH{xso;CQXow7-Fe*Ag1_K}?j9n9O0}XMbPUh*AhY%Th>i7JAlR*RnWMJH8 zd;*>>O#y=I_Cetmc=yK_c2Ec7Adx(m3@Jx!H0~cVuq1p>*%4_4^U(YHQbJ^8tC3=0 zJ%ZPxxN&kDsT!C88Slu=#Qu=sM>Gbz7-VJw-Z~%i17w`Zj+O$?NCTvv0Hp5Zk*1%E14xyBjT$(SK|lFGa>l?UT0oKk5WWzI<{LciHz5#~f(hsTfVc^PFw3A~0U#&<2u=l203rl{ zNXsLeJQoE(#A`)W`~^Y=fVdMI;s-#O0T69z(ULH~dI&_j8qXIZ2*iD!gtLD@*g+u7 z^Ql+?2yy^oL7oqQcm_a><&iVshXW99Un3L#0wD)L_)>&|L0w->03tVu41jFF}klWj{4J?*zeb_cDk>b3vuH2|uio#OuUy}zH3dx5F5 zGuWHEh=Ij{t7OQp!&3Y3Rl&%XrB_vFSf&iIykeO=H2XbLLXHU|i1guifrZ+w_f1(=VtqWK&$@afQwtoX^q&Fw?A7u;GP16j z!^e>Ih53(S{H7g-=?t_(|BO>5cmh`y%kXGI6aT!x)M#lkeCoBg_FT2tY{h9hW(+y? zcNSi&_B{sz`GjKp4@ZR7IhRqZcllVs1A~T;c~*-X#3={ndA04QLpuJEMu2(IcfnUB z4rpFN{Ts?ai}qPl{@gl)p`~?K|4lLN9Jl$=(HK(S>+hRtikA&&AGYgUGcT@`brsj` zytw<+-0|fVfa+Kbuxf>Ob3zgJfS=wpe^eW%7qSL4U*y+R*^eja=$cfzcJxYaK7PQB|O&s7Z z3heP>xbqz{9QYm8I};PZ)cc1wJ+Tegl!Z(px98r5XL;QOl0&qr02a`G+bf+`4s-Qg zUHA`3Rn(79684t|Op46S#$RgsG51o{Q|-kouq_~5_=fJgkswC&O&=g(^he153!^)+ zKXFqRN*Z2ojPiEd31rD5lLm1EU}G*FzX9E93=9KD9Q%&|V$H+rG0F*kIDxlM`G~2m z+5{zms{{|vt%&J&UfRw39N7!KZD$EQN#V?tQ;;tXVwd=jKEiLF0XE(lk);UaH@F@z z)bPto?u(YC+#^TEF;ONs@S7|k=x~0NH6#b*N6KR_LFBi1B5HtY_A91%6C=_nF!@(7 z1z#WBqIMq3);;$^3<`ZzvA?0+ArGz(B9dL>{{S_i9&pgPahD-)bEnm~@IIPzr%WxB z9@;Qbut1-BYHd2?#rtB+NB#(xGYCfXwY`PFo2t|}7q2YCY7RfPX>K(R(`v5S^MEi- z32>{E!hc-qhA)hfq^+It_(?~IYlqAr=(A=2;0B_wclGg|P4~ZrHoRtQ`@j7zo}c9i z_SL+hD`8^yODG#&F@vqSNeS(jaaKNo$uoo4`?5?kH`YFSE7Rdc80NgZ$X zE0v0abb5X+z$IEp>enW&lzf4lw~Ivr)$hIbO@*AdyED}V$e4e#UG(~#>Z*S;KN4d;Mt$z{hzwfx}}mNZb|n_40Ez+U6~)qGmaXlY}P)0}U%k)5x2 zoWh>V83s7*2Zs!JwEWNICv0qTUEt#iPPwYWAPlyCb)OGj)UK@y=m)+Zrrk26*#{wW zA*2_aN?urJA-&N2v)g}b<7z;C8qZ^1vw`T(pMJ?0_opT}geQc*JCmXTRtRL&|C1%c z^YiiER;3YN+x`6M!!T{_ZR*p0|JJl#@bfc0Z8`roXQ9b)_iNyS(HV4`fT<}Q@CC%D zi|Fde3@*%%5cicZO_0I9lN;h zYuwufr`V2TE+QVkyQ9J}KfG@>-m~_pTtqLvP$Tw>1-hn7GU544)4&dkK&}_#wPRh= zzOZ=5YUXv2kU$$B^af!WCtFU6Y~3-tGGEH)4V=MX(>`&)FNsGZMaxu0M8 zh?-lZtP{#4AOujHW7SlmrL%288kd3Jym6gNGVLrdGi=$fSII#VkP8b_1mU4C45@(N0tVpM7Nn5?M#f~5S1=dr zE-+iOXT<4oD6pRV){b1xMJ@OJ22x7c_Z+Yo=O1-l#J~fLq#$DbiwV9^=C58fu~c!; zkirymOx|T#@Gf`jEw%>@sKToP1vk>D0rr-g<-f`v;II77LfGTXI_OO5or>1G_SsN^ z?Ie*y$ot(MLRo%WFo^y~z&tzK6`HoM?OVlMLT*8p5!%02iu+W%PfV6}xT-|)nYMin z&k|)rm6WB=bHu-ihmy-!?^7bERuV?DW3;c3jq%)ma)%zqLpgaC>ot(KoRNsLzr+o2 zoeFQ&2u2-MVfbYe#oEf{-L1m&`o#Vq1~Aw1RkSSoSz2vWB|#*53a3{R?D(bmP2l%V z)*x~RF{-dQ#t7QD={ei+hw*1uFA=HpU=lg<6d=+(z(&pE=lpR&zP?bcgC}7Dd%OXMq1vUy6ZR^5JU+8@i-RyjWtG4@@Rb}F0Q=Yyv=YYIjctq^`4I}*|52gVAqy5SN zI>Eh*8sh?>iJvUzZ^?cMBwO+dKt@-=Ae@y;<&5kQl2Kr4Xe)8b=q)JijX1ku1lid>+Lpy2%6xs-jBF;vIB zwRFCq;_3w~OHQD?%pL#Ef<>mJTuye%5SFF_^NnZ@gZ&pD_H2eUJvQia96h}$K=7Xf zj8($Le`Tx+Fmvmo#SHG8X>xxUJ-Wgcqzmg_(d*9hR!va*H4D)By?^I@s{E+XhmJvY zY`@Ep5y=<&m&;F%Mn2oGrAaC^-8ze)et~=zNFG-`O)_^dx~@>^X29Zf zaKC`p^w}ngrTeLBhG)UVFk`6^u1B60uyINe|H{T;M&O8H=Ju~$7(MDbJY&~Hh$Lp| zTc-U~E`Y?diJswPTXdYOL-OZa3Q&05`00%j=>HWJSa9NVh+5Yw-gVn$aa0*?jLS%Y z3;GI*S4(I?Xa@yaF=YgscU1wV?UDZ$)6;NvEk$!fmN0MUjcK1BLUS(y`?qPaG2}o! z?z1Q4HckJP)dfi)mG6Vwo2=K6gyJzmWpUdBnuD*@m(UWR-!qbQsRq<|c{a@cjp%;7 z4%-WV*=<-AOW6N1->HRlx~x#P4PJ}owIswE6CD}s5j3z+Oo1WC&z`TtQfg~z`(5Kk zT09W80o%v_itb6+Q~^8E@mcOA8-u9L?Q?U(;Tb#kTMG;hmF4xY_;`MgDVSnJl-wa_XQszwRDi}J8#z+a9dB=+9Q_8fLsmDj8Metx_+ zNk}qry9fx;)Zr-5WD4#mt%B#3F3-1ru;YJ|7|ahZ86w!LWjm_gc)mGxdoCf4X%<~K zUc92s@45Z?U~{?4fv_W)*3^*wL4Qt-(|CXAb&g18p9rJEfsG9ASozR6MZDT?*UecL zsY?{SQ%7vHV3P^+bj0A237~& z<(3$@wt%Dl;s~LKv=&V%oG-dC{Tx6X(i+oRX8I583E}0Qxt}GZ`HKDuigPY|rK*0>Iv#1Z#X|+MDY} z;~Twy>Y?EFW0%;Ja{2iR=e4hG+KQt!njQ}Ng5;rP_IQZOOEGnA>D}R^YWl0rtheZq zc9z1f9!Up^8>{21@4JV!DRy}95wDY1Xv8CAlBNpG*kSgU9Gy{N-KOcEwK9xsEgTZY zBX!JlWi^3PKA8T8Nbe2(XI#YMb(xxw(Njd`ULDtUD|hB6*q!G~yN#N(>mm)Y;2qgX zO~Qh%#bgKVMjT7_9@7$`jyoSO5Gv&BFGuG%!zS9-mSi)M%h;3h^I?s+VjVRj4{Pc+ z!VV1t9}>M`g3S9r1*qf}Dln5Ko_S3mP94LPESGdszWANmSkbwU%GoA}z~9OgT}Gjv z2#iEMF}yuOrDr*<37gnFSe{fHrC&+CK55=JTC*OCmqD9qF|)FblV`aCvzM{kF50V7 zOZ0hw4G%P;S56x2wxsYz*Q+Ac0~3sz$ba^_c@C`K{6k16ok;5Y&HkxJ45*z4f8L^$ z7Z<8o4N=2r79fbNv}H28x+g1Mv#zpsDcRNFu+uDHl=eW}d_754Q`Ae=xUjQ^nFgIcG3jbJ<@E*L;yxSD z^R9QYd<3mN9@=F+j4{!^W92EAJSe|GAm|f+v~NGh;#y^7 zO5*#S_eSMr?`JC`0k!?U^p8y&;YRWe+e3#3!>UpD+U@IRk|vR(hRhj}dn<7T(JHmC zcZUgW!p|0y`|_r)yw&WU6X(1nCh8vL^!>|EVs-YpbCjRM?)I<&n>my`wme;JJN=4u zxXy7AH~^EPY;>qOiXT!--hVCmshC9OoV9DFa|7CEwLex!-nmsAXDGfJ?XJW# z;l2B;6jg~$^RRdy2$TL3ksO*w#w6Hm9 z1msHb75|i@{1Qm=I&63b>dp!lT!rZExZ{$+$CdK?BQ6bmhG*az%Qtst#=9l^tuLZ; zR}=#E7g2bJlUv>Wgf8fU-s-rg&j;U)xFyG7$Z@e)i+&e;)U91K1_z&tRRE;>s)7SwIMJ6PdV3bWA_c*z*v1_f7?DFG1JGFoBSrP&-2}! zegz*IqidCTjVIp?g&2hloXm# z+gUNJF`_Q-`Lv>R6k3B-v&Zm;fAh+2(&2$#qK}HOLM3|eGg?~@BWMGsc<7SFo>9RD zDo#-~s@6NAH^Q<)l#}QRB}ptGMUzxpY{2ddd&853@NyogC=8 z$qtkZZt8R!npgC@E?==<<{en&*%H7rW%m4-nDyF*4XgeuL_^6|QObHX=wRN}b31#YyVvIQWFH)M^p5hvShqVU zxPfDE=`di)IzsN^O$|GF`&2lMto0oxR#5nThBVEa_#ZO&+s|h}Vtp~)kf|A%z^ow0 zCON?FAL-i5jz$BzSn!X_FpEnMu7}$@T$-KQ*5TK(93Dd%)!Z zMQ&T4t;>~$Ikakai?fhz4~xXRTRw7H*Ib_TnUt`oXAVCYEQxXK4|3cI8Z!SBrB-Nj zp>9nbwmv(X(c?2Ow#*tXS7l=~Q1^*EH$(hci@3^zPf6W;9b#IQ2d%C9<7`-o0Kxbp z=5POd-Q~A8R){ziQH7#E8tY$X-_1owatUUv3KyP=2v9^Wso2p*ZP*aZ($HKGqxEK6 zvaj?UT1nv;MtSY5JTbg17=G6AAn!c)$H|{4UNwI0)X20vS-V0#w>jQ-WYs>6q-0U7 zD>q8K-dCfQQ}y`uLUL|8R7ZL@|9`>9W{_H#ayc7G%-XA)`fx9Gxo)h~oI_k4cnH7t zw%LGHJC)&RvpQ^2-W<6dNQTB13SVJ4oiMwPE*!S2rJTh!+)Z#a*994XBJi}ng7j|lvf8Mjnh>Wb&%0d%hK-cScZ(%^4?}Cn zs;wyJUXf-zJ~KVqw+5W&NS$J5t=jjtr6Miy!?4Xjamu=x4tC_x8M%c-q&c%f-Q{%~ z(yT{AinYo|H4kwsH?m*nD}JB0Ka%cVJ2TTi7CPPCd%inV9i(OQM;^AkLWd+42Bx)3 zt8{xSYAs=-!tcBNC>5f_om`oKPZm&Kn1Cu`bp-#A2c47;ZxyPs!Ezci_Y81=sTIIQ zB&{7yGR?iv*1p>P!Ac2}sOQoPag=4lmZG+Ta4JW>zNxPL#`d!V#T~hp#u;=~PF);E z(6zU8v*d)#A-LC97VSoc#~+3a^G#x@4Y_unpMLjAq4JS{kNgS4rn|pYe{vWcttI z-gpRd0C8qHksV~NFRjWW|MUjN^`T6l|~C?u*QnVraL|DU*~;A zGm7fkI#-Iix-0s%XQrwt@9onv&DszHy9_F=JnH}2*cjs=lKAvDGMsuIcUy;R)V_Lm zifEQ_nb3fRMig1ilQ4oiaY%>uv&vxD<}@6&1-$46d6z9(&0{)wT|Y_7$|%}|XYMUJ z)*F3yuiyIqT;9iWak&nLH@omVicpc@pHlDNPPuxo)~OzQn~Y#Uk$P+N!UFk0dhN!U zIq7t~1Fu;Ii&eqpN%swj95g{j)Bd&+2;vmE>kLLb`I3l?(WvhCp7N9r4J=~@tG$a{ zO|IuB36gobHsPL;7x2+`Fg$C!zIWGe^Rci(ozifQ>!+^n{Lz?q_qV&ZXS@LCo=TZo;mFd=NvK%oUcD(~J)|k*9Pc!Yy|?p1 zDTyuY&=d9Ce7ah|EX%N)DQn8g>4EuMk`n1OF!U7>8^&+_{vV1*$JxbmwsG>^hrv*x zWjw3H7euE4#Q6v|QVrN~i;`?OrJfGE^k~{Xbwdz%RlsQJJ53j3z zHlI6d@nNXDI-q-EdasH$X>iCnE=l%Asg;4UGg|*e+ca$;pV^a=F+5ggi`)*i@Og%3@=;k1~3_LJzlw%c5O|4MUoat{KUYa6uZi^-ltI zTr&4Niv`l8xD;Ip2`&kkA2fs(&&l);%6L+r*MNgoD+YBOoQN2L81V1vdH^C0f(g0Xi;L zZ9C1}mpbx14VGQjhKq!_UF60y$IXad6`-DtLRp8X!abJELsU2+=*NS0#Y6xb-*MaZ zXNTjy#GT>B)2#g(UtafNgsE`^82#7gF(KH$b2lB8b?eYpsNjqeZ*uiiL6)`nZQ3pH(z1Mu%4!VMTxTZxeB7~FMF4zYd^P%@ucq%N@#?WIzK-$D zpiEH2hdBF6TJNvs5eF`BZEe0oSSq=O71d#mNxp~9Jky~W4&p=Ir*1E5{zWWl6>idg=c! zIQE>$Vz2s9^vI;8mH~9sYm-&zZw9~^F+F4MaRlg4B}r+Rn@)lVC$__KG8X7olJTIVjq4TvZ42GGKZJ z8nWI~j6zLOo6UT`UB0v6o)KAN%T`Ck3C4xx0Mmj(KmEn$Z^X?UR5<`RUdYEXA)Oq9 z)HglySg*nK)1BuD^ib#3*A|54twxENJF2%_)}pW}84pI%rFK3DOSTp&fx)xOpfK}c z<*yDH7zVi;7p~nS97wi#(W>eN^)}l>K@XZIR6$(02+Sg-b6}zn3=ScG?&gC@y(2J_ zQ8V_acbWV0$lTaEUKjZNQy`~L%kP81pL!sk(%Ht@++jYTw|EN3Q71Sxf{(wq65JuJLsH*J@zh+a&jsdi5%t zQ^_H~D&jq1_4b%CmE__`mVUSxJ%y$)8gcS zJsORlr2kq{gZsEh7T#T^k|u!a=~G{5Sr%D@G-FhISVha@@Z-x-ONm ze^gOTUjjyHLm=~LGD-iJ?!2aKZm-dt8wik)eHdyQJ-9UYAC~OlJ-TfcC!+r4`oIAN zlLxB&e_OI3K0C84kb)wltZRSA_p+833tb_Y(p$&A=kb%CS}-`xYVPi^x8rlSpMqCq ziAQ4X*_mi{e}2#!0dx0q0e1R-Mp~fhGUr04=&S5_gpEh3n$>M~7YzI>PycmlVHXTd zBB!xixo9#nEo3SAm7H$o(y{AuA@INukOKG}n;6$$`kf}^XR?AJzqwlghG7aSCy2`v z|C`!$uXiKGE2R?l>bUPWABgL|OUM2ny0KtR;wqjDo9M=eN4ci>qJ3{xlFsJJ%wENe z_>sqV4UF6ajY$RMv{`lUdT>uu&r0jC;Asb;e5kJ07+nn4`} z?wu9bd2pTs8XqwJIiYeC7vSuo*~#8tayzWh**U*qvhejx1`N{-%7c}qCI6B{5t8P- z?n>>l2nS_s8b;62JgH80k@@SpW*0`O)4nHKyidiscDxJlJD5f9j{4s8S7BUmUV8H4 zMA#Rv#Hs;jA{V?}df}WX9g#8ED;#by^*=K;tKkTbPkXHoy^Y@cn&f-t8mH=@&S!VZ zbe{~2n5UauKiL)|cx_*c!qUFfE{pE?xeKE|(!a+Q;}xhl39GgwFXz8uivy#AP!lhQ zYUwWrxr%^SnY>CIxiqgUIN*vT8SdOk>mQ42Z?93F8xSWzNyVQ`aAT0lZP#iBr^KCk z&|7-m=HB}nh0f~w(&FArq+}~@2D;wMh&r4OwvaGurJClxS<)i^XCi%0tW7|kzJE+ zZLd?G3zsS9x4U$H20Wz);sq#%K)QV0{BuUc7ZHjXrhb~eY(DIfOVN`QORB>nGR4x{ z43_wA=d^mYlJYQe309bpC5>Qy2IZvAMETmmp?TAu=VDXREu$_e`F9qF{WrBg8m zJnu`j8q!)qN-x~cNWDTn&J}SbpV{%}^vr58%+;s7P{n48j8Em#)V^8uNL>k|lfgRLQbdEeiIps2|C)j!a+v4i2Od0F6H$&Hf!@=#t#uA9FIJ6rCC57?{;o zc;Km{$V9*61!f;Dlin60A56`3_#bn7)esI1!Lfh!wq2<@dz&!9y^LLSLAD_MnX9OB zj=?4TZ%?VXenzIaow`>~&g22};zxu#RhyLYNx=2FgK|M}D;mSZF6iC^bG8BL@Yi#X zuPu`On%}`W)Pt9{F@(5^(mcP1S)mzKyH9vU|Go(;-{O&LVZIko|EA?&4XlHd1kO_fEY*r!}g9R;^YgT|x+zMT&X zjyO0klMNkmTFN^a(u90E48zp#d*YmBlE|AM>2E+}r1ff+{F8IKo!^F$X{Px8C2pDU z4<^@SfBxwLW(Qtp6qv?z^(K4%xxGdjL|>IY3nKfLU&uZVF5mMqxC~;=dw|iy_VNE!8kY*2n}}K|LhW+2Gs+ zp1=R>S6Vi4guHn-hU5MC4ifFufDg3%$mixG=Rjie%U}AjVLKRgsPzm<##kM;*QmXE ztX3r|jiZ)vE$+9?`HcNFkhUj0>S~7%@2%Znz6a@$skRzEBLaLpbjB{6;_=JB_74cM z%bpuwjbk=4$y0q+CBoZh-2OJF#JcD&x^HII?>8CNxSDhcizDOJs>BQ3vL3hP$zA&& zal}x>9QdgEdhSSFL2L5kkJvY?zlQjqQ{0~mm?cmF(~V@K^HXxdJnrDACdl3TV~q8W zMfoRQ(L~J;QNG>RIyfz&7rlx}L<_;CnZNcR4b!jvyIypPm)YFWSCt+1uB9Pzjad?a zN&wg{&gi+nB5({stdpoGGKO@xL(?J4b6ORpcXa21!JmOQoP2~$A0yxA>f2zDG?qZT ze1oB+4uhEj)FC45@>z)xe>0F1@9xZS z!Db$9>6a^+y#+oK$+Vn~-Iy3gpf0g%a-Qn%6E}UhP>QktxF_1`Fz@b5w^?I_?Fwm- z2IekAH*HGBVhAp0$MvoSOQy0am5>bG2a4cG?n!tPjak8*Azq`BASm$edW~Dt zONHgb2w*ZxzQvvfnI|o%h@kVZ8UE0Ys1HGS%t*I#lvjP!1%B4yQG`@@sNUaeDq$w< zxfT$LfysBhB&`#53|+bpj4p~4r01iFSN=;{K=CtglO2t5Q0Dv^0%DC|Yc9=K2Ya0F zGJc;yBf9I?{>U?CZ?PXl7bEMuCag@ZhczU_-1jRuIe|-!1D2~+pW^hNv#-90EzFMh zVn=I)P-L2CQuMV3LN^3p77@1O>j;3l_k38EVn;{yqb8 z$Q)rHqyRh?YcaV0EO6%gR@cEj$Yt#He*wuMlr>KrA7%a>aC{@ZjefMLT3~>dg++yL zq(tn-vRtnHXdmJ#sG!z9)^_5xF7)NwSqU;bgW}hJozA z>=jU%0lwCPhmsXBg_o-)q|M?)xy|~iwV%0Od@WUIRkt55`jmSX6Bs6FJUN^)@a4(y zHUw}i2I1008(M~;ml^wZB*~-Ut(`HzI`s-&knPe-)kluaTka~TBR&*QnH zq(C5q1;1tJH3OZ6Jh3kI+L#06q%lW_7-ke=cQ@ZU!G0+iI2zojrtrWkc4D|T->vBs zCePi|(77g%Y-rfZ{e3R(14D#PM*FL#7=uIqw{4&N%?;bJPDGEMm1yN5azZcpl~lia zhSY%^^%V=OSI~*~=|1!2Uq}FSfIa2w&~|B22U%wfq2yKewLcQZ8Sx`+>!obmc!;fi zk_o=(p{St@blw}xFl&W}g*dvzoSo0(np|@Hcn(jSVI%kyU98n3mV7 zZtxCq7V&M;!*(D)&kL?X085!Gn*AmHH#j4#bdezZXw5&mS-~x?WiyD{_$2MhU%n|fCSPrmksl_<(Yo^?dGNa_shfOJvsS2an(kGj@bw-;#0tcr_G otX0JnjVBkM_X%(r08+RW4e`^3D2><{9 literal 0 HcmV?d00001 diff --git a/docs/docs/security.md b/docs/docs/security.md new file mode 100644 index 00000000..de1acb5e --- /dev/null +++ b/docs/docs/security.md @@ -0,0 +1,219 @@ +--- +sidebar_position: 5 +title: Security +--- + +Core Concepts +------------- + +API authentication is largely a solved problem and generally outside the scope of Jersey WS Template. + +Jersey WS Template does, however, adds a layer of security on its own by validating [OAuth 2 access token] on all +incoming request. Each API request requires a standard **"Authentication": "Bearer "** token header: + +![Error loading oauth2-filtering.png](./img/oauth2-filtering.png) + +To define a token validator, simply implement the **AccessTokenValidator** like so: + +```java +public class JwtTokenValidator implements AccessTokenValidator { + + @Override + public boolean validate(@NotNull final String accessToken) { + // use https://github.com/auth0/java-jwt as recommended by https://jwt.io/ + return true; + } +} +``` + +and bind the validator, in [BinderFactory], using + +```java +bind(new JwtTokenValidator()).to(AccessTokenValidator.class); +``` + +For example, to validate a ES384 token using _AccessTokenValidator_, we may implement a _ES384JwtTokenValidator_ +validator below: + +:::note + +Note that the implementation below depends on 2 JWT libraries: + +```xml + + com.auth0 + java-jwt + 4.4.0 + + + com.auth0 + jwks-rsa + 0.22.1 + +``` + +::: + +```java +import com.auth0.jwk.InvalidPublicKeyException; +import com.auth0.jwk.Jwk; +import com.auth0.jwk.JwkException; +import com.auth0.jwk.JwkProvider; +import com.auth0.jwk.JwkProviderBuilder; +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.interfaces.Verification; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.validation.constraints.NotNull; +import net.jcip.annotations.Immutable; +import net.jcip.annotations.ThreadSafe; + +import java.net.MalformedURLException; +import java.net.URL; +import java.security.interfaces.ECPublicKey; +import java.util.Objects; + +/** + * {@link ES384JwtTokenValidator} validates an JWT token in ES384 JWS form. + * + * It validates the access token by verifying the integrity of the header and payload to ensure that they have not been + * altered by using token's signature section. + */ +@Immutable +@ThreadSafe +public class ES384JwtTokenValidator implements AccessTokenValidator { + + private static final Logger LOG = LoggerFactory.getLogger(ES384JwtTokenValidator.class); + + private final String jwksUrl; + + /** + * Constructs a new {@link ES384JwtTokenValidator} that verfies the signed JWT token with the JWK keys stored at a + * specified URL. + * + * @param jwksUrl The provided JWKS URL that, on GET, returns a json object such as + *