Saturday, September 25, 2021

Consume a RESTful Service secured with Basic Authentication.

Approac1:  Once Basic Authentication is set up for the template, each request will be sent preemptively containing the full credentials necessary to perform the authentication process. The credentials will be encoded and will use the Authorization HTTP Header, in accordance with the specs of the Basic Authentication scheme. An example would look like this:

Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==

Setting up the RestTemplate

Bootstrapping the RestTemplate into the Spring context can be done by simply declaring a bean for it; however, setting up the RestTemplate with Basic Authentication will require manual intervention, so instead of declaring the bean directly, a Spring FactoryBean will be used for more flexibility. This factory will create and configure the template on initialization:

@Component

public class RestTemplateFactory

  implements FactoryBean<RestTemplate>, InitializingBean {

    private RestTemplate restTemplate;

    public RestTemplate getObject() {

        return restTemplate;

    }

    public Class<RestTemplate> getObjectType() {

        return RestTemplate.class;

    }

    public boolean isSingleton() {

        return true;

    }

    public void afterPropertiesSet() {

        HttpHost host = new HttpHost("localhost", 8082, "http");

        restTemplate = new RestTemplate(

          new HttpComponentsClientHttpRequestFactoryBasicAuth(host));

    }

}

The host and port values should be dependent on the environment – allowing the client the flexibility to define one set of values for integration testing and another for production use. The values can be managed by the first class Spring support for properties files.

Manual Management of the Authorization HTTP Header

The process of creating the Authorization header is relatively straightforward for Basic Authentication, so it can pretty much be done manually with a few lines of code:

HttpHeaders createHeaders(String username, String password){

   return new HttpHeaders() {{

         String auth = username + ":" + password;

         byte[] encodedAuth = Base64.encodeBase64( 

            auth.getBytes(Charset.forName("US-ASCII")) );

         String authHeader = "Basic " + new String( encodedAuth );

         set( "Authorization", authHeader );

      }};

}

Then, sending a request becomes just as simple:

restTemplate.exchange

 (uri, HttpMethod.POST, new HttpEntity<T>(createHeaders(username, password)), clazz);

Automatic Management of the Authorization HTTP Header

Both Spring 3.0 and 3.1 and now 4.x have very good support for the Apache HTTP libraries:

  • Spring 3.0, the CommonsClientHttpRequestFactory integrated with the now end-of-life'd HttpClient 3.x
  • Spring 3.1 introduced support for the current HttpClient 4.x via HttpComponentsClientHttpRequestFactory (support added in the JIRA SPR-6180)
  • Spring 4.0 introduced async support via the HttpComponentsAsyncClientHttpRequestFactory

Let's start setting things up with HttpClient 4 and Spring 4.

The RestTemplate will require an HTTP request factory – a factory that supports Basic Authentication – so far, so good. However, using the existing HttpComponentsClientHttpRequestFactory directly will prove to be difficult, as the architecture of RestTemplate was designed without good support for HttpContext – an instrumental piece of the puzzle. And so we'll need to subclass HttpComponentsClientHttpRequestFactory and override the createHttpContext method:

public class HttpComponentsClientHttpRequestFactoryBasicAuth 

  extends HttpComponentsClientHttpRequestFactory {


    HttpHost host;


    public HttpComponentsClientHttpRequestFactoryBasicAuth(HttpHost host) {

        super();

        this.host = host;

    }


    protected HttpContext createHttpContext(HttpMethod httpMethod, URI uri) {

        return createHttpContext();

    }

        private HttpContext createHttpContext() {

        AuthCache authCache = new BasicAuthCache();

        BasicScheme basicAuth = new BasicScheme();

        authCache.put(host, basicAuth);

        BasicHttpContext localcontext = new BasicHttpContext();

        localcontext.setAttribute(HttpClientContext.AUTH_CACHE, authCache);

        return localcontext;

    }

}

It is here – in the creation of the HttpContext – that the basic authentication support is built in. As you can see, doing preemptive Basic Authentication with HttpClient 4.x is a bit of a burden: the authentication info is cached and the process of setting up this authentication cache is very manual and unintuitive.

And with that, everything is in place – the RestTemplate will now be able to support the Basic Authentication scheme just by adding a BasicAuthorizationInterceptor;

restTemplate.getInterceptors().add(

  new BasicAuthorizationInterceptor("username", "password"));

And the request:

restTemplate.exchange(

  "http://localhost:8082/spring-security-rest-basic-auth/api/foos/1", 

  HttpMethod.GET, null, Foo.class);

For an in-depth discussion on how to secure the REST Service itself, check out this article.

Maven Dependencies

The following Maven dependencies are required for the RestTemplate itself and for the HttpClient library

<dependency>

   <groupId>org.springframework</groupId>

   <artifactId>spring-webmvc</artifactId>

   <version>5.0.6.RELEASE</version>

</dependency>

<dependency>

   <groupId>org.apache.httpcomponents</groupId>

   <artifactId>httpclient</artifactId>

   <version>4.5.3</version>

