JAX-RS Security using JSON Web Encryption(JWE) with AngularJS, Bootstrap, Grid-UI and MongoDB Example

JAX-RS Security using JSON Web Encryption(JWE) with AngularJS, Bootstrap, Grid-UI and MongoDB Example

In this tutorial we will discuss how to use AngularJS, Bootstrap and Grid-UI to connect to secure JAX-RS RESTful web services using JWE/JWT/JWS for Authentication and Authorization. In our example implementation, we will be using Symmetric Encryption where the receiver and sender share a common key. In our previous tutorial, JAX-RS Security using JSON Web Encryption (JWE) with JWS/JWT for Authentication and Authorization we discussed the backend aspects including RESTful Web Services using JAX-RS, Jackson, Jose4j (JOSE is short for Javascript Object Signing and Encryption) using JSON Web Encryption(JWE), JSON Web Key (JWK), JSON Web Signature(JWS), and JSON Web Tokens(JWT).

Authentication and Authorization Login Security using AngularJS, Bootstrap, GridUI and MongoDB

In this JSON Web Encryption(JWE) tutorial we will create a simple form-based logon screen using Bootstrap, AngularJS and Grid-UI. This front-end application will integrate with our JAX-RS Restful Web Services to perform the authentication and authorization using JSON Web Tokens, JSON Web Encryption, JSON Web Key, and JSON Web Signature. The logon page will be used to capture the username/password and call the authenticate rest call. Our authenticate rest call will use these credentials and compare them against our MongoDB database. Upon, successful authentication an encrypted authorization token will be returned to the user. The user must then use this token on all subsequent calls by passing it in the request header. All of the restricted end-points will expect the encrypted and valid JWT token to be present in the header.

jaxrs_jwe_security_login3

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 (.).

jwe_structure

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
  • MongoDB
  • AngularJS
  • Bootstrap the most popular HTML, CSS, and JS framework for developing responsive web projects
  • Grid UI

Included Files

In our front-end UI application, the following list of files resides in out include folder.

angular-animate.js
angular-spinner.js
angular-touch.js
angular.js
animate.min.css
app.js
applogin.js
bootstrap.css
bootstrap.js
csv.js
font-awesome.css
font-awesome.min.css
jquery-1.11.3.js
jquery.layout.js
moment.js
pdfmake.js
spin.js
styles.css
ui-bootstrap-tpls-0.13.0.min.js
ui-grid-unstable.css
ui-grid-unstable.js
ui-grid.css
ui-grid.js
vfs_fonts.js

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.

jax-rs_jwe_ui_security_proj

RESTful Web Service End Points

Restricted URIs will be shown with LOCK icon in the table below.

#URIMethodDescription
1/rest/security/statusGETDisplays the current status of the API being used. Non-restricted REST end-point
2/rest/security/authenticateGETAuthenticates 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/getallrolesGETRetrieves all roles for a given username by returning the roles as a JSON array. **Restricted REST end-point
4/rest/security/showallitemsGETRetrieves all items in our MongoDB datastore return the entire collection as a JSON array. **Restricted REST end-point

Our Login Bootstrap/AngularJS Application

In our login screen, I have added 3D shadows and some amount of animation to make the login screen pop and give it some character. In addition, I have added Bootstrap’s validation styles for error, warning, and success messages to provide the user feedback. To use, I have added .has-warning, .has-error, or .has-success to the parent elements. So depending on whether the fields are populated or not, they will change in color from RED to GREEN. In this example, I did not add any special validations like minimum or maximum length requirements.

jaxrs_jwe_security_login1

Reveal Password via jQuery

I have added some jQuery to allow us to show the password field when pressing and holding down the eye_icon. I have chosen to use keypress instead of onClick for security reasons. That way, the password is only ever visible during active user input.

jaxrs_jwe_security_login2

jQuery Javascript Snippet

