Getting Started With Ionic & NgCordova

In my most recent engagement, I’ve been working on a hybrid mobile app built using Ionic and ngCordova. Functionality-wise, the app itself is fairly straightforward, but since this is my first project that directly targets mobile devices (as opposed to responsive web), I’ve learned a few things that I think are worth sharing. Like most posts, the information contained has been cobbled together from many different sources during my time on this project. The purpose of this post is to walk through how to configure a new, fully testable project using Ionic and ngCordova. Like always, the code for this post is in Github at (https://github.com/jrodenbostel/getting-started-with-ionic-and-ngcordova).

Ionic (http://ionicframework.com) Hybrid mobile app development frameworks have been around for quite some time now. The Ionic Framework is one of the better entries I’ve seen to date. Based on current web technologies and frameworks (HTML5, CSS3, AngularJs (https://angularjs.org)), and leveraging a tried and true native container that runs on many devices (http://cordova.apache.org), Ionic provides a mostly-familiar starting point for folks new to mobile development. On top of that, Ionic is also packaged with a nice set of UI components and icons that help applications look nice as well as function smoothly.

ngCordova (http://ngcordova.com) The ngCordova project basically wraps the Cordova API to make it more Angular-friendly by giving the developer the ability to inject Cordova components as dependencies in your Angular controllers, services, etc. This project is still new and changing rapidly, but simplifies development greatly, and makes code that calls Cordova from within Ionic more readable and more easily testable.

Others (Yeoman, Grunt, Bower, Karma, Protractor, Mocha, Chai) These are the tools we’ll use to build our app. They are used for a variety of things, all are introduced from the same source – Yeoman (http://yeoman.io). Remember how revolutionary the scaffolding features of Rails were when they first surfaced? Yeoman provides scaffolding like that, and anyone can write a generator. There happens to be a generator for Ionic, and in my opinion, it’s all but necessary to use. Out of the box, you get a working app shell, a robust Grunt (http://gruntjs.com) script for app assembly, packaging, emulation, etc, dependency injection via Bower (http://bower.io), and example Mocha (http://mochajs.org) tests running via Karma (http://karma-runner.github.io/0.12/index.html). The only item we’ll add is support for end to end integration tests with Protractor (https://github.com/angular/protractor).

Prerequisites Before we start, you’ll need to have node.js (http://nodejs.org) and npm (https://www.npmjs.org) installed on your machine. Installation instructions can be found here (http://nodejs.org/download/) and here (http://blog.npmjs.org/post/85484771375/how-to-install-npm).

Step 1 – Scaffold Install Yeoman using the following command:

npm install -g yo

Install the Ionic Generator for Yeoman using the following command:

npm install -g generator-ionic

Create a folder for your project and use the newly install generator to build the shell of an Ionic app. Be sure you’re executing these commands in the root of your project folder. You can play around and answer the questions however you’d like. If you’re interested in following along, I’ve included the answers I’ve used and the relevant output below:

Justins-MacBook-Pro:getting-started-with-ionic-and-ngcordova justin$ yo ionic
    _             _
   (_)           (_)
    _  ___  _ __  _  ___
   | |/ _ \| '_ \| |/ __|
   | | (_) | | | | | (__
   |_|\___/|_| |_|_|\___|

[?] Would you like to use Sass with Compass (requires Ruby)? Yes
Created a new Cordova project with name "GettingStartedWithIonicAndNgcordova" and id "com.example.GettingStartedWithIonicAndNgcordova"
[?] Which Cordova plugins would you like to include? org.apache.cordova.console, org.apache.cordova.device
[?] Which starter template [T] or example app [A] would you like to use? [T] Tabs

Install plugins registered at plugins.cordova.io: grunt plugin:add:org.apache.cordova.globalization
Or install plugins direct from source: grunt plugin:add:https://github.com/apache/cordova-plugin-console.git

Installing selected Cordova plugins, please wait.
Installing starter template. Please wait

     info ... Fetching http://github.com/diegonetto/ionic-starter-tabs/archive/master.tar.gz ...
     info This might take a few moments

Step 2 – Run! Validate there weren’t any issues running the generator by starting the app. The Yeoman generator we’ve used includes a full-featured build script that includes a variety of ways to start up our app. We’ll use more features of the script later, but for a complete list of available commands visit the generator’s Github page (https://github.com/diegonetto/generator-ionic).For now, we’ll serve the app with the simple http server included as part of our sample app (courtesy of the Yeoman generator) using the following command (from the root of your project folder:

grunt serve

This should have started the server and opened your default browser. In the browser, you should see something similar to the screenshot below:

Screen Shot 2015-02-04 at 2.39.34 PM

Step 3 – ngCordova We’re off to a nice start – a fully functional app, running in the browser, with automated chai tests (using Karma (http://karma-runner.github.io/0.12/index.html) via grunt test) and some static code analysis (using jshint (http://jshint.com/) via grunt jshint) in place, all as a result of our Yeoman generator. If we explore the generated code, we notice that the app itself is very simple. As soon as we start writing code that depends on device APIs (checking for a network connection, identifying the current device, etc), we run into a problem: there’s only a global reference to Cordova, and there isn’t a nice way to inject Cordova into our Angular controllers, especially for testing. This is where ngCordova comes into play. Here, we’ll write some simple code that checks the device platform the app is currently running on, and display it on the opening screen. Let’s start by writing a test* that looks for an object in scope of the DashCtrl called ‘devicePlatform’. First, there are a few different ways to run the tests. One enables watching, but doesn’t run the tests immediately (you have to leave this on, and it runs tests as/when files in your project change), and the other just runs the tests on demand. With watching:

grunt test

On demand:

grunt karma

At the bottom of ‘/test/spec/controllers.js’, add a test for the DashCtrl with the following code:

describe('Controller: DashCtrl', function () {

  var should = chai.should();

  // load the controller's module
  beforeEach(module('GettingStartedWithIonicAndNgcordova'));

  var DashCtrl,
    scope;

  // Initialize the controller and a mock scope
  beforeEach(inject(function ($controller, $rootScope) {
    scope = $rootScope.$new();
    DashCtrl = $controller('DashCtrl', {
      $scope: scope
    });
  }));

  it('should inspect the current devicePlatform', function () {
    scope.devicePlatform.should.equal('ios');
  });

});

Immediately after adding that code to our test file (if you’re using ‘grunt test’) or running the tests on demand (using ‘grunt karma’), we should see results in our terminal window, and we should see that this test has failed because ‘devicePlatform’ is undefined in the DashCtrl’s scope.

PhantomJS 1.9.8 (Mac OS X) Controller: DashCtrl should inspect the current devicePlatform FAILED
TypeError: 'undefined' is not an object (evaluating 'scope.devicePlatform.should')

Next, we’ll install ngCordova and implement the logic this test is exercising. Detailed instructions on installing ngCordova can be found here (http://ngcordova.com/docs/install/).The simplest install is via Bower using the following command:

bower install ngCordova

Add a reference to the newly installed ngCordova to your app/index.html file, above the reference to cordova, such that:

    <script src="lib/ngCordova/dist/ng-cordova.js"></script>
    <!-- cordova script (this will be a 404 during development) -->
    <script src="cordova.js"></script>

To get the device OS, we’ll need to use Cordova’s Device plugin. If you haven’t already, we’ll need to make sure that it’s installed. Use the following command to install it:

cordova plugin add org.apache.cordova.device

Next, we’ll add ngCordova to our project as a module. In app/scripts/app.js, change this line:

angular.module('GettingStartedWithIonicAndNgcordova', ['ionic', 'config', 'GettingStartedWithIonicAndNgcordova.controllers', ‘GettingStartedWithIonicAndNgcordova.services’])

to:

angular.module('GettingStartedWithIonicAndNgcordova', ['ionic', 'config', 'GettingStartedWithIonicAndNgcordova.controllers', 'GettingStartedWithIonicAndNgcordova.services', 'ngCordova'])

Next, let’s write the code that adds the device platform to the DashCtrl scope. Start by injecting the device plugin into the DashCtrl using the code below:

.controller('DashCtrl', function($scope, $cordovaDevice) {
})

Then create the devicePlatform scope variable and set it’s value to the device’s actual platform using the following code:

.controller('DashCtrl', function($scope, $cordovaDevice) {
     $scope.devicePlatform = $cordovaDevice.getPlatform();
})

Finally, add a reference to the device plugin to templates/tab-dash.html:

<ion-view title="Dashboard">
  <ion-content class="has-header padding">
    <h1>Dash</h1>
     <h2>{{devicePlatform}}</h2>
  </ion-content>
</ion-view>

You’ll notice that when we run our test again, they still fail. This is because karma tests run in the browser – the browser doesn’t interact with Cordova plugins – there’s no platform for the browser, there’s no ‘model’, there’s no device for Cordova to plug in to. We’ll need to add a few more things to get this working. At this point, if you’re interested in continuing on under the assumption that you’ll only be unit testing and never testing in the browser (which includes automated end to end testing) prior to testing on the device, you can simply mock any calls to Cordova using spies/doubles. I think there’s value in automated end to end testing and manual browser testing prior to testing on devices. I think it’s an easy and efficient way to troubleshoot your code in an environment isolated from platform dependencies. In that case, we’ll use ngCordovaMocks (and some grunt scripting) to make our unit tests pass in our development environment, we’ll add Protractor so we can test our app end-to-end prior to running on the device, and finally, we’ll run the app in the iOS emulator to complete our validation.

ngCordovaMocks You might notice in the ngCordova Bower package that there are an additional set of files named ‘ng-cordova-mocks’. These compliment ngCordova by providing empty implementations of the services that ngCordova wraps, which can be injected in place of the standard ngCordova implementations for testing purposes. First, we’ll need to add references in two places – our application config, and our test config. For the application configuration, update our app’s module definition in /scripts/app.js:

angular.module('GettingStartedWithIonicAndNgcordova', ['ionic', 'config', 'GettingStartedWithIonicAndNgcordova.controllers', 'GettingStartedWithIonicAndNgcordova.services', 'ngCordovaMocks'])

The test config can be found in our Grunt script. In /Gruntfile.js, find the karma task. In the karma task, you should see a configuration option named ‘files’. Add a line to update it to the following:

        files: [
          '<%= yeoman.app %>/lib/angular/angular.js',
          '<%= yeoman.app %>/lib/angular-animate/angular-animate.js',
          '<%= yeoman.app %>/lib/angular-sanitize/angular-sanitize.js',
          '<%= yeoman.app %>/lib/angular-ui-router/release/angular-ui-router.js',
          '<%= yeoman.app %>/lib/ionic/release/js/ionic.js',
          '<%= yeoman.app %>/lib/ionic/release/js/ionic-angular.js',
          '<%= yeoman.app %>/lib/angular-mocks/angular-mocks.js',
          '<%= yeoman.app %>/lib/ngCordova/dist/ng-cordova-mocks.js',
          '<%= yeoman.app %>/<%= yeoman.scripts %>/**/*.js',
          'test/mock/**/*.js',
          'test/spec/**/*.js'
        ],

Now, we’ll update our test to use the new ngCordovaMocks library. We’ll add a reference to the ngCordovaMocks module, we’ll inject a decorated version of our $cordovaDevice plugin into our DashCtrl, and we’ll update our test condition accordingly.

describe('Controller: DashCtrl', function () {

  var should = chai.should(), $cordovaDevice = null, $httpBackend, DashCtrl, scope;

     beforeEach(module('GettingStartedWithIonicAndNgcordova'));
     beforeEach(module('ngCordovaMocks'));

     beforeEach(inject(function (_$cordovaDevice_) {
          $cordovaDevice = _$cordovaDevice_;
     }));

  // Initialize the controller and a mock scope
  beforeEach(inject(function ($controller, $rootScope, _$httpBackend_) {
          $httpBackend = _$httpBackend_;
          $httpBackend.when('GET', /templates\S/).respond("");
          $cordovaDevice.platform = 'TEST VALUE';
    scope = $rootScope.$new();
    DashCtrl = $controller('DashCtrl', {
      $scope: scope
    });
          $httpBackend.flush();
  }));

  it('should inspect the current deviceType', function () {
    scope.devicePlatform.should.equal('TEST VALUE');
  });

});

You can see now we’re decorating $cordovaDevice, supplying it with a value for it’s platform property, and we’re asserting that the $cordovaDevice.getPlatform() method is returning the correct value via our $scope.devicePlatform variable. We’ve also added a mock $httpBackend (and subsequent flush) that will listen to and ignore any page requests triggered by our controller initializing. In this way, we can simulate a specific platform and exercise our code in unit tests, AND our app still runs in the browser. At this point, running in the browser without ngCordovaMocks would cause failures. To really see the value of ngCordovaMocks, we’ll add support for Protractor tests.

Protractor (https://github.com/angular/protractor) First, we’ll need to install two node modules that give us new grunt tasks: one to control a Selenium Webdriver (http://www.seleniumhq.org), and one to run our protractor tests.

npm install grunt-protractor-webdriver --save-dev
npm install grunt-protractor-runner --save-dev

While grunt-protractor-runner installs a controller for Selenium Webdriver, we still need a Selenium server. We can install a standalone Selenium server by running the following the root of our project:

node_modules/protractor/bin/webdriver-manager update

Next, we’ll update our grunt script to include configurations for the new tasks, and add a new task of our own. Include these new tasks somewhere in your grunt.initConfig object:

    protractor_webdriver: {
      all: {
        command: 'webdriver-manager start'
      }
    },
    protractor: {
      options: {
        keepAlive: true, // If false, the grunt process stops when the test fails.
        noColor: false // If true, protractor will not use colors in its output.
      },
      all: {
        options: {
          configFile: 'test/protractor-conf.js'
        }
      }
    },

Then register our custom task somewhere after grunt.initConfig:

  grunt.registerTask('test_e2e', [
    'protractor_webdriver',
    'protractor'
  ]);

We’re not doing anything special in this config – we’re basically using a grunt task to control the Selenium server we could otherwise control from the CLI, and we’re offloading much of our protractor config to a properties file. Next, create the properties file at the path listed above (test/protractor-conf.js):

exports.config = {
    seleniumAddress: 'http://localhost:4444/wd/hub',

    specs: [
        'e2e/**/*.js'
    ],

    framework: 'mocha',

    capabilities: {
        'browserName': 'chrome',
        'chromeOptions': {
          args: ['--args','--disable-web-security']
        }
    },

    /**
     * This should point to your running app instance, for relative path resolution in tests.
     */
    baseUrl: 'http://localhost:8100',
};

Last, we’ll write a new end to end test case and execute it. Create a file at /test/e2e (which is the directory we included in our protractor configuration above). I named mine ‘tabs.js’. Add the content below to the file:

var chai = require('chai');
var chaiAsPromised = require('chai-as-promised');

chai.use(chaiAsPromised);
var expect = chai.expect;

describe('Ionic Dash Tab', function() {

  var decoratedModule = function() {
          var ngCordovaMocks = angular.module('ngCordovaMocks');
          var injector = angular.injector(['ngCordovaMocks', 'ng']);
    ngCordovaMocks.service('$cordovaDevice', function() {
               var cordovaDevice = injector.get('$cordovaDevice');
               cordovaDevice.platform = 'ios';
      return cordovaDevice;
    });
  };

  it('should have the correct heading', function() {
          browser.addMockModule('ngCordovaMocks', decoratedModule);
    browser.get('http://localhost:8100');

          var heading = element(by.css('h2'));
          expect(heading.getText()).to.eventually.equal('ios');
  });
});

In the code above, we’re decorating the $cordovaDevice service (much like we were in the unit tests), by first getting a reference to the ngCordovaMocks module, then getting a handle on the injector instance from the ngCordovaMocks module, then getting the $cordovaDevice service itself, and finally decorating the service by setting the platform to our desired value. In the test itself, we’re adding our newly decorated ngCordovaMocks module to Protractor’s browser instance. At this point, running the tests should yield positive results. You can run them using the custom task we registered (assuming your server is already running), by using the following command (be sure your server is running with ‘grunt serve’:

grunt test_e2e

Dynamic Configuration Since we’ve updated our app to only use ngCordovaMocks instead of ngCordova, we need the ability to switch between using the two seamlessly. Inspiration from this portion of the post comes from this post (http://www.ecofic.com/about/blog/getting-started-with-ng-cordova-mocks). To do this, we’ll use the grunt-text-replace grunt task. Install the grunt-text-replace node package using the following statement:

npm install grunt-text-replace --save-dev

Next, add the following config to Gruntfile.js somewhere in your grunt.initConfig object:

 replace: {
            production: {
              src: [
                '<%= yeoman.app %>/index.html',
                '<%= yeoman.app %>/<%= yeoman.scripts %>/app.js'
              ],
              overwrite: true,
              replacements:[
                { from: 'lib/ngCordova/dist/ng-cordova-mocks.js', to: 'lib/ngCordova/dist/ng-cordova.js' },
                { from: '\'ngCordovaMocks\'', to: '\'ngCordova\'' }
              ]
            },
            development: {
              src: [
                '<%= yeoman.app %>/index.html',
                '<%= yeoman.app %>/<%= yeoman.scripts %>/app.js'
              ],
              overwrite: true,
              replacements:[
                { from: 'lib/ngCordova/dist/ng-cordova.js', to: 'lib/ngCordova/dist/ng-cordova-mocks.js' },
                { from: '\'ngCordova\'', to: '\'ngCordovaMocks\'' }
              ]
            }
          },

Now we’ll add calls to these tasks to our existing Grunt tasks, as well as create a new init-development task, as seen below:

  grunt.registerTask('test', [
    'replace:development',
    'clean',
    'concurrent:test',
    'autoprefixer',
          'karma',
    'karma:unit:start',
    'watch:karma'
  ]);

  grunt.registerTask('serve', function (target) {
    if (target === 'compress') {
      return grunt.task.run(['compress', 'ionic:serve']);
    }

    grunt.config('concurrent.ionic.tasks', ['ionic:serve', 'watch']);
    grunt.task.run(['init-development', 'concurrent:ionic']);
  });

  grunt.registerTask('init', [
    'replace:production',
    'clean',
    'wiredep',
    'concurrent:server',
    'autoprefixer',
    'newer:copy:app',
    'newer:copy:tmp'
  ]);

  grunt.registerTask('init-development', [
    'replace:development',
    'clean',
    'wiredep',
    'concurrent:server',
    'autoprefixer',
    'newer:copy:app',
    'newer:copy:tmp'
  ]);

  grunt.registerTask('test_e2e', [
     'replace:development',
    'protractor_webdriver',
    'protractor'
  ]);

When we run ‘grunt serve’, the application will start in the web server, and will be running with ngCordovaMocks. From here, we can run our automated end to end tests using ‘grunt test_e2e’. We can also simply run the unit tests standalone using ‘grunt test’. You can see the way the tasks above changed to make that possible – calls to ‘replace:development’ prior to the tasks executing. In the case of the ‘test’ task, it was altered slightly to also include the ‘karma’ task to run the tests through after initial invocation, then followed by a test watcher. At this point, we can also run in our emulator without issue. To do that, we’ll quickly add the iOS platform to our project, and kick off the emulator to see the ‘real’ platform displayed on the screen.

grunt platform:add:ios

…followed by:

grunt emulate:ios

At this point, we’ll start to see a slight divergence from the way the app is functioning on the web versus in our emulator. In some cases, the device is available before all of the Cordova plugins are loaded. Furthermore, the way the screen refreshes as a result of this is also slightly different. To counter this, we’ll have to add a bit of logic to our controller to wait for the device to be ready. Update the DashCtrl with the following code:

.controller('DashCtrl', function($scope, $cordovaDevice, $ionicPlatform) {
     $ionicPlatform.ready(function() {
          $scope.devicePlatform = $cordovaDevice.getPlatform();
     });
})

Run the emulator again, and we should see the app functioning properly:

Screen Shot 2015-02-04 at 1.51.16 PM

That’s it! We should now be able to run in the browser, in the emulator, and through our automated tests with consistency. This setup has paid efficiency dividends for me on my current project, and I hope it helps folks get started on the right foot. It was a lot longer than I thought it would be. *As previously stated, I ran the Yeoman generator with the ‘Tabs’ example project option. Turns out it came with a broken test. I added a question to an open issue on this at the ngCordova project’s Github (https://github.com/driftyco/ng-cordova) page You can find the fixed test below:

'use strict';

describe('Controller: FriendsCtrl', function () {

  var should = chai.should();

  // load the controller's module
  beforeEach(module('GettingStartedWithIonicAndNgcordova'));

  var FriendsCtrl,
    scope;

  // Initialize the controller and a mock scope
  beforeEach(inject(function ($controller, $rootScope) {
    scope = $rootScope.$new();
    FriendsCtrl = $controller('FriendsCtrl', {
      $scope: scope
    });
  }));

  it('should attach a list of pets to the scope', function () {
    scope.friends.should.have.length(4);
  });
});

Advertisements

Upcoming in 2015

Over the course of the last 3 months, I’ve been involved in a hybrid mobile project using the Ionic framework and Node.js, which has allowed me to apply prior knowledge and learn more about AngularJS, latest-generation javascript features, Yeoman, Bower, Grunt, Crashlytics, and Apache Cordova. I’ve also been spending time learning how to interact with an Arduino using Node.js for a personal project. Stay tuned for posts on those topics in the coming months. Happy New Year!

Flyway Jasypt Gradle

By the way, if you happen to be using Flyway (http://flywaydb.org/) for your migrations, Gradle (http://gradle.org) for your build/tasks and Jasypt (http://jasypt.org) for encryption, here’s an example Gradle task for tying the three together.

flyway {
    StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
    encryptor.setPassword('testtest'); //<-- your Jasypt password can come from anywhere.  Hopefully somewhere more secure than this.

    def flywayFile = file('flyway.properties')

    Properties flywayProps = new EncryptableProperties(encryptor)
    flywayFile.withInputStream { stream ->
        flywayProps.load(stream)
    }

    url = flywayProps.url
    user = flywayProps.username
    password = flywayProps.password

    initOnMigrate = true
}

Part 5a: Additional Credential Security – Spring Data JPA + Jasypt

In this short addendum to my earlier series on Spring Boot, I’ll be covering a fairly trivial problem whose solution I had a somewhat hard time finding an example of. Like normal, I hope this will help someone out who is just getting started.

On my current project, we’re working with a security-focused consulting firm named Jemurai (http://jemurai.com). Before I go further I wanted to mention that I learned about this tool and many, many other things security-related from one of their people (@mkonda). A great learning experience, and a pleasure to work with. Check them out.

Back to my current project. We had a goal to encrypt sensitive data at rest in properties files. One of the things you’ll notice if you’ve been following through Part 5 is that the database credentials are stored, in plain text, in properties files. In this installment, we’ll be encrypting the password in that credential using Jasypt, an encryption tool for Java.

As usual, for this installment, I’ve created a copy of the code from Part 5 and created a new project called Part 5 With Jasypt. It’s committed to Github, ready for cloning.

Download and Configure
Add the jasypt dependency to your build.gradle file:

/build.gradle:

dependencies {
    compile('org.springframework.boot:spring-boot-starter-web')
    compile('org.springframework.boot:spring-boot-starter-security')
    compile('org.springframework.boot:spring-boot-starter-data-jpa')
    compile('org.thymeleaf:thymeleaf-spring4:2.1.2.RELEASE')
    compile('org.jasypt:jasypt-spring31:1.9.2') //<—here it is.
    runtime('mysql:mysql-connector-java:5.1.6')

    testCompile('junit:junit')
}

Next, download the jasypt distribution from http://jaspyt.org: http://sourceforge.net/projects/jasypt/files/jasypt/jasypt%201.9.2/. Since we’ll be encrypting the database credentials that are currently stored in a properties file, follow the instructions on the jasypt site outlining using their CLI tools (http://www.jasypt.org/encrypting-configuration.html) to encrypt your credential. I used the command below to perform this task. The task was run from the ‘/bin’ directory in the jasypt distribution. The param named “input” should be string you wish to encrypt, and the password param is the decryption key used to decode your password as it’s being ingested by Spring during app startup.

Justins-MacBook-Pro:bin justin$ ./encrypt.sh input="password" password=testtest

…and the output should look something similar to this:

    ----ENVIRONMENT-----------------

    Runtime: Oracle Corporation Java HotSpot(TM) 64-Bit Server VM 24.51-b03



    ----ARGUMENTS-------------------

    input: password
    password: testtest



    ----OUTPUT----------------------

    xpPrNtXz+SQmTYB0WQrc+2T8ZTubofox

Updating Properties
If you read the jasypt manual, you’ve probably already updated your properties file with the newly encrypted value. In order to decrypt it, we need to give jasypt a directive – an indicator that is used by jasypt to determine which values need to be decrypted while they’re being ingested. I’ve updated my application.properties file to include my newly encrypted password and the encryption directive.

spring.datasource.url=jdbc:mysql://localhost:3306/beyond-the-examples
spring.datasource.username=root
spring.datasource.password=ENC(xpPrNtXz+SQmTYB0WQrc+2T8ZTubofox)
spring.datasource.driverClassName=com.mysql.jdbc.Driver

spring.jpa.hibernate.dialect= org.hibernate.dialect.MySQLInnoDBDialect
spring.jpa.generate-ddl=false

Updating Spring Config
Last, you’ll need to update your Spring config to use Jasypt utilities to decrypt the password while ingesting the necessary properties prior to constructing our datasource.

    @Value("${spring.datasource.driverClassName}")
    private String databaseDriverClassName;

    @Value("${spring.datasource.url}")
    private String datasourceUrl;

    @Value("${spring.datasource.username}")
    private String databaseUsername;

    private String databasePassword;

    @Bean
    public DataSource datasource() throws IOException {
        org.apache.tomcat.jdbc.pool.DataSource ds = new org.apache.tomcat.jdbc.pool.DataSource();
        ds.setDriverClassName(databaseDriverClassName);
        ds.setUrl(datasourceUrl);
        ds.setUsername(databaseUsername);
        ds.setPassword(getSecurePassword());

        return ds;
    }

    private String getSecurePassword() throws IOException {
        StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
        encryptor.setPassword(System.getProperty("blogpost.jasypt.key"));
        Properties props = new EncryptableProperties(encryptor);
        props.load(this.getClass().getClassLoader().getResourceAsStream("application.properties"));
        return props.getProperty("datasource.password");
    }

Here you can see I’ve changed the reference to my “databasePassword” property – it is no longer being populated by the @Value annotation. You can also see that I’ve replaced the value passed to the mutator of the password property of the datasource bean with a reference to a helper method that retrieves our password.

In that method (“getSecurePassword”), there are a few things going on. We’re retrieving the password the encryptor will use to decrypt the value stored in our properties file. Note that it’s the same value we used on the CLI during encoding. Also note that I’m assuming the value used for this is stored in a system property. Anywhere you can read it from will work, although environment variables/system properties seem to be the best place from what I’ve heard anecdotally and read in the Jasypt docs. There are some tricks to passing JVM options to the bootRun task. See here and here for examples. You can see we’re using an extension of java.util.Properties called ‘EncryptableProperties’. This is a Jasypt class that understands how to react when reading a property value enclosed by the directive we talked about above. After that, we’re using these properties like we would use any other java.util.Properties class.

Testing
To test our changes, simply execute the app using gradle bootRun. If we did it property (and remembered to specify our jasypt password somewhere), we should notice no user-facing changes – just the same app we had before, but now with encrypted properties. Note that I specified the value of my SystemProperty in the gradle script.

Conclusion
I hope you found this entry a useful continuation of the Jasypt and Spring.io documentation. Check back soon for another installment!

Part 5: Integrating Spring Security with Spring Boot Web

Spring Boot provides utilities for quick and easy setup of Spring Security via auto-configuration and Java-based configuration. The getting started guide is quick and easy leads through configuring an in-memory AuthenticationManager in just minutes. Going beyond these examples, this installation will quickly review the getting started guide provided at Spring.io, and conclude with the configuration of a datasource-backed AuthenticationManager that uses Spring Data JPA, and the MySQL database platform.

As usual, for this installment, I’ve created a copy of the code from Part 4 and created a new project called Part 5. It’s committed to Github, ready for cloning.

Updating Dependencies
To install Spring Security, we first need to update our gradle script to include a dependency on spring-boot-starter-security. Update build.gradle to include the following dependency as seen below.

/build.gradle:

dependencies {
    compile("org.springframework.boot:spring-boot-starter-web")
    compile("org.springframework.boot:spring-boot-starter-security")
    compile("org.thymeleaf:thymeleaf-spring4:2.1.2.RELEASE")

    testCompile("junit:junit")
}

Following that, executing a build should pull in our new dependencies.

Creating The Security Configuration
Continuing to lift code from the Spring.io docs for review, below you’ll find the example of the base Java security configuration. We’ll review the important bits after the jump. We’ll create this in the same directory as our other configuration files:

/src/main/java/com.rodenbostel.sample/SecurityConfiguration.java:

package com.rodenbostel.sample;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

@Configuration
@EnableWebMvcSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests().anyRequest().authenticated();
        http
                .formLogin().failureUrl("/login?error")
                .defaultSuccessUrl("/")
                .loginPage("/login")
                .permitAll()
                .and()
                .logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/login")
                .permitAll();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
    }
}

As usual, the @Configuration annotation lets Spring know that this file contains configuration information. The next two annotations (@EnableWebMvcSecurity and @EnableGlobalMethodSecurity(prePostEnabled=true)) setup the automatically-configured portions of our security scheme, provided by Spring-Boot. EnableWebMvcSecurity basically pulls in the default SpringSecurity/SpringMVC integration. It’s an extension of the WebMvcConfigurerAdapter, and adds methods for handling and generating CSRF tokens and resolving the logged in user, and configures default AuthenticationManagers and Pre/Post object authorization implementations. The @EnableGlobalMethodSecurity sets up processors for authorization advice that can be added around methods and classes. This authorization advice lets a developer write Spring EL that inspects input parameters and return types.

Our SecurityConfiguration class also extends WebSecurityConfigurerAdapter. In Spring/Spring Boot, Configurer Adapters are classes that construct default bean configurations and contain empty methods which are meant to be overridden. Overriding these methods allow a developer to customize the Web Security Configuration during startup. Typically, the default configurations are constructed, and immediately following, the empty methods are called. If you’ve overridden an empty method, you’re able to inject custom behavior into the default configuration during the startup of the container.

In our case, the two coded parts of our SecurityConfiguration class (two methods named “configure”) are examples of these empty methods meant to be overridden. During container startup, after the HttpSecurity object’s default configuration is specified, our overridden method is called. Here we are able to customize the default configuration by specifying which requests to authorize, and how to route various security-related requests: default success URL, error routing, where to send logouts, etc. Also during container startup, after the AuthenticationManagerBuilder is configured, our configure method is called, and in this case we’re altering the default configuration, giving instructions to the AuthenticationManagerBuilder to build an in-memory AuthenticationManager with a default user credential and role.

You’ll notice in this configuration we’ve specified several URL paths that do not exist. There’s no login page or controller, and no way for a user to interact with the security configuration when the app is started up. Next, we’ll need to construct and wire in a login page to complete our beginning configuration.

Building The Login Page
The login page in the Spring.io sample is very straightforward. Just a simple form with an input for username and password. Let’s build that and review a few key parts.

/src/main/resources/templates/login.html:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
    <title>Spring Security Example</title>
</head>
<body>
<div th:if="${param.error}">
    Invalid username and password.
</div>
<div th:if="${param.logout}">
    You have been logged out.
</div>
<form th:action="@{/login}" method="post">
    <div><label> User Name : <input type="text" name="username"/> </label></div>
    <div><label> Password: <input type="password" name="password"/> </label></div>
    <div><input type="submit" value="Sign In"/></div>
</form>
</body>
</html>

Most importantly, we have inputs with the names of “username” and “password”. These are the Spring Security defaults. If you’re routing a request to Spring Security to authenticate, these are the parameters on the request that it will be looking for. Next, you’ll also notice that there are Thymeleaf conditionals (th:if) for displaying logout and error messages if they are present in the response parameters during rendering. You’ll also notice the path to this page is “/login”, and the action on this form routes back to “/login” – but we don’t have those registered anywhere…

Registering the Login Action
The path our login form is posting to is the default used by Spring Security. This is where what used to be called the “j_spring_security_check” servlet is listening for requests to authenticate. The request path (where we’re retrieving the login form by issuing a GET to /login) is normally mapped to a controller, but in this case, since we’re using automatically configured features of Spring Boot, we need to specify this mapping in our application configuration. Add the code below to your application configuration. You may notice the use of another @Override method – another hook where we can add logic to customize our application…

/src/main/java/com.rodenbostel.sample.Application.java:

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/login").setViewName("login");
    }

Log In!
Start your server, and try to access the app again. For me, that’s simply visiting http://localhost:8080.

Screen Shot 2014-05-30 at 4.59.05 PM

I’m immediately challenged.

If I put in an invalid username or password, we should see an error:

Screen Shot 2014-05-30 at 4.59.27 PM

If I put in the correct credentials (id: user/password: password), we should be able to log in:

Screen Shot 2014-05-30 at 5.00.33 PM

Screen Shot 2014-05-30 at 5.00.38 PM

There’s quite a bit missing here still – let’s take this example a bit further – we’ll wire in components that would make this configuration closer to production ready – an AuthenticationManager backed by JDBC, configurable password encoders, and a UserDetailsService implementation that we can use to manage users.

Beyond The Examples
To begin taking steps closer to this solution being production-ready, we first need to back our app with a database. I’ll be using MySQL. I’ll assume you’ve got it installed and running (if you’re on a mac, I’d use Homebrew to accomplish that.

First, we’ll add the MySQL dependency to our gradle script:

/build.gradle:

dependencies {
    compile("org.springframework.boot:spring-boot-starter-web")
    compile("org.springframework.boot:spring-boot-starter-security")
    compile("org.springframework.boot:spring-boot-starter-data-jpa")
    compile("org.thymeleaf:thymeleaf-spring4:2.1.2.RELEASE")
    runtime('mysql:mysql-connector-java:5.1.6')

    testCompile("junit:junit")
}

Configuring A Datasource
I’ll be calling my schema in MySQL “beyond-the-examples”. I’ll assume you’ve used the same name. Conveniently, Spring Boot Starter projects have an automatically configured property source path. This means that using a properties file for configuration data we’d like to externalize simply requires creating an “application.properties” file and putting it somewhere on the application’s classpath. We’ll create that file now, and add properties that we’ll use to set up our datasource.

/src/main/resources/application.properties:

spring.datasource.url=jdbc:mysql://localhost:3306/beyond-the-examples
spring.datasource.username=root
spring.datasource.password=
spring.datasource.driverClassName=com.mysql.jdbc.Driver

spring.jpa.hibernate.dialect= org.hibernate.dialect.MySQLInnoDBDialect
spring.jpa.generate-ddl=false

You can see I’m using the default configuration for MySQL. I wouldn’t recommend that for production.

Next, we’ll build references to these properties in our application’s configuration, so that we can use them to create a datasource bean that we can inject into our security configuration. Update the application configuration file to add these properties:

/src/main/java/com.rodenbostel.sample.Application.java:

    @Value("${spring.datasource.driverClassName}")
    private String databaseDriverClassName;

    @Value("${spring.datasource.url}")
    private String datasourceUrl;

    @Value("${spring.datasource.username}")
    private String databaseUsername;

    @Value("${spring.datasource.password}")
    private String databasePassword;

Next create a Datasource @Bean using these properties in the same file.

/src/main/java/com.rodenbostel.sample.Application.java:

    @Bean
    public DataSource datasource() {
        org.apache.tomcat.jdbc.pool.DataSource ds = new org.apache.tomcat.jdbc.pool.DataSource();
        ds.setDriverClassName(databaseDriverClassName);
        ds.setUrl(datasourceUrl);
        ds.setUsername(databaseUsername);
        ds.setPassword(databasePassword);

        return ds;
    }

Now, we have a datasource configured that we can @Autowire into any of our Spring beans, configuration or otherwise.

Create the Spring Security Tables
The DDL from the Spring.io docs is for HSQLDB. It’s syntax is not compliant with MySQL. Shout out to this guy (http://springinpractice.com/2010/07/06/spring-security-database-schemas-for-mysql) for publishing the MySQL versions of the default Spring Security schema. If you’re using MySQL like me, use the DDL from that blog to create a “users” table and an “authorities” table, then thank him. Since we’ll be properly encoding our passwords, we may want to make that password column a bit wider. Here’s what I ran:

create table users (    username varchar(50) not null primary key,    password varchar(255) not null,    enabled boolean not null) engine = InnoDb;create table authorities (    username varchar(50) not null,    authority varchar(50) not null,    foreign key (username) references users (username),    unique index authorities_idx_1 (username, authority)) engine = InnoDb;

Building The New Configuration
To start using the new datasource in the security configuration, we first need to wire the datasource bean into our SecurityConfiguration class. Update your SecurityConfiguration file to instruct spring to @Autowire this bean:

/src/main/java/com.rodenbostel.sample.SecurityConfiguration.java:

    @Autowired
    private DataSource datasource;

Next, we’re going to make a few significant changes to our AuthenticationManagerBuilder configuration to reference this datasource and a few other things, which I’ll review after the code:

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        JdbcUserDetailsManager userDetailsService = new JdbcUserDetailsManager();
        userDetailsService.setDataSource(datasource);
        PasswordEncoder encoder = new BCryptPasswordEncoder();

        auth.userDetailsService(userDetailsService).passwordEncoder(encoder);
        auth.jdbcAuthentication().dataSource(datasource);

        if(!userDetailsService.userExists("user")) {
            List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
            authorities.add(new SimpleGrantedAuthority("USER"));
            User userDetails = new User("user", encoder.encode("password"), authorities);

            userDetailsService.createUser(userDetails);
        }
    }


Prior to this, our AuthenticationManagerBuilder was configured on a single line - we were using an in-memory configuration, and creating a user directly on it.  Here, we’ll use the AuthenticationManagerBuilder to move from using:


auth.inMemoryAuthentication()

to using:

auth.jdbcAuthentication().dataSource(datasource);

Assuming there are already users in the database, believe it or not, that’s all we need to begin using the JDBC-backed AuthenticationManager. The requirement for creating new users and managing existing users is a foregone conclusion. In our case, we’d like to automatically configure a default user on app startup just like we were before. We can get a handle on the automatically configuration (by Spring Boot) UserDetailsService through our AuthenticationManagerBuilder at:

auth.getDefaultUserDetailsService();

…but that doesn’t quite do everything we need. On the first line of our updated AuthenticationManagerBuilder configuration method, you can see we’ve created a new instance of one of the provide implementations of UserDetailsService provided by Spring. If you don’t have a reason to customize how you manage users in your system, that is a perfectly suitable implementation, but there are things to consider. Please consult the API docs for more detail (http://docs.spring.io/spring-security/site/docs/3.2.4.RELEASE/apidocs/org/springframework/security/provisioning/JdbcUserDetailsManager.html). After creating the new reference to the JdbcUserDetailsManager, we need to set a reference to our datasource on it. Following that, we add our encoder for storing our passwords securely, and then we use the JdbcUserDetailsManager’s built-in functionality to check to see if our test user exists, and create him if he doesn’t.

Testing Again
Running the application should yield no change in behavior when compared with what we saw earlier. This is desired. What we will see that’s different will be in our database. Startup the app using: "gradle bootRun", and using your favorite database management tool, query the database to see our newly create user and their encoded password:

Screen Shot 2014-05-30 at 5.44.38 PM

Conclusion
I cobbled the information in this post from many sources - some I’ve remembered and have mentioned, and others I have not. I hope putting this information in a single post helps whoever stumbles upon it! That concludes this series of Spring Boot posts, but during the time I’ve been writing these, I’ve come up with two more topics to touch on, mostly surrounding further securing your app (http://www.jasypt.org/) and easier maintenance of your database tables (http://flywaydb.org/). Check back soon!

Part 4: Internationalization in Spring Boot

One of the many features provided by Spring Boot’s automatic configuration is a ResourceBundleMessageSource. This is the foundation of the support for internationalization provided by Spring as part of Spring Boot. However, resolving the user’s current locale, and being able to switch locales on demand is a little trickier, and is something that I had a hard time finding a comprehensive example of. In this installment, we’ll cover how to set up a resource bundle for messages, how to name them to support locales, and finally, how to wire a LocaleResolver and LocaleChangeInterceptor into our test application so that we can implement and test Spring’s internationalization support.

As usual, for this installment, I’ve created a copy of the code from Part 3 and created a new project called Part 4. It’s committed to Github, ready for cloning.

The Resource Bundle
First, we’ll create a resource bundle with message files for English and Spanish in our sample application. When provided a locale, the auto-configured message source can dynamically look up a message file using a default base file name – for example, the default messages file will be named “messages.properties”, whereas the file for Spanish messages will be called “messages_es.properties”. Likewise, the file for English language messages will be “messages_en.properties”. Basically, the message source resolves the file name in which to look for message properties by concatenating Spring’s default base file name “messages”, with an underscore and the locale name. The default location for these files, as specified by the auto-configuration, is “/src/main/resources”.

Create two files in /src/main/resources: messages_en.properties, and messages_es.properties.

We’ll add a couple of messages to each file, and we’ll use them to change the labels on our sample application’s based on the client’s locale.

Update “/src/main/resources/messages_en.properties”, adding the following properties and values:

/src/main/resources/messages_en.properties:

field1 = Field 1
field2 = Field 2

and likewise, for our Spanish clients, update “src/main/resources/messages_es.properties” to include the Spanish versions of the same properties and values (note that I do not speak Spanish. I typed these names into Google Translate. I think they are accurate enough for this example)

/src/main/resources/messages_es.properties:

field1 = El Campo 1
field2 = El Campo 2

Updating The View Template
The view template will need to be updated with placeholders containing the keys that Thymeleaf can use to swap in values from our message files. We’ve seen usages of ${} and *{} in these tutorials. The next one we’ll use is #{}, which is the placeholder Thymeleaf uses to bind messages, and can be used to do general string manipulation within the view.

Update our view template to include two new placeholders such that:

/src/main/resources/hello.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head lang="en">
    <meta charset="UTF-8" />
    <title>HELLO</title>
</head>
<body>
<p th:text="${message}"></p>
<form id="gizmo-form" role="form" th:action="@{/save}" method="post" th:object="${gizmo}">
    <div>
        <label for="field1" th:text="#{field1}"></label>
        <input type="text" id="field1" name="field1" th:field="${gizmo.field1}"/>
    </div>
    <div>
        <label for="field2" th:text="#{field2}"></label>
        <input type="text" id="field2" name="field2" th:field="${gizmo.field2}"/>
    </div>
    <div>
        <ul>
            <li th:each="item, stat : *{children}" class="itemRow">
                <div>
                    <label th:for="${'childField1-'+stat.index}">Field 1</label>
                    <input type="text" class="form-control quantity" name="childField1"
                           th:field="*{children[__${stat.index}__].childField1}" th:id="${'childField1-'+stat.index}"/>

                    <label th:for="${'childField2-'+stat.index}">Field 2</label>
                    <input type="text" class="form-control quantity" name="childField2"
                           th:field="*{children[__${stat.index}__].childField2}" th:id="${'childField2-'+stat.index}"/>
                </div>
            </li>
        </ul>
    </div>
    <div>
        <button type="submit">Save</button>
    </div>
</form>
</body>
</html>

You can see we’ve removed the label text (formerly “Field 1” and “Field 2″ for the ‘field1’ and ‘field2’ properties on the Gizmo object and added new th:text references containing our new binding types. In these cases, our goal will be to replace #{field1} with the value of the “field1” property in the appropriate messages file, and to replace the value of #{field2} in a similar fashion. If you were to start the server at this point, the solution would still not work. The last step ties the views to the message files.

Configuring The LocaleResolver
In order for Spring to know which message file’s values to make available to Thymeleaf, the application needs to be able to determine which locale the application is currently running in. For this, we need to configure a LocaleResolver.

In ‘src/main/java/com.rodenbostel.sample.Application.java’ file, configure a new LocaleResolver bean.

    @Bean
    public LocaleResolver localeResolver() {
        SessionLocaleResolver slr = new SessionLocaleResolver();
        slr.setDefaultLocale(Locale.US);
        return slr;
    }

This will configure a locale resolver that fits the following description, according to the Spring API:

“Implementation of LocaleResolver that uses a locale attribute in the user’s session in case of a custom setting, with a fallback to the specified default locale or the request’s accept-header locale”

The only item remaining is how to switch the locale without updating the configuration of the bean above and restarting our server or changing our default or accept-header specified locale.

Configuring a LocaleChangeInterceptor
Configuring an interceptor that is responsible or swapping out the current locale allows for easy testing by a developer, and also gives you the option of including a select list in your UI that lets the user pick the locale they prefer. Add the following bean to your ‘src/main/java/com.rodenbostel.sample.Application.java’ file:

    @Bean
    public LocaleChangeInterceptor localeChangeInterceptor() {
        LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
        lci.setParamName("lang");
        return lci;
    }

As you can see, this interceptor will look for a request parameter named ‘lang’ and will use its value to determine which locale to switch to. For example, adding ‘lang=en’ to the end of any request will render the messages from default English locale’s message file. Changing that parameter to ‘lang=es’ will render the Spanish version. For any interceptor to take effect, we need to add it to the application’s interceptor registry. In order to do that, we need to get a handle and override Spring Boot Web’s addInterceptor configuration method.

In order to do that, we need to update our ‘src/main/java/com.rodenbostel.sample.WebApplication.java’ file to extend WebMvcConfigurerAdapter. The WebMvcConfigurerAdapter’s existence is based on around activities such as this. It provides hooks to override base Spring configuration. Let’s update the class to extend this super class and add our interceptor:

package com.rodenbostel.sample;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;

import java.util.Locale;

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class Application extends WebMvcConfigurerAdapter {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public LocaleResolver localeResolver() {
        SessionLocaleResolver slr = new SessionLocaleResolver();
        slr.setDefaultLocale(Locale.US);
        return slr;
    }

    @Bean
    public LocaleChangeInterceptor localeChangeInterceptor() {
        LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
        lci.setParamName("lang");
        return lci;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(localeChangeInterceptor());
    }

}

Testing
Start your server and observe the default locale in action:
Screen Shot 2014-05-13 at 5.20.00 PM
then, add our request parameter and refresh the page to change the locale so that our Spanish language file is loaded:
Screen Shot 2014-05-13 at 5.20.52 PM
As Spring Boot matures, I’m sure the available examples will be updated to include end to end samples such as this. In the mean time, I hope you found this simple example useful. Check back soon for Spring Security integration in Part 5.