[prev in list] [next in list] [prev in thread] [next in thread] 

List:       openejb-cvs
Subject:    [1/3] tomee git commit: TOMEE-2333: Sample project with MP 1.1 public key configuration
From:       jgallimore () apache ! org
Date:       2018-12-31 15:27:27
Message-ID: 5165db15acc14b108f57bf8299d9fd09 () git ! apache ! org
[Download RAW message or body]

Repository: tomee
Updated Branches:
  refs/heads/master 6e90fbc66 -> 14b62eff3


TOMEE-2333: Sample project with MP 1.1 public key configuration


Project: http://git-wip-us.apache.org/repos/asf/tomee/repo
Commit: http://git-wip-us.apache.org/repos/asf/tomee/commit/632597cc
Tree: http://git-wip-us.apache.org/repos/asf/tomee/tree/632597cc
Diff: http://git-wip-us.apache.org/repos/asf/tomee/diff/632597cc

Branch: refs/heads/master
Commit: 632597ccf15933ca3d28bc6672cdadc693f5445f
Parents: 17674f3
Author: Ivan St. Ivanov <ivan.st.ivanov@gmail.com>
Authored: Wed Dec 19 21:24:26 2018 +0200
Committer: Ivan St. Ivanov <ivan.st.ivanov@gmail.com>
Committed: Mon Dec 31 13:39:34 2018 +0200

----------------------------------------------------------------------
 examples/mp-rest-jwt-public-key/README.adoc     | 110 ++++++++++
 examples/mp-rest-jwt-public-key/pom.xml         | 208 +++++++++++++++++++
 .../superbiz/bookstore/ApplicationConfig.java   |  27 +++
 .../java/org/superbiz/bookstore/model/Book.java |  57 +++++
 .../org/superbiz/bookstore/model/BooksBean.java |  49 +++++
 .../superbiz/bookstore/rest/BookResource.java   |  83 ++++++++
 .../META-INF/microprofile-config.properties     |   1 +
 .../org/superbiz/bookstore/BookstoreTest.java   | 164 +++++++++++++++
 .../java/org/superbiz/bookstore/TokenUtils.java | 116 +++++++++++
 .../src/test/resources/arquillian.xml           |  31 +++
 .../src/test/resources/privateKey.pem           |  28 +++
 11 files changed, 874 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tomee/blob/632597cc/examples/mp-rest-jwt-public-key/README.adoc
                
----------------------------------------------------------------------
diff --git a/examples/mp-rest-jwt-public-key/README.adoc \
b/examples/mp-rest-jwt-public-key/README.adoc new file mode 100644
index 0000000..7a57f2c
--- /dev/null
+++ b/examples/mp-rest-jwt-public-key/README.adoc
@@ -0,0 +1,110 @@
+:index-group: MicroProfile
+:jbake-type: page
+:jbake-status: published
+
+= MP REST JWT with Public key from MP Config
+
+This is an example of how to configure and use MicroProfile JWT 1.1 in TomEE.
+
+== Run the test
+
+This project includes a sample application and an Arquillian test to showcase role \
based access control (RBAC) with JWT in MicroProfile. +In order to run the scenario, \
you can execute the following command: +
+[source, bash]
+----
+mvn clean test
+----
+
+The application represents a book store REST resource with a few endpoints.
+They all expect that the client provides a valid JSON web token (JWT) representing a \
user having certain roles. +The Arquillian test is responsible for generating the \
JWTs and attaching them to the HTTP requests. +
+== Configuration in TomEE
+
+In order to enable JWT at all, you need to annotate your REST application class with \
the `org.eclipse.microprofile.auth.LoginConfig` annotation. +In this example, the \
class is `ApplicationConfig`. +
+Another thing that needs to be done is configuring the public key to verify the \
signature of the JWT that is attached in the `Authorization` header. +It is signed \
upon creation with the issuer private key. +This is done to avoid tempering with the \
token while it travels from the caller to the endpoint. +Usually the JWT issuing \
happens in a special module or microservice responsible for authenticating the users. \
+In this sample project this happens in the `BookstoreTest`. +
+Each MicroProfile JWT supporting runtime should be able to check whether the \
signature is correct and whether the signed content is not changed along the way. +In \
order to do that, it needs to have access to a public key. +This public key may be in \
PKCS#8 PEM, JWK or JWKS format. +Since MP JWT 1.1 (which is supported by TomEE), the \
key may be provided as a string in the `mp.jwt.verify.publickey` config property or \
as a file location or URL specified in the `mp.jwt.verify.publickey.location` config \
property. +
+In this sample project you can see the first option.
+The file `src/main/resource/META-INF/microprofile-config.properties` contains the \
following entry: +
+[source,properties]
+----
+mp.jwt.verify.publickey=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlivFI8qB4D0y2jy0C \
fEqFyy46R0o7S8TKpsx5xbHKoU1VWg6QkQm+ntyIv1p4kE1sPEQO73+HY8+Bzs75XwRTYL1BmR1w8J5hmjVWjc \
6R2BTBGAYRPFRhor3kpM6ni2SPmNNhurEAHw7TaqszP5eUF/F9+KEBWkwVta+PZ37bwqSE4sCb1soZFrVz/UT/ \
LF4tYpuVYt3YbqToZ3pZOZ9AX2o1GCG3xwOjkc4x0W7ezbQZdC9iftPxVHR8irOijJRRjcPDtA6vPKpzLl6CyYnsIYPd99ltwxTHjr3npfv/3Lw50bAkbT4HeLFxTx4flEoZLKO/g0bAoV2uqBhkA9xnQIDAQAB
 +----
