Skip to content

Commit

Permalink
This closes #233
Browse files Browse the repository at this point in the history
  • Loading branch information
jeanouii committed Dec 12, 2018
2 parents a44544b + cc865ac commit 185b74a
Show file tree
Hide file tree
Showing 26 changed files with 607 additions and 618 deletions.
303 changes: 303 additions & 0 deletions examples/mp-rest-jwt/README.md
@@ -0,0 +1,303 @@
# MP REST JWT
This is a basic example on how to configure and use MicroProfile JWT in TomEE.

## Run the tests for different scenarios related with JWT validation

mvn clean test

## Configuration in TomEE

The class `MoviesMPJWTConfigurationProvider.java` provides to TomEE the figuration need it for JWT validation.

package org.superbiz.moviefun.rest;

import org.apache.tomee.microprofile.jwt.config.JWTAuthContextInfo;

import javax.enterprise.context.Dependent;
import javax.enterprise.inject.Produces;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Optional;

@Dependent
public class MoviesMPJWTConfigurationProvider {

@Produces
Optional<JWTAuthContextInfo> getOptionalContextInfo() throws NoSuchAlgorithmException, InvalidKeySpecException {
JWTAuthContextInfo contextInfo = new JWTAuthContextInfo();

// todo use MP Config to load the configuration
contextInfo.setIssuedBy("https://server.example.com");

final String pemEncoded = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlivFI8qB4D0y2jy0CfEq" +
"Fyy46R0o7S8TKpsx5xbHKoU1VWg6QkQm+ntyIv1p4kE1sPEQO73+HY8+Bzs75XwR" +
"TYL1BmR1w8J5hmjVWjc6R2BTBGAYRPFRhor3kpM6ni2SPmNNhurEAHw7TaqszP5e" +
"UF/F9+KEBWkwVta+PZ37bwqSE4sCb1soZFrVz/UT/LF4tYpuVYt3YbqToZ3pZOZ9" +
"AX2o1GCG3xwOjkc4x0W7ezbQZdC9iftPxVHR8irOijJRRjcPDtA6vPKpzLl6CyYn" +
"sIYPd99ltwxTHjr3npfv/3Lw50bAkbT4HeLFxTx4flEoZLKO/g0bAoV2uqBhkA9x" +
"nQIDAQAB";
byte[] encodedBytes = Base64.getDecoder().decode(pemEncoded);

final X509EncodedKeySpec spec = new X509EncodedKeySpec(encodedBytes);
final KeyFactory kf = KeyFactory.getInstance("RSA");
final RSAPublicKey pk = (RSAPublicKey) kf.generatePublic(spec);

contextInfo.setSignerKey(pk);

return Optional.of(contextInfo);
}

@Produces
JWTAuthContextInfo getContextInfo() throws InvalidKeySpecException, NoSuchAlgorithmException {
return getOptionalContextInfo().get();
}
}

## Use MicroProfile JWT in TomEE

The JAX-RS resource `MoviesRest.java` contains several endpoint that are secured using the standard
annotation `@RolesAllowed`. MicroProfile JWT takes care of performing the validation for incoming
requests with `Authorization` header providing a signed `Access Token`


package org.superbiz.moviefun.rest;
import org.superbiz.moviefun.Movie;
import org.superbiz.moviefun.MoviesBean;
import javax.annotation.security.RolesAllowed;
import javax.inject.Inject;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import java.util.List;
@Path("cinema")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class MoviesRest {
@Inject
private MoviesBean moviesBean;
@GET
@Produces(MediaType.TEXT_PLAIN)
public String status() {
return "ok";
}
@GET
@Path("/movies")
@RolesAllowed({"crud", "read-only"})
public List<Movie> getListOfMovies() {
return moviesBean.getMovies();
}
@GET
@Path("/movies/{id}")
@RolesAllowed({"crud", "read-only"})
public Movie getMovie(@PathParam("id") int id) {
return moviesBean.getMovie(id);
}
@POST
@Path("/movies")
@RolesAllowed("crud")
public void addMovie(Movie newMovie) {
moviesBean.addMovie(newMovie);
}
@DELETE
@Path("/movies/{id}")
@RolesAllowed("crud")
public void deleteMovie(@PathParam("id") int id) {
moviesBean.deleteMovie(id);
}
@PUT
@Path("/movies")
@RolesAllowed("crud")
public void updateMovie(Movie updatedMovie) {
moviesBean.updateMovie(updatedMovie);
}
}

@Inject
@ConfigProperty(name = "java.runtime.version")
private String javaVersion;
## About the Test architecture

The test cases from this project are builded using Arquillian. The arquillian configuration can be found in
`src/test/resources/arquillian.xml`

The class `TokenUtils.java` is used during the test to act as an Authorization server who generates `Access Tokens` based
on the configuration files `privateKey.pem`,`publicKey.pem`,`Token1.json`, and `Token2.json`.
`nimbus-jose-jwt` is the library used for JWT generation during the tests.

`Token1.json`

{
"iss": "https://server.example.com",
"jti": "a-123",
"sub": "24400320",
"upn": "jdoe@example.com",
"preferred_username": "jdoe",
"aud": "s6BhdRkqt3",
"exp": 1311281970,
"iat": 1311280970,
"auth_time": 1311280969,
"groups": [
"group1",
"group2",
"crud",
"read-only"
]
}


`Token2.json`

{
"iss": "https://server.example.com",
"jti": "a-123",
"sub": "24400320",
"upn": "alice@example.com",
"preferred_username": "alice",
"aud": "s6BhdRkqt3",
"exp": 1311281970,
"iat": 1311280970,
"auth_time": 1311280969,
"groups": [
"read-only"
]
}




## Test Scenarios

`MovieTest.java` contains 4 OAuth2 scenarios for different JWT combinations.

package org.superbiz.moviefun;

import org.apache.cxf.feature.LoggingFeature;
import org.apache.cxf.jaxrs.client.WebClient;
import org.apache.johnzon.jaxrs.JohnzonProvider;
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;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.superbiz.moviefun.rest.ApplicationConfig;
import org.superbiz.moviefun.rest.MoviesMPJWTConfigurationProvider;
import org.superbiz.moviefun.rest.MoviesRest;

import javax.ws.rs.core.Response;
import java.net.URL;
import java.util.Collection;
import java.util.HashMap;
import java.util.logging.Logger;

import static java.util.Collections.singletonList;
import static org.junit.Assert.assertTrue;

@RunWith(Arquillian.class)
public class MoviesTest {

@Deployment(testable = false)
public static WebArchive createDeployment() {
final WebArchive webArchive = ShrinkWrap.create(WebArchive.class, "test.war")
.addClasses(Movie.class, MoviesBean.class, MoviesTest.class)
.addClasses(MoviesRest.class, ApplicationConfig.class)
.addClass(MoviesMPJWTConfigurationProvider.class)
.addAsWebInfResource(new StringAsset("<beans/>"), "beans.xml");

System.out.println(webArchive.toString(true));

return webArchive;
}

@ArquillianResource
private URL base;


private final static Logger LOGGER = Logger.getLogger(MoviesTest.class.getName());

@Test
public void movieRestTest() throws Exception {

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/cinema/").get(String.class);
LOGGER.info("responsePayload = " + responsePayload);
assertTrue(responsePayload.equalsIgnoreCase("ok"));


//POST (Using token1.json with group of claims: [CRUD])
Movie newMovie = new Movie(1, "David Dobkin", "Wedding Crashers");
Response response = webClient.reset()
.path("/rest/cinema/movies")
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + token(1))
.post(newMovie);
LOGGER.info("responseCode = " + response.getStatus());
assertTrue(response.getStatus() == 204);


//GET movies (Using token1.json with group of claims: [read-only])
//This test should be updated to use token2.json once TOMEE- gets resolved.
Collection<? extends Movie> movies = webClient
.reset()
.path("/rest/cinema/movies")
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + token(1))
.getCollection(Movie.class);
LOGGER.info(movies.toString());
assertTrue(movies.size() == 1);


//Should return a 403 since POST require group of claims: [crud] but Token 2 has only [read-only].
Movie secondNewMovie = new Movie(2, "Todd Phillips", "Starsky & Hutch");
Response responseWithError = webClient.reset()
.path("/rest/cinema/movies")
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + token(2))
.post(secondNewMovie);
LOGGER.info("responseCode = " + responseWithError.getStatus());
assertTrue(responseWithError.getStatus() == 403);


//Should return a 401 since the header Authorization is not part of the POST request.
Response responseWith401Error = webClient.reset()
.path("/rest/cinema/movies")
.header("Content-Type", "application/json")
.post(new Movie());
LOGGER.info("responseCode = " + responseWith401Error.getStatus());
assertTrue(responseWith401Error.getStatus() == 401);

}


private String token(int token_type) throws Exception {
HashMap<String, Long> timeClaims = new HashMap<>();
if (token_type == 1) {
return TokenUtils.generateTokenString("/Token1.json", null, timeClaims);
} else {
return TokenUtils.generateTokenString("/Token2.json", null, timeClaims);
}
}

}
58 changes: 2 additions & 56 deletions examples/rest-mp-jwt/pom.xml → examples/mp-rest-jwt/pom.xml
Expand Up @@ -21,10 +21,10 @@
<modelVersion>4.0.0</modelVersion>

<groupId>org.superbiz</groupId>
<artifactId>rest-mp-jwt</artifactId>
<artifactId>mp-rest-jwt</artifactId>
<version>8.0.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>OpenEJB :: Examples :: REST MP-JWT</name>
<name>OpenEJB :: Examples :: MP REST JWT</name>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
Expand Down Expand Up @@ -95,13 +95,6 @@
</dependencies>
</dependencyManagement>

<repositories>
<repository>
<id>apache-m2-snapshot</id>
<name>Apache Snapshot Repository</name>
<url>https://repository.apache.org/content/groups/snapshots</url>
</repository>
</repositories>

<dependencies>
<dependency>
Expand Down Expand Up @@ -164,53 +157,6 @@
<type>pom</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.tomee</groupId>
<artifactId>ziplock</artifactId>
<version>${tomee.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.webjars</groupId>
<artifactId>backbonejs</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>handlebars</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>2.1.0-1</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>json2</artifactId>
<version>20110223</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>less</artifactId>
<version>1.6.0-1</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>requirejs</artifactId>
<version>2.1.10</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>requirejs-text</artifactId>
<version>2.0.10</version>
</dependency>
</dependencies>

<profiles>
Expand Down

0 comments on commit 185b74a

Please sign in to comment.