JAX-RS Security using JSON Web Encryption (JWE) with JWS/JWT for Authentication and Authorization
JAX-RS Security using JSON Web Encryption(JWE) with JWK/JWS/JWT for Authentication and Authorization Example
In this tutorial we will discuss how to secure JAX-RS RESTful web services using JSON Web Encryption(JWE), JSON Web Key (JWK), JSON Web Signature(JWS), and JSON Web Tokens(JWT) for Authentication and Authorization. JSON Web Encryption (JWE) encrypted content using Javascript Object Notation (JSON) based structures. In our example implementation, we will be using Symmetric Encryption where the receiver and sender share a common key. In the next tutorial JAX-RS Security using JSON Web Encryption(JWE) with AngularJS, Bootstrap, Grid-UI and MongoDB Example we discuss the User Interface (UI) components and how they integrate with the backend.
What is JSON Web Encryption (JWE)?
JSON Web Encryption, JWE for short, are encrypted using cryptographic algorithms and serialized for tokenization in HTTP authorization headers. In order to ensure the message or token has not been altered in any way the token contains a digital signature (JWS) that is cryptographically encrypted using a strong algorithm such as HMAC SHA-256.
CONTENT MASTER KEY ENCRYPTION
JWE supports three forms of Content Master Key (CMK) encryption:
- Asymmetric encryption under the recipient’s public key.
- Symmetric encryption under a key shared between the sender and receiver.
- Symmetric encryption under a key agreed upon between the sender and receiver.
Structure of JSON Web Encryption Compact Serialization
A JSON Web Encryption compact serialization is structured into five parts: The JWE Protected Header, JWE Encrypted Key, JWE Initialization Vector, JWE Ciphertext, and the JWE Authentication Tag separated by period character (.).

JWT Token encrypted using JWE and signed with JWS
Below you can see an actual encrypted JSON Web Token using JWE and base64 encoded and signed with JWS to ensure the contents have not been modified in any way.
eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjpudWxsLCJ jdHkiOiJKV1QifQ..TKJLnTuWFr-c88OpGrwWWw.QMr2usYlLPj4EosyZ-VFHopCkavJE QucDlPCjNaA2Rk78B1BUwci2sb6m8ZVveXKNPGGu6yl3Yd4tE_4-LyVX_kgQFEdxjAO9Y kwJ-iANdrBdomjrmarNXyqapgHuJ0Z5aTKKzSc8mbOlFbcohHp9eexDjbI1Rgy7Fxzgez eOkqkGbIuy8KGI0siCBFp6ttm5rfePCU7bjeBkPjECPk8WvxMyH9VmLJArUu1vZnEO0AZ Qvcmc8ijmId4ezX1a89KmOkxh-I_h3H8DU9Yx7On7JKonHbm7xFx9jH4nwPVtl0FB2LW7 EdZtD-baH2tgSz8jiDSjEkgaEtp61wGgynG9BG_XlO0mw-Imu_aFjz2j9bMPpZUFdlHrE ljqBYRcP9d.yrsAzczT88htfobE1B_9lg
Getting Started
In order to run this tutorial yourself, you will need the following:
- Java JDK 1.6 or greater
- Favorite IDE Spring Tool Suite (STS), Eclipse IDE or NetBeans (I happen to be using STS because it comes with a Tomcat server built-in)
- Tomcat 7 or greater or other popular container (Weblogic, Websphere, Glassfish, JBoss, VMWare vFabric, etc). For this tutorial I am using VMware vFabric tc Server Developer Edition which is essentially an enhanced Tomcat instance integrated with Spring STS
- Jersey JAX-RS
- MongoDB
- Jackson Mapper for Object to JSON and vice-versa serialization/deserialization
- Http Client provids an efficient and feature-rich package implementing the client side of the most recent HTTP standards and recommendations
- JOSE4j (Javascript Object Signing and Encryption)
- log4J (for logging purposes)
Required Libraries
Copy all of the following jars to WebContent->WEB-INF->lib folder.
asm-3.1.jar commons-codec-1.9.jar commons-logging-1.2.jar fluent-hc-4.5.jar httpclient-4.5.jar httpclient-cache-4.5.jar httpclient-win-4.5.jar httpcore-4.4.1.jar httpmime-4.5.jar jackson-core-asl-1.9.2.jar jackson-jaxrs-1.9.2.jar jackson-mapper-asl-1.9.2.jar jackson-xc-1.9.2.jar jersey-client-1.18.jar jersey-core-1.18.jar jersey-json-1.18.jar jersey-server-1.18.jar jersey-servlet-1.18.jar jna-4.1.0.jar jna-platform-4.1.0.jar jose4j-0.4.4.jar json-simple-1.1.1.jar jsr311-api-1.1.1.jar log4j-1.2.17.jar mongo-java-driver-3.0.2.jar persistence-api-1.0.2.jar slf4j-api-1.7.13.jar
Complete Project Overview
I have added the project overview to give you a full view of the structure and show you all files contained in this sample project.

RESTful Web Service End Points
Restricted URIs will be shown with LOCK icon in the table below.
# | URI | Method | Description |
---|---|---|---|
1 | /rest/security/status | GET | Displays the current status of the API being used. Non-restricted REST end-point |
2 | /rest/security/authenticate | GET | Authenticates the user by using the username and password that is passed in the header against the User in the User Collection in MongoDB data store. |
3 | /rest/security/getallroles | GET | Retrieves all roles for a given token by returning the roles as a JSON array. **Restricted REST end-point |
4 | /rest/security/showallitems | GET | Retrieves all items in our MongoDB datastore return the entire collection as a JSON array. **Restricted REST end-point |
JSON Web Encryption Authorization and Authentication Communication Flow