In the showPassword javascript function, we use jQuery to get the attribute type of an element whose id=password using the #password jquery tagID selector. Using the tagID selector we return back the type of element selected, and if it is of type ‘password’ we change its type to ‘text’ type (which makes the password become visible). Additionally, we look for an element with a tagID of eye_icon and remove a class called fa-eye, and add a class called fa-eye-slash (toggling the icon from eye_icon to eye_icon_slash).

<script type="text/javascript">
 function showPassword() {
  console.log("Inside showPassword...");
  var type = $("#password").attr("type");
  if (type == "password") { 
    $("#password").attr("type", "text"); 
    $("#eye_icon").removeClass('fa fa-eye').addClass('fa fa-eye-slash');
  }
 }
   
 function hidePassword() {
  console.log("Inside hidePassword...");
  var type = $("#password").attr("type");
  if (type == "text") { 
    $("#password").attr("type", "password");
    $("#eye_icon").removeClass('fa fa-eye-slash').addClass('fa fa-eye');
  }
 } 
 ...
</script>  

Password input and input-group-addon Snippet

<span class="input-group-addon">
  <i class="fa fa-key fa-fw"></i>
</span>

<input type="password" class="form-control" 
        id="password" 
        name="password" 
        required 
        ng-model="login.password"  
        placeholder="Password" />
        
<span class="input-group-addon">
  <a href="" onmousedown="showPassword();" 
              onmouseup="hidePassword();" 
              onmouseout="hidePassword();">
    <i id="eye_icon" class="fa fa-eye revealIcon"></i>
  </a>
</span>

Login Screen (login.jsp)

<%@ page language="java" %>
<%@ page import="java.util.*" %>
<%@ page import="java.text.*" %>

