You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tomee.apache.org by ra...@apache.org on 2019/01/04 12:00:00 UTC

[tomee] 02/06: TOMEE-2332: example for JWKs usage in MicroProfile JWT with TomEE

This is an automated email from the ASF dual-hosted git repository.

radcortez pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/tomee.git

commit 14ad55dcca1c0e331d1bdbedf6c079610ebda5c7
Author: cotnic <mi...@cotnic.com>
AuthorDate: Wed Jan 2 11:13:25 2019 +0100

    TOMEE-2332: example for JWKs usage in MicroProfile JWT with TomEE
---
 examples/mp-rest-jwt-jwk/README.adoc               |  76 ++++++++
 examples/mp-rest-jwt-jwk/pom.xml                   | 215 +++++++++++++++++++++
 .../main/java/org/superbiz/rest/ProductRest.java   |  24 ++-
 .../java/org/superbiz/service/ProductService.java  |   4 +-
 .../META-INF/microprofile-config.properties        |   2 +
 .../mp-rest-jwt-jwk/src/main/resources/jwks.json   |  28 +++
 .../java/org/superbiz/rest/KeyGeneratorUtil.java   |  42 ++++
 .../java/org/superbiz/rest/ProductRestClient.java} |  60 ++----
 .../test/java/org/superbiz/rest/ProductsTest.java  |  98 +++++-----
 .../test/java/org/superbiz/rest/TokenUtils.java    |  84 ++++++++
 .../META-INF/microprofile-config.properties        |   3 +
 .../src/test/resources/arquillian.xml              |  35 ++++
 .../src/test/resources/jwt-alice.json              |  10 +
 .../src/test/resources/jwt-john.json               |  10 +
 .../src/test/resources/privateKey002.pem           |  24 +++
 .../src/test/resources/privateKey004.pem           |  24 +++
 16 files changed, 647 insertions(+), 92 deletions(-)

