Angular-js and Restful web services access

Following my previous article about the integration of Spring Security with web services, I will now explain how I integrate it with an Angular-js project.
For that purpose, I am a little bit lazy and I use a project I already written instead of building a completely new one.

The project is a “funny” pet-store based on Spring boot (I like it definitely!), using an H2 database and Flyway to restore its content at each start.
The full project is available on Github.

The web services are defined with Spring MVC in the net.classnotfound.pet.web.PetController:

package net.classnotfound.pet.web;
...
@RestController
public class PetController {
	
	@Autowired
	private PetService petService;
	
	@RequestMapping(value = "/pet/all", method = RequestMethod.GET)
	@ResponseBody
	public Collection<JsonPet> findAll() {
		...
	}
	
	@Transactional(propagation= Propagation.SUPPORTS)
	@RequestMapping(value = "/pet/{id}", method = RequestMethod.GET)
	@ResponseBody
	public JsonPet find(@PathVariable("id") final Integer id) {
		...
	}

	@Transactional(propagation= Propagation.REQUIRED)
	@RequestMapping(value = "/pet", method = RequestMethod.POST, consumes = {"application/json" })
	@ResponseStatus(HttpStatus.CREATED)
	public @ResponseBody JsonPet save(@RequestBody final JsonPet jsonPet) {
		...
	}
}

The PetService is the class responsible of business processes (mainly access data here).

We can see that there are 3 actions available to:

  • display the list of pets in the store
  • display the detail of a pet
  • create a “new” pet in the store

The attributes of the requestMapping annotation seem explicit, for example, with @RequestMapping(value = “/pet”, method = RequestMethod.POST, consumes = {“application/json” }), I express that the method can be reach with /pet URL, by using the POST method and it needs json.
Based on Spring, the conversion from Json to Java Object (and the opposite) is totally hidden (it relies on Jackson API) and it avoid this burden to the developer (meaning me, here!).

Now that the landscape is set, let’s have a look at the security configuration (it should be familiar to whose who read my previous article):

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

@Override
protected void configure(HttpSecurity http) throws Exception { 
	
	http
	.csrf().disable()
	.authorizeRequests()
	.antMatchers("/login").anonymous()
	.antMatchers(HttpMethod.GET, "/pet/**").access("hasRole('USER')")
	.antMatchers(HttpMethod.POST, "/pet/**").access("hasRole('ADMIN')");
	
	http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
	FormLoginConfigurer formLogin = http.formLogin();
	formLogin.loginProcessingUrl("/login");
	formLogin.usernameParameter("username");
	formLogin.passwordParameter("password");
	formLogin.successHandler(authenticationSuccessHandler);
	formLogin.failureHandler(authenticationFailureHandler);
	
}

I started with the users definition with their roles.
And after that, Y can see how I restricted access to the features, as previously, I disabled the CSRF protection but I will come back to it in a next article.

Now, if a unauthenticated user tries to access the protected resources, he will receive an HTTP 401 error. Let’s see how I deal with it on the client side (Angular-js).
The web pages are located in

The idea is to have the generic process to intercept the right HTTP code when the user needs to be authenticated, and redirect him automatically to the login page .
The application is based on the single page principle, for that purpose, I use the ngRoute module, it is configured in the app.js file:

petstoreApp.config(['$routeProvider',
  function($routeProvider) {
    $routeProvider.
      when('/pets', {
	      templateUrl: 'partials/pet-list.html',
	      controller: 'PetListCtrl'
      }).
      when('/pets/new', {
    	  templateUrl: 'partials/pet-new.html',
    	  controller: 'PetNewCtrl'
      }).
      when('/pets/:petId', {
          templateUrl: 'partials/pet-detail.html',
          controller: 'PetDetailCtrl'
      }).
      when('/login', {
          templateUrl: 'partials/login.html',
          controller: 'LoginCtrl'
        }).
      otherwise({
        redirectTo: '/pets'
      });
  }]);

As expected, we can find our 3 pages related to the web services features and another one to ask credentials to users. This one will be used when redirecting unauthenticated users.
For that purpose, the application is configured with an Angular-js interceptor. All the requests sent or received can be manipulated by interceptor, this is very useful for common purposes like error management or authentication, and it is exactly the way I use it (still in the app.js file):

petstoreApp.factory('errorInterceptor', ['$q', '$location', '$window', '$injector', function($q, $location, $window, $injector) {
	var authSrv;
	var popupSrv;
	var errorInterceptor = {
			'responseError' : function(rejection) {
				var status = rejection.status;
				if (rejection.data){
					if (status == 401) {//Unauthenticated, redirect to login page
						$window.location.href="/#/login";
						return;
					} else if (status == 418) {//undefined error->HTTP code 418 ( I'm a teapot) is responsibility of the caller
						return $q.reject(rejection);
					} else{
						popupSrv = popupSrv || $injector.get('popUpSrv');
						popupSrv.alert("Erreur: "+rejection.data.message+ (rejection.data.stackTrace?"\r\n___________________________________\r\n\r\nErreur complète:\r\n" + rejection.data.stackTrace:''));
						return;
					} 
				} else {
					popupSrv = popupSrv || $injector.get('popUpSrv');
					popupSrv.alert("Erreur inconnue( http status: "+status+")");
					return;
				}
				return $q.reject(rejection);
			}};
	return errorInterceptor;
}]);
petstoreApp.config(['$httpProvider', function($httpProvider) {  
    $httpProvider.interceptors.push('errorInterceptor');
}]);

The interceptor is in charge of managing the HTTP 401 code by redirecting to the login form, and also of the display of a generic message error.
For more information about interceptors and their usage, you can directly have a look at the official site.

And now, it works! The web site can access resources protected with Spring security. In the real world, I should display the new pet button only for admin users, it means that I should also provide a service indicate which actions are available for a user.

For those who missed the point, the sources of the project are available here.

Leave a comment