<html ng-app="app">
  <head>
    <meta http-equiv="cache-control" content="max-age=0" />
    <meta http-equiv="cache-control" content="no-cache" />
    <meta http-equiv="expires" content="0" />
    <meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" />
    <meta http-equiv="pragma" content="no-cache" /> 
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    
    <!--[if lte IE 7]>
      <style type="text/css"> body { font-size: 85%; } </style>
    <![endif]-->
  
    <!-- 1.3.15 -->
    <script src="include/angular.js"></script>
    <script src="include/angular-touch.js"></script>
    <script src="include/angular-animate.js"></script>
    <script src="include/applogin.js"></script>
    <script src="include/jquery-1.11.3.js"></script>
    <script src="include/jquery.layout.js"></script>
    <script src="include/spin.js"></script>
    <script src="include/angular-spinner.js"></script>
    <link rel="stylesheet" href="include/font-awesome.min.css">
    
    <script src="include/ui-bootstrap-tpls-0.13.0.min.js"></script>
    <script src="include/bootstrap.js"></script>
    
    <link rel="stylesheet" href="include/animate.min.css">    
    <link rel="styleSheet" href="include/styles.css" />
    <link rel="stylesheet" href="include/bootstrap.css">
    
    <script type="text/javascript">
    function showPassword() {
     console.log("Inside showPassword...");
     var type = $("#password").attr("type");
     if (type == "password") { 
      $("#password").attr("type", "text"); 
      $("#eye_icon").removeClass('fa fa-eye').addClass('fa fa-eye-slash');
     }
    }
    
    function hidePassword() {
     console.log("Inside hidePassword...");
     var type = $("#password").attr("type");
     if (type == "text") { 
      $("#password").attr("type", "password");
      $("#eye_icon").removeClass('fa fa-eye-slash').addClass('fa fa-eye');
     }
    }
    
    function submitForm() {
      console.log("Inside submitForm...");
      
      var username = $("#username").val();
      var user_len =  $("#username").val().length;
      
      var plainText = $("#password").val();
      var pw_len =  $("#password").val().length;
      
      if (user_len == 0) {
        $( "#error" ).html("Username is required, please try again...");
        return;
      }
      
      if (pw_len == 0) {
        $( "#error" ).html("Password is required, please try again");
        return;
      }
      var base64Text = window.btoa(unescape(encodeURIComponent(plainText)));
      $("#encoded_pw").val(base64Text);
      
      document.myform.submit();
    };
    
    function clearErrors() {
      console.log("Inside clearErrors...");
      $( "#error" ).html("");
    };
    
    $( document ).ready(function() {
      console.log("Document Ready Now...");
      
      $("#username").keypress(function(event) {
          console.log("keypress event..." + event);
          if (event.which == 13) {
              event.preventDefault();
              submitForm();
          }
      });
      
      $("#password").keypress(function(event) {
        console.log("keypress event..." + event);
        if (event.which == 13) {
            event.preventDefault();
            submitForm();
        }
      });
    });
    </script>
  </head>
  
  <%
  boolean isDebug = false;
  String debugParam = request.getParameter("debug");
  if (debugParam != null && (debugParam.toLowerCase().equals("true") || 
                              debugParam.toLowerCase().equals("yes") || 
                              debugParam.equals("1"))) {
    isDebug = true;
  }
  
  session = request.getSession();
  String error_msg = (String)session.getAttribute("error");
  %>

  <body class="login-background" ng-controller="MainCtrl">
    <script type="text/ng-template" id="myModalContent.html">
  <div class="modal-header-error">
    <h4 class="modal-title-error"><span class="glyphicon glyphicon-alert" 
       aria-hidden="true"></span>  {{modal.title}}</h4>
  </div>
  <div class="modal-body">
    <b>{{modal.message}}</b>
  </div>
  <div class="modal-footer">
    <button class="btn btn-danger" ng-click="ok()">OK</button>
  </div>
  </script>

   <div class="login-panel">
     <div class="animated bounceIn shadow">
      <div class="panel panel-info">
       <div class="panel-heading"><i class="fa fa-lock fa-2x"></i>
        <font class="loginTitle"> Login Security via JWT, JWS and JWE</font></div>
         <div class="panel-body">
          <form id="myform" name="myform" method="POST" action="processLogin.jsp">
           <div class="form-fields">
            <span us-spinner spinner-key="spinner-1"></span>
            <div class="col-lg-10">
             <input id="encoded_pw" name="encoded_pw" type="hidden" 
                ng-model="login.encoded_pw"/>
             
             <div class="form-group has-feedback" ng-class="{'has-error': 
              myform.username.$invalid, 'has-success': myform.username.$valid}">
              <div class="input-group margin-bottom-sm" >
               <span class="input-group-addon"><i class="fa fa-user fa-fw"></i></span>
               <input type="text" class="form-control" id="username" name="username" 
               required ng-model="login.username" placeholder="Username" focus-on="setFocus"/>
              </div>
             </div>
             
             <div><br/></div>
             <div class="form-group has-feedback" ng-class="{'has-error': 
               myform.password.$invalid, 'has-success': myform.password.$valid}">
              <div class="input-group margin-bottom-sm" >
               <span class="input-group-addon"><i class="fa fa-key fa-fw"></i></span>
               <input type="password" class="form-control" id="password" name="password" 
                     required ng-model="login.password"  placeholder="Password" />
               <span class="input-group-addon"><a href="" onmousedown="showPassword();" 
                  onmouseup="hidePassword();" onmouseout="hidePassword();">
                  <i id="eye_icon" class="fa fa-eye revealIcon"></i></a></span>
              </div>
             </div>
             
             <div id="failure-message" class="login-message">
               <p id="error" name="error">
             <% if (error_msg != null) {
               out.print(error_msg);
             } %>
             </p>
             </div>
             
             <div style="float: right;"><br/>
               <button id="clear" type="button" class="btn btn-primary" style="width: 80px;" 
                  ng-click="clearLogin()" onclick="clearErrors()">
               <i class="fa fa-times"></i> Clear</button>
               
               <button id="login" type="button" class="btn btn-primary" 
                style="width: 100px;" onclick="submitForm();">
               <i class="fa fa-chevron-circle-left"></i>&nbsp; Login</button>
             </div>
            </div>
           </div>
          </form>
         </div>
        </div>
      </div>
     <div ng-element-ready="setDefaults('<%=isDebug %>')"></div>
    <div ng-element-ready="init()"></div>
   </div>
  </body>