+
+== Working with JWT
+
+The `BookResource` class in this sample project shows two cases where you can use \
the MP JWT spec: obtaining the value of a JWT claim and role based access control of \
REST endpoints. +
+=== Obtaining claim values
+
+The JSON web token (JWT) attached in the `Authorization` HTTP header is essentially \
a JSON object containing various attributes. +Those attributes are called _claims_.
+You can obtain the value of each claim inside a CDI bean by injecting it and \
qualifying it with the `@Claim` annotation. +
+For example, if you want to retrieve the preferred username claim, you can do it \
like that: +
+[source,java]
+----
+    @Inject
+    @Claim(standard = Claims.preferred_username)
+    private String userName;
+----
+
+Note that you cannot inject claims this way in a REST resource class that contains \
unauthenticated endpoints too. +TomEE will nevertheless try to extract the claim from \
the JWT. +So if there is no JWT or if the claim is not there, the request will fail.
+
+=== Role based access control (RBAC)
+
+One of the standard claims defined in the MP JWT specification is `groups`.
+It contains a list of strings, which represent the groups to which the caller \
belongs. +The specification does not distinguish user roles and user groups.
+So the `groups` claim may also contain the roles assigned to a given user.
+
+In this regard, MP JWT has great integration with the existing Java EE security \
mechanisms, such as the `@RolesAllowed` annotation. +So the following `BookResource` \
method can be called by users that are either in the `reader` or in the `manager` \
role (or in both): +
+[source,java]
+----
+    @GET
+    @Path("/{id}")
+    @RolesAllowed({"manager", "reader"})
+    public Book getBook(@PathParam("id") int id) {
+        return booksBean.getBook(id);
+    }
+----
+
+However, the method below will result in HTTP code 403 if called by a user that \
lacks the `manager` role in its `groups` claim: +
+[source,java]
+----
+    @POST
+    @RolesAllowed("manager")
+    public void addBook(Book newBook) {
+        booksBean.addBook(newBook);
+    }
+----
+
+== The bookstore test
+
+The sample project contains an Arquillian test \
(`org.superbiz.bookstore.BookstoreTest`) used for a couple of reasons: +
+* Generating the JSON web token (JWT)
+* Showcasing the behavior of TomEE in different situations
+** Retrieving a claim value
+** Calling REST endpoints with appropriate roles
+** Calling a REST endpoint with a wrong role (resulting in HTTP status code 403)
+** Calling a REST endpoint without JWT (resulting in HTTP status code 401)
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tomee/blob/632597cc/examples/mp-rest-jwt-public-key/pom.xml
                
