Thursday, September 27, 2012

Spring Security using API Authentication

Background


While there are many blog posts that detail how to use Spring Security, I often still find it challenging to configure when a problem domain lies outside of the standard LDAP or database authentication. In this post, I'll describe some simple customizations to Spring Security that enable it to be used with a REST-based API call. Specifically, the use case is where you have an API service that will return a user object that includes a SHA-256 password hash.

Setup 

The prerequisites for running this sample is Git and Maven, and your choice of IDE (tested with both Eclipse and IntelliJ).

The source code can be found at: https://github.com/dajevu/Spring3SecurityUsingAPI. After pulling down the code, perform the following steps:
  1. In a terminal window, cd to the Shared directory located under the root where the source code resides.
  2. Issue the command mvn clean install. This will build the Shared sub-project and install the jar into your local mvn repository.
  3. Within Eclipse or IntelliJ, import the project as a Maven project. In Eclipse, this will result in 3 projects being created: Shared, SpringWebApp, and RestfulAPI. In IntelliJ, this will be represented as sub-projects. No errors should exist after the compilation process is complete.
  4. Change directory to RestfulAPI. Then, issue the command mvn jetty:run to run the API webapp.  You can then issue the following URL that will bring back a User object represented in JSON: http://localhost:9090/RestfulAPI/api/v1/user/john
  5. Open up a new terminal window, cd to SpringWebApp directory located under the project root. Issue the command mvn jetty:run. This will launch a standard Spring webapp that incorporates Spring Security. You can access the single HTML page at: http://localhost:8080/SpringWebApp/. After clicking the Login link, login with the username of john and a password of doe. You should be redirected to a Hello Admin page. 

In order to demonstrate the solution, three maven modules are used, which are illustrated below:

  • SpringWebApp. This is a typical Spring webapp that serves up a single JSP page. The contents of the page will vary depending upon whether the user is currently logged in or not. When first visiting the page, a Login link will appear, which directs them to the built-in Spring Security login form. When they attempt to login, a RESTEasy client is used to place a call to the API service (described below), which returns a JSON string that is converted into a Java object via the RESTEasy client. The details of how Spring Security is configured is discussed in the following sections.
  • RestfulAPI. An API service that serves JSON requests. It is configured using RESTEasy (a JAX-RS implementation), and is described in more detail in the next section.
  • Shared. This contains a few Java classes that are shared between the other two projects. Specifically, the User object DTO, and the RESTEasy proxy definition (it's shared because it can also be used by the RESTEasy client).

RestfulAPI Dissection


The API webapp is configured using RESTEasy's Spring implementation. The RESTEasy documentation is very thorough, so I won't go into a detailed explanation of its setup. A single API call is defined (in UserProxy in the Shared project) that returns a static JSON string. The API's proxy (or interface) is defined as follows:

For those of you familiar with JAX-RS, you'll easily follow this configuration. It defines an API URI that will respond to requests sent to the URL path of /api/v1/user/{username} where {username} is replaced with an actual username value. The implementation of this service, which simply returns a static response, is shown below:

About the only thing remotely complicated is the use of the SHA-256 hashing of the user's password. We'll see shortly how this get's interpreted by Spring Security. When the URL is accessed, the following JSON string is returned:

The webapp's web.xml contains the setup configuration to service RESTEasy requests, so if you're curious, take a look at that.

SpringWebApp Dissection


Now we can look at the Spring Security configuration. The web.xml file for the project configures it as a Spring application, and specifies the file applicationContext-security.xml as the initial Spring configuration file.  Let's take a closer look at this file, as this is where most of the magic occurs:

Let's go through each of line numbers to describe their functionality.  Lines 3 through 5 instructs Spring to look for Spring-backed classes in the com.acme directory and that Spring annotations will be supported.  Line 7 is used to load the properties specified in the application.properties file (this is used to specify the API host). Lines 9 through 11 enable Spring Security for the application. Normally, as a child element to http, you would specify which pages should be protected using roles, but to keep this example simple, that wasn't configured.

Lines 13-17 are where the customizations to base Spring Security begin. We define a custom authentication-provider called userDetailsSrv through its bean ref. That bean is implemented through the custom class com.acme.security.UserDetailsService (line 19). Let's take a closer look at this class:

As you can see, this class implements the Spring interface org.springframework.security.core.userdetails.UserDetailsService.  This requires overriding the method loadUserByUsername.  This method is responsible for retrieving the user from the authentication provider/source. The returned user (or if no matching user is found, a UsernameNotFoundException is thrown - line 28) must contain a password property in order for Spring Security to compare against what was provided in the form. In this case, as we've seen previously, the password is returned in a SHA-256 hash.

In our API implementation, the user lookup is pulled using the APIHelper class, which we'll cover next. The returned API data is then populated in the custom class called UserDetails.  This implements the Spring interface with the same name.  That interface requires an concrete implementation of the getUsername() and getPassword() methods.  Spring will invoke those in the next processing step of Security to compare those values against what was recorded in the web form.

How does Spring go about comparing the password returned in the SHA-256 against the form password value. If you look back at the XML configuration, it contained this setting:
Notice the passwordEncoder -- this reference points to the Spring class ShaPasswordEncoder. This class will compute an SHA-256 password of the password provided through the web form, and then Spring will compare that computed value against what we returned via the API.

Let's close this out by looking at the APIHelper class:

The first thing you'll on lines 8 and 9 is the injection of the API.host property. As you recall, this was set in the application.properties file. This identifies the host in which to post the API call (since it's running locally, localhost is specified).  Lines 17 through 20 use one of the RESTEasy client mechanisms to post a JSON RESTful call (RESTEasy also has what is called client proxy implementation, which is easier to use/less code, but doesn't provide as much low-level control).  The resulting response from the API is then converted from JSON into the User Java object by way Jackson in line 26. That Java object is then returned to the UserDetails service.


Summary/Wrap-up


As you can see, the actual work involved in customizing Spring Security to authenticate against an API call (or really any external service) is really rather straightforward. Only a few classes have to be implemented, but it can be tricky trying to figure this out for the first time. Hence, the reason I included the complete end-to-end example. 

2 comments:

Sergei Kovalev said...

Thank you, good information, exactly what I was looking for. I wonder, is that a bad practice to point client application to central database instead of using rest api.
I mean if I in my distributed grails application will configure user and role classes to point to datasource which is remote mysql and in mysql give read only permissions to app user to access user and role. Is there any issues with that approach. I know I would need to give remote access to mysql from anywhere but if permissions set up correctly it still should be secure

Sergei Kovalev said...

Thank you, that is exactly what I was looking for.