</html>

Logoff JSP (logoff.jsp)

<%@ page language="java" %>
<%@ page import="java.util.*, org.apache.log4j.Logger" %>

<%
  Logger logger = Logger.getLogger("com.avaldes.logoff");
  String redirectURL = "login.jsp";
  
  logger.info("redirectURL...: " + redirectURL);
  
  session = request.getSession();
  session.invalidate();
 
  response.sendRedirect(redirectURL);
%>

processLogin (processLogin.jsp)

<%@ page language="java" %>
<%@ page import="java.util.*" %>
<%@ page import="java.util.*, org.apache.log4j.Logger, 
                com.avaldes.util.ApacheConnection,
                org.json.simple.parser.*, org.json.simple.*" %>

<%! static Logger logger=Logger.getLogger("com.avaldes.processLogin"); %>

<%
  //Logger logger = Logger.getLogger("com.avaldes.processLogin");
  int MaxInactiveInterval = 30*60;   // 30 minutes
  String fullProtocol = request.getProtocol().toLowerCase();
  String protocol[] = fullProtocol.split("/");
  String baseUrl = protocol[0]+"://" + request.getHeader("Host");
  String url=baseUrl+"/JweSecurityExample/rest/security/authenticate";
  
  String username = request.getParameter("username");
  String password = request.getParameter("password");
  String loginURL = "login.jsp";
  String targetURL = loginURL; 

  if (username != null && username.equals("")) {
    username = null;
  }
  
  if (password != null && password.equals("")) {
    password = null;
  }
  logger.info("URL...........: [" + url + "]");
  logger.info("USERNAME......: [" + username + "]");
  logger.info("PASSWORD......: [" + password + "]");
  
  // add checks for username / password 
  if (username != null && password != null) {
    String redirectURL = baseUrl 
                         + "/JweSecurityExample/InventoryApp.jsp";
    
    ApacheConnection httpConnection = new ApacheConnection();
    
    Map<String,String> header = new HashMap<String,String>();
    header.put("username", username);
    header.put("password", password);

    String authenticationJSON = 
                        httpConnection.executePost(url, header, null);
  
    logger.info("Authentication JSON...: " + authenticationJSON);
    
    JSONParser authParser=new JSONParser();
    JSONObject jsonAuthObj 
							= (JSONObject) authParser.parse(authenticationJSON);
    
    Long authStatusCode = (Long) jsonAuthObj.get("status_code");
    String authMessage = (String) jsonAuthObj.get("message");
    
    logger.info("JSONObject....: " + jsonAuthObj);
    logger.info("status_code...: " + authStatusCode);
    logger.info("message.......: " + authMessage);
    
    session = request.getSession();
    
    if (authStatusCode != null  && authStatusCode.intValue() != 200) {
      if (authStatusCode.intValue() == 403) {
        session.setAttribute("error", 
					"Username/Password are incorrect, please try again...");
      }     
      if (authStatusCode.intValue() == 412) {
        session.setAttribute("error", 
					"Username/Password is required, please try again...");
      }     
      targetURL = loginURL;
    } else {
      session.setAttribute("username", username); 
      session.setAttribute("token", authMessage);
      targetURL = redirectURL;
    }
    session.setMaxInactiveInterval(MaxInactiveInterval);
    
  } else {
    logger.error("Username or Password is NULL...");
  }
  
  response.sendRedirect(targetURL);
%>

Our Sample Application

