Develop a web application with Angular2

Angular2 promises many improvements over the previous version.

It’s simpler to learn thanks to easier and more concise concepts like component-based architecture.

The Angular2 module system makes it easier to develop complex and larger projects.

Angular2 is faster than its ancestor thanks to completely rewritten data binding and change detection.

Router module is improved by providing new features like sibling views and nested states.

The aim of this post is to discover some new Angular2 features by developing a web project that uses apart Angular2 other projects like Express, TypeScript and MongoDb.

The source code is provided on GitHub here. You can run the project at this link.

Project structure

The Angular2 sample project has this structure.

ng2-project
    |
    +-client
    |   +-app 
    |   +-assets (Directoy conatins images and css)
    |   +-index.html
    |   +-tsconfig.json
    |   \-typings.json
    |
    +-server 
    |   +-server.ts
    |   +-tsconfig.json
    |   \-typings.json
    |
    +-dist (Directory generated by gulp)
    |
    +-gulpfile.js
    \-pakage.json

You can explore the project structure on Github here

Project dependencies

The package.json file identifies npm package dependencies for the project.

{
  "name": "ng2-project",
  "version": "1.0.0",
  "main": "server/index.js",
  "license": "ISC",
  "scripts": {
    "start": "concurrently \"npm run tsc:w\" \"npm run lite\" ",
    "tsc": "tsc",
    "tsc:w": "tsc -w",
    "lite": "lite-server",
    "postinstall": "gulp"
  },
  "dependencies": {
    "@angular/core": "2.0.0",
    "@angular/common": "2.0.0",
    "@angular/compiler": "2.0.0",
    "@angular/http": "2.0.0",
    "@angular/platform-browser": "2.0.0",
    "@angular/platform-browser-dynamic": "2.0.0",
    "@angular/router": "3.0.0",
    "@angular/upgrade": "2.0.0",
    "systemjs": "0.19.27",
    "es6-shim": "^0.35.0",
    "reflect-metadata": "^0.1.3",
    "rxjs": "5.0.0-beta.12",
    "zone.js": "^0.6.23",
    "angular2-in-memory-web-api": "0.0.20",
    "material-design-lite": "1.1.2",
    "express": "4.13.4",
    "mongodb": "2.1.18",
  },
  "devDependencies": {
    "concurrently": "^2.0.0",
    "lite-server": "^2.1.0",
    "typescript": "1.8.10",
    "del": "2.2.0",
    "gulp": "3.9.1",
    "gulp-concat": "2.6.0",
    "gulp-sourcemaps": "1.6.0",
    "gulp-typescript": "2.13.4",
    "gulp-tsd": "0.1.1",
    "gulp-typings": "1.3.6",
    "run-sequence": "1.1.5"
  }
}

1- Server Side

We start with the server side implementation, and then we can test the rest API before using it in the client side code.

1.1 Add tsconfig.json file

The tsconfig.json file provides a TypeScript compiler configuration.

{
  "version": "1.8.10",
  "compilerOptions": {
    "target": "ES5",
    "module": "commonjs",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "removeComments": false,
    "noImplicitAny": false
  },
  "files": [
    "typings/main.d.ts"
  ],
  "exclude": [
    "typings/browser",
    "typings/browser.d.ts"
  ]
}

1.1 Add typings file

TypeScript compiler doesn’t recognize natively some JavaScript libraries features and syntax.
Some of the included files that provides required description for TypeScript. Others don’t provide it and they will require to be configured in typings.js file.

{
  "version": false,
  "ambientDependencies": {
    "node": "github:DefinitelyTyped/DefinitelyTyped/node/node.d.ts#a44529fcb1e1cdc0355e71c42a6048de99b57c34",
    "express-serve-static-core": "github:DefinitelyTyped/DefinitelyTyped/express-serve-static-core/express-serve-static-core.d.ts#a44529fcb1e1cdc0355e71c42a6048de99b57c34",
    "express": "github:DefinitelyTyped/DefinitelyTyped/express/express.d.ts#a44529fcb1e1cdc0355e71c42a6048de99b57c34",
    "serve-static": "github:DefinitelyTyped/DefinitelyTyped/serve-static/serve-static.d.ts#a44529fcb1e1cdc0355e71c42a6048de99b57c34",
    "mime": "github:DefinitelyTyped/DefinitelyTyped/mime/mime.d.ts#a44529fcb1e1cdc0355e71c42a6048de99b57c34"
  }
}

1.2 – Install and create database

To install MongoDB you can download and install it by following instructions in this link https://docs.mongodb.com/manual/installation.
Next we will need to initialize database data. you can use the code bellow:

use mydata

db.users.insert([
{"id" : "1" , "name" : "Steve" , "mail" : "steve@apple.com" , "phone" : "+33 0120255555" , "adress" : "280 Central Park West New York, NY 10024"},
{"id" : "2" , "name" : "Narco" , "mail" : "narco@gamil.com" , "phone" : "+33 0120255555" , "adress" : "280 Central Park West New York, NY 10024"},
{"id" : "3" , "name" : "Bombasto" , "mail" : "bombasto@gmail.com" , "phone" : "+33 0120255555" , "adress" : "280 Central Park West New York, NY 10024"},
{"id" : "4" , "name" : "Celeritas" , "mail" : "celeritas@gmail.com" , "phone" : "+33 0120255555" , "adress" : "280 Central Park West New York, NY 10024"},
{"id" : "5" , "name" : "Magneta" , "mail" : "magneta@gmail.com" , "phone" : "+33 0120255555" , "adress" : "280 Central Park West New York, NY 10024"},
{"id" : "6" , "name" : "RubberMan" , "mail" : "rubber.man@gmail.com" , "phone" : "+33 0120255555" , "adress" : "280 Central Park West New York, NY 10024"}
]);

You can use MongoDb as a service provided by mLab. All what you have to do is to sign up, create a database and add a user to access the database by using a simple web user interface.