</dependency>

Optionally, if the HTTP Authorization header is constructed manually, then an additional library is required for the encoding support:

<dependency>

   <groupId>commons-codec</groupId>

   <artifactId>commons-codec</artifactId>

   <version>1.10</version>

</dependency>

You will find the newest versions in the Maven repository.

Conclusion

Although the 3.x branch of development for Apache HttpClient has reached the end of life for a while now, and the Spring support for that version has been fully deprecated, much of the information that can be found on RestTemplate and security still doesn't account for the current HttpClient 4.x releases. This article is an attempt to change that through a detailed, step by step discussion on how to set up Basic Authentication with the RestTemplate and how to use it to consume a secured REST API.

To go beyond the code samples in the article with the implementation of both the consuming side, examined here, but also the actual RESTful Service, have a look at the project over on Github.

This is a Maven-based project, so it should be easy to import and run as it is.

=====================================================

Approach 2:  Java restful webservices with HTTP basic authentication.

In the context of a HTTP transaction, basic access authentication is a method for an HTTP user agent to provide a user name and password when making a request.

HTTP Basic authentication implementation is the simplest technique for enforcing access controls to web resources because it doesn't require cookies, session identifier and login pages. Rather, HTTP Basic authentication uses static, standard HTTP headers which means that no handshakes have to be done in anticipation.

When the user agent wants to send the server authentication credentials it may use the Authorization header. The Authorization header is constructed as follows:

1) Username and password are combined into a string "username:password"

2) The resulting string is then encoded using Base64 encoding

3) The authorization method and a space i.e. "Basic " is then put before the encoded string. 

For example, if the user agent uses 'Aladdin' as the username and 'open sesame' as the password then the header is formed as follows:

Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==

Java Rest Service method with GET Request which supports HTTP basic authentication

package com.java2novice.restful; 

import java.io.IOException; 

import javax.ws.rs.GET;

import javax.ws.rs.HeaderParam;

import javax.ws.rs.Path;

import javax.ws.rs.PathParam;

import javax.ws.rs.Produces;

import javax.ws.rs.core.MediaType;

import sun.misc.BASE64Decoder; 

import com.java2novice.model.Order;

 

@Path("/order-inventory")

public class OrderInventoryService { 

    @GET

    @Path("/order/{orderId}")

    @Produces(MediaType.APPLICATION_JSON)

    public Object getUserById(@PathParam("orderId") Integer orderId, 

                            @HeaderParam("authorization") String authString){

         

        if(!isUserAuthenticated(authString)){

            return "{\"error\":\"User not authenticated\"}";

        }

        Order ord = new Order();

        ord.setCustmer("Java2Novice");

        ord.setAddress("Bangalore");

        ord.setAmount("$2000");

        return ord;

    }

     

    private boolean isUserAuthenticated(String authString){

         

        String decodedAuth = "";

        // Header is in the format "Basic 5tyc0uiDat4"

        // We need to extract data before decoding it back to original string

        String[] authParts = authString.split("\\s+");

        String authInfo = authParts[1];

        // Decode the data back to original string

        byte[] bytes = null;

        try {

            bytes = new BASE64Decoder().decodeBuffer(authInfo);

        } catch (IOException e) {

            // TODO Auto-generated catch block

            e.printStackTrace();

        }

        decodedAuth = new String(bytes);

        System.out.println(decodedAuth);         

        /**

         * here you include your logic to validate user authentication.

         * it can be using ldap, or token exchange mechanism or your 

         * custom authentication mechanism.

         */

        // your validation code goes here....         

        return true;

    }

}

Java Client for GET Request using Jersey API with HTTP basic authentication

package com.java2novice.rest.client; 

import sun.misc.BASE64Encoder; 

import com.sun.jersey.api.client.Client;

import com.sun.jersey.api.client.ClientResponse;

import com.sun.jersey.api.client.WebResource;

 

public class JersyGetClient { 

    public static void main(String a[]){         

        String url = "http://localhost:8080/RestfulWebServices/order-inventory/order/1016";

        String name = "java2novice";

        String password = "Simple4u!";

        String authString = name + ":" + password;

        String authStringEnc = new BASE64Encoder().encode(authString.getBytes());

        System.out.println("Base64 encoded auth string: " + authStringEnc);

        Client restClient = Client.create();

        WebResource webResource = restClient.resource(url);

        ClientResponse resp = webResource.accept("application/json")

                                         .header("Authorization", "Basic " + authStringEnc)

                                         .get(ClientResponse.class);

        if(resp.getStatus() != 200){

            System.err.println("Unable to connect to the server");

        }

        String output = resp.getEntity(String.class);

        System.out.println("response: "+output);

    }

}

No comments:

Post a Comment

Java 9 and Java11 and Java17, Java 21 Features

 Java 9 and Java11 and Java17 features along with explanation and examples in realtime scenarios Here's a detailed breakdown of Java 9, ...