If you remember our previous tutorial, we learned that once we have authenticated, our backend restful web service generates the JWE/JWT token which is injected into our Java Session object. In our application, we are retrieving the username and JWE/JWT token passed from this session object. Our JWE/JWT token contains claims containing a list of roles assigned to a specific user. In our application, if the user has the admin role then admin options will be available to them.

jaxrs_jwe_grid_ui

Grid-UI Inventory Application (InventoryApp.jsp)

<%@ page language="java" %>
<%@ page import="org.apache.log4j.Logger" %>

<!doctype html>
<html ng-app="app">
  <head>
    <script src="include/angular.js"></script>
    <script src="include/angular-touch.js"></script>
    <script src="include/angular-animate.js"></script>
    <script src="include/csv.js"></script>
    <script src="include/vfs_fonts.js"></script>
    <script src="include/pdfmake.js"></script>
    <script src="include/jquery-1.11.3.js"></script>
    <script src="include/jquery.layout.js"></script>
    <script src="include/ui-grid.js"></script>
    <script src="include/angular-spinner.js"></script>
    <script src="include/spin.js"></script>
    <script src="include/app.js"></script>
    
    <script src="include/ui-bootstrap-tpls-0.13.0.min.js"></script>
    <script src="include/bootstrap.js"></script>
    
    <link rel="stylesheet" href="http://ui-grid.info/release/ui-grid.css">
    <link rel="stylesheet" href="include/font-awesome.min.css">
    <link rel="styleSheet" href="include/styles.css" />
    <link rel="stylesheet" href="include/bootstrap.css">
  </head>
  
  <%! static Logger logger = Logger.getLogger("com.avaldes.InventoryApp"); %>
  
  <%
  String fullProtocol = request.getProtocol().toLowerCase();
  String protocol[] = fullProtocol.split("/");
  String baseUrl = protocol[0] + "://" + request.getHeader("Host");
  
  session = request.getSession();
  String username = (String) session.getAttribute("username");
  String token = (String) session.getAttribute("token");
  
  logger.info("username..: " + username);
  logger.info("token.....: " + token);
  
  if (token == null) {
    String loginURL = "login.jsp";
    response.sendRedirect(loginURL);
  }
  %>

  <body>
    <div ng-controller="MainCtrl">
      <div class="page-header">
        <h2><strong>JWT/JWS/JWE Sample Application<br>
          <small>Using JSON Web Tokens, JSON Web Signature 
                  and JSON Web Encryption</small></strong></h2>
        <span ng-show="userRoles.indexOf('admin') > 0">
          <button id="login" type="button" 
            onClick="alert('Show Admin Window...')" 
            class="btn btn-primary" style="width: 100px;">
          <i class="fa fa-user fa-fw"></i>&nbsp; Admin</button>
        </span>
        <a href="logoff.jsp">
          <button id="login" type="button" class="btn btn-primary" 
            style="width: 100px;" >
        <i class="fa fa-power-off"></i>&nbsp; Logout</button></a>
        <span class="right_justified">
          <button class="btn btn-info" style="width: 200px;">
            Welcome {{username}}
          </button>
        </span>
      </div>
      <div class="row">
        <div class="span4">
          <span us-spinner spinner-key="spinner-0"></span>
          <div id="grid1" ui-grid="gridOptions" class="grid"></div>
        </div>
      </div>
      <div ng-element-ready="setDefaults('<%=baseUrl%>', 
         '<%=username %>', '<%=token %>')"></div>
      <div ng-element-ready="loadAllRoles()"></div>    
      <div ng-element-ready="loadAllItems()"></div>    
    </div>
  </body>
</html>

AngularJS Application (app.js)

var app = angular.module('app', ['ngTouch', 'angularSpinner', 
           'ui.grid', 'ui.grid.resizeColumns', 'ui.grid.moveColumns']);

