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