diff --git a/examples/mp-rest-jwt-jwk/README.adoc b/examples/mp-rest-jwt-jwk/README.adoc
index e69de29..08e9969 100644
--- a/examples/mp-rest-jwt-jwk/README.adoc
+++ b/examples/mp-rest-jwt-jwk/README.adoc
@@ -0,0 +1,76 @@
+= MicroProfile JWT JWKs
+:index-group: MicroProfile
+:jbake-type: page
+:jbake-status: published
+
+This is an example on how to use MicroProfile JWT in TomEE by using the
+public key as JWKs.
+
+== Run the application:
+
+[source, bash]
+----
+mvn clean install tomee:run
+----
+
+This example is a CRUD application for products available.
+
+== Requirments and configuration
+
+For usage of MicroProfile JWT we have to change the following to our
+project:
+
+[arabic]
+. Add the dependency to our `pom.xml` file:
++
+....
+<dependency>
+    <groupId>org.eclipse.microprofile.jwt</groupId>
+    <artifactId>microprofile-jwt-auth-api</artifactId>
+    <version>${mp-jwt.version}</version>
+    <scope>provided</scope>
+</dependency>
+....
+. Annotate our `Application.class` with `@LoginConfig(authMethod = "MP-JWT")`
+
+. Provide public and private key for authentication. And specify the location of the public key and the issuer in our
+`microprofile-config.properties` file.
++
+[source,properties]
+----
+mp.jwt.verify.publickey.location=/jwks.pem
+mp.jwt.verify.issuer=https://example.com
+----
+
+. Define `@RolesAllowed()` on the endpoints we want to protect.
+
+== About the application architecture
+
+The application enables us to manipulate and view products with specific users. We have two users
+`Alice Wonder` and `John Doe`. They can read, create, edit and delete specific entries.
+
+`jwt-john.json`
+
+[source,json]
+----
+{
+  "iss": "https://example.com",
+  "sub": "24400320",
+  "name": "John Doe",
+  "upn": "john.doe@example.com",
+  "preferred_username": "john",
+  "groups": [
+    "guest", "admin"
+  ]
+}
+----
+
+== Access the endpoints with JWT token
+
+We access endpoints from our test class by creating a `JWT` with the help of
+our `TokenUtils.generateJWTString(String jsonResource, String keyId)` which signs our user
+data in json format with the help of our `src/test/resources/{keyId}` key.
+
+We can also generate new `privateKey.pem` and `publicKey.pem` with the
+`GenerateKeyUtils.generateKeyPair(String keyAlgorithm, int keySize)` method which
+then creates the `publicKey.pem` also in `JWK` format.
\ No newline at end of file
diff --git a/examples/mp-rest-jwt-jwk/pom.xml b/examples/mp-rest-jwt-jwk/pom.xml
new file mode 100644
index 0000000..8a2e025
--- /dev/null
+++ b/examples/mp-rest-jwt-jwk/pom.xml
@@ -0,0 +1,215 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.superbiz</groupId>
+    <artifactId>mp-rest-jwt-jwk</artifactId>
+    <version>8.0.0-SNAPSHOT</version>
+    <packaging>war</packaging>
+    <name>OpenEJB :: Examples :: MP REST JWT JWK</name>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <tomee.version>8.0.0-SNAPSHOT</tomee.version>
+        <version.shrinkwrap.resolver>2.0.0</version.shrinkwrap.resolver>
+        <mp-jwt.version>1.1</mp-jwt.version>
+        <mp-rest-client.version>1.1</mp-rest-client.version>
+    </properties>
+
+    <build>
+        <defaultGoal>install</defaultGoal>
+        <finalName>phonestore</finalName>
+
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <version>2.18.1</version>
+                <configuration>
+                    <reuseForks>false</reuseForks>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-war-plugin</artifactId>
+                <version>3.1.0</version>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.5.1</version>
+                <configuration>
+                    <source>1.8</source>
+                    <target>1.8</target>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.tomee.maven</groupId>
+                <artifactId>tomee-maven-plugin</artifactId>
+                <version>${tomee.version}</version>
+                <configuration>
+                    <tomeeClassifier>microprofile</tomeeClassifier>
+                    <args>-Xmx512m -XX:PermSize=256m</args>
+                    <config>${project.basedir}/src/main/tomee/</config>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencyManagement>
+        <dependencies>
+            <!-- Override dependency resolver with test version. This must go *BEFORE*
+              the Arquillian BOM. -->
+            <dependency>
+                <groupId>org.jboss.shrinkwrap.resolver</groupId>
+                <artifactId>shrinkwrap-resolver-bom</artifactId>
+                <version>${version.shrinkwrap.resolver}</version>
+                <scope>import</scope>
+                <type>pom</type>
+            </dependency>
+            <!-- Now pull in our server-based unit testing framework -->
+            <dependency>
+                <groupId>org.jboss.arquillian</groupId>
+                <artifactId>arquillian-bom</artifactId>
+                <version>1.0.3.Final</version>
+                <scope>import</scope>
+                <type>pom</type>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.tomee</groupId>
+            <artifactId>javaee-api</artifactId>
+            <version>8.0</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.eclipse.microprofile.jwt</groupId>
+            <artifactId>microprofile-jwt-auth-api</artifactId>
+            <version>${mp-jwt.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.microprofile.rest.client</groupId>
+            <artifactId>microprofile-rest-client-api</artifactId>
+            <version>${mp-rest-client.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.nimbusds</groupId>
+            <artifactId>nimbus-jose-jwt</artifactId>
+            <version>4.23</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.12</version>
+            <scope>test</scope>
+        </dependency>
+
+        <!--
+        The <scope>test</scope> guarantees that non of your runtime
+        code is dependent on any OpenEJB classes.
+        -->
+        <dependency>
+            <groupId>org.apache.tomee</groupId>
+            <artifactId>openejb-cxf-rs</artifactId>
+            <version>${tomee.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.tomee</groupId>
+            <artifactId>openejb-core</artifactId>
+            <version>${tomee.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>commons-lang</groupId>
+            <artifactId>commons-lang</artifactId>
+            <version>2.4</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.arquillian.junit</groupId>
+            <artifactId>arquillian-junit-container</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.shrinkwrap.resolver</groupId>
+            <artifactId>shrinkwrap-resolver-depchain</artifactId>
+            <type>pom</type>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <profiles>
+        <profile>
+            <id>arquillian-tomee-remote</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>org.apache.tomee</groupId>
+                    <artifactId>arquillian-tomee-remote</artifactId>
+                    <version>${tomee.version}</version>
+                    <scope>test</scope>
+                </dependency>
+                <dependency>
+                    <groupId>org.apache.tomee</groupId>
+                    <artifactId>apache-tomee</artifactId>
+                    <version>${tomee.version}</version>
+                    <type>zip</type>
+                    <classifier>microprofile</classifier>
+                    <scope>test</scope>
+                </dependency>
+                <dependency>
+                    <groupId>org.apache.tomee</groupId>
+                    <artifactId>mp-jwt</artifactId>
+                    <version>${tomee.version}</version>
+                    <scope>provided</scope>
+                </dependency>
+            </dependencies>
+        </profile>
+    </profiles>
+
+    <!--
+    This section allows you to configure where to publish libraries for sharing.
+    It is not required and may be deleted.  For more information see:
+    http://maven.apache.org/plugins/maven-deploy-plugin/
+    -->
+    <distributionManagement>
+        <repository>
+            <id>localhost</id>
+            <url>file://${basedir}/target/repo/</url>
+        </repository>
+        <snapshotRepository>
+            <id>localhost</id>
+            <url>file://${basedir}/target/snapshot-repo/</url>
+        </snapshotRepository>
+    </distributionManagement>
+</project>
diff --git a/examples/mp-rest-jwt-jwk/src/main/java/org/superbiz/rest/ProductRest.java b/examples/mp-rest-jwt-jwk/src/main/java/org/superbiz/rest/ProductRest.java
index 499f634..8b4ba74 100644
--- a/examples/mp-rest-jwt-jwk/src/main/java/org/superbiz/rest/ProductRest.java
+++ b/examples/mp-rest-jwt-jwk/src/main/java/org/superbiz/rest/ProductRest.java
@@ -24,6 +24,7 @@ import javax.annotation.security.RolesAllowed;
 import javax.inject.Inject;
 import javax.ws.rs.*;
 import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
 import java.util.List;
 
 @Path("store")
@@ -43,35 +44,46 @@ public class ProductRest {
     @GET
     @Path("/products")
     @RolesAllowed({"guest", "admin"})
-    public List<Product> getListOfMovies() {
+    public List<Product> getListOfProducts() {
         return productService.getProducts();
     }
 
     @GET
     @Path("/products/{id}")
     @RolesAllowed({"guest", "admin"})
-    public Product getMovie(@PathParam("id") int id) {
+    public Product getProduct(@PathParam("id") int id) {
         return productService.getProduct(id);
     }
 
     @POST
     @Path("/products")
     @RolesAllowed({"admin"})
-    public void addMovie(Product product) {
-        productService.addProduct(product);
+    public Response addProduct(Product product) {
+        return Response.status(Response.Status.CREATED)
+                .entity(productService.addProduct(product))
+                .build();
     }
 
     @DELETE
     @Path("/products/{id}")
     @RolesAllowed({"admin"})
-    public void deleteMovie(@PathParam("id") int id) {
+    public Response deleteProduct(@PathParam("id") int id) {
         productService.deleteProduct(id);
+
+        return Response
+                .status(Response.Status.NO_CONTENT)
+                .build();
     }
 
     @PUT
     @Path("/products")
     @RolesAllowed({"admin"})
-    public void updateMovie(Product product) {
+    public Response updateProduct(Product product) {
         productService.updateProduct(product);
+
+        return Response
+                .status(Response.Status.OK)
+                .entity(product)
+                .build();
     }
 }
diff --git a/examples/mp-rest-jwt-jwk/src/main/java/org/superbiz/service/ProductService.java b/examples/mp-rest-jwt-jwk/src/main/java/org/superbiz/service/ProductService.java
index 698c507..ab917a3 100644
--- a/examples/mp-rest-jwt-jwk/src/main/java/org/superbiz/service/ProductService.java
+++ b/examples/mp-rest-jwt-jwk/src/main/java/org/superbiz/service/ProductService.java
@@ -40,8 +40,10 @@ public class ProductService {
         return productsInStore.get(id);
     }
 
-    public void addProduct(Product product) {
+    public Product addProduct(Product product) {
         productsInStore.put(product.getId(), product);
+
+        return product;
     }
 
     public void deleteProduct(int id) {
diff --git a/examples/mp-rest-jwt-jwk/src/main/resources/META-INF/microprofile-config.properties b/examples/mp-rest-jwt-jwk/src/main/resources/META-INF/microprofile-config.properties
new file mode 100644
index 0000000..43e9a86
--- /dev/null
+++ b/examples/mp-rest-jwt-jwk/src/main/resources/META-INF/microprofile-config.properties
@@ -0,0 +1,2 @@
+mp.jwt.verify.publickey.location=/jwks.json
+mp.jwt.verify.issuer=https://example.com
\ No newline at end of file
diff --git a/examples/mp-rest-jwt-jwk/src/main/resources/jwks.json b/examples/mp-rest-jwt-jwk/src/main/resources/jwks.json
new file mode 100644
index 0000000..cd5bb2f
--- /dev/null
+++ b/examples/mp-rest-jwt-jwk/src/main/resources/jwks.json
@@ -0,0 +1,28 @@
+{
+  "keys": [
+    {
+      "kid": "privateKey001.pem",
+      "kty": "RSA",
+      "e": "AQAB",
+      "n": "wHV0xEZDVMgVGxH8cdQqncMEg72fUQfKBwO-wVJSv1xYLBoPDK228oGHrPLbLGGW9QrXqekVoGlyld5j9xT74iMyzw3onyQywL5cTfxWNxzowTbQQQUSJ5kNF130K5tyJAqUMRZ3L69r1_pHjtKSxZcGXdb_w8L7qcvQ7kNoCRR0erLBTkDgM2hA2hYf8QxKc_ua8XP8V5Eqn4Elo2EILjVzV_X3GArU00Ets15rLRc8KK_99sQFKrgB2IFW-LfUQD7pvOXyIaF560DF57kdf2ZMFNQpYa3wphHgC8EVgblaMG-1cvda-ZBtcF0Q-efSxk2SQJyyETvEXq0SKcXjww"
+    },
+    {
+      "kid": "privateKey002.pem",
+      "kty": "RSA",
+      "e": "AQAB",
+      "n": "oodvcIxwKkxfFP8KwQV9Upxl8tqTmPOBFVu1In2a9dOfREZep5bJCwJcSu5wZ1IpE_e7vMi8PktXF2B0r0yHmelP98hcFHFzeNxvgXmMWpzPx84FZXP1EgCBP5DzGCvC-PBvMfYMM81UNESOyCD4wF_JnnuIxFDf0JUlQrF81DjuQzspK3j7ENRc8QR0PLKbD8Dq7T1RRQoT5F5pkXgCMreaib_rgL-3snBbTjjp8UyhfLyuEJrJX_ee_5T4WS7xU42BxKv_jJmHixmv9EHBMOVKqXzLgFlF0tAuI_qHGl0HaRNencTE2O3C_b56VHXp1sH6RM65m2MIciDqKeGGUw"
+    },
+    {
+      "kid": "privateKey003.pem",
+      "kty": "RSA",
+      "e": "AQAB",
+      "n": "hrPRLyhDpMGU-RgTFwEeYoaoOTSJIA1hMjj0CoBxC6sZOu5X3gHC4luytIvwYDOEcp9L_G5JLPgJrOs-IHn8SK4IPacdEjz_yua18984TnH-Jy8k2T_Clf2gGgdeu3MG2byBmnU3rxGKIayHGp9RAXPYlJZOJppo6JA7e3uBEkH9nJMumNEpFABgBKRimg8MEnBaQtsEUkYM8nFFtzn9Qahvy9c3scTdwxCos0JdERlsK6ImhqCDSsicDbOkeE7sAzVRAnpDCr9_j0OEh2-z6gH9aLxNNTuG7vHCf-UzYIZpHjG3ezSLhiWpFsXWokc8tCtM7alZap9pbzh2YU5j0w"
+    },
+    {
+      "kid": "privateKey004.pem",
+      "kty": "RSA",
+      "e": "AQAB",
+      "n": "iNaojcX7ASG-5-ex2u4H72nvisfr-tydFVHrkocLdDUa_Uy2QXvqB-wp14C1mJdrWnAB2oqlY8RuuachdFg7FgDtgm1XdBv4ORqnF6ljrnYrbIcAtig1il1K-8JQZSy9WNMvBF9LMGjY3Y3G2hw3bsit0-lJQaP7m_ZmwSwIc6LSpP3wLBw2u3I5IO05BTbZstV3UcDRllm_cqU_0gZJEy6MsNbQiXrHX5bN_xsw256Cr7sPsSuTmSVx5M8tOcwFOKmvklDbnDqidMw7un4IvcMt8wpinGImnKi4AiiBtDRBGlgj879iNyJa1xKYmHNe1WkJhjwQZ_ZgbZiCBAAghw"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/examples/mp-rest-jwt-jwk/src/test/java/org/superbiz/rest/KeyGeneratorUtil.java b/examples/mp-rest-jwt-jwk/src/test/java/org/superbiz/rest/KeyGeneratorUtil.java
new file mode 100644
index 0000000..e9f9949
--- /dev/null
+++ b/examples/mp-rest-jwt-jwk/src/test/java/org/superbiz/rest/KeyGeneratorUtil.java
@@ -0,0 +1,42 @@
+package org.superbiz.rest;
+
+import com.nimbusds.jose.jwk.KeyUse;
+import com.nimbusds.jose.jwk.RSAKey;
+
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.RSAPublicKey;
+import java.security.interfaces.RSAPrivateKey;
+import java.util.Base64;
+import java.util.UUID;
+
+public class KeyGeneratorUtil {
+
+    public static void main(String[] args) throws NoSuchAlgorithmException {
+        generateKeyPair("RSA", 2048);
+    }
+
+    public static void generateKeyPair(String keyAlgorithm, int keySize) throws NoSuchAlgorithmException {
+        KeyPairGenerator kpg = KeyPairGenerator.getInstance(keyAlgorithm); // RSA
+        kpg.initialize(keySize); // 2048
+        KeyPair kp = kpg.generateKeyPair();
+
+        System.out.println("-----BEGIN PRIVATE KEY-----");
+        System.out.println(Base64.getMimeEncoder().encodeToString(kp.getPrivate().getEncoded()));
+        System.out.println("-----END PRIVATE KEY-----");
+        System.out.println("-----BEGIN PUBLIC KEY-----");
+        System.out.println(Base64.getMimeEncoder().encodeToString(kp.getPublic().getEncoded()));
+        System.out.println("-----END PUBLIC KEY-----");
+
+        RSAPublicKey publicKey = (RSAPublicKey) kp.getPublic();
+
+        RSAKey jwk = new RSAKey.Builder(publicKey)
+                .privateKey((RSAPrivateKey) kp.getPrivate())
+                .keyUse(KeyUse.SIGNATURE)
+                .keyID(UUID.randomUUID().toString())
+                .build();
+
+        System.out.println(jwk.toJSONObject().toJSONString());
+    }
+}
diff --git a/examples/mp-rest-jwt-jwk/src/main/java/org/superbiz/rest/ProductRest.java b/examples/mp-rest-jwt-jwk/src/test/java/org/superbiz/rest/ProductRestClient.java
similarity index 52%
copy from examples/mp-rest-jwt-jwk/src/main/java/org/superbiz/rest/ProductRest.java
copy to examples/mp-rest-jwt-jwk/src/test/java/org/superbiz/rest/ProductRestClient.java
index 499f634..97b93eb 100644
--- a/examples/mp-rest-jwt-jwk/src/main/java/org/superbiz/rest/ProductRest.java
+++ b/examples/mp-rest-jwt-jwk/src/test/java/org/superbiz/rest/ProductRestClient.java
@@ -16,62 +16,42 @@
  */
 package org.superbiz.rest;
 
-
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
 import org.superbiz.entity.Product;
-import org.superbiz.service.ProductService;
 
-import javax.annotation.security.RolesAllowed;
-import javax.inject.Inject;
+import javax.enterprise.context.Dependent;
 import javax.ws.rs.*;
 import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
 import java.util.List;
 
-@Path("store")
-@Produces(MediaType.APPLICATION_JSON)
-@Consumes(MediaType.APPLICATION_JSON)
-public class ProductRest {
-
-    @Inject
-    private ProductService productService;
+@Dependent
+@RegisterRestClient
+@Path("/test/rest/store")
+@Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN})
+@Consumes({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN})
+public interface ProductRestClient {
 
     @GET
-    @Produces(MediaType.TEXT_PLAIN)
-    public String status() {
-        return "running";
-    }
+    String status();
 
     @GET
-    @Path("/products")
-    @RolesAllowed({"guest", "admin"})
-    public List<Product> getListOfMovies() {
-        return productService.getProducts();
-    }
+    @Path("/products/{id}")
+    Response getProduct(@HeaderParam("Authorization") String authHeaderValue, @PathParam("id") int id);
 
     @GET
-    @Path("/products/{id}")
-    @RolesAllowed({"guest", "admin"})
-    public Product getMovie(@PathParam("id") int id) {
-        return productService.getProduct(id);
-    }
+    @Path("/products")
+    List<Product> getProductList(@HeaderParam("Authorization") String authHeaderValue);
 
     @POST
     @Path("/products")
-    @RolesAllowed({"admin"})
-    public void addMovie(Product product) {
-        productService.addProduct(product);
-    }
-
-    @DELETE
-    @Path("/products/{id}")
-    @RolesAllowed({"admin"})
-    public void deleteMovie(@PathParam("id") int id) {
-        productService.deleteProduct(id);
-    }
+    Response addProduct(@HeaderParam("Authorization") String authHeaderValue, Product newProduct);
 
     @PUT
     @Path("/products")
-    @RolesAllowed({"admin"})
-    public void updateMovie(Product product) {
-        productService.updateProduct(product);
-    }
+    Response updateProduct(@HeaderParam("Authorization") String authHeaderValue, Product updatedProduct);
+
+    @DELETE
+    @Path("/products/{id}")
+    Response deleteProduct(@HeaderParam("Authorization") String authHeaderValue, @PathParam("id") int id);
 }
diff --git a/examples/mp-rest-jwt-jwk/src/test/java/org/superbiz/rest/ProductsTest.java b/examples/mp-rest-jwt-jwk/src/test/java/org/superbiz/rest/ProductsTest.java
index c8ceb6f..2eb6dfb 100644
--- a/examples/mp-rest-jwt-jwk/src/test/java/org/superbiz/rest/ProductsTest.java
+++ b/examples/mp-rest-jwt-jwk/src/test/java/org/superbiz/rest/ProductsTest.java
@@ -1,11 +1,8 @@
 package org.superbiz.rest;
 
-import org.apache.cxf.feature.LoggingFeature;
-import org.apache.cxf.jaxrs.client.WebClient;
-import org.apache.johnzon.jaxrs.JohnzonProvider;
+import org.eclipse.microprofile.rest.client.inject.RestClient;
 import org.jboss.arquillian.container.test.api.Deployment;
 import org.jboss.arquillian.junit.Arquillian;
-import org.jboss.arquillian.test.api.ArquillianResource;
 import org.jboss.shrinkwrap.api.ShrinkWrap;
 import org.jboss.shrinkwrap.api.asset.StringAsset;
 import org.jboss.shrinkwrap.api.spec.WebArchive;
@@ -14,73 +11,84 @@ import org.junit.runner.RunWith;
 import org.superbiz.entity.Product;
 import org.superbiz.service.ProductService;
 
-import java.net.URL;
-import java.util.logging.Logger;
+import javax.inject.Inject;
+import javax.ws.rs.core.Response;
+import java.math.BigDecimal;
+import java.util.List;
 
-import static java.util.Collections.singletonList;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
 
 @RunWith(Arquillian.class)
 public class ProductsTest {
 
-    private final static Logger LOGGER = Logger.getLogger(ProductsTest.class.getName());
-    @ArquillianResource
-    private URL base;
+    @Inject
+    @RestClient
+    private ProductRestClient productRestClient;
 
-    @Deployment(testable = false)
+    @Deployment()
     public static WebArchive createDeployment() {
         final WebArchive webArchive = ShrinkWrap.create(WebArchive.class, "test.war")
-                .addClasses(Product.class, ProductService.class, ProductsTest.class)
                 .addClasses(ProductRest.class, RestApplication.class)
-//                .addClass(MoviesMPJWTConfigurationProvider.class)
-                .addAsWebInfResource(new StringAsset("<beans/>"), "beans.xml");
-
-        System.out.println(webArchive.toString(true));
-
+                .addClasses(Product.class)
+                .addClass(ProductService.class)
+                .addClasses(ProductRestClient.class, TokenUtils.class)
+                .addPackages(true, "com.nimbusds", "net.minidev.json")
+                .addAsWebInfResource(new StringAsset("<beans/>"), "beans.xml")
+                .addAsResource("META-INF/microprofile-config.properties")
+                .addAsResource("jwt-john.json")
+                .addAsResource("privateKey002.pem")
+                .addAsResource("jwks.json");
         return webArchive;
     }
 
     @Test
     public void runningOfProductsApiTest() {
 
-        final WebClient webClient = WebClient
-                .create(base.toExternalForm(), singletonList(new JohnzonProvider<>()),
-                        singletonList(new LoggingFeature()), null);
-
-
-        //Testing rest endpoint deployment (GET  without security header)
-        String responsePayload = webClient.reset().path("/rest/store/").get(String.class);
-        LOGGER.info("responsePayload = " + responsePayload);
-        assertTrue(responsePayload.equalsIgnoreCase("running"));
+        assertEquals("running", productRestClient.status());
     }
 
     @Test
-    public void createProductTest() {
+    public void shouldMakeProductFlow() throws Exception {
+        Product productHuawei = new Product();
+        productHuawei.setId(1);
+        productHuawei.setName("Huawei P20 Pro");
+        productHuawei.setPrice(new BigDecimal(820.41));
+        productHuawei.setStock(6);
 
-        throw new RuntimeException("TODO Implement!");
-    }
+        int statusCode = productRestClient.addProduct("Bearer " + createJwtToken(), productHuawei).getStatus();
 
-    @Test
-    public void getAllProductsTest() {
+        assertEquals(Response.Status.CREATED.getStatusCode(), statusCode);
 
-        throw new RuntimeException("TODO Implement!");
-    }
 
-    @Test
-    public void getProductWithIdTest() {
+        Product productSamsung = new Product();
+        productSamsung.setId(2);
+        productSamsung.setName("Samsung S9");
+        productSamsung.setPrice(new BigDecimal(844.42));
+        productSamsung.setStock(2);
 
-        throw new RuntimeException("TODO Implement!");
-    }
+        statusCode = productRestClient.addProduct("Bearer " + createJwtToken(), productSamsung).getStatus();
 
-    @Test
-    public void updateProductTest() {
+        assertEquals(Response.Status.CREATED.getStatusCode(), statusCode);
 
-        throw new RuntimeException("TODO Implement!");
-    }
+        productSamsung.setStock(5);
 
-    @Test
-    public void deleteProductTest() {
+        Response response = productRestClient.updateProduct("Bearer " + createJwtToken(), productSamsung);
+
+        Product updatedProduct = response.readEntity(Product.class);
+
+        assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
+        assertEquals(5, updatedProduct.getStock().intValue());
+
+        List<Product> products = productRestClient.getProductList("Bearer " + createJwtToken());
+
+        assertEquals(2, products.size());
+
+        statusCode = productRestClient.deleteProduct("Bearer " + createJwtToken(), 2).getStatus();
+
+        assertEquals(Response.Status.NO_CONTENT.getStatusCode(), statusCode);
+    }
 
-        throw new RuntimeException("TODO Implement!");
+    private String createJwtToken() throws Exception {
+        return TokenUtils.generateJWTString("jwt-john.json", "privateKey002.pem");
     }
 }
diff --git a/examples/mp-rest-jwt-jwk/src/test/java/org/superbiz/rest/TokenUtils.java b/examples/mp-rest-jwt-jwk/src/test/java/org/superbiz/rest/TokenUtils.java
new file mode 100644
index 0000000..7f8e365
--- /dev/null
+++ b/examples/mp-rest-jwt-jwk/src/test/java/org/superbiz/rest/TokenUtils.java
@@ -0,0 +1,84 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.superbiz.rest;
+
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jose.crypto.RSASSASigner;
+import com.nimbusds.jwt.SignedJWT;
+import net.minidev.json.JSONObject;
+import net.minidev.json.parser.JSONParser;
+import org.eclipse.microprofile.jwt.Claims;
+
+import java.security.KeyFactory;
+import java.security.PrivateKey;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Base64;
+
+import static com.nimbusds.jose.JOSEObjectType.JWT;
+import static com.nimbusds.jose.JWSAlgorithm.RS256;
+import static com.nimbusds.jwt.JWTClaimsSet.parse;
+import static java.lang.Thread.currentThread;
+import static net.minidev.json.parser.JSONParser.DEFAULT_PERMISSIVE_MODE;
+
+public class TokenUtils {
+
+
+    public static String generateJWTString(String jsonResource, String keyId) throws Exception {
+        byte[] byteBuffer = new byte[16384];
+        currentThread().getContextClassLoader()
+                .getResource(jsonResource)
+                .openStream()
+                .read(byteBuffer);
+
+        JSONParser parser = new JSONParser(DEFAULT_PERMISSIVE_MODE);
+        JSONObject jwtJson = (JSONObject) parser.parse(byteBuffer);
+
+        long currentTimeInSecs = (System.currentTimeMillis() / 1000);
+        long expirationTime = currentTimeInSecs + 1000;
+
+        jwtJson.put(Claims.iat.name(), currentTimeInSecs);
+        jwtJson.put(Claims.auth_time.name(), currentTimeInSecs);
+        jwtJson.put(Claims.exp.name(), expirationTime);
+
+        SignedJWT signedJWT = new SignedJWT(new JWSHeader
+                .Builder(RS256)
+                .keyID(keyId) // /privateKey002.pem
+                .type(JWT)
+                .build(), parse(jwtJson));
+
+        signedJWT.sign(new RSASSASigner(readPrivateKey(keyId)));
+
+        return signedJWT.serialize();
+    }
+
+    public static PrivateKey readPrivateKey(String resourceName) throws Exception {
+        byte[] byteBuffer = new byte[16384];
+        int length = currentThread().getContextClassLoader()
+                .getResource(resourceName)
+                .openStream()
+                .read(byteBuffer);
+
+        String key = new String(byteBuffer, 0, length).replaceAll("-----BEGIN (.*)-----", "")
+                .replaceAll("-----END (.*)----", "")
+                .replaceAll("\r\n", "")
+                .replaceAll("\n", "")
+                .trim();
+
+        return KeyFactory.getInstance("RSA")
+                .generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(key)));
+    }
+}
diff --git a/examples/mp-rest-jwt-jwk/src/test/resources/META-INF/microprofile-config.properties b/examples/mp-rest-jwt-jwk/src/test/resources/META-INF/microprofile-config.properties
new file mode 100644
index 0000000..d16e2e1
--- /dev/null
+++ b/examples/mp-rest-jwt-jwk/src/test/resources/META-INF/microprofile-config.properties
@@ -0,0 +1,3 @@
+org.superbiz.rest.ProductRestClient/mp-rest/url=http://localhost:4444
+mp.jwt.verify.publickey.location=/jwks.json
+mp.jwt.verify.issuer=https://example.com
\ No newline at end of file
diff --git a/examples/mp-rest-jwt-jwk/src/test/resources/arquillian.xml b/examples/mp-rest-jwt-jwk/src/test/resources/arquillian.xml
new file mode 100644
index 0000000..31daf70
--- /dev/null
+++ b/examples/mp-rest-jwt-jwk/src/test/resources/arquillian.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You 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.
+-->
+<arquillian xmlns="http://jboss.org/schema/arquillian"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xsi:schemaLocation="
+              http://jboss.org/schema/arquillian
+              http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
+
+    <container qualifier="server" default="true">
+        <configuration>
+            <property name="httpsPort">-1</property>
+            <property name="httpPort">4444</property>
+            <property name="stopPort">-1</property>
+            <property name="ajpPort">-1</property>
+            <property name="classifier">microprofile</property>
+            <property name="simpleLog">true</property>
+            <property name="cleanOnStartUp">true</property>
+            <property name="dir">target/server</property>
+            <property name="appWorkingDir">target/arquillian</property>
+        </configuration>
+    </container>
+</arquillian>
\ No newline at end of file
diff --git a/examples/mp-rest-jwt-jwk/src/test/resources/jwt-alice.json b/examples/mp-rest-jwt-jwk/src/test/resources/jwt-alice.json
new file mode 100644
index 0000000..efc8c9f
--- /dev/null
+++ b/examples/mp-rest-jwt-jwk/src/test/resources/jwt-alice.json
@@ -0,0 +1,10 @@
+{
+  "iss": "https://example.com",
+  "sub": "24400623",
+  "name": "Alice Wonder",
+  "upn": "alice.wonder@example.com",
+  "preferred_username": "alice",
+  "groups": [
+    "guest"
+  ]
+}
\ No newline at end of file
diff --git a/examples/mp-rest-jwt-jwk/src/test/resources/jwt-john.json b/examples/mp-rest-jwt-jwk/src/test/resources/jwt-john.json
new file mode 100644
index 0000000..6aa3bce
--- /dev/null
+++ b/examples/mp-rest-jwt-jwk/src/test/resources/jwt-john.json
@@ -0,0 +1,10 @@
+{
+  "iss": "https://example.com",
+  "sub": "24400320",
+  "name": "John Doe",
+  "upn": "john.doe@example.com",
+  "preferred_username": "john",
+  "groups": [
+    "guest", "admin"
+  ]
+}
\ No newline at end of file
diff --git a/examples/mp-rest-jwt-jwk/src/test/resources/privateKey002.pem b/examples/mp-rest-jwt-jwk/src/test/resources/privateKey002.pem
new file mode 100644
index 0000000..802446d
--- /dev/null
+++ b/examples/mp-rest-jwt-jwk/src/test/resources/privateKey002.pem
@@ -0,0 +1,24 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCih29wjHAqTF8U/wrBBX1SnGXy
+2pOY84EVW7UifZr1059ERl6nlskLAlxK7nBnUikT97u8yLw+S1cXYHSvTIeZ6U/3yFwUcXN43G+B
+eYxanM/HzgVlc/USAIE/kPMYK8L48G8x9gwzzVQ0RI7IIPjAX8mee4jEUN/QlSVCsXzUOO5DOykr
+ePsQ1FzxBHQ8spsPwOrtPVFFChPkXmmReAIyt5qJv+uAv7eycFtOOOnxTKF8vK4Qmslf957/lPhZ
+LvFTjYHEq/+MmYeLGa/0QcEw5UqpfMuAWUXS0C4j+ocaXQdpE16dxMTY7cL9vnpUdenWwfpEzrmb
+YwhyIOop4YZTAgMBAAECggEAd2SEYapY70mhA1yDet3cfSY04hzdFhuy9Iyk2Exq3DD0K4SCHhxv
+XW4DfGwCGHRLhsaSnBDd7+kKdjq+HNRcPJ0eyIff1Iiu6dcM9pDioOHW5REb97YiDnJef+KsIVJs
+bNC67nmv5xHrzGWcebl24mK7Sne3NXevopsdfwvBBmgFhSi1psnSdmabucT/5ibiG9W6U0/EaUO/
+8qaavClhv1SH8gsoUBXvknrpS5Wr+01Qmre80NXpBd6JVloS32pwnBmWEhsjBUEJMNVM7Hl4Xxh0
+iZBmnqCJqVBGEGYwBS/YlPF0nNcUm4VEorycnuqe3Y6vP1jP+zMPZYAXs0CBkQKBgQD75U/iBnAS
+LePvbuXjoFxZ2CWJS3ukwyUyfJG7z95gn603X4h024t9IIUuH7isWQPpMQ13Rg7MdHzq6+Loqhon
+mBwGYeCHlhRTsmcYORQrgUjcNdaWOmAELYv+V0G9Vz9X/HDUvAczyVExXo4Pjgv7me1rr6vCmJ6N
+KuoGcEArWQKBgQClLV0jFYgj1x+SbPUNkMyUBHK/47bWmbnooTsWzfZxcSU5KLNb7yygaIvGzSSm
+LJJFnlJeJ0597xpVW2ex6Eeg4AK5GNflGNrlmE+SWIKJq7bZx6fChDK3OK77MK8bql566PEUj3bu
+mQg97KtCh4s2xceE/rWxqlOm35GOmdNFiwKBgQDFOJWC8mds1GFSZhG4VyX3cjRxepgkOGY3UTEJ
+S4dhP6PvZu0AEaT1IzEjG9MLneZh/fX9HO0ZR1tG08mlQQmZVo8asCeMAQWJQnVVkdso9OCHCeAp
+XysuGjsxuD/Qby85RH1TEqTQ9x6K+O1hYGYhaDNrzO8+PSBmhuMUh262gQKBgDCdhnEiEDzEP0Wg
+jgudF6llGcUCp7jH7CDc+4A9xJAlBhipswiW/6KCwskTbfr+2VpmO8X8eay1KCIBUibZv+NIq2SB
+PGHwi9TRnHHtXRZaFOpKSkUFFcw5gb7q2E8jOMWiM/qiMxYMspFPTCS7siy/z3aEZUPSZuaRnMzE
+15r7AoGBAMf45K+exgsuWSi18RwQqk/RO8ZVZXSep3XnRdGIFeh4SzaMnT/sUMGyLfnrSOzwxSui
+nIE1a6cgCWBtyYqQ6Lw1Byh9lDs8OTGY/EtY4L/QO8nMmS0NKroYNPUmnKQhg+qOH/AFh/22uDLK
+hEardSx8CzhNujiIGwdg+7F2MiwP
+-----END PRIVATE KEY-----
\ No newline at end of file
diff --git a/examples/mp-rest-jwt-jwk/src/test/resources/privateKey004.pem b/examples/mp-rest-jwt-jwk/src/test/resources/privateKey004.pem
new file mode 100644
index 0000000..23451b5
--- /dev/null
+++ b/examples/mp-rest-jwt-jwk/src/test/resources/privateKey004.pem
@@ -0,0 +1,24 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCI1qiNxfsBIb7n57Ha7gfvae+K
+x+v63J0VUeuShwt0NRr9TLZBe+oH7CnXgLWYl2tacAHaiqVjxG65pyF0WDsWAO2CbVd0G/g5GqcX
+qWOuditshwC2KDWKXUr7wlBlLL1Y0y8EX0swaNjdjcbaHDduyK3T6UlBo/ub9mbBLAhzotKk/fAs
+HDa7cjkg7TkFNtmy1XdRwNGWWb9ypT/SBkkTLoyw1tCJesdfls3/GzDbnoKvuw+xK5OZJXHkzy05
+zAU4qa+SUNucOqJ0zDu6fgi9wy3zCmKcYiacqLgCKIG0NEEaWCPzv2I3IlrXEpiYc17VaQmGPBBn
+9mBtmIIEACCHAgMBAAECggEAbc7MPdDFBvh8iP6N4+Clr4L0PgsGnC3TRFuTzebe0ycWfHPFwbDd
+cfQa85uOnl/MPyuo4SXnaLMmI4cxunpfF94wujxiNIOJYtG3iq5clpCvcgy4DnUf2ePZm0QoXbhU
+TeZSUZDi9nr8pHX0P+zqstUJYQdQyQL9kv9dH+Dk+11eM679NBJW/T+bwKKnfV1NHYhe2Ld9SyoK
+glzLfYy9sF7kagHFtlAfzaGsNvaAXmeZ6YlZsHrBnqOkikwwIJqfcMooL8LK2EWzms7cjFZzonma
+yRQ0W1AwsUDLMjXmLVC4FhfP0WRfZKI1OXCk0oTISi6iIErSwuC2eMWoTiyq8QKBgQDEChvi9T6I
+bpwO043iz8v5Hcj12XQjcxNUQB/nJMY9wM/wvD+2BtvLKOVKXMW7y+GibA/hWJIOXkcTmRi2wdUA
+s+gQbAlofjLVbYkLXM6MENmRTNXOURF0eGGrOOXrzosYZme18GTjfEhy9bAIKG6RhDSxZVF9exoj
+AK00NE9X+QKBgQCysRz09AE0lr7h/ZaEWZm7X7mhYep85HW/3rTYvaTFf4edv0wUB+VjD4tsvitE
+T7b+hsdzrGfW8+Z/Iked+e9lABP0eM6dT4hpkqS3rcUPn59WpcqrYSJ7PHVKmgwbztYdmHiPzxUn
+qkxDvJNhslWGX+9GMjYweLVjgibTkrZcfwKBgF5Z1y4WhrA3PBjOrP06sZsGQNBChmkBW44zBqfC
+xT63a90bXTaIeoR+/Ewb9nb6G3CGveXhMPqFWYQPLRvYkmGyNMCinqySAHlELK8xTZ+QBIawCj8w
+OUxrG+bCjbY+RCfMMaIZPxVVnbDmmoBypTSYApxWfS/9bYjnLHVxr7ZRAoGBAKrLsULMqw5J+99k
+FGcigVdgcry3K3r+nzGqu67i7Ug80jJMM3p95ZoetIRW7GIZ025LAv8kTgheDfV8nXl4+IHQZkJo
+6gvghiFqofhSpqV9S751L1dJu7yRGAcbYPF/bJbTMoE6TR0hoE2qRDDAVRDgR4MP4U3JQo7/Sv81
+HwsfAoGAKWK3v80fbklOs0eP0c8h97d8PwjlbK13h4paFK1D29ftYh9I3T/Fbcr0oGRvAORZ210T
+pODTW2j7SO2Q2jO2dFGTnabAYUoZrgztU97hWG2QGSVjil1HYmgqoKPijkydmW0i0Bj87/1SpFHQ
+G3XrPbgOH3T6Xbezrl9oyJ4qvEQ=
+-----END PRIVATE KEY-----
\ No newline at end of file