app.config(['usSpinnerConfigProvider', 
                                function (usSpinnerConfigProvider) {
    usSpinnerConfigProvider.setDefaults({
    lines: 13, // The number of lines to draw
      length: 5, // The length of each line
      width: 4, // The line thickness
      radius: 8, // The radius of the inner circle
      corners: 1, // Corner roundness (0..1)
      rotate: 0, // The rotation offset
      direction: 1, // 1: clockwise, -1: counterclockwise
      color: '#333', // #rgb or #rrggbb or array of colors
      speed: 1, // Rounds per second
      trail: 80, // Afterglow percentage
      shadow: false, // Whether to render a shadow
      hwaccel: false, // Whether to use hardware acceleration
      className: 'spinner', // The CSS class to assign to the spinner
      zIndex: 2e9, // The z-index (defaults to 2000000000)
      top: '50%', // Top position relative to parent
      left: '50%' // Left position relative to parent
    });
}]);

app.service('ajaxService', function($http) {
  this.getData = function(URL, ajaxMethod, ajaxParams, token) {
    var restURL = URL + ajaxParams;
    console.log("Inside ajaxService...");
    console.log("Connection using URL=[" + restURL + "], 
            Method=[" + ajaxMethod + "]");
    
      return $http({
          method: ajaxMethod,
          url: restURL,
          headers: { 'token': token }
       });
   };

  this.postData = function(URL, ajaxMethod, jsonData, ajaxParams) {
    var restURL = URL + ajaxParams;
    console.log("Inside ajaxService POST...");
    console.log("Connection using URL=[" + restURL + "], 
            Method=[" + ajaxMethod + "]");
    
      return $http({
          method: ajaxMethod,
          url: restURL,
          headers: {'Content-Type': 'application/json'},
          data: jsonData,
       });
    
  };
});

/* ------------------------------------------------------------------
* MAIN CONTROLLER  
--------------------------------------------------------------------*/
app.controller('MainCtrl', function ($scope, $http, $log, 
                  uiGridConstants, ajaxService, usSpinnerService) {

  $scope.gridOptions = { 
    enableCellEditOnFocus: false,
    enableGridMenu: false,
    enableSorting: true,
    enableRowSelection: true,
    enableRowHeaderSelection: false,
    enableColumnResizing: true,
  };
  
  $scope.gridOptions.columnDefs = [
  { name: '_id', 
    displayName: 'ID', 
    width: 120, 
    maxWidth: 150, 
    minWidth: 90, 
  },
  { name: 'item-id',
    displayName: 'Item-ID', 
    width: 120, 
    maxWidth: 150, 
    minWidth: 90, 
  },
  { name: 'item-name', 
    displayName: 'Item-Name', 
    width: 510, 
    maxWidth: 800, 
    minWidth: 400, 
  },
  { name: 'price', 
    displayName: 'Price',  
    width: 120, 
    maxWidth: 200, 
    minWidth: 70, 
  },
  { name: 'quantity', 
    displayName: 'Quantity',  
    width: 110, 
    maxWidth: 200, 
    minWidth: 70, 
  }
   ];
  
  $scope.startSpin = function(key) {
    usSpinnerService.spin(key);
  };
  
  $scope.stopSpin = function(key) { 
    usSpinnerService.stop(key);
  };

  $scope.loadAllRoles = function() {
  $scope.startSpin('spinner-0');
  console.log("Inside loadUserRoles " + $scope.loadAllRolesUrl);

  function onSuccess(response) {
    console.log("+++++loadUserRoles SUCCESS++++++");
    if (response.data.status_code != '403' || 
              response.data.status_code != '404') { 
      $scope.userRoles  =  response.data;
    }
    $scope.stopSpin('spinner-0');
  };
    
  function onError(response) {
    console.log("-------loadUserRoles FAILED-------");
    $scope.stopSpin('spinner-0');
    console.log("Inside loadUserRoles error condition...");
  };  
  
  //----MAKE AJAX REQUEST CALL to GET DATA----
  ajaxService.getData($scope.loadAllRolesUrl, 'GET', '', 
                      $scope.token).then(onSuccess, onError);
  };

  $scope.loadAllItems = function() {
  $scope.startSpin('spinner-0');
  console.log("Inside loadAllItems " + $scope.loadAllItemsUrl);

  function onSuccess(response) {
    console.log("+++++loadAllItems SUCCESS++++++");
    if (response.data.status_code != '404') { 
      $scope.gridOptions.data  =  response.data;
    }
    $scope.stopSpin('spinner-0');
  };

  function onError(response) {
    console.log("-------loadAllItems FAILED-------");
    $scope.stopSpin('spinner-0');
    console.log("Inside loadAllItems error condition...");
  };  
  
  //----MAKE AJAX REQUEST CALL to GET DATA----
  ajaxService.getData($scope.loadAllItemsUrl, 'GET', '', 
                      $scope.token).then(onSuccess, onError);
  };
  
  $scope.setDefaults = function(baseUrl, username, token) {
  $scope.loadAllRolesUrl = baseUrl 
          + "/JweSecurityExample/rest/security/getallroles";
  $scope.loadAllItemsUrl = baseUrl 
          + "/JweSecurityExample/rest/security/showallitems";
  $scope.username = username;
  $scope.token = token;
  
  console.log("Setting Defaults");
  console.log("loadAllRolesUrl....: " + $scope.loadAllRolesUrl);
  console.log("loadAllItemsUrl....: " + $scope.loadAllItemsUrl);  
  console.log("username...........: " + $scope.username);
  console.log("token..............: " + $scope.token);
  };
});

app.directive('ngElementReady', [function() {
    return {
      priority: Number.MIN_SAFE_INTEGER, 
      restrict: "A",
      link: function($scope, $element, $attributes) {
          $scope.$eval($attributes.ngElementReady);
      }
    };
}]);

AngularJS ApplicationLogin (applogin.js)

var app = angular.module('app', ['ui.bootstrap', 'angularSpinner']);

app.config(['usSpinnerConfigProvider', 
                                function (usSpinnerConfigProvider) {
    usSpinnerConfigProvider.setDefaults({
    lines: 13, // The number of lines to draw
      length: 5, // The length of each line
      width: 4, // The line thickness
      radius: 8, // The radius of the inner circle
      corners: 1, // Corner roundness (0..1)
      rotate: 0, // The rotation offset
      direction: 1, // 1: clockwise, -1: counterclockwise
      color: '#333', // #rgb or #rrggbb or array of colors
      speed: 1, // Rounds per second
      trail: 80, // Afterglow percentage
      shadow: false, // Whether to render a shadow
      hwaccel: false, // Whether to use hardware acceleration
      className: 'spinner', // The CSS class to assign to the spinner
      zIndex: 2e9, // The z-index (defaults to 2000000000)
      top: '50%', // Top position relative to parent
      left: '50%' // Left position relative to parent
    });
}]);

app.directive('ngElementReady', [function() {
    return {
        priority: Number.MIN_SAFE_INTEGER, 
        restrict: "A",
        link: function($scope, $element, $attributes) {
            $scope.$eval($attributes.ngElementReady); 
        }
    };
}]);

app.directive('focusOn', function() {
     return function(scope, elem, attr) {
        scope.$on(attr.focusOn, function(e) {
            elem[0].focus();
        });
     };
  });

app.service('ajaxService', function($http) {
  this.getData = function(URL, ajaxMethod, ajaxParams) {
    var restURL = URL + ajaxParams;
    console.log("Inside ajaxService...");
    console.log("Connection using URL=[" + restURL + "], 
                              Method=[" + ajaxMethod + "]");
     return $http({
     method: ajaxMethod,
     url: restURL,
    });
   };

  this.postData = function(URL, ajaxMethod, jsonData, ajaxParams) {
    var restURL = URL + ajaxParams;
    console.log("Inside ajaxService POST...");
    console.log("Connection using URL=[" + restURL + "], 
                              Method=[" + ajaxMethod + "]");
    
       return $http({
          method: ajaxMethod,
          url: restURL,
          headers: {'Content-Type': 'application/json'},
          data: jsonData,
      });
  };
  
  this.postFormData=function(URL, ajaxMethod, jsonData, ajaxParams) {
   var restURL = URL + ajaxParams;
   console.log("Inside ajaxService POST...");
   console.log("Connection using URL=[" + restURL + "], 
                              Method=[" + ajaxMethod + "]");
    
   return $http({
     method: ajaxMethod,
     url: restURL,
     headers:{'Content-Type':'application/x-www-form-urlencoded' },
     data: jsonData,
   });
  };
});
      
/* ---------------------------------------------------------------
* MAIN CONTROLLER  
-----------------------------------------------------------------*/
app.controller('MainCtrl', function ($scope, $rootScope, $http, 
   $log, $timeout, $modal, $filter, ajaxService, usSpinnerService) {
  
  $scope.showModal = false;
  $scope.debugFlag = false;
  $scope.modal = {};
  $scope.login = {};
  
  $scope.startSpin = function(key) {
    usSpinnerService.spin(key);
  };
  
  $scope.stopSpin = function(key) {
    usSpinnerService.stop(key);
  };
  
  $scope.init = function() {
    console.log("Inside init()...");
    $scope.login = {};
    $scope.$broadcast('setFocus');
  };
  
  $scope.setDefaults = function(debugFlag) {
    $scope.debugFlag = debugFlag;
  };
  
  $scope.clearLogin = function() {
    console.log('Inside clearLogin...');
    $scope.login = {};
    $rootScope.$broadcast('setFocus');
  };
  
  $scope.processLogin = function() {
  $scope.startSpin('spinner-1');

  console.log('Inside loginUser: ');

  //---Cancel Modal Dialog Window---
  $scope.cancel = function () {
    console.log('Closing Modal Dialog Window...');
    $scope.showModal = false;
  };

  getLoginURL = "processLogin.jsp?";
  getLoginURL += '&etc=' + new Date().getTime();
  
  console.log("getLoginURL...: " + getLoginURL);
  
  function onSuccess(response) {
    console.log("+++++getLoginURL SUCCESS++++++");
    if ($scope.debugFlag == 'true') {
      console.log("Inside getLoginURL response..." 
                        + JSON.stringify(response.data));
    } else {
      console.log("Inside getLoginURL response...
          (XML response is being skipped, debug=false)");
    }
    if (response.data.status_code == '404') {
      $scope.showModalWindow('Error!',response.data.message, 'sm');
    } else {
    }
    $scope.stopSpin('spinner-1');
  };

  function onError(response) {
    console.log("-------getLoginURL FAILED-------");
    $scope.stopSpin('spinner-1');
    console.log("Inside getLoginURL error condition...");
    $scope.showModalWindow('Error!', response.data.message, 'sm');
  };

  //----MAKE AJAX REQUEST CALL to POST DATA----
  ajaxService.postFormData(getLoginURL, 'POST', 
          $scope.login, '').then(onSuccess, onError);
  };
});
    
/* -----------------------------------------------------------------
* MODAL DIALOG WINDOW CONTROLLER  
-------------------------------------------------------------------*/
app.controller('ModalInstanceCtrl', function($scope,$modalInstance) {
  $scope.ok = function () {
    $modalInstance.dismiss('cancel');
  };
});

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"]
}

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:

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!!!

java_jaxrs_jwe_ui_security

Please Share Us on Social Media

Facebooktwitterredditpinterestlinkedinmail

Leave a Reply

Your email address will not be published. Required fields are marked *