1.3 – Implement the Rest API

In this project we use Express.js to implement the server side API.

Express.js is a web application framework for Node.js, It is designed for building web applications and APIs.

import express = require('express');
import path = require('path');
var port: number = process.env.PORT || 3000;
var app = express();
var MongoClient = require('mongodb').MongoClient;
var database;

app.use('/assets', express.static(path.resolve(__dirname, 'assets')));
app.use('/app', express.static(path.resolve(__dirname, 'app')));
app.use('/libs', express.static(path.resolve(__dirname, 'libs')));

var server = app.listen(port, function() {
    var port = server.address().port;
    console.log("This express app is listening on port " + port);
});

MongoClient.connect('mongodb://test:test@ds021761.mlab.com:21761/mydata', 
    function(err, db) {
    if (err) {
        throw err;
    }
    database = db;
});

app.get("/api/users", (req, res) => {
    // Get all registrations
    database.collection('users').find().toArray(function(err, result) {
        if (err) {
            console.log("Can't get users from database : " + err);
            throw err;
        }
        console.log("/api/users result size = " + result.length);
        res.send(result);
    });
});

app.get("/api/users/:id", (req, res) => {
    database.collection('users').findOne( {id : req.params.id} , function(err, item) {
        if (err) {
            console.log("Can't get users from database : " + err);
            throw err;
        }
        console.log("found user :" + JSON.stringify(item));
        res.send(item);
    });
});

var renderIndex = (req: express.Request, res: express.Response) => {
    res.sendFile(path.resolve(__dirname, 'index.html'));
}

app.get('/*', renderIndex);

1.4 Build and test server side code

To build a project we use Gulp that install typings and compiles TypeScript files.

Bellow the source code of gulpfile.js file

var gulp = require('gulp');
var sourcemaps = require('gulp-sourcemaps');
var ts = require('gulp-typescript');
var runSequence = require('run-sequence');
var gulpTypings = require("gulp-typings");

// TYPINGS
gulp.task("installTypings", function () {
    var stream = gulp.src(["./server/typings.json"])
        .pipe(gulpTypings());
    return stream;
});

// SERVER
gulp.task('buildServer', function () {
    var tsProject = ts.createProject('server/tsconfig.json');
    var tsResult = gulp.src('server/**/*.ts')
        .pipe(sourcemaps.init())
        .pipe(ts(tsProject))
    return tsResult.js
        .pipe(sourcemaps.write())
        .pipe(gulp.dest('dist'))
});

gulp.task('build', function (callback) {
    runSequence('installTypings', 'buildServer', callback);
});

gulp.task('default', ['build']);

To build the project, Open a terminal window and enter this command:

npm install

Next, run the node server:

node dist/server.js

Then test the API in your browseron this url:
http://localhost:8080/api/users

If everything is well done, the browser display an array of users in JSON format.

2- Client side

2.1 Add typings file

Bellow the source code of client typings.json

{
  "ambientDependencies": {
    "es6-shim": "registry:dt/es6-shim#0.31.2+20160317120654"
  }
}

2.2 Add typescript configuration file

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "moduleResolution": "node",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "removeComments": false,
    "noImplicitAny": false
  },
  "exclude": [
    "node_modules",
    "typings/main",
    "typings/main.d.ts"
  ]
}

2.3 Add SystemJs config file

SystemJs is a dynamic module loader. It loads ES6 modules, AMD, CommonJS and global scripts in the browser and NodeJS.
SystemJs will load libraries when they are required.

The configuration below tells SystemJs where to look for a module when component is imported.

(function(global) {

    // map tells the System loader where to look for things
    var map = {
        'app':      'app',        
        // angular bundles
        '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
        '@angular/common': 'npm:@angular/common/bundles/common.umd.js',
        '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
        '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
        '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
        '@angular/http': 'npm:@angular/http/bundles/http.umd.js',
        '@angular/router': 'npm:@angular/router/bundles/router.umd.js',
        'rxjs':     'libs/rxjs',
        '@angular': 'libs/@angular'
    };

    // packages tells the System loader how to load when no filename and/or no extension
    var packages = {
        'app':      { main: './main.js',  defaultExtension: 'js' },
        'rxjs':     { defaultExtension: 'js' },
        'angular2-in-memory-web-api': {defaultExtension: 'js'}
    };

    var config = {
        paths: { // paths serve as alias
          'npm:': 'libs/'
        },
        map: map,
        packages: packages
    }

    System.config(config);
})(this);

2.4 Add index.html page

Here, we import style sheets, load libraries and configure SystemJs.

We provide to SystemJS the root module (app) of the application. Then SytemJs will look for all dependencies referenced by the app module.

<html lang="en">
<head>
    <base href="/"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">

    <title>ng2-project</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:regular,bold,italic,thin,light,bolditalic,black,medium&amp;lang=en">
    <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
    <link rel="stylesheet" href="https://code.getmdl.io/1.1.2/material.indigo-pink.min.css">
    <link rel="stylesheet" href="assets/css/styles.css">

    <!-- 1. Load libraries -->
    <!-- IE required polyfills, in this exact order -->
    <script src="libs/es6-shim/es6-shim.min.js"></script>
    <script src="libs/zone.js/dist/zone.js"></script>
    <script src="libs/reflect-metadata/Reflect.js"></script>
    <script src="libs/systemjs/dist/system.src.js"></script>

    <!-- 2. Configure SystemJS -->
    <script src="app/systemjs.config.js"></script>
    <script>
        System.import('app').catch(function (err) {
            console.error(err);
        });
    </script>

    <script src="libs/material-design-lite/dist/material.min.js"></script>

</head>

<!-- 3. Display the application -->
<body class="mdl-color--grey-100">
    <my-app>Loading...</my-app>
</body>
</html>

2.5 Add application module : app.module.ts

NgModule is a decorator function that describes the module. The most important properties are:

  • declarations – the view classes that belong to this module.
  • imports – other modules whose exported classes are needed by component templates declared in this module.
  • providers – creators of services that this module contributes to the global collection of services; they become accessible in all parts of the app.
  • bootstrap – the main application view, called the root component, that hosts all other app views. Only the root module should set this bootstrap property.

Here’s a root module of our application:

import { NgModule }             from '@angular/core';
import { BrowserModule }        from '@angular/platform-browser';
import { HttpModule }           from '@angular/http';
import { AppComponent }         from './app.component';
import { routing, appRoutingProviders }  from './app.routing';
import {HomeComponent}          from './home/home.comonent';
import {UserListComponent}      from './users/user-list.component';
import {UserDetailComponent}    from './users/user-detail.component';

@NgModule({
  imports: [
    BrowserModule,
    routing,
    HttpModule
  ],
  declarations: [
    AppComponent,
    HomeComponent,
    UserListComponent,
    UserDetailComponent
  ],
  providers: [
    appRoutingProviders
  ],
  bootstrap: [ AppComponent ]
})

export class AppModule { }

2.6 Add main.ts file

Here we launch the application by bootstrapping its root module.

/// <reference path="../typings/browser.d.ts" />
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app.module';
platformBrowserDynamic().bootstrapModule(AppModule);

2.7 Configure router : app.routing.ts

Here we configure the router that looks for a corresponding Route from which it can determine the component to display.

In the following example, we configure our application with three route definitions.

import { ModuleWithProviders } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import {HomeComponent}         from './home/home.comonent';
import {HomeResolver}         from './home/home.resolver';
import {HomeService}         from './home/home.service';
import {UserListComponent}     from './users/user-list.component';
import {UserDetailComponent}   from './users/user-detail.component';
import {UserDetailResolver}   from './users/user-detail.resolver';
import {UserListResolver}   from './users/user-list.resolver';
import {UserService}   from './users/user.service';

export const appRoutes : Routes =[
    {path: '',  
     component: HomeComponent,
     resolve : {technologies: HomeResolver}
    },
    {path: 'users', 
     component: UserListComponent,
     resolve : {users: UserListResolver}
    },
    {path: 'user/:id', 
     component: UserDetailComponent, 
     resolve : {user: UserDetailResolver}
    }];

export const appRoutingProviders: any[] = [HomeResolver, UserListResolver, 
        UserDetailResolver, HomeService, UserService];
export const routing: ModuleWithProviders = RouterModule.forRoot(appRoutes);

To achieve a better user-experience when browsing between pages, we use resolver.

It avoids displaying pages before data finished loading.

It also makes the controller’s code much cleaner in contrast to fetching data inside the controller.

import {Injectable} from '@angular/core';
import {Resolve, ActivatedRouteSnapshot, RouterStateSnapshot} from '@angular/router';
import {Observable} from 'rxjs/Observable';
import { User, UserService } from './user.service';

@Injectable()
export class UserListResolver implements Resolve<User[]> {
 constructor(
        private service: UserService
 ) {}
 resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<User[]> {
        return this.service.getUsers();
 }
}

2.7 Add components

2.6.1 Add application main component

import {Component} from '@angular/core';
import {MDL} from './directives/MaterialDesignLite';
import './rxjs-operators';

@Component({
    selector: 'my-app',
    templateUrl: 'app/app.component.html'
})

export class AppComponent { }

“rxjs-oppertors” references the “rxjs-oppertors.ts” file where we will imports all required rxjs statics and operators we need for this application.

// Statics
import 'rxjs/add/observable/throw';
// Operators
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/toPromise';

Next, we write the template html (app.component.html) code.

<div mdl class="mdl-layout mdl-js-layout mdl-layout--fixed-header">
  <header class="mdl-layout__header">
    <div class="mdl-layout__header-row">
      <!-- Title -->
      <span class="mdl-layout-title">Title</span>
      <!-- Add spacer, to align navigation to the right -->
      <div class="mdl-layout-spacer"></div>
      <!-- Navigation. We hide it in small screens. -->
      <nav class="mdl-navigation mdl-layout--large-screen-only">
        <a class="mdl-navigation__link" [routerLink]="['/']" >Home</a>
        <a class="mdl-navigation__link" [routerLink]="['users']">Users</a>
      </nav>
    </div>
  </header>
  <div class="mdl-layout__drawer">
    <span class="mdl-layout-title">Title</span>
    <nav class="mdl-navigation">
      <a class="mdl-navigation__link" [routerLink]="['/']" >Home</a>
      <a class="mdl-navigation__link" [routerLink]="['/users']">Users</a>
    </nav>
  </div>
  <main class="mdl-layout__content">
       <router-outlet></router-outlet>
  </main>
</div>

2.6.2 Add users list component

This page calls a rest web service and display users list.

First, we develop service module.

import {Injectable} from '@angular/core';
import {Http, Headers, Response} from '@angular/http';
import {Observable}     from 'rxjs/Observable';

export class User {
    constructor(public id:number, public name:string, 
         public email:string, public phone:string, public adress:string) {
    }
}

@Injectable()
export class UserService {
    constructor (private http: Http) {}
    private _usersUrl = 'api/users';

    private extractData(res: Response) {
        if (res.status < 200 || res.status >= 300) {
            throw new Error('Bad response status: ' + res.status);
        }
        return res.json();
    }

    private handleError (error: any) {
        let errMsg = error.message || 'Server error';
        console.error(errMsg); // log to console instead
        return Observable.throw(errMsg);
    }

    getUsers() : Observable<User[]> {
        return this.http.get(this._usersUrl)
            .map(this.extractData)
            .catch(this.handleError);
    }

    getUser(id:number | string) : Observable<User> {
        return this.http.get(this._usersUrl + "/" + id)
            .map(this.extractData)
            .catch(this.handleError);
    }
}

Next, we develop the UsersList component.

import {Component, OnInit}   from '@angular/core';
import {Router, ActivatedRoute} from '@angular/router';
import {User}   from './user.service';


@Component({
    templateUrl: 'app/users/user-list.component.html'
})
export class UserListComponent implements OnInit {
    users:User[];
    errorMessage:string;
    private selectedId:number;

    constructor(private route: ActivatedRoute,
                private router:Router) {
    }

    ngOnInit() {
        this.users = this.route.snapshot.data['users'];
    }

    isSelected(user:User) {
        return user.id === this.selectedId;
    }

    onSelect(user:User) {
        this.router.navigate(['/user', user.id]);
    }
}

The source code bellow presents the template html code

<div class="page-content mdl-grid">
    <div class="mdl-grid--no-spacing mdl-card mdl-shadow--4dp mdl-cell mdl-cell--12-col">
    <div class="users-card-wide">
        <div class="mdl-card__title mdl-color--purple-700">
            <h1 class="mdl-card__title-text">Users</h1>
        </div>
        <div class="mdl-list">
          <div class="mdl-list__item mdl-list__item--two-line" *ngFor="let user of users"
            [class.selected]="isSelected(user)"
            (click)="onSelect(user)">
            <span class="mdl-list__item-primary-content">
              <img class="user-list-avatar mdl-list__item-avatar" 
                   src="assets/img/users/user-{{user.id}}.jpg">
              <span>{{user.name}}</span>
              <span class="mdl-list__item-sub-title">
                {{user.mail}}
              </span>
            </span>
            <a class="mdl-list__item-secondary-action" href="#">
                <i class="material-icons">star</i>
            </a>
          </div>
        </div>
    </div>
</div>

3. Build project

To build the client side and the server side code of our project, we update gulpfile.js.

var gulp = require('gulp');
var path = require('path');
var sourcemaps = require('gulp-sourcemaps');
var ts = require('gulp-typescript');
var del = require('del');
var runSequence = require('run-sequence');
var gulpTypings = require("gulp-typings");


gulp.task('clean', function () {
    return del('dist')
});


// TYPINGS
gulp.task("installTypings", function () {
    var stream = gulp.src(["./client/typings.json", "./server/typings.json"])
        .pipe(gulpTypings());
    return stream;
});

gulp.task("deleteDupTypings", function () {
    return del(["./client/typings/main.d.ts",
        "./client/typings/main",
        "./server/typings/browser.d.ts",
        "./server/typings/browser"]);
});


// SERVER
gulp.task('buildServer', function () {
    // code provided previded in the previous section
});

// CLIENT
/*
 jsNPMDependencies, sometimes order matters here! so becareful!
 */
var jsNPMDependencies = [
    'material-design-lite/dist/material.min.js',
    'es6-shim/es6-shim.min.js',
    'zone.js/dist/zone.js',
    'reflect-metadata/Reflect.js',
    'systemjs/dist/system.src.js',
    'rxjs/**/*.js',
    '@angular/**/*.js'
];

gulp.task('buildIndex', function () {
    var mappedPaths = jsNPMDependencies.map(function (file) {
        return path.resolve('node_modules', file)
    });

    //Let's copy our head dependencies into a dist/libs
    var copyJsNPMDependencies = gulp.src(mappedPaths, {base: 'node_modules'})
        .pipe(gulp.dest('dist/libs'));

    //Let's copy html and js to dist
    var copyIndex = gulp.src(['client/**/*.html', 'client/**/*.js'])
        .pipe(gulp.dest('dist'));

    var copyAsserts = gulp.src('client/assets/**/*')
        .pipe(gulp.dest('dist/assets'));

    return [copyJsNPMDependencies, copyIndex, copyAsserts];
});

gulp.task('buildClient', function () {
    var tsProject = ts.createProject('client/tsconfig.json');
    var tsResult = gulp.src('client/**/*.ts')
        .pipe(sourcemaps.init())
        .pipe(ts(tsProject));
    return tsResult.js
        .pipe(sourcemaps.write())
        .pipe(gulp.dest('dist'));
});

// WATCH
gulp.task('watch', ['buildServer'], function () {
    gulp.watch('server/*.ts', ['buildServer']);
    gulp.watch('client/**/*.ts', ['buildClient']);
});

// BUILD
gulp.task('build', function (callback) {
    runSequence('clean', 'installTypings', 'deleteDupTypings', 'buildServer', 
                  'buildIndex', 'buildClient', callback);
});

gulp.task('default', ['build']);

Next, Open a terminal window and enter this command:

npm install

4. Run the project

Finally, we launch the node server.

node dist/server.js

Open your browser and test the application at this URL http://localhost:8080.

angular2_sample_prinscreen

Conclusion

First impression after developing this sample project, Angular2 needs more investment than its ancestor from developer to start developing application. It requires learning Typescript and understanding other features like SystemJs and Typings.

Angular2 have the advantage to be modular and more structured than its previous version that make it a good choice for large applications development.

Advertisements

Secure AngularJs application with Spring Security

Most of JEE developers are aware of web applications security requirements and are familiar with security frameworks like Spring-Security.

But when we have to secure a web application based on front-end framework like AngularJs, we will have to deal with some specific features and we will need to customize the server-side configuration.

For example, some commonly used features like Spring-Security taglib can’t be used because page is rendered client-side.

Photo credit: SLO Icon Design
In this post, I will present a sample project to deal with some common security features in an AngularJs project.
You can get project source code on GitHub here and you can run the application here.


1 – Back-end security management

This parts provides instructions on how to add Spring Security to an existing application using Spring java based config.

1.1 – Back-end dependencies

In order to use Spring Security you must add the necessary dependencies. For the sample we will add the following Spring Security dependencies:

      <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <version>4.0.3.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>4.0.3.RELEASE</version>
        </dependency>

1.2 – Spring security config

In this step, we create a Spring Security configuration.

@EnableWebSecurity
@Configuration
@ComponentScan(basePackages = {"com.mycompany.myproject.security"})
public class SecurityConfig extends WebSecurityConfigurerAdapter {