----------------------------------------------------------------------
diff --git a/examples/mp-rest-jwt-public-key/pom.xml \
b/examples/mp-rest-jwt-public-key/pom.xml new file mode 100644
index 0000000..1c6fa0d
--- /dev/null
+++ b/examples/mp-rest-jwt-public-key/pom.xml
@@ -0,0 +1,208 @@
+<?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-public-key</artifactId>
+  <version>8.0.0-SNAPSHOT</version>
+  <packaging>war</packaging>
+  <name>OpenEJB :: Examples :: MP REST JWT Public Key</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>
+  </properties>
+
+  <build>
+    <defaultGoal>install</defaultGoal>
+    <finalName>bookstore</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>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>

http://git-wip-us.apache.org/repos/asf/tomee/blob/632597cc/examples/mp-rest-jwt-public-key/src/main/java/org/superbiz/bookstore/ApplicationConfig.java
                
----------------------------------------------------------------------
diff --git a/examples/mp-rest-jwt-public-key/src/main/java/org/superbiz/bookstore/ApplicationConfig.java \
b/examples/mp-rest-jwt-public-key/src/main/java/org/superbiz/bookstore/ApplicationConfig.java
 new file mode 100644
index 0000000..78afcf9
--- /dev/null
+++ b/examples/mp-rest-jwt-public-key/src/main/java/org/superbiz/bookstore/ApplicationConfig.java
 @@ -0,0 +1,27 @@
