[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