1 – Initial Request for Protected Resource
C:\curl>curl -H "Content-Type: application/json" -H "username: apacheuser" -H "password: Summer95!" -v -X POST http://localhost:8080/JweSecurityExample/rest/security/authenticate * Trying 127.0.0.1... connected * Connected to localhost (127.0.0.1) port 8080 (#0) > POST /JweSecurityExample/rest/security/authenticate HTTP/1.1 > Host: localhost:8080 > User-Agent: curl/7.48.0 > Accept: */* > Content-Type: application/json > username: apacheuser > password: Summer95!
Username/Password in Header
For security reason we recommend that the username and password be included in the HTTP Headers instead of HTTP Parameters via (@QueryParam). This is especially important when using TLS/SSL as it will guarantee that request data is encrypted end to end and prevent man in the middle attacks.
2 – Server Responds with Success Code and Payload (on Success)
At this point, our server responds with the encrypted and digitally signed JWT token for us to use.
< HTTP/1.1 200 OK < Server: Apache-Coyote/1.1 < Content-Type: application/json < Transfer-Encoding: chunked < Date: Sat, 12 Mar 2016 16:07:34 GMT < {"message":"eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjpudWxsLCJ jdHkiOiJKV1QifQ..upoa4h2LsxIbPfEv2gMmjQ.e0OCTvkFPOy5LVlcqlstOz3ZlAZXjymHQNd cYQh7O-hh4jJEeQDPaHXafvysU_OEImB9qPrp1uDfp0ZdDh6ISvZxARIPRtJ-GyFi_X0fuBpacF 7FAhu7uObEBl60QulWS4msJLOKt3NE2g-Xskl1-3_SGhdbku5w0wQ66_TkZGS9DXtpCP5emeEvO dJKsJ00Bbd0Fw1eAxWvsvSD9dFPG6qJhKR-V4tXN4semV17c7uH6INWzsaVCAkqGzrcrsZNL316 wcCZhq279FWj7Kz2zwGDKA1aMFgQ8Kv5UdH566xW8CXMK785MLs1FNvaKj4Q_04vR-p7UTD5EMX IVJq6xAE8webTdBrebdOrIlKx4CVXmovyGAJGQicVmuTHIshz._mw0nYAfCIAD-1eqto33jw" ,"status_code":200} * Connection #0 to host localhost left intact
3 – JSON Web Encryption (JWE)/JSON Web Token in Header
All subsequent calls should contain this encrypted JWT Token as shown below (carriage returns have been added for readability).
curl -H "Content-Type: application/json" -H "token: eyJhbGciOiJkaXIiLCJlbmMiO iJBMTI4Q0JDLUhTMjU2Iiwia2lkIjpudWxsLCJjdHkiOiJKV1QifQ..upoa4h2LsxIbPfEv2gMmjQ .e0OCTvkFPOy5LVlcqlstOz3ZlAZXjymHQNdcYQh7O-hh4jJEeQDPaHXafvysU_OEImB9qPrp1uDf p0ZdDh6ISvZxARIPRtJ-GyFi_X0fuBpacF7FAhu7uObEBl60QulWS4msJLOKt3NE2g-Xskl1-3_SG hdbku5w0wQ66_TkZGS9DXtpCP5emeEvOdJKsJ00Bbd0Fw1eAxWvsvSD9dFPG6qJhKR-V4tXN4semV 17c7uH6INWzsaVCAkqGzrcrsZNL316wcCZhq279FWj7Kz2zwGDKA1aMFgQ8Kv5UdH566xW8CXMK78 5MLs1FNvaKj4Q_04vR-p7UTD5EMXIVJq6xAE8webTdBrebdOrIlKx4CVXmovyGAJGQicVmuTHIshz ._mw0nYAfCIAD-1eqto33jw" -v -X GET http://localhost:8080/JweSecurityExample/rest/security/showallitems * Trying ::1... * Connected to localhost (127.0.0.1) port 8080 (#0) > GET /JweSecurityExample/rest/security/showallitems HTTP/1.1 > Host: localhost:8080 > User-Agent: curl/7.48.0 > Accept: */* > Content-Type: application/json > token: eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjpudWxsLCJjdHki OiJKV1QifQ..upoa4h2LsxIbPfEv2gMmjQ.e0OCTvkFPOy5LVlcqlstOz3ZlAZXjymHQNdcYQh7O- hh4jJEeQDPaHXafvysU_OEImB9qPrp1uDfp0ZdDh6ISvZxARIPRtJ-GyFi_X0fuBpacF7FAhu7uOb EBl60QulWS4msJLOKt3NE2g-Xskl1-3_SGhdbku5w0wQ66_TkZGS9DXtpCP5emeEvOdJKsJ00Bbd0 Fw1eAxWvsvSD9dFPG6qJhKR-V4tXN4semV17c7uH6INWzsaVCAkqGzrcrsZNL316wcCZhq279FWj7 Kz2zwGDKA1aMFgQ8Kv5UdH566xW8CXMK785MLs1FNvaKj4Q_04vR-p7UTD5EMXIVJq6xAE8webTdB rebdOrIlKx4CVXmovyGAJGQicVmuTHIshz._mw0nYAfCIAD-1eqto33jw >
4 – Server Responds with Success Code and Payload (on Success)
< HTTP/1.1 200 OK < Server: Apache-Coyote/1.1 < Content-Type: application/json < Transfer-Encoding: chunked < Date: Sat, 12 Mar 2016 16:17:34 GMT < [{"_id":"10029T1","item-id":"123","item-name":"KitchenAid Artisan 5 qt. Stand Mixer","price":314.99,"quantity":13},{"_id":"12349K1","item-id": "k10001","item-name":"Keurig K10 MINI Plus Brewing System","price": 79.99,"quantity":36},{"_id":"83949PT","item-id":"EPP1029","item-name": "Electric Power Pressure Cooker XL (8 qt)","price":119.99,"quantity":8}, {"_id":"71829Y","item-id":"IQ50009","item-name":"KitchenIQ 50009 Edge Grip 2 Stage Knife Sharpener, Black","price":5.79,"quantity":23},{"_id": "30814B","item-id":"3081414B","item-name":"La Crosse Technology 308-1414B Wireless Atomic Digital Color Forecast Station","price":49.99, "quantity":6},{"_id":"PAN110CFM","item-id":"110CFM","item-name": "Panasonic FV-11VQ5 WhisperCeiling 110 CFM Ceiling Mounted Fan, White", "price":113.79,"quantity":7},{"_id":"AS4175","item-id":"AS4175", "item-name":"American Standard 4175.300.075 Colony Soft Pull-Down Kitchen Faucet, Stainless Steel","price":120.0,"quantity":9},{"_id": "FM3700B","item-id":"FM3700B","item-name":"PUR Advanced Faucet Water Filter Chrome FM-3700B","price":23.95,"quantity":27},{"_id":"ARC150SB", "item-id":"ARC150SB","item-name":"Aroma 20 Cup Cooked (10 cup uncooked) Digital Rice Cooker, Slow Cooker","price":36.99,"quantity":13},{"_id": "CPT180TST","item-id":"CPT180TST","item-name":"Cuisinart Metal Classic 4-Slice Toaster","price":69.99,"quantity":6},{"_id":"GR4NWPAN","item-id": "GR4NWPAN","item-name":"Cuisinart Griddler and Waffle Maker with Removable Plates","price":99.99,"quantity":13}] * Connection #0 to host localhost left intact
The Item Model (Item.java)
This will be used to as the object which we store and retrieve in order to test out our application. I added it because I wanted my web service to store and retrieve some Java object.
package com.avaldes.model; import javax.persistence.Id; import org.codehaus.jackson.annotate.JsonProperty; public class Item { @Id private String _id; private String itemId; private String itemName; private double itemPrice; private int itemQuantity; public Item() {} public Item(String _id, String itemId, String itemName, double itemPrice, int itemQuantity) { super(); this._id = _id; this.itemId = itemId; this.itemName = itemName; this.itemPrice = itemPrice; this.itemQuantity = itemQuantity; } public String get_id() { return _id; } public void set_id(String _id) { this._id = _id; } @JsonProperty(value = "item-id") public String getItemId() { return itemId; } public void setItemId(String itemId) { this.itemId = itemId; } @JsonProperty(value = "item-name") public String getItemName() { return itemName; } public void setItemName(String itemName) { this.itemName = itemName; } @JsonProperty(value = "price") public double getItemPrice() { return itemPrice; } public void setItemPrice(double itemPrice) { this.itemPrice = itemPrice; } @JsonProperty(value = "quantity") public int getItemQuantity() { return itemQuantity; } public void setItemQuantity(int itemQuantity) { this.itemQuantity = itemQuantity; } @Override public String toString() { return "Item [_id=" + _id + ", itemId=" + itemId + ", itemName=" + itemName + ", itemPrice=" + itemPrice + ", itemQuantity=" + itemQuantity + "]"; } }
The User Model (User.java)
This will be used to as the object which we store and retrieve in order to test out our application. I added it because I wanted my web service to store and retrieve some Java object.
package com.avaldes.model; import java.util.ArrayList; import java.util.List; import javax.persistence.Id; import org.codehaus.jackson.annotate.JsonProperty; public class User { @Id private String _id; private String username; private String password; private String firm; private List<String> rolesList = new ArrayList<String>(); public User() {} public User(String _id, String username, String password, List<String> rolesList) { super(); this._id = _id; this.username = username; this.password = password; this.rolesList = rolesList; } @JsonProperty(value = "_id") public String get_id() { return _id; } public void set_id(String _id) { this._id = _id; } @JsonProperty(value = "username") public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } @JsonProperty(value = "password") public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @JsonProperty(value = "firm") public String getFirm() { return firm; } public void setFirm(String firm) { this.firm = firm; } @JsonProperty(value = "roles") public List<String> getRolesList() { return rolesList; } public void setRolesList(List<String> rolesList) { this.rolesList = rolesList; } @Override public String toString() { return "User [_id=" + _id + ", username=" + username + ", password=" + password + ", rolesList=" + rolesList + "]"; } }
The Singleton Class for Mongo Database (MongoDBSingleton.java)
package com.avaldes.util; import java.io.IOException; import java.util.Properties; import org.apache.log4j.Logger; import com.mongodb.MongoClient; import com.mongodb.MongoClientURI; import com.mongodb.client.MongoDatabase; public class MongoDBSingleton { static Logger logger = Logger.getLogger(MongoDBSingleton.class); private static final String properties_filename = "mongodb.properties"; private static MongoClient mongo = null; private static MongoDatabase mongoDatabase = null; private static String hostname = null; private static int port = 0; private static String username = null; private static String password = null; private static String database = null; private static class Holder { private static final MongoDBSingleton instance = new MongoDBSingleton(); } private MongoDBSingleton() { logger.info("Inside MongoDBSingleton..."); ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); Properties properties = new Properties(); try { logger.info("Reading mongo.properties..."); properties.load( classLoader.getResourceAsStream(properties_filename)); hostname = properties.getProperty("mongodb.hostname"); logger.info("mongodb.hostname....: " + hostname); String portStr = properties.getProperty("mongodb.port"); port = Integer.parseInt(portStr); logger.info("mongodb.port........: " + port); username = properties.getProperty("mongodb.username"); logger.info("mongodb.username....: " + username); password = properties.getProperty("mongodb.password"); logger.info("mongodb.password....: " + password); database = properties.getProperty("mongodb.database"); logger.info("mongodb.database....: " + database); } catch (IOException e) { e.printStackTrace(); } }; public static MongoDBSingleton getInstance() { return Holder.instance; } public MongoClient getMongoClient() { String URI = String.format("mongodb://%s:%s@%s:%d/?authSource=%s", username, password, hostname, port, database); MongoClientURI mongoClientURI = new MongoClientURI(URI); mongo = new MongoClient(mongoClientURI); return mongo; } public MongoDatabase getDatabase() { if (mongoDatabase == null) { mongo = getMongoClient(); } return mongo.getDatabase(database); } }
Complete Program (JWESecurityExample.java)
package com.avaldes.service; import java.io.IOException; import java.util.ArrayList; import java.util.List; import javax.ws.rs.GET; import javax.ws.rs.HeaderParam; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import org.apache.log4j.Logger; import org.bson.Document; import org.bson.json.JsonParseException; import org.codehaus.jackson.JsonGenerationException; import org.codehaus.jackson.map.JsonMappingException; import org.codehaus.jackson.map.ObjectMapper; import org.jose4j.jwe.ContentEncryptionAlgorithmIdentifiers; import org.jose4j.jwe.JsonWebEncryption; import org.jose4j.jwe.KeyManagementAlgorithmIdentifiers; import org.jose4j.jwk.JsonWebKey; import org.jose4j.jwk.JsonWebKey.Factory; import org.jose4j.jws.AlgorithmIdentifiers; import org.jose4j.jws.JsonWebSignature; import org.jose4j.jwt.JwtClaims; import org.jose4j.jwt.MalformedClaimException; import org.jose4j.jwt.consumer.InvalidJwtException; import org.jose4j.jwt.consumer.JwtConsumer; import org.jose4j.jwt.consumer.JwtConsumerBuilder; import org.jose4j.lang.JoseException; import com.avaldes.model.Item; import com.avaldes.model.StatusMessage; import com.avaldes.model.User; import com.avaldes.util.MongoDBSingleton; import com.mongodb.client.MongoDatabase; @Path("/security") public class JWESecurityExample { static Logger logger = Logger.getLogger(JWESecurityExample.class); static JsonWebKey jwKey = null; static { logger.info("Inside static initializer..."); // Setting up Direct Symmetric Encryption and Decryption String jwkJson = "{\"kty\":\"oct\", \"k\":\"9d6722d6-b45c-4dcb-bd73-2e057c44eb93-928390\"}"; try { new JsonWebKey.Factory(); jwKey = Factory.newJwk(jwkJson); } catch (JoseException e) { e.printStackTrace(); } } @Path("/status") @GET @Produces(MediaType.TEXT_HTML) public String returnVersion() { return "JweSecurityExample Status is OK..."; } @Path("/authenticate") @POST @Produces(MediaType.APPLICATION_JSON) public Response authenticateCredentials( @HeaderParam("username") String username, @HeaderParam("password") String password) throws JsonGenerationException, JsonMappingException, IOException { logger.info("Authenticating User Credentials..."); if (username == null) { StatusMessage statusMessage = new StatusMessage(); statusMessage .setStatus(Status.PRECONDITION_FAILED.getStatusCode()); statusMessage.setMessage("Username value is missing!!!"); return Response .status(Status.PRECONDITION_FAILED.getStatusCode()) .entity(statusMessage).build(); } if (password == null) { StatusMessage statusMessage = new StatusMessage(); statusMessage .setStatus(Status.PRECONDITION_FAILED.getStatusCode()); statusMessage.setMessage("Password value is missing!!!"); return Response .status(Status.PRECONDITION_FAILED.getStatusCode()) .entity(statusMessage).build(); } User user = validateUser(username, password); logger.info("User after validateUser => " + user); if (user == null) { StatusMessage statusMessage = new StatusMessage(); statusMessage.setStatus(Status.FORBIDDEN.getStatusCode()); statusMessage .setMessage("Access Denied for this functionality !!!"); logger.info("statusMessage ==> " + statusMessage); return Response.status(Status.FORBIDDEN.getStatusCode()) .entity(statusMessage).build(); } logger.info("User Information => " + user); // Create the Claims, which will be the content of the JWT JwtClaims claims = new JwtClaims(); claims.setIssuer("avaldes.com"); claims.setExpirationTimeMinutesInTheFuture(10); claims.setGeneratedJwtId(); claims.setIssuedAtToNow(); claims.setNotBeforeMinutesInThePast(2); claims.setSubject(user.getUsername()); claims.setStringListClaim("roles", user.getRolesList()); JsonWebSignature jws = new JsonWebSignature(); logger.info("Claims => " + claims.toJson()); // The payload of the JWS is JSON content of the JWT Claims jws.setPayload(claims.toJson()); jws.setKeyIdHeaderValue(jwKey.getKeyId()); jws.setKey(jwKey.getKey()); jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.HMAC_SHA256); String jwt = null; try { jwt = jws.getCompactSerialization(); } catch (JoseException e) { e.printStackTrace(); } JsonWebEncryption jwe = new JsonWebEncryption(); jwe.setAlgorithmHeaderValue( KeyManagementAlgorithmIdentifiers.DIRECT); jwe.setEncryptionMethodHeaderParameter( ContentEncryptionAlgorithmIdentifiers.AES_128_CBC_HMAC_SHA_256); jwe.setKey(jwKey.getKey()); jwe.setKeyIdHeaderValue(jwKey.getKeyId()); jwe.setContentTypeHeaderValue("JWT"); jwe.setPayload(jwt); String jweSerialization = null; try { jweSerialization = jwe.getCompactSerialization(); } catch (JoseException e) { e.printStackTrace(); } StatusMessage statusMessage = new StatusMessage(); statusMessage.setStatus(Status.OK.getStatusCode()); statusMessage.setMessage(jweSerialization); logger.info("statusMessage ==> " + statusMessage); return Response.status(Status.OK.getStatusCode()) .entity(statusMessage).build(); } // --- Protected resource using JWT/JWE Token --- @Path("/getallroles") @GET @Produces(MediaType.APPLICATION_JSON) public Response getAllRoles(@HeaderParam("token") String token) throws JsonGenerationException, JsonMappingException, IOException { logger.info("Inside getAllRoles..."); List<String> allRoles = null; if (token == null) { StatusMessage statusMessage = new StatusMessage(); statusMessage.setStatus(Status.FORBIDDEN.getStatusCode()); statusMessage .setMessage("Access Denied for this functionality !!!"); return Response.status(Status.FORBIDDEN.getStatusCode()) .entity(statusMessage).build(); } logger.info("JWK (1) ===> " + jwKey.toJson()); // Validate Token's authenticity and check claims JwtConsumer jwtConsumer = new JwtConsumerBuilder() .setRequireExpirationTime() .setAllowedClockSkewInSeconds(30) .setRequireSubject() .setExpectedIssuer("avaldes.com") .setDecryptionKey(jwKey.getKey()) .setVerificationKey(jwKey.getKey()).build(); try { // Validate the JWT and process it to the Claims JwtClaims jwtClaims = jwtConsumer.processToClaims(token); logger.info("JWT validation succeeded! " + jwtClaims); try { allRoles = jwtClaims.getStringListClaimValue("roles"); } catch (MalformedClaimException e) { e.printStackTrace(); } } catch (InvalidJwtException e) { logger.error("JWT is Invalid: " + e); StatusMessage statusMessage = new StatusMessage(); statusMessage.setStatus(Status.FORBIDDEN.getStatusCode()); statusMessage .setMessage("Access Denied for this functionality !!!"); return Response.status(Status.FORBIDDEN.getStatusCode()) .entity(statusMessage).build(); } return Response.status(200).entity(allRoles).build(); } // --- Protected resource using JWT/JWE Token --- @Path("/showallitems") @GET @Produces(MediaType.APPLICATION_JSON) public Response showAllItems(@HeaderParam("token") String token) throws JsonGenerationException, JsonMappingException, IOException { Item item = null; logger.info("Inside showAllItems..."); if (token == null) { StatusMessage statusMessage = new StatusMessage(); statusMessage.setStatus(Status.FORBIDDEN.getStatusCode()); statusMessage .setMessage("Access Denied for this functionality !!!"); return Response.status(Status.FORBIDDEN.getStatusCode()) .entity(statusMessage).build(); } logger.info("JWK (1) ===> " + jwKey.toJson()); // Validate Token's authenticity and check claims JwtConsumer jwtConsumer = new JwtConsumerBuilder() .setRequireExpirationTime() .setAllowedClockSkewInSeconds(30) .setRequireSubject() .setExpectedIssuer("avaldes.com") .setDecryptionKey(jwKey.getKey()) .setVerificationKey(jwKey.getKey()).build(); try { // Validate the JWT and process it to the Claims JwtClaims jwtClaims = jwtConsumer.processToClaims(token); logger.info("JWT validation succeeded! " + jwtClaims); } catch (InvalidJwtException e) { logger.error("JWT is Invalid: " + e); StatusMessage statusMessage = new StatusMessage(); statusMessage.setStatus(Status.FORBIDDEN.getStatusCode()); statusMessage .setMessage("Access Denied for this functionality !!!"); return Response.status(Status.FORBIDDEN.getStatusCode()) .entity(statusMessage).build(); } MongoDBSingleton mongoDB = MongoDBSingleton.getInstance(); MongoDatabase db = mongoDB.getDatabase(); List<Document> results = db.getCollection("items").find() .into(new ArrayList<Document>()); int size = results.size(); if (size == 0) { StatusMessage statusMessage = new StatusMessage(); statusMessage .setStatus(Status.PRECONDITION_FAILED.getStatusCode()); statusMessage.setMessage("There are no Items to display !!!"); return Response .status(Status.PRECONDITION_FAILED.getStatusCode()) .entity(statusMessage).build(); } List<Item> allItems = new ArrayList<Item>(); for (Document current : results) { ObjectMapper mapper = new ObjectMapper(); try { logger.info(current.toJson()); item = mapper.readValue(current.toJson(), Item.class); allItems.add(item); } catch (JsonParseException e) { e.printStackTrace(); } catch (JsonMappingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } return Response.status(200).entity(allItems).build(); } private User validateUser(String username, String password) { MongoDBSingleton mongoDB = MongoDBSingleton.getInstance(); MongoDatabase db = mongoDB.getDatabase(); List<Document> results = null; logger.info("Inside of validateUser..."); results = db.getCollection("users") .find(new Document("username", username)).limit(1) .into(new ArrayList<Document>()); int size = results.size(); logger.info("size of results==> " + size); if (size > 0) { for (Document current : results) { ObjectMapper mapper = new ObjectMapper(); User user = null; try { logger.info(current.toJson()); user = mapper.readValue(current.toJson(), User.class); } catch (JsonParseException e) { e.printStackTrace(); } catch (JsonMappingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } if (user != null && username.equals(user.getUsername()) && password.equals(user.getPassword())) { return user; } else { return null; } } return null; } else { return null; } } }
LOG4J Configuration File (log4j.xml)
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE log4j:configuration PUBLIC " -//APACHE//DTD LOG4J 1.2//EN" "log4j.dtd"> <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"> <!-- Appenders --> <appender name="console" class="org.apache.log4j.ConsoleAppender"> <param name="Target" value="System.out" /> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%-5p: %c - %m%n" /> </layout> </appender> <!-- Application Loggers --> <logger name="com.avaldes"> <level value="info" /> </logger> <!-- Root Logger --> <root> <priority value="warn" /> <appender-ref ref="console" /> </root> </log4j:configuration>
Web Deployment Descriptor (web.xml)
This is a pretty straight forward deployment descriptor file – only thing you need to add is the location of you java package in the Jersey ServletContainer entry as init-param. Please ensure you add it to the web.xml file as shown below.
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation= http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> <display-name>JAX-RS JSON Web Token Application</display-name> <welcome-file-list> <welcome-file>index.html</welcome-file> <welcome-file>index.jsp</welcome-file> </welcome-file-list> <servlet> <servlet-name>Jersey REST Service</servlet-name> <servlet-class> com.sun.jersey.spi.container.servlet.ServletContainer </servlet-class> <init-param> <param-name> com.sun.jersey.config.property.packages </param-name> <param-value>com.avaldes</param-value> </init-param> <init-param> <param-name> com.sun.jersey.api.json.POJOMappingFeature </param-name> <param-value>true</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>Jersey REST Service</servlet-name> <url-pattern>/rest/*</url-pattern> </servlet-mapping> </web-app>
MongoDB Items Collections
{ "_id" : "10029T1", "item-id" : "123", "item-name" : "KitchenAid Artisan 5 qt. Stand Mixer", "price" : 314.99, "quantity" : 13 } { "_id" : "12349K1", "item-id" : "k10001", "item-name" : "Keurig K10 MINI Plus Brewing System", "price" : 79.99, "quantity" : 36 } { "_id" : "83949PT", "item-id" : "EPP1029", "item-name" : "Electric Power Pressure Cooker XL (8 qt)", "price" : 119.99, "quantity" : 8 } { "_id" : "71829Y", "item-id" : "IQ50009", "item-name" : "KitchenIQ 50009 Edge Grip 2 Stage Knife Sharpener, Black", "price" : 5.79, "quantity" : 23 } { "_id" : "30814B", "item-id" : "3081414B", "item-name" : "La Crosse Technology 308-1414B Wireless Atomic Digital Color Forecast Station", "price" : 49.99, "quantity" : 6 } { "_id" : "PAN110CFM", "item-id" : "110CFM", "item-name" : "Panasonic FV-11VQ5 WhisperCeiling 110 CFM Ceiling Mounted Fan, White", "price" : 113.79, "quantity" : 7 } { "_id" : "AS4175", "item-id" : "AS4175", "item-name" : "American Standard 4175.300.075 Colony Soft Pull-Down Kitchen Faucet, Stainless Steel", "price" : 120, "quantity" : 9 } { "_id" : "FM3700B", "item-id" : "FM3700B", "item-name" : "PUR Advanced Faucet Water Filter Chrome FM-3700B", "price" : 23.95, "quantity" : 27 } { "_id" : "ARC150SB", "item-id" : "ARC150SB", "item-name" : "Aroma 20 Cup Cooked (10 cup uncooked) Digital Rice Cooker, Slow Cooker", "price" : 36.99, "quantity" : 13 } { "_id" : "CPT180TST", "item-id" : "CPT180TST", "item-name" : "Cuisinart Metal Classic 4-Slice Toaster", "price" : 69.99, "quantity" : 6 } { "_id" : "GR4NWPAN", "item-id" : "GR4NWPAN", "item-name" : "Cuisinart Griddler® and Waffle Maker with Removable Plates", "price" : 99.99, "quantity" : 13 }
MongoDB Users Collections
{ "_id" : "1", "username" : "apacheuser", "password" : "Summer95!", "firm" : "Apache", "roles" : ["client", "admin"] } { "_id" : "2", "username" : "springuser", "password" : "Spring99!", "firm" : "SpringSource", "roles" : ["client"] } { "_id" : "3", "username" : "user3", "password" : "Autumn03!", "firm" : "RedHat", "roles" : ["client"] }
MongoDB Property File
We will be storing all of the MongoDB Database credentials in a property file that will only be accessible by the application running on the server.
#----MongoDB Database Details----- mongodb.hostname=localhost mongodb.port=27017 mongodb.username=webuser mongodb.password=W3b$ervic3s! mongodb.database=jwtDB
Testing out the Web Services
In addition to using our AngularJS/Bootstrap/Grid-UI web application to test out our restful services I used both CURL and Postman which is a Google Chrome Application. Using this tool I validated each of the REST API calls. Please review the screen shots below:
Using CURL to Test out the JWE/JWT/JWS Authorization and Authentication
C:\curl>curl -H "Content-Type: application/json" -H "username: apacheuser" -H "password: Summer95!" -v -X POST http://localhost:8080/JweSecurityExample/rest/security/authenticate * Trying 127.0.0.1... connected * Connected to localhost (127.0.0.1) port 8080 (#0) > POST /JweSecurityExample/rest/security/authenticate HTTP/1.1 > Host: localhost:8080 > User-Agent: curl/7.48.0 > Accept: */* > Content-Type: application/json > username: apacheuser > password: Summer95! < HTTP/1.1 200 OK < Server: Apache-Coyote/1.1 < Content-Type: application/json < Transfer-Encoding: chunked < Date: Sat, 12 Mar 2016 16:07:34 GMT < {"message":"eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjpudWxsLCJ jdHkiOiJKV1QifQ..upoa4h2LsxIbPfEv2gMmjQ.e0OCTvkFPOy5LVlcqlstOz3ZlAZXjymHQNd cYQh7O-hh4jJEeQDPaHXafvysU_OEImB9qPrp1uDfp0ZdDh6ISvZxARIPRtJ-GyFi_X0fuBpacF 7FAhu7uObEBl60QulWS4msJLOKt3NE2g-Xskl1-3_SGhdbku5w0wQ66_TkZGS9DXtpCP5emeEvO dJKsJ00Bbd0Fw1eAxWvsvSD9dFPG6qJhKR-V4tXN4semV17c7uH6INWzsaVCAkqGzrcrsZNL316 wcCZhq279FWj7Kz2zwGDKA1aMFgQ8Kv5UdH566xW8CXMK785MLs1FNvaKj4Q_04vR-p7UTD5EMX IVJq6xAE8webTdBrebdOrIlKx4CVXmovyGAJGQicVmuTHIshz._mw0nYAfCIAD-1eqto33jw" ,"status_code":200} * Connection #0 to host localhost left intact ------------------------------------------------------------ curl -H "Content-Type: application/json" -H "token: eyJhbGciOiJkaXIiLCJlbmMiO iJBMTI4Q0JDLUhTMjU2Iiwia2lkIjpudWxsLCJjdHkiOiJKV1QifQ..upoa4h2LsxIbPfEv2gMmjQ .e0OCTvkFPOy5LVlcqlstOz3ZlAZXjymHQNdcYQh7O-hh4jJEeQDPaHXafvysU_OEImB9qPrp1uDf p0ZdDh6ISvZxARIPRtJ-GyFi_X0fuBpacF7FAhu7uObEBl60QulWS4msJLOKt3NE2g-Xskl1-3_SG hdbku5w0wQ66_TkZGS9DXtpCP5emeEvOdJKsJ00Bbd0Fw1eAxWvsvSD9dFPG6qJhKR-V4tXN4semV 17c7uH6INWzsaVCAkqGzrcrsZNL316wcCZhq279FWj7Kz2zwGDKA1aMFgQ8Kv5UdH566xW8CXMK78 5MLs1FNvaKj4Q_04vR-p7UTD5EMXIVJq6xAE8webTdBrebdOrIlKx4CVXmovyGAJGQicVmuTHIshz ._mw0nYAfCIAD-1eqto33jw" -v -X GET http://localhost:8080/JweSecurityExample/rest/security/showallitems * Trying ::1... * Connected to localhost (127.0.0.1) port 8080 (#0) > GET /JweSecurityExample/rest/security/showallitems HTTP/1.1 > Host: localhost:8080 > User-Agent: curl/7.48.0 > Accept: */* > Content-Type: application/json > token: eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjpudWxsLCJjdHki OiJKV1QifQ..upoa4h2LsxIbPfEv2gMmjQ.e0OCTvkFPOy5LVlcqlstOz3ZlAZXjymHQNdcYQh7O- hh4jJEeQDPaHXafvysU_OEImB9qPrp1uDfp0ZdDh6ISvZxARIPRtJ-GyFi_X0fuBpacF7FAhu7uOb EBl60QulWS4msJLOKt3NE2g-Xskl1-3_SGhdbku5w0wQ66_TkZGS9DXtpCP5emeEvOdJKsJ00Bbd0 Fw1eAxWvsvSD9dFPG6qJhKR-V4tXN4semV17c7uH6INWzsaVCAkqGzrcrsZNL316wcCZhq279FWj7 Kz2zwGDKA1aMFgQ8Kv5UdH566xW8CXMK785MLs1FNvaKj4Q_04vR-p7UTD5EMXIVJq6xAE8webTdB rebdOrIlKx4CVXmovyGAJGQicVmuTHIshz._mw0nYAfCIAD-1eqto33jw > < HTTP/1.1 200 OK < Server: Apache-Coyote/1.1 < Content-Type: application/json < Transfer-Encoding: chunked < Date: Sat, 12 Mar 2016 16:17:34 GMT < [{"_id":"10029T1","item-id":"123","item-name":"KitchenAid Artisan 5 qt. Stand Mixer","price":314.99,"quantity":13},{"_id":"12349K1","item-id": "k10001","item-name":"Keurig K10 MINI Plus Brewing System","price": 79.99,"quantity":36},{"_id":"83949PT","item-id":"EPP1029","item-name": "Electric Power Pressure Cooker XL (8 qt)","price":119.99,"quantity":8}, {"_id":"71829Y","item-id":"IQ50009","item-name":"KitchenIQ 50009 Edge Grip 2 Stage Knife Sharpener, Black","price":5.79,"quantity":23},{"_id": "30814B","item-id":"3081414B","item-name":"La Crosse Technology 308-1414B Wireless Atomic Digital Color Forecast Station","price":49.99, "quantity":6},{"_id":"PAN110CFM","item-id":"110CFM","item-name": "Panasonic FV-11VQ5 WhisperCeiling 110 CFM Ceiling Mounted Fan, White", "price":113.79,"quantity":7},{"_id":"AS4175","item-id":"AS4175", "item-name":"American Standard 4175.300.075 Colony Soft Pull-Down Kitchen Faucet, Stainless Steel","price":120.0,"quantity":9},{"_id": "FM3700B","item-id":"FM3700B","item-name":"PUR Advanced Faucet Water Filter Chrome FM-3700B","price":23.95,"quantity":27},{"_id":"ARC150SB", "item-id":"ARC150SB","item-name":"Aroma 20 Cup Cooked (10 cup uncooked) Digital Rice Cooker, Slow Cooker","price":36.99,"quantity":13},{"_id": "CPT180TST","item-id":"CPT180TST","item-name":"Cuisinart Metal Classic 4-Slice Toaster","price":69.99,"quantity":6},{"_id":"GR4NWPAN","item-id": "GR4NWPAN","item-name":"Cuisinart Griddler and Waffle Maker with Removable Plates","price":99.99,"quantity":13}] * Connection #0 to host localhost left intact ------------------------------------------------------------ C:\curl>curl -H "Content-Type: application/json" -H "token: eyJhbGciO iJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjpudWxsLCJjdHkiOiJKV1Qif Q..h-o8owUQfhL22Q414bk3SQ.Rb-5FvjjZ6hD80MH4t2sMGTqWSoAyYFHBmsW_5YG5dA V7ZgVEHMhXZHdQaSX_ijCEQYfjp_uKCubnATH48BmP3FauWg0_1u-Nbmbedyy2Cefx1UE 7SebOK9P77HNu54NVJtLNu_WqCMCmnEroa1yJ34KSD3i2wFUyASP3n6nU8v7YJp6ySf8A utrB6_vJJP3goVeHWIir-pqaE1VmQ_ub7cWXJaM_8dJDk9C6qHP0Qf2ZqqhzuN6xY-NO_ DKRA6WDcCNfXkD5uwDJjQjt2y9lwCObb7YXkeWVlSP0UqOSsSPZ8KegBh1odYhyIj0cA oEdEgKsLR_ZNsk1sGne6Bj51kHfw82nGfXUpdoSR0U-Tg7GiCUDT6iQ7AAMDUX-wj_.5H THvY-sxMUZgj254jJ1Kw" -v -X GET http://localhost:8080/JweSecurityExample/rest/security/getallroles * Trying ::1... * Connected to localhost (127.0.0.1) port 8080 (#0) > GET /JweSecurityExample/rest/security/getallroles HTTP/1.1 > Host: localhost:8080 > User-Agent: curl/7.48.0 > Accept: */* > Content-Type: application/json > token: eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjpudWx sLCJjdHkiOiJKV1QifQ..h-o8owUQfhL22Q414bk3SQ.Rb-5FvjjZ6hD80MH4t2sMGTq WSoAyYFHBmsW_5YG5dAV7ZgVEHMhXZHdQaSX_ijCEQYfjp_uKCubnATH48BmP3FauWg0 _1u-Nbmbedyy2Cefx1UE7SebOK9P77HNu54NVJtLNu_WqCMCmnEroa1yJ34KSD3i2wFU yASP3n6nU8v7YJp6ySf8AutrB6_vJJP3goVeHWIir-pqaE1VmQ_ub7cWXJaM_8dJDk9C 6qHP0Qf2ZqqhzuN6xY-NO_DKRA6WDcCNfXkD5uwDJjQjt2y9lwCObb7YXkeWVlSP0UqO SsSPZ8KegBh1odYhyIj0cAoEdEgKsLR_ZNsk1sGne6Bj51kHfw82nGfXUpdoSR0U-Tg7 GiCUDT6iQ7AAMDUX-wj_.5HTHvY-sxMUZgj254jJ1Kw > < HTTP/1.1 200 OK < Server: Apache-Coyote/1.1 < Content-Type: application/json < Transfer-Encoding: chunked < Date: Date: Sat, 12 Mar 2016 16:41:53 GMT < ["client","admin"] * Connection #0 to host localhost left intact
Testing Application and POSTMAN Chrome Extension
Download the Complete Source Code
That’s It!
I hope you enjoyed this tutorial. It was certainly a lot of fun putting it together and testing it out. Please continue to share the love and like us so that we can continue bringing you quality tutorials. Happy Coding!!!

Please Share Us on Social Media






Leave a Reply