+/*
+ * 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.bookstore;
+
+import org.eclipse.microprofile.auth.LoginConfig;
+
+import javax.ws.rs.ApplicationPath;
+import javax.ws.rs.core.Application;
+
+@ApplicationPath("/rest")
+@LoginConfig(authMethod = "MP-JWT")
+public class ApplicationConfig extends Application {
+}

http://git-wip-us.apache.org/repos/asf/tomee/blob/632597cc/examples/mp-rest-jwt-public-key/src/main/java/org/superbiz/bookstore/model/Book.java
                
----------------------------------------------------------------------
diff --git a/examples/mp-rest-jwt-public-key/src/main/java/org/superbiz/bookstore/model/Book.java \
b/examples/mp-rest-jwt-public-key/src/main/java/org/superbiz/bookstore/model/Book.java
 new file mode 100644
index 0000000..63657e4
--- /dev/null
+++ b/examples/mp-rest-jwt-public-key/src/main/java/org/superbiz/bookstore/model/Book.java
 @@ -0,0 +1,57 @@
+/*
+ * 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.bookstore.model;
+
+public class Book {
+
+    private int id;
+    private String title;
+    private String author;
+
+    public Book() {
+    }
+
+    public Book(int id, String title, String author) {
+        this.id = id;
+        this.title = title;
+        this.author = author;
+    }
+
+    public int getId() {
+        return id;
+    }
+
+    public void setId(int id) {
+        this.id = id;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public String getAuthor() {
+        return author;
+    }
+
+    public void setAuthor(String author) {
+        this.author = author;
+    }
+}

http://git-wip-us.apache.org/repos/asf/tomee/blob/632597cc/examples/mp-rest-jwt-public-key/src/main/java/org/superbiz/bookstore/model/BooksBean.java
                
----------------------------------------------------------------------
diff --git a/examples/mp-rest-jwt-public-key/src/main/java/org/superbiz/bookstore/model/BooksBean.java \
b/examples/mp-rest-jwt-public-key/src/main/java/org/superbiz/bookstore/model/BooksBean.java
 new file mode 100644
index 0000000..b17bc35
--- /dev/null
+++ b/examples/mp-rest-jwt-public-key/src/main/java/org/superbiz/bookstore/model/BooksBean.java
 @@ -0,0 +1,49 @@
+/*
+ * 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.bookstore.model;
+
+import javax.enterprise.context.ApplicationScoped;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+@ApplicationScoped
+public class BooksBean {
+
+    private Map<Integer, Book> store = new ConcurrentHashMap<>();
+
+    public void addBook(Book book) {
+        store.put(book.getId(), book);
+    }
+
+    public List<Book> getAll() {
+        return new ArrayList<>(store.values());
+    }
+
+    public Book getBook(int id) {
+        return store.get(id);
+    }
+
+    public void updateBook(Book book) {
+        store.put(book.getId(), book);
+    }
+
+    public void deleteBook(int id) {
+        store.remove(id);
+    }
+}

http://git-wip-us.apache.org/repos/asf/tomee/blob/632597cc/examples/mp-rest-jwt-public-key/src/main/java/org/superbiz/bookstore/rest/BookResource.java
                
----------------------------------------------------------------------
diff --git a/examples/mp-rest-jwt-public-key/src/main/java/org/superbiz/bookstore/rest/BookResource.java \
b/examples/mp-rest-jwt-public-key/src/main/java/org/superbiz/bookstore/rest/BookResource.java
 new file mode 100644
index 0000000..c0752cf
--- /dev/null
+++ b/examples/mp-rest-jwt-public-key/src/main/java/org/superbiz/bookstore/rest/BookResource.java
 @@ -0,0 +1,83 @@
+/*
+ * 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.bookstore.rest;
+
+import org.eclipse.microprofile.jwt.Claim;
+import org.eclipse.microprofile.jwt.Claims;
+import org.superbiz.bookstore.model.Book;
+import org.superbiz.bookstore.model.BooksBean;
+
+import javax.annotation.security.RolesAllowed;
+import javax.enterprise.context.RequestScoped;
+import javax.inject.Inject;
+import javax.ws.rs.*;
+import javax.ws.rs.core.MediaType;
+import java.util.List;
+
+@Path("/bookstore")
+@Produces(MediaType.APPLICATION_JSON)
+@Consumes(MediaType.APPLICATION_JSON)
+@RequestScoped
+public class BookResource {
+
+    @Inject
+    private BooksBean booksBean;
+
+    @Inject
+    @Claim(standard = Claims.preferred_username)
+    private String userName;
+
+    @GET
+    @Path("/me")
+    public String getLoggedUser() {
+        return userName;
+    }
+
+    @GET
+    @RolesAllowed({"manager", "reader"})
+    public List<Book> getAllBooks() {
+        return booksBean.getAll();
+    }
+
+    @GET
+    @Path("/{id}")
+    @RolesAllowed({"manager", "reader"})
+    public Book getBook(@PathParam("id") int id) {
+        return booksBean.getBook(id);
+    }
+
+    @POST
+    @RolesAllowed("manager")
+    public void addBook(Book newBook) {
+        booksBean.addBook(newBook);
+    }
+
+    @PUT
+    @RolesAllowed("manager")
+    public void updateBook(Book updatedBook) {
+        booksBean.updateBook(updatedBook);
+    }
+
+    @DELETE
+    @Path("/{id}")
+    @RolesAllowed("manager")
+    public void deleteBook(@PathParam("id") int id) {
+        booksBean.deleteBook(id);
+    }
+
+
+}

http://git-wip-us.apache.org/repos/asf/tomee/blob/632597cc/examples/mp-rest-jwt-public-key/src/main/resources/META-INF/microprofile-config.properties
                
----------------------------------------------------------------------
diff --git a/examples/mp-rest-jwt-public-key/src/main/resources/META-INF/microprofile-config.properties \
b/examples/mp-rest-jwt-public-key/src/main/resources/META-INF/microprofile-config.properties
 new file mode 100644
index 0000000..f17d97f
--- /dev/null
+++ b/examples/mp-rest-jwt-public-key/src/main/resources/META-INF/microprofile-config.properties
 @@ -0,0 +1 @@
+mp.jwt.verify.publickey=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlivFI8qB4D0y2jy0C \
fEqFyy46R0o7S8TKpsx5xbHKoU1VWg6QkQm+ntyIv1p4kE1sPEQO73+HY8+Bzs75XwRTYL1BmR1w8J5hmjVWjc \
6R2BTBGAYRPFRhor3kpM6ni2SPmNNhurEAHw7TaqszP5eUF/F9+KEBWkwVta+PZ37bwqSE4sCb1soZFrVz/UT/ \
LF4tYpuVYt3YbqToZ3pZOZ9AX2o1GCG3xwOjkc4x0W7ezbQZdC9iftPxVHR8irOijJRRjcPDtA6vPKpzLl6CyYnsIYPd99ltwxTHjr3npfv/3Lw50bAkbT4HeLFxTx4flEoZLKO/g0bAoV2uqBhkA9xnQIDAQAB
 \ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tomee/blob/632597cc/examples/mp-rest-jwt-public-key/src/test/java/org/superbiz/bookstore/BookstoreTest.java
                
----------------------------------------------------------------------
diff --git a/examples/mp-rest-jwt-public-key/src/test/java/org/superbiz/bookstore/BookstoreTest.java \
b/examples/mp-rest-jwt-public-key/src/test/java/org/superbiz/bookstore/BookstoreTest.java
 new file mode 100644
index 0000000..66ef7c4
--- /dev/null
+++ b/examples/mp-rest-jwt-public-key/src/test/java/org/superbiz/bookstore/BookstoreTest.java
 @@ -0,0 +1,164 @@
+/*
+ * 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.bookstore;
+
+import com.nimbusds.jose.JOSEObjectType;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jose.crypto.RSASSASigner;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
+import net.minidev.json.JSONObject;
+import org.apache.cxf.feature.LoggingFeature;
+import org.apache.cxf.jaxrs.client.WebClient;
+import org.apache.johnzon.jaxrs.JohnzonProvider;
+import org.eclipse.microprofile.jwt.Claims;
+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.spec.WebArchive;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.superbiz.bookstore.model.Book;
+
+import javax.ws.rs.core.Response;
+import java.net.URL;
+import java.security.PrivateKey;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.logging.Logger;
+
+import static java.util.Collections.singletonList;
+import static org.junit.Assert.assertEquals;
+import static org.superbiz.bookstore.TokenUtils.readPrivateKey;
+
+@RunWith(Arquillian.class)
+public class BookstoreTest {
+
+    private final static Logger LOGGER = \
Logger.getLogger(BookstoreTest.class.getName()); +
+    @Deployment(testable = false)
+    public static WebArchive createDeployment() {
+        final WebArchive webArchive = ShrinkWrap.create(WebArchive.class, \
"test.war") +                .addPackages(true, ApplicationConfig.class.getPackage())
+                .addAsResource("META-INF/microprofile-config.properties");
+
+        System.out.println("Deployment: " + webArchive.toString(true));
+
+        return webArchive;
+    }
+
+    @ArquillianResource
+    private URL base;
+
+    @Test
+    public void movieRestTest() throws Exception {
+        final WebClient webClient = WebClient
+                .create(base.toExternalForm(), singletonList(new \
JohnzonProvider<>()), +                        singletonList(new LoggingFeature()), \
null); +
+
+        // Testing REST endpoint that returns the value of a JWT claim
+        String responsePayload = webClient.reset()
+                .path("/rest/bookstore/me")
+                .header("Authorization", "Bearer " + token(true))
+                .get(String.class);
+        LOGGER.info("responsePayload = " + responsePayload);
+        assertEquals("alice", responsePayload);
+
+
+        // Testing REST endpoint with group claims manager
+        Book newBook = new Book(1, "The Lord of the Rings", "J.R.R.Tolkien");
+        Response response = webClient.reset()
+                .path("/rest/bookstore")
+                .header("Content-Type", "application/json")
+                .header("Authorization", "Bearer " + token(true))
+                .post(newBook);
+        LOGGER.info("responseCode = " + response.getStatus());
+        assertEquals(204, response.getStatus());
+
+        // Testing REST endpoint with group claims reader
+        Collection<? extends Book> books = webClient
+                .reset()
+                .path("/rest/bookstore")
+                .header("Content-Type", "application/json")
+                .header("Authorization", "Bearer " + token(false))
+                .getCollection(Book.class);
+        LOGGER.info(books.toString());
+        assertEquals(1, books.size());
+
+
+        // Should return a 403 since POST requires group claim manager but provided \
token has only reader. +        Book secondBook = new Book(2, "Mistborn: The Final \
Empire", "Brandon Sanderson"); +        Response responseWithError = \
webClient.reset() +                .path("/rest/bookstore")
+                .header("Content-Type", "application/json")
+                .header("Authorization", "Bearer " + token(false))
+                .post(secondBook);
+        LOGGER.info("responseCode = " + responseWithError.getStatus());
+        assertEquals(403, responseWithError.getStatus());
+
+
+        // Should return a 401 since the POST request lacks the Authorization header
+        Response responseWith401Error = webClient.reset()
+                .path("/rest/bookstore")
+                .header("Content-Type", "application/json")
+                .post(new Book());
+        LOGGER.info("responseCode = " + responseWith401Error.getStatus());
+        assertEquals(401, responseWith401Error.getStatus());
+    }
+
+    private String token(boolean managerUser) {
+        JSONObject claims = new JSONObject();
+
+        claims.put(Claims.iss.name(), "https://server.example.com");
+        claims.put(Claims.upn.name(), managerUser ? "alice@example.com" : \
"bob@exmaple.com"); +        long currentTimeInSecs = System.currentTimeMillis() / \
1000; +        claims.put(Claims.iat.name(), currentTimeInSecs);
+        claims.put(Claims.auth_time.name(), currentTimeInSecs);
+        claims.put(Claims.exp.name(), currentTimeInSecs + 300);
+        claims.put(Claims.jti.name(), "a-123");
+        claims.put(Claims.sub.name(), "24400320");
+        claims.put(Claims.preferred_username.name(), managerUser ? "alice" : "bob");
+        claims.put(Claims.aud.name(), "s6BhdRkqt3");
+        List<String> groups = new ArrayList<>();
+        if (managerUser) {
+            groups.add("manager");
+            groups.add("reader");
+        } else {
+            groups.add("reader");
+        }
+        claims.put(Claims.groups.name(), groups);
+
+        try {
+            PrivateKey pk = readPrivateKey("/privateKey.pem");
+            JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256)
+                    .keyID("/privateKey.pem")
+                    .type(JOSEObjectType.JWT)
+                    .build();
+
+            JWTClaimsSet claimsSet = JWTClaimsSet.parse(claims);
+            SignedJWT jwt = new SignedJWT(header, claimsSet);
+            jwt.sign(new RSASSASigner(pk));
+            return jwt.serialize();
+        } catch (Exception e) {
+            throw new RuntimeException("Could not sign JWT");
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/tomee/blob/632597cc/examples/mp-rest-jwt-public-key/src/test/java/org/superbiz/bookstore/TokenUtils.java
                
----------------------------------------------------------------------
diff --git a/examples/mp-rest-jwt-public-key/src/test/java/org/superbiz/bookstore/TokenUtils.java \
b/examples/mp-rest-jwt-public-key/src/test/java/org/superbiz/bookstore/TokenUtils.java
 new file mode 100644
index 0000000..7bd1a87
--- /dev/null
+++ b/examples/mp-rest-jwt-public-key/src/test/java/org/superbiz/bookstore/TokenUtils.java
 @@ -0,0 +1,116 @@
+/*
+ * 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.bookstore;
+
+import java.io.InputStream;
+import java.security.*;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Base64;
+
+/**
+ * Utilities for generating a JWT for testing
+ */
+public class TokenUtils {
+
+    private TokenUtils() {
+    }
+
+    /**
+     * Read a PEM encoded private key from the classpath
+     *
+     * @param pemResName - key file resource name
+     * @return PrivateKey
+     * @throws Exception on decode failure
+     */
+    public static PrivateKey readPrivateKey(String pemResName) throws Exception {
+        InputStream contentIS = TokenUtils.class.getResourceAsStream(pemResName);
+        byte[] tmp = new byte[4096];
+        int length = contentIS.read(tmp);
+        return decodePrivateKey(new String(tmp, 0, length));
+    }
+
+    /**
+     * Read a PEM encoded public key from the classpath
+     *
+     * @param pemResName - key file resource name
+     * @return PublicKey
+     * @throws Exception on decode failure
+     */
+    public static PublicKey readPublicKey(String pemResName) throws Exception {
+        InputStream contentIS = TokenUtils.class.getResourceAsStream(pemResName);
+        byte[] tmp = new byte[4096];
+        int length = contentIS.read(tmp);
+        return decodePublicKey(new String(tmp, 0, length));
+    }
+
+    /**
+     * Generate a new RSA keypair.
+     *
+     * @param keySize - the size of the key
+     * @return KeyPair
+     * @throws NoSuchAlgorithmException on failure to load RSA key generator
+     */
+    public static KeyPair generateKeyPair(int keySize) throws \
NoSuchAlgorithmException { +        KeyPairGenerator keyPairGenerator = \
KeyPairGenerator.getInstance("RSA"); +        keyPairGenerator.initialize(keySize);
+        return keyPairGenerator.genKeyPair();
+    }
+
+    /**
+     * Decode a PEM encoded private key string to an RSA PrivateKey
+     *
+     * @param pemEncoded - PEM string for private key
+     * @return PrivateKey
+     * @throws Exception on decode failure
+     */
+    public static PrivateKey decodePrivateKey(String pemEncoded) throws Exception {
+        pemEncoded = removeBeginEnd(pemEncoded);
+        byte[] pkcs8EncodedBytes = Base64.getDecoder().decode(pemEncoded);
+
+        // extract the private key
+
+        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8EncodedBytes);
+        KeyFactory kf = KeyFactory.getInstance("RSA");
+        return kf.generatePrivate(keySpec);
+    }
+
+    /**
+     * Decode a PEM encoded public key string to an RSA PublicKey
+     *
+     * @param pemEncoded - PEM string for private key
+     * @return PublicKey
+     * @throws Exception on decode failure
+     */
+    public static PublicKey decodePublicKey(String pemEncoded) throws Exception {
+        pemEncoded = removeBeginEnd(pemEncoded);
+        byte[] encodedBytes = Base64.getDecoder().decode(pemEncoded);
+
+        X509EncodedKeySpec spec = new X509EncodedKeySpec(encodedBytes);
+        KeyFactory kf = KeyFactory.getInstance("RSA");
+        return kf.generatePublic(spec);
+    }
+
+    private static String removeBeginEnd(String pem) {
+        pem = pem.replaceAll("-----BEGIN (.*)-----", "");
+        pem = pem.replaceAll("-----END (.*)----", "");
+        pem = pem.replaceAll("\r\n", "");
+        pem = pem.replaceAll("\n", "");
+        return pem.trim();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/tomee/blob/632597cc/examples/mp-rest-jwt-public-key/src/test/resources/arquillian.xml
                
----------------------------------------------------------------------
diff --git a/examples/mp-rest-jwt-public-key/src/test/resources/arquillian.xml \
b/examples/mp-rest-jwt-public-key/src/test/resources/arquillian.xml new file mode \
100644 index 0000000..764e42c
--- /dev/null
+++ b/examples/mp-rest-jwt-public-key/src/test/resources/arquillian.xml
@@ -0,0 +1,31 @@
+<?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:xsi="http://www.w3.org/2001/XMLSchema-instance"
+>
+
+    <container qualifier="tomee" default="true">
+        <configuration>
+            <property name="httpPort">-1</property>
+            <property name="stopPort">-1</property>
+            <property name="classifier">microprofile</property>
+            <property name="dir">target/apache-tomee-remote</property>
+            <property \
name="appWorkingDir">target/arquillian-test-working-dir</property> +        \
</configuration> +    </container>
+</arquillian>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tomee/blob/632597cc/examples/mp-rest-jwt-public-key/src/test/resources/privateKey.pem
                
----------------------------------------------------------------------
diff --git a/examples/mp-rest-jwt-public-key/src/test/resources/privateKey.pem \
b/examples/mp-rest-jwt-public-key/src/test/resources/privateKey.pem new file mode \
100644 index 0000000..e20d80b
--- /dev/null
+++ b/examples/mp-rest-jwt-public-key/src/test/resources/privateKey.pem
@@ -0,0 +1,28 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCWK8UjyoHgPTLa
+PLQJ8SoXLLjpHSjtLxMqmzHnFscqhTVVaDpCRCb6e3Ii/WniQTWw8RA7vf4djz4H
+OzvlfBFNgvUGZHXDwnmGaNVaNzpHYFMEYBhE8VGGiveSkzqeLZI+Y02G6sQAfDtN
+qqzM/l5QX8X34oQFaTBW1r49nftvCpITiwJvWyhkWtXP9RP8sXi1im5Vi3dhupOh
+nelk5n0BfajUYIbfHA6ORzjHRbt7NtBl0L2J+0/FUdHyKs6KMlFGNw8O0Dq88qnM
+uXoLJiewhg9332W3DFMeOveel+//cvDnRsCRtPgd4sXFPHh+UShkso7+DRsChXa6
+oGGQD3GdAgMBAAECggEAAjfTSZwMHwvIXIDZB+yP+pemg4ryt84iMlbofclQV8hv
+6TsI4UGwcbKxFOM5VSYxbNOisb80qasb929gixsyBjsQ8284bhPJR7r0q8h1C+jY
+URA6S4pk8d/LmFakXwG9Tz6YPo3pJziuh48lzkFTk0xW2Dp4SLwtAptZY/+ZXyJ6
+96QXDrZKSSM99Jh9s7a0ST66WoxSS0UC51ak+Keb0KJ1jz4bIJ2C3r4rYlSu4hHB
+Y73GfkWORtQuyUDa9yDOem0/z0nr6pp+pBSXPLHADsqvZiIhxD/O0Xk5I6/zVHB3
+zuoQqLERk0WvA8FXz2o8AYwcQRY2g30eX9kU4uDQAQKBgQDmf7KGImUGitsEPepF
+KH5yLWYWqghHx6wfV+fdbBxoqn9WlwcQ7JbynIiVx8MX8/1lLCCe8v41ypu/eLtP
+iY1ev2IKdrUStvYRSsFigRkuPHUo1ajsGHQd+ucTDf58mn7kRLW1JGMeGxo/t32B
+m96Af6AiPWPEJuVfgGV0iwg+HQKBgQCmyPzL9M2rhYZn1AozRUguvlpmJHU2DpqS
+34Q+7x2Ghf7MgBUhqE0t3FAOxEC7IYBwHmeYOvFR8ZkVRKNF4gbnF9RtLdz0DMEG
+5qsMnvJUSQbNB1yVjUCnDAtElqiFRlQ/k0LgYkjKDY7LfciZl9uJRl0OSYeX/qG2
+tRW09tOpgQKBgBSGkpM3RN/MRayfBtmZvYjVWh3yjkI2GbHA1jj1g6IebLB9SnfL
+WbXJErCj1U+wvoPf5hfBc7m+jRgD3Eo86YXibQyZfY5pFIh9q7Ll5CQl5hj4zc4Y
+b16sFR+xQ1Q9Pcd+BuBWmSz5JOE/qcF869dthgkGhnfVLt/OQzqZluZRAoGAXQ09
+nT0TkmKIvlza5Af/YbTqEpq8mlBDhTYXPlWCD4+qvMWpBII1rSSBtftgcgca9XLB
+MXmRMbqtQeRtg4u7dishZVh1MeP7vbHsNLppUQT9Ol6lFPsd2xUpJDc6BkFat62d
+Xjr3iWNPC9E9nhPPdCNBv7reX7q81obpeXFMXgECgYEAmk2Qlus3OV0tfoNRqNpe
+Mb0teduf2+h3xaI1XDIzPVtZF35ELY/RkAHlmWRT4PCdR0zXDidE67L6XdJyecSt
+FdOUH8z5qUraVVebRFvJqf/oGsXc4+ex1ZKUTbY0wqY1y9E39yvB3MaTmZFuuqk8
+f3cg+fr8aou7pr9SHhJlZCU=
+-----END RSA PRIVATE KEY-----


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic