Social login with Spring-Social

spring-social

Social login has the advantage to simplify user account creation and to provide application with more accurate user information.

Implementing a social login with each social networking sites using an OAuth protocol could be a headache for a developer.

Spring provides social API and implements it for most of famous social networking site like LinkedIn, Facebook and Twitter. This API integrates easily to a Spring security project to provide authentication using OAuth2 protocol.

This post presents how to use Spring social to authenticate users for an Angular and Spring based project.

You can get project source code on GitHub and you can run the application here.

1 – Add maven dependencies

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

<dependency>
   <groupId>org.springframework.social</groupId>
   <artifactId>spring-social-config</artifactId>
   <version>${spring-social-version}</version>
</dependency>

<dependency>
   <groupId>org.springframework.social</groupId>
   <artifactId>spring-social-security</artifactId>
   <version>${spring-social-version}</version>
</dependency>

<dependency>
   <groupId>org.springframework.social</groupId>
   <artifactId>spring-social-linkedin</artifactId>
   <version>1.0.0.RELEASE</version>
</dependency>

<!-- add more social dependencies like github, twitter, google ... -->

2 – Spring Security config

We have to setup a security filter, SocialAuthenticationFilter, to integrate it with Spring Security so that a social network provider can be activated when a user needs to be authenticated. The security filer will listen to URL’s that start with /auth and route incoming requests to the corresponding social network provider. A request sent to /auth/linkedin will be redirected to the LinkedIn provider. The security filter is configured by an SpringSocialConfigurer.

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@ComponentScan(basePackages = {"com.mycompany.myproject.security"})
public class SecurityConfig extends WebSecurityConfigurerAdapter {
 
    // Dependency injection code ...

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

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .headers().disable()
            .csrf().disable()
            .authorizeRequests()
                .antMatchers("/auth/**").permitAll()
                .antMatchers("/signup/**").permitAll()
                .antMatchers("/**").authenticated()
                .and()
            .exceptionHandling()
                // Exception Handling config ...
            .formLogin()
                // Login config ...
            .logout()
                // Logout config ...
            .rememberMe()
                // Remember me config ...
            .apply(new SpringSocialConfigurer()
                .postLoginUrl("/")
                .defaultFailureUrl("/#/login")
                .alwaysUsePostLoginUrl(true))
                .and()
    }

    @Bean
    public SocialUserDetailsService socialUsersDetailService() {
        return new SimpleSocialUsersDetailService(userDetailsService());
    }
}

Next, we implement a SocialUserDetailsService to provide user details for the SpringSocialConfigurer.

public class SimpleSocialUsersDetailService implements SocialUserDetailsService {

    private UserDetailsService userDetailsService;

    public SimpleSocialUsersDetailService(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    @Override
    public SocialUserDetails loadUserByUserId(String userId) 
                           throws UsernameNotFoundException, DataAccessException {
        UserDetails userDetails = userDetailsService.loadUserByUsername(userId);
        return new SocialUser(userDetails.getUsername(), 
                           userDetails.getPassword(), userDetails.getAuthorities());
    }

}

2 – Spring Social config

We can configure Spring Social by following these steps:

  • Implement the SocialConfigurer interface.
  • Add @EnableSocial annotation to have the Spring MVC configuration defined in SocialConfiguration imported.
  • Configure each provider with the standard OAuth Client Id and Client Secret properties. This is done in the class SocialConfigurer where we add a connection factory implementation for each social network service.
  • Implement getUserIdSource() method. The UserIdSource object returned by this method is responsible of determining the correct account id of the user. Because our case the username of the user as an account id, we implement this method by returning a new AuthenticationNameUserIdSource object.
  • Implement the getUsersConnectionRepository() method. In this application we use the provided Jdbc Spring Implementation.
@Configuration
@EnableSocial
@ComponentScan(basePackages = {"com.mycompany.myproject.social"})
public class SocialConfig implements SocialConfigurer {

 @Autowired
 private AccountConnectionSignUpService accountConnectionSignUpService;

 @Autowired
 private DataSource dataSource;

 @Override
 public void addConnectionFactories(ConnectionFactoryConfigurer cfc, Environment env) {
  cfc.addConnectionFactory(new LinkedInConnectionFactory(
   env.getProperty("spring.social.linkedin.appId"),
   env.getProperty("spring.social.linkedin.appSecret")));
  cfc.addConnectionFactory(new GitHubConnectionFactory(
   env.getProperty("spring.social.github.appId"),
   env.getProperty("spring.social.github.appSecret")));
  cfc.addConnectionFactory(new TwitterConnectionFactory(
   env.getProperty("spring.social.twitter.appId"),
   env.getProperty("spring.social.twitter.appSecret")));
  GoogleConnectionFactory gcf = new GoogleConnectionFactory(
   env.getProperty("spring.social.google.appId"),
   env.getProperty("spring.social.google.appSecret"));
  gcf.setScope("email");
  cfc.addConnectionFactory(gcf);
 }

 @Override
 public UserIdSource getUserIdSource() {
  return new AuthenticationNameUserIdSource();
 }

 @Override
 public UsersConnectionRepository
 getUsersConnectionRepository(ConnectionFactoryLocator cfl) {
  JdbcUsersConnectionRepository repository =
   new JdbcUsersConnectionRepository(dataSource, cfl, Encryptors.noOpText());
  repository.setConnectionSignUp(accountConnectionSignUpService);
  return repository;
 }
}

The JdbcUsersConnectionRepository implementation requires adding UserConnection table to the database schema.

We use the following SQL statement to create the table:

create table UserConnection (userId varchar(255) not null,
	providerId varchar(255) not null,
	providerUserId varchar(255),
	rank int not null,
	displayName varchar(255),
	profileUrl varchar(512),
	imageUrl varchar(512),
	accessToken varchar(512) not null,
	secret varchar(512),
	refreshToken varchar(512),
	expireTime bigint,
	primary key (userId, providerId, providerUserId));
create unique index UserConnectionRank on UserConnection(userId, providerId, rank);

Next, we implement our ConnectionSignUp implementation. After user social authentication, we store user profile information in the user repository.

@Component
public class AccountConnectionSignUpService implements ConnectionSignUp {

 @Autowired
 private UserRepo usersRepo;

 @Override
 public String execute(Connection < ? > connection) {
  Profile profile = new Profile();
  BeanUtils.copyProperties(connection.fetchUserProfile(), profile);
  String userId = UUID.randomUUID().toString();
  profile.setImageUrl(connection.getImageUrl());
  usersRepo.createUser(userId, profile);
  return userId;
 }
}

3 – Get OAuth application Id and secret

The links bellows allow you to get an applications ids and secrets:

These screenshots show steps to follow to get OAuth application Ids and secrets for LinkedIn and Google.

This slideshow requires JavaScript.

Conclusion

Spring-Social is not just for Social login, it provides APIs for most of known SaaS (Software as a Service) providers; for example Spring-Social provides an API for getting user connections or friends.

In this post, we provide a sample project to implement Social login for a web application. For more details, you can checkout the source code from GitHub and test online the application here.

Reference

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

REST API documentation with Swagger, SpringMVC and AngularJS.

New front-end frameworks like AngularJS consume Rest API’s. As a result, we develop more Rest web services than SOAP ones.
Unlike SOAP, REST doesn’t provide wsdl (Web Services Description Language) to provide a definition of how the webservice works.

Well documented API make life easier for front-end developer and it is essential requirement for defining API’s success.

Swagger is one of the most popular frameworks for Restful API documentation. It provides documentation generation and a user interface to display the documentation in an awesome manner.

This post presents how to generate Rest API documentation for a Spring MVC project using Swagger, springfox and angular-swagger-ui.

You can download the source code of this article in this link : https://github.com/samer-abdelkafi/ng-swagger-project. And you can test it here

Add Springfox dependency

<dependency>
	<groupId>io.springfox</groupId>
	<artifactId>springfox-swagger2</artifactId>
	<version>2.2.2</version>	
</dependency>

Add configuration

All what you have to do as a Spring configuration is to add @EnableSwagger2 annotation to your existent MVC config class.

@Configuration
@EnableWebMvc
@EnableSwagger2
@ComponentScan(basePackages = { "com.mycompany.myproject.web.controller" })
public class MvcConfig extends WebMvcConfigurerAdapter {
  // Existent configuration code

}

Next, you can add swagger annotation to your controller.

Swagger annotation are not mandatory, springfox generate swagger docs for Spring MVC controllers. But, you may need them to customize your Rest API documentation.

For more information about swagger annotation, visit this link.

@RestController
@Api(description = "Users management API")
public class UserController {
  // Existent controller code
}

Test swagger code generation

If you request this URL (http://localhost:8080/v2/api-docs) in your browser, you will get json format swagger documenation.

{  
   "swagger":"2.0",
   "info":{  
      "description":"Api Documentation",
      "version":"1.0",
      "title":"Api Documentation",
      "termsOfService":"urn:tos",
      "contact":{  
         "name":"Contact Email"
      },
      "license":{  
         "name":"Apache 2.0",
         "url":"http://www.apache.org/licenses/LICENSE-2.0"
      }
   },
   "host":"localhost:8080",
   "basePath":"/",
   "tags":[  
      {  
         "name":"user-controller",
         "description":"Users management API"
      }
   ],
   // other details ...
}

Display documentation

Swagger provides user interface (Swagger UI) that dynamically generate beautiful documentation and sandbox from a Swagger-compliant API.

In this post, we will use an AngularJS implementation of Swagger UI (angular-swagger-ui). The advantage of this choice is that we can integrate the documentation page to an existent AngularJS project.

Add a angular-swagger-ui dependency

In this sample we use webjars. You may need to add webjars config to your Spring MVC project. For more information visit this link.

<dependency>
	<groupId>org.webjars.bower</groupId>
	<artifactId>angular-swagger-ui</artifactId>
	<version>0.2.3</version>
</dependency>

Run the angular-swagger-ui in this link:
http://localhost:8080/webjars/angular-swagger-ui/0.2.3/dist/index.html

Next, copy the swagger api url to the input and click on the “explore” button.

angular_swagger_ui_sample

Integrate the Swagger-UI to your AngularJs application

Add angular-swagger-ui dependency to your Html page.

<!DOCTYPE html>
<html ng-app="myApp">
<head lang="en">
    <meta charset="UTF-8">
    <link href="webjars/bootstrap/3.3.2/css/bootstrap.min.css" rel="stylesheet" >
    <link href="webjars/angular-swagger-ui/0.2.3/dist/css/swagger-ui.min.css" 
          rel="stylesheet" >
    <title></title>
</head>
<body>

<div class="navbar navbar-default">
    <div class="navbar-header">
		<!-- extra html code ... -->
    </div>
    <div class="navbar-collapse collapse navbar-responsive-collapse">
        <ul class="nav navbar-nav">
            <li><a href="#/users">Users</a></li>
            <li><a href="#/apiDoc">API Doc.</a></li>
        </ul>
    </div>
</div>

<div class="container">
	<section ng-view=""></section>
</div>

<script src="webjars/jquery/2.1.1/jquery.js"></script>
<script src="webjars/bootstrap/3.3.2/js/bootstrap.min.js"></script>
<script src="webjars/angular-swagger-ui/0.2.3/dist/scripts/swagger-ui.min.js"></script>
<script src="resources/js/app.js"></script>

</body>
</html>

Create a new html page with the source code bellow.

<style>
    .swagger-validator {
        display: none;
    }
</style>
<div class="well page">
    <h3 ng-show="isLoading">loading ...</h3>
    <div swagger-ui url="swaggerUrl" loading="isLoading" parser="json" 
         api-explorer="true"  error-handler="myErrorHandler">
    </div>
</div>

Modify the javascript code:

  • Include swaggerUi as a dependency.
  • Add a path apiDoc to the routeProvider.
  • Add an ApiDocController.
angular
    .module('myApp', ['ngResource', 'ngRoute', 'ngSanitize', 'swaggerUi'])
    .config(function ($routeProvider) {
        $routeProvider.when('/users', {
            templateUrl: 'partials/users.html',
            controller: 'UsersController',
        }).when('/apiDoc', {
            templateUrl: 'partials/apiDoc.html',
            controller: 'ApiDocController'
        }).otherwise({
            redirectTo: '/users',
        });
    })
    .service('UsersService', function ($log, $resource) {
        return {
            getAll: function () {
                var userResource = $resource('users', {}, {
                    query: {method: 'GET', params: {}, isArray: true}
                });
                return userResource.query();
            }
        }
    })
    .controller('UsersController', function ($scope, $log, UsersService) {
        $scope.users = UsersService.getAll();
    })
    .controller('ApiDocController', function ($scope) {
        // init form
        $scope.isLoading = false;
        $scope.url = $scope.swaggerUrl = 'v2/api-docs';
        // error management
        $scope.myErrorHandler = function (data, status) {
            alert('failed to load swagger: ' + status + '   ' + data);
        };

        $scope.infos = false;
    });

Deploy and run your application. If you access the “API Doc.” page, you will get a web page as shown below. You can test the sample application of this post in this link.

localhost_8080_apiDoc1

Conclusion

Springfox and Swagger are great tools for generating your Rest API documentation. They are easy to integrate in your project and they provide a rich documentation that reduces costs and dramatically improves developer adoption.

However, the current version 2.2.2 of Springfox doesn’t support the generation of documentation of spring-data-rest Rest API. In this case, you have to consider Spring REST Docs project which is a serious alternative to swagger.