 public static final String REMEMBER_ME_KEY = "rememberme_key";

 @Autowired
 private RestUnauthorizedEntryPoint restAuthenticationEntryPoint;

 // Autowire other required beans 

 @Autowired
 public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
  auth.userDetailsService(userDetailsService);
 }

 @Override
 public void configure(WebSecurity web) throws Exception {
  web.ignoring().antMatchers("/resources/**", "/index.html", "/login.html",
   "/partials/**", "/", "/error/**");
 }

 @Override
 protected void configure(HttpSecurity http) throws Exception {
  http
   .headers().disable()
   .csrf().disable()
   .authorizeRequests()
    .antMatchers("/v2/api-docs").hasAnyAuthority("admin")
    .antMatchers("/users/**").hasAnyAuthority("admin")
    .anyRequest().authenticated()
    .and()
   .exceptionHandling()
    .authenticationEntryPoint(restAuthenticationEntryPoint)
    .accessDeniedHandler(restAccessDeniedHandler)
    .and()
   .formLogin()
    .loginProcessingUrl("/authenticate")
    .successHandler(restAuthenticationSuccessHandler)
    .failureHandler(restAuthenticationFailureHandler)
    .usernameParameter("username")
    .passwordParameter("password")
    .permitAll()
    .and()
   .logout()
    .logoutUrl("/logout")
    .logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler())
    .deleteCookies("JSESSIONID")
    .permitAll()
    .and()
   .rememberMe()
    .rememberMeServices(rememberMeServices)
    .key(REMEMBER_ME_KEY)
    .and();
 }
}

1.2.1 – Authentication config

Authentication entry point
We need a custom authenticationEntryPoint because default Spring-Security config will redirect to login page. In our case we need just a https status 401 and a json response.

@Component
public class RestUnauthorizedEntryPoint implements AuthenticationEntryPoint {

 @Override
 public void commence(HttpServletRequest request, HttpServletResponse response,
  AuthenticationException exception) throws IOException, ServletException {
  SecurityUtils.sendError(response, exception, HttpServletResponse.SC_UNAUTHORIZED,
   "Authentication failed");
 }
}

Login success handler
The login success handler returns http status 200 with user info in json format.

@Component
public class RestAuthenticationSuccessHandler 
extends SimpleUrlAuthenticationSuccessHandler {

 @Autowired
 private UserRepo userService;

 @Override
 public void onAuthenticationSuccess(HttpServletRequest request, 
  HttpServletResponse response, Authentication authentication)
 throws ServletException, IOException {
  User user = userService.findByLogin(authentication.getName());
  SecurityUtils.sendResponse(response, HttpServletResponse.SC_OK, user);
 }
}

Login fail handler
The login fail handler returns http status 401.

public class RestAuthenticationFailureHandler
extends SimpleUrlAuthenticationFailureHandler {

 @Override
 public void onAuthenticationFailure(HttpServletRequest request,
  HttpServletResponse response, AuthenticationException exception)
 throws IOException, ServletException {
  SecurityUtils.sendError(response, exception, HttpServletResponse.SC_UNAUTHORIZED,
   "Authentication failed");
 }
}

Logout success handler
When logout succeeds, we need to return ok status instead of login page redirection.
Spring security implements logout handler that returns ok status.

org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler

Account Rest web service
The account Rest web service returns the authenticated user account information if he’s already authenticated.

@RestController
public class SeurityController {

 @Autowired
 private UserRepo userRepo;

 @RequestMapping(value = "/security/account", method = RequestMethod.GET)
 public @ResponseBody
 User getUserAccount() {
  User user = userRepo.findByLogin(SecurityUtils.getCurrentLogin());
  user.setPassword(null);
  return user;
 }

}

1.2.2 – Authorization config

You can manage authorization by specifying authorized roles for each secured URL.

@Configuration
// @other annotations ..
public class SecurityConfig extends WebSecurityConfigurerAdapter {

// extra config code ...

@Override
protected void configure(HttpSecurity http) throws Exception {
  http
    .authorizeRequests()
    .antMatchers("/v2/api-docs").hasAnyAuthority("admin")
    .antMatchers("/users/**").hasAnyAuthority("admin")
    .anyRequest().authenticated()
    .and()
    // others config ...
}

}

Using Spring-Security annotations is another way to manage authorization.

First, we enable method security in the config class

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
// @other annotations ..
public class SecurityConfig extends WebSecurityConfigurerAdapter {
  // config code ... 
}

Next, we add annotation for a method that must have access restriction.

@RestController
public class SeurityController {

    @PreAuthorize("hasAuthority('admin')")
    @RequestMapping(value = "/security/tokens", method = RequestMethod.GET)
    public @ResponseBody
    List<Token> getTokens () {
       // method code ...
    }
}

Access denied handler
The access denied handler returns http status 403.

@Component
public class RestAccessDeniedHandler implements AccessDeniedHandler {

 @Override
 public void handle(HttpServletRequest request, HttpServletResponse response,
  AccessDeniedException exception) throws IOException, ServletException {
  SecurityUtils.sendError(response, exception, HttpServletResponse.SC_FORBIDDEN, 
   "Not authorized resources");
 }
}

2 – Front-end security management

2.1 – Front-end dependencies

If you are using Bower, you need to add to your bower.json file the dependencies in the code bellow.

{
  "name": "example",
  "version": "0.0.1",
  "dependencies": {
    "jquery": "2.1.4",
    "angular": "~1.4",
    "angular-route": "~1.4",
    "angular-resource": "~1.4",
    "angular-sanitize": "~1.4",
    "angular-animate": "~1.4",
    "angular-spinkit": "0.3.3",
    "angular-http-auth": "1.2.2",
    // others dependencies
  }
}

2.2 – AngularJs config

The code source bellow presents the routeProvider configuration.

var myapp = angular
    .module('myApp', ['ngResource', 'ngRoute', 'http-auth-interceptor']);

myapp.config(function ($routeProvider, USER_ROLES) {

    $routeProvider.when("/home", {
        templateUrl: "partials/home.html"      
    }).when('/login', {
        templateUrl: 'partials/login.html',
        controller: 'LoginController'
    }).when("/error/:code", {
        templateUrl: "partials/error.html",
        controller: "ErrorController"
    }).
       // Other routes config ...
       // ......
    }).otherwise({
        redirectTo: '/error/404'
    });
});

2.3 – Authentication management

First, create services.


myapp.service('Session', function () {
    this.create = function (data) {
        this.id = data.id;
        this.login = data.login;
        this.firstName = data.firstName;
        this.lastName = data.familyName;
        this.email = data.email;
        this.userRoles = [];
        angular.forEach(data.authorities, function (value, key) {
            this.push(value.name);
        }, this.userRoles);
    };
    this.invalidate = function () {
        this.id = null;
        this.login = null;
        this.firstName = null;
        this.lastName = null;
        this.email = null;
        this.userRoles = null;
    };
    return this;
});

myapp.service('AuthSharedService', function($rootScope, $http, authService, Session) {
 return {
  login: function(userName, password, rememberMe) {
   var config = {
    params: {
     username: userName,
     password: password,
     rememberme: rememberMe
    },
    ignoreAuthModule: 'ignoreAuthModule'
   };
   $http.post('authenticate', '', config)
    .success(function(data, status, headers, config) {
     authService.loginConfirmed(data);
    }).error(function(data, status, headers, config) {
     $rootScope.authenticationError = true;
     Session.invalidate();
    });
  }
 };
});

Next, write a login controller.

myapp.controller('LoginController', function($rootScope, $scope, AuthSharedService) {
 $scope.rememberMe = true;
 $scope.login = function() {
  $rootScope.authenticationError = false;
  AuthSharedService.login($scope.username, $scope.password, $scope.rememberMe);
 }
});

Finally, create login.html partial page.

<form>
    <div class="form-group"
         ng-class="{'has-error is-focused' : authenticationError}">
        <input id="login" ng-model="username" type="text" class="form-control" 
               required="required" placeholder="login"/>
        <span ng-show="authenticationError" class="help-block">
            Please check your credentials and try again.
        </span>
    </div>

    <div class="form-group">
        <input id="password" ng-model="password" type="password" class="form-control" 
               required="required" placeholder="password"/>
    </div>

    <input type="checkbox" ng-model="rememberMe"/><span> Remember me</span>

    <button ng-click="login()" >Login</button>
</form>

2.4 – Authorization management

In this part, we deal with server-side authorization error and with client-side authorization managing.

2.4.1 Manage server-side authorization

If the requested resource or service isn’t authorized for the authenticated User, the request will fail with response status 403.
The client-side logic must intercept the error and redirect the user to a 403 error page.

myapp.run(function($rootScope, $location, $http, AuthSharedService, Session,
 USER_ROLES, $q, $timeout) {
// Call when the 403 response is returned by the server
 $rootScope.$on('event:auth-forbidden', function(rejection) {
  $rootScope.$evalAsync(function() {
   $location.path('/error/403').replace();
  });
 });
}

2.4.2 – Client-side authorization management

It’s better to manage authorization client-side for better performance and to avoid servers-side errors.

We added an access property which details if the route requires the user to be logged in and what permissions the user must have to access the route.


myapp.constant('USER_ROLES', {
    all: '*',
    admin: 'admin',
    user: 'user'
});

myapp.config(function ($routeProvider, USER_ROLES) {

   $routeProvider.when("/home", {
        templateUrl: "partials/home.html",
        controller: 'HomeController',
        access: {
            loginRequired: true,
            authorizedRoles: [USER_ROLES.all]
        }
    }).when('/users', {
        templateUrl: 'partials/users.html',
        controller: 'UsersController',
        access: {
            loginRequired: true,
            authorizedRoles: [USER_ROLES.admin]
        }
    })
    // other routs config ...
    // ... 
    .otherwise({
        redirectTo: '/error/404',
        access: {
            loginRequired: false,
            authorizedRoles: [USER_ROLES.all]
        }
    });
}

Next, we add a service function to check if authenticated user has required roles to access the resource.

myapp.service('AuthSharedService', function (Session) {
    return {
        // other functions ...
        isAuthorized: function (authorizedRoles) {
            if (!angular.isArray(authorizedRoles)) {
                if (authorizedRoles == '*') {
                    return true;
                }
                authorizedRoles = [authorizedRoles];
            }
            var isAuthorized = false;
            angular.forEach(authorizedRoles, function (authorizedRole) {
                var authorized = (!!Session.login &&
                Session.userRoles.indexOf(authorizedRole) !== -1);
                if (authorized || authorizedRole == '*') {
                    isAuthorized = true;
                }
            });
            return isAuthorized;
        }
    };
});

Finally, we implement a listener on the $routeChangeStart event to track the next route navigation.
– If the user is not yet authenticated the function broadcast “event:auth-loginRequired”.
– If the user is not authorized the function broadcast “event:auth-loginRequired”.

myapp.run(function($rootScope, AuthSharedService, USER_ROLES) {

 $rootScope.$on('$routeChangeStart', function(event, next) {
 if (next.originalPath === "/login" && $rootScope.authenticated) {
   event.preventDefault();
  } else if (next.access && next.access.loginRequired && !$rootScope.authenticated) {
   event.preventDefault();
   $rootScope.$broadcast("event:auth-loginRequired", {});
  }else if(next.access && !AuthSharedService.isAuthorized(next.access.authorizedRoles)) {
   event.preventDefault();
   $rootScope.$broadcast("event:auth-forbidden", {});
  }
 });

}

2.4.3 – Manage already authenticated user

If the user is already authenticated and he has a valid remember me token he should access the application to the requested page.

When the application starts running, it requests account Rest web service. If the user is already authenticated the rest API will return user details.

We have to carry about account Rest web service response time. That’s why we redirect the user to loading page until getting the response to check authorities for accessing the requested page.

First, we update the AutehtSharedService with getAccount function.

myapp.service('AuthSharedService', function ($rootScope, $http, $resource, 
    authService, Session) {
    return {
        login: function (userName, password, rememberMe) {
            // login code ...
        },
        getAccount: function () {
            $rootScope.loadingAccount = true;
            $http.get('security/account')
                .then(function (response) {
                    authService.loginConfirmed(response.data);
                });
        },
        isAuthorized: function (authorizedRoles) {
            // isAuthorized code ..
        }
    };
});

Next, we add code to handle events and call getAccount function.

myapp.run(function($rootScope, $location, $http, AuthSharedService, Session,
 USER_ROLES, $q, $timeout) {

 $rootScope.$on('$routeChangeStart', function(event, next) {
  // route change start code ...
 });

 // Call when the the client is confirmed
 $rootScope.$on('event:auth-loginConfirmed', function(event, data) {
  $rootScope.loadingAccount = false;
  var nextLocation = ($rootScope.requestedUrl ? $rootScope.requestedUrl : "/home");
  var delay = ($location.path() === "/loading" ? 1500 : 0);

  $timeout(function() {
   Session.create(data);
   $rootScope.account = Session;
   $rootScope.authenticated = true;
   $location.path(nextLocation).replace();
  }, delay);

 });

 // Call when the 401 response is returned by the server
 $rootScope.$on('event:auth-loginRequired', function(event, data) {
  if ($rootScope.loadingAccount && data.status !== 401) {
   $rootScope.requestedUrl = $location.path()
   $location.path('/loading');
  } else {
   Session.invalidate();
   $rootScope.authenticated = false;
   $rootScope.loadingAccount = false;
   $location.path('/login');
  }
 });

 $rootScope.$on('event:auth-forbidden', function(rejection) {
  // auth-forbidden code ...
 });

 // Get already authenticated user account
 AuthSharedService.getAccount();

});

2.5 – Securing UI Elements

To secure UI elements, we will create a directive that accepts as attribute a comma-separated list of authorized roles.

myapp.directive('access', [
    'AuthSharedService',
    function (AuthSharedService) {
        return {
            restrict: 'A',
            link: function (scope, element, attrs) {
                var roles = attrs.access.split(',');
                if (roles.length > 0) {
                    if (AuthSharedService.isAuthorized(roles)) {
                        element.removeClass('hide');
                    } else {
                        element.addClass('hide');
                    }
                }
            }
        };
    }]);

Next, we specify authorized roles in the access property.


      <ul class="nav navbar-nav">
            <li><a href="#/home" class="mdi-action-home"></a></li>
            <li><a href="#/users">Users</a></li>
            <li><a href="#/apiDoc">API Doc.</a></li>
            <li><a href="#/tokens" access="admin">Sessions</a></li>
        </ul>

Conclusion

Even if security is managed client-side with all security features, it’s important to manage security server-side.
Client-side security is easy to hack; user can inspect code and change CSS and display hidden elements and it can display network requests and execute them out of the application.

I hope this post helped you to get an overview on securing an Angular web application with Spring Security. For more information you can read source code in Github and run a demo here.

Thanks for your comments 🙂

References

Integrate Gulp on a Maven managed project

Using front-end frameworks for a java web application like AngularJs and Bootstrap needs some caution to manage JavaScript and Css dependencies.

There are two tools family for front-end libraries management. The Java ones like Wro4j and Jawr witch have the advantage to integrate easily to Java project management softs like maven and grails and don’t need any extra knowledge.

The second ones are JavaScript tools like Grunt, Bower and Gulp. Those tools have a great success and have the advantage to be used for front-end frameworks development.

Photo credit: Tony lea

As a result, you will face less problems where managing front-end dependencies and optimizing web resources.

This this post presents a solution to use Maven, Bower and Gulp to manage front-end libraries for a java web project.

The source code of this post is available in this link and you can test it here.

Add frontend maven Plugin

Frontend plugin install node and npm and It lets you run Bower and Gulp.

Add the xml bellow to your project pom.xml file to configure “frontend-maven-plugin”.

<build>
    <plugins>    
         <plugin>
             <groupId>com.github.eirslett</groupId>
             <artifactId>frontend-maven-plugin</artifactId>
             <version>0.0.20</version>
             <executions>
                 <execution>
                     <id>install node and npm</id>
                     <goals>
                         <goal>install-node-and-npm</goal>
                     </goals>
                     <phase>generate-resources</phase>
                     <configuration>
                         <nodeVersion>v0.10.26</nodeVersion>
                         <npmVersion>1.4.3</npmVersion>
                     </configuration>
                </execution>
                <execution>
                     <id>npm install</id>
                     <goals>
                        <goal>npm</goal>
                     </goals>
                     <phase>generate-resources</phase>
                </execution>
                <execution>
                     <id>bower install</id>
                     <goals>
                        <goal>bower</goal>
                     </goals>
                     <configuration>
                        <arguments>install</arguments>
                     </configuration>
                </execution>
                <execution>
                     <id>gulp build</id>
                     <goals>
                         <goal>gulp</goal>
                     </goals>
                     <phase>generate-resources</phase>
                </execution>
            </executions>
         </plugin>
     </plugins>
</build>

Add front-end dependencies

Bower works by fetching and installing front-end packages. For more information about bower visit the website bower.io.

Add a bower.json file with front-end dependencies in the root directory of your maven project (next to the pom.xml file).

{
  "name": "example",
  "version": "0.0.1",
  "dependencies": {
    "jquery": "~2.1.3",
    "angular": "1.4.5",
    "angular-route": "1.4.5",
    "angular-resource": "1.4.5",
    "angular-sanitize": "1.4.5",
    "bootstrap": "3.3.4",
    "bootstrap-material-design": "0.3.0",
    "angular-swagger-ui": "~0.2.3"
  },
  "private": true
}

Add Glup module dependencies

Add package.json file with the content bellow.

{
  "name": "example",
  "version": "0.0.1",
  "dependencies": {
    "bower": "~1.3.12",
    "gulp": "^3.9.0",
    "gulp-concat": "^2.6.0",
    "gulp-concat-vendor": "0.0.4",
    "gulp-uglify": "^1.3.0",
    "main-bower-files": "^2.9.0",
    "gulp-minify-css" : "^1.2.1",
    "gulp-notify":"2.2.0",
    "gulp-inject": "1.5.0",
    "gulp-run-sequence": "0.3.2",
    "stream-series": "0.1.1",
    "gulp-gzip": "1.2.0",
    "gulp-clone": "1.0.0"
  },
  "jspm": {
    "dependencies": {
      "jquery": "github:jquery/jquery@^2.1.3"
    },
    "devDependencies": {
      "traceur": "github:jmcriffey/bower-traceur@0.0.88",
      "traceur-runtime": "github:jmcriffey/bower-traceur-runtime@0.0.88"
    }
  },
  "scripts": {
    "prebuild": "npm install",
    "build": "gulp"
  }
}

Add a Gulp execution file

Create the Gulp execution file gulpfile.js with the content bellow.

var gulp           = require('gulp');
var concat         = require('gulp-concat');
var concatVendor   = require('gulp-concat-vendor');
var uglify         = require('gulp-uglify');
var minify         = require('gulp-minify-css')
var mainBowerFiles = require('main-bower-files');
var inject         = require('gulp-inject');
var runSequence    = require('gulp-run-sequence');
var gzip           = require('gulp-gzip');
var clone          = require('gulp-clone');
var series         = require('stream-series');

var vendorJs;
var vendorCss;

gulp.task('lib-js-files', function () {
    vendorJs = gulp.src(mainBowerFiles('**/*.js'),{ base: 'bower_components' })
        .pipe(concatVendor('lib.min.js'))
        .pipe(uglify())
        .pipe(gulp.dest('src/main/webapp/resources/vendor/js'));

    vendorJs.pipe(clone())
        .pipe(gzip())
        .pipe(gulp.dest('src/main/webapp/resources/vendor/js'));
});

gulp.task('lib-css-files', function () {
    vendorCss = gulp.src(mainBowerFiles('**/*.css'), {base: 'bower_components'})
        .pipe(concat('lib.min.css'))
        .pipe(minify())
        .pipe(gulp.dest('src/main/webapp/resources/vendor/css'));

    vendorCss.pipe(clone())
        .pipe(clone())
        .pipe(gzip())
        .pipe(gulp.dest('src/main/webapp/resources/vendor/css'));
});

gulp.task('index', function () {
    var target = gulp.src("src/main/webapp/index.html");
    var sources = gulp.src(['src/main/webapp/resources/js/*.js', 'src/main/webapp/resources/css/*.css'], {read: false});
    return target.pipe(inject(series(vendorJs, vendorCss, sources), {relative: true}))
        .pipe(gulp.dest('src/main/webapp'));
});

gulp.task('copyFonts', function() {
    gulp.src(mainBowerFiles('**/dist/fonts/*.{ttf,woff,woff2,eof,svg}'))
        .pipe(gulp.dest('src/main/webapp/resources/vendor/fonts'));
});

// Default Task
gulp.task('default', function () {
    runSequence('lib-js-files', 'lib-css-files', 'index', 'copyFonts');
});

The gulp script has 4 tasks:

  • lib-js-files : concat, uglify and compress all vendor javascript dependencies
  • lib-css-files : concat, minify and compress all vendor css dependencies
  • index : inject the dependencies to file
  • copyFonts : copy fonts to vendor/font directory to keep css references

The graph bellow presents gulp tasks flows.

gulpgliffy

Configure Spring to resolve Gzip files

In this step, we configure Spring MVC framework to look for Gzip files for the application web resources.

@Configuration
@EnableWebMvc
// Extra config annotations 
public class MvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/vendor/**")
                .addResourceLocations("/resources/vendor/")
                .setCachePeriod(0)
                .resourceChain(true)
                .addResolver(new GzipResourceResolver())
                .addResolver(new PathResourceResolver());
    }

    // Extra config here

}

Build and deploy your application

Build your application by running :

mvn clean install

Under OpenShift, you may need to change home directory (npm needs to create files under HOME directory):
>HOME=$OPENSHIFT_DATA_DIR

If everything is well done, your project directory will have the structure described below.

project
   +-- bower_components **
   +-- node **
   +-- node_module **
   +-- src
   |    +-- main
   |    |     +-- java
   |    |     +-- resources
   |    |     \-- webapp
   |    |           +-- resources
   |    |           |      +-- css
   |    |           |      +-- js
   |    |           |      \-- vendor **
   |    |           |            +-- css
   |    |           |            |    +-- lib.min.css
   |    |           |            |    \-- lib.min.css.gz
   |    |           |            +-- fonts 
   |    |           |            |    +-- glyphicons-halflings-regular.svg
   |    |           |            |    +-- ....
   |    |           |            \-- js
   |    |           |                 +-- lib.min.js
   |    |           |                 \-- lib.min.js.gz
   |    |           +-- WEB-INF
   |    |           \-- index.html 
   |    +-- test
   +-- target
   +-- bower.json
   +-- gulpfile.js
   +-- package.json
   \-- pom.xml

Finally, deploy and run the application. The sample application of this post is deployed on this link.

The two pictures bellow present Chrome DevTools to compare network performance of the optimized web application which has a load time of 2.74s and the same application without resource optimization which has a load time of 3.37s.

2015_09_23_16_02_46_tomcat7_samerabdelkafi.rhcloud.com_fng_project_apiDoc 2015_09_23_16_03_24_tomcat7_samerabdelkafi.rhcloud.com_ng_swagger_apiDoc