Nov 10, 2021 2:55:35 PM Thomas Dumont avatar

Expose a REST API with Lutece

Introduction

Representational state transfer (REST) ​​web services fully expose their functionality as a collection of resources (URI) that are identifiable and accessible by the syntax and semantics of the HTTP protocol. REST Web Services are based on the web architecture and its basic standards: HTTP and URI. The data provided by these services is usually available in several formats: XML, JSON or HTML.

CRUD operations on a resource are performed using HTTP methods: PUT (create), GET (read), POST (update), DELETE (delete).

The advantages of this type of webservices are:

  • They can be used by any brick (rich or mobile client, other IS, other service ...) in any techno
  • They are exposed via HTTP: public API and humanly understandable
  • They are scalable and hideable especially by the web equipment: proxys, caches.

Several frameworks are available to facilitate the realization of REST web services. We can notably mention Restlet or Apache CXF.

For the default implementation proposed by Lutece, the Jersey framework has been selected. Jersey is the reference implementation of JAX-RS (JSR 311) which refers to the specification of RESTful web services.

Integration in Lutece : The REST plugin

The REST plugin provides a standard service layer for all REST modules. It automatically detects all resources whose class is declared in the Spring context file of the plugin or module. This operation is performed when the webapp is launched and the detected resources are displayed in the logs.

The plugin then uses a servlet filter, based on the URI /rest and performs the dispatching to the resource URIs. When the REST plugin is deployed, there is therefore no particular development or parameterization to be performed outside the classes of the resources to be provided and declared in the context files.

The use of the plugin-rest therefore requires 3 steps :

  • Added the dependency to plug-and-rest in the pom.xml file
  • Implementing a class representing the REST resource: see example below.
  • Declaration of this class in the spring context file (ex webapp / WEB-INF / conf / plugins / <plugin> _context.xml): <bean id = .. class = ..>

Jersey annotations

The writing of web services is greatly facilitated with Jersey through annotations.

The main annotations are as follows :

HTTP request type supported by method @GET, @PUT, @POST, @DELETE
URI path supported by the method @Path( path )
Requested answer format @Produces( format ) Format examples : MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, ...
Nature of input data @Consumes( format ) Format examples : MediaType.APPLICATION_FORM_URLENCODED

See the JAX-RS documentation

Here is an example :

@Path( RestConstants.BASE_PATH + "customer" )
public class CustomerRest
{
@Path( RestConstants.BASE_PATH + "customer" )
public class CustomerRest
{
    public class CustomersResource 
    {
        @GET
        @Path( '/customers' )
        @Produces( MediaType.APPLICATION_XML )
        public List<Customer> getCustomers( ) 
        {
            ... 
        }

        @GET
        @Path( '/customers/{id}' )
        public Customer getCustomer( @PathParam( 'id' ) int nId ) 
        {
            ...
        }

        @PUT
        @Path( '/customers/add' )
        @Produces( MediaType.TEXT_PLAIN )
        @Consumes( MediaType.APPLICATION_XML )
        public String addCustomer( Customer customer ) 
        {
            ...
        }

        ...
    }
}

Standards and naming conventions

URI

The root of URIs is defined with the @Path annotation as follows :

@Path(RestConstants.BASE_PATH + Constants.PLUGIN_NAME)

List of resources :

/rest/{myplugin}[/{mymodule}]/(myressources}/

Resource :

/rest/{myplugin}[/{mymodule}]/(myressources}/{id}

Query parameters : sort, start, count (or rows)

Java

  • REST layer is packaged as a module
  • Module name : {myplugin}-rest
  • Name of class corresponding to the resource : {MyResource}Rest.java
  • Name of package containing resource classes : rs

Security

The problem of securing REST web services lies in the fact that they are based on a stateless protocol. Each request is independent and there is no notion of session.

Security, when it must be implemented, must therefore be conveyed at the level of each request. The most suitable mechanisms are those based on signatures, tokens or keys associated with each HTTP request.

The SignRequest library

This library provides an API for defining HTTP request authentication services : the RequestAuthenticator. It proposes several implementations including HeaderHashRequestAuthenticator which makes it possible to create and validate a signature of the request based on the parameters of this one and on a shared secret between the client and the server. If the signature is missing, incorrect or made with the wrong key, the request will be rejected.

This library is not dependent on Lutece's core. It can therefore be easily used by other non-Lutece Java applications such as Android applications for example.

The authenticator and filter implementations provided by this library can respond to many situations but are also examples. They can be expanded or modified as needed

The parameters of HeaderHashAuthenticator

This authenticator must be configured using several parameters:

  • the hash service. The SignRequest library provides a HashService API and an implementation using the SHA-1 algorithm.
  • the private key corresponding to the shared secret between the client and the server
  • the list of query parameters that are used to compose the signature
  • the validity period of the signature in seconds. The value 0 indicates that the duration is not controlled.

Configuration of a RequestAuthenticator in the REST plugin

The security of all requests can be done at the REST plugin by injecting via context Spring a authenticator.

By default, the REST plugin uses the NoSecurityRequestAuthenticator implementation that allows all requests. The example below shows a configuration using the HeaderHashRequestAuthenticator and its specific setting.

<bean id='rest.hashService' class='fr.paris.lutece.util.signrequest.security.Sha1HashService' />
<bean id='rest.requestAuthenticator' class='fr.paris.lutece.util.signrequest.HeaderHashAuthenticator' >
    <property name='hashService' ref='rest.hashService' />
    <property name='signatureElements' > 
        <list>
            <value>key</value>
        </list>
    </property>
    <property name='privateKey'>
        <value>change me</value>
    </property>
    <property name='validityTimePeriod'>
        <value>0</value>
    </property>
</bean>

Another RequestAuthenticator called RequestHashAthenticator is available in the library. The only difference is that the signature and the timestamp are passed as a parameter of the HTTP request instead of Headers of it. The signature is less masked, but it makes it possible to secure requests made by hypertext links.

Configuring a Lutece servlet filter

In the XML file of a REST module it is possible to declare a filter that will provide specific security to the module resources as follows :

<filters>
    <filter>
        <filter-name>myresourcesecurity</filter-name>
        <url-pattern>/rest/myresource/*</url-pattern>
        <filter-class>fr.paris.lutece.util.signrequest.servlet.HeaderHashRequestFilter</filter-class>
        <init-param>
            <param-name>elementsSignature</param-name>
            <param-value>id-resource,name,description</param-value>
        </init-param>
        <init-param>
            <param-name>validityTimePeriod</param-name>
            <param-value>0</param-value>
        </init-param>
        <init-param>
            <param-name>privateKey</param-name>
            <param-value>change me</param-value>
        </init-param>
    </filter>
</filters>