Saturday, 18 April 2020

Spring RestTemplate basic authentication example

Learn to add basic authentication to http requests invoked by Spring RestTemplate while accessing rest apis over the network.

1. Maven dependency

To work with Spring RestTemplate and HttpClient API, we must include spring-boot-starter-web and httpclient dependencies in pom.xml file.
In this RestTemplate basic authentication tutorial, we are using dependencies.
pom.xml
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.5.RELEASE</version>
    <relativePath />
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
        <version>4.5.3</version>
    </dependency>
</dependencies>

2. Enable BasicAuth in RestTemplate

To enable basic authentication in RestTemplate for outgoing rest requests, we shall configure CredentialsProvider into HttpClient API. This HttpClient will be used by RestTemplate to send HTTP requests to backend rest apis.
In this example, we are creating a Junit test which invokes a basic auth secured rest api.
Basic auth setup
package com.howtodoinjava.rest;
import java.net.URI;
import java.net.URISyntaxException;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.AuthCache;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.HttpClient;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.BasicAuthCache;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.context.annotation.Bean;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.client.RestTemplate;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)
public class SpringBootDemoApplicationTests
{  
    @LocalServerPort
    int randomServerPort;
     
    //Timeout value in milliseconds
    int timeout = 10_000;
     
    public RestTemplate restTemplate;
     
    @Before
    public void setUp()
    {
        restTemplate = new RestTemplate(getClientHttpRequestFactory());
    }
     
    private HttpComponentsClientHttpRequestFactory getClientHttpRequestFactory()
    {
        HttpComponentsClientHttpRequestFactory clientHttpRequestFactory
                          = new HttpComponentsClientHttpRequestFactory();
         
        clientHttpRequestFactory.setHttpClient(httpClient());
              
        return clientHttpRequestFactory;
    }
     
    private HttpClient httpClient()
    {
        CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(AuthScope.ANY,
                        new UsernamePasswordCredentials("admin", "password"));
        HttpClient client = HttpClientBuilder
                                .create()
                                .setDefaultCredentialsProvider(credentialsProvider)
                                .build();
        return client;
    }
     
    @Test
    public void testGetEmployeeList_success() throws URISyntaxException
    {
        final String baseUrl = "http://localhost:"+randomServerPort+"/employees/";
        URI uri = new URI(baseUrl);
        ResponseEntity<String> result = restTemplate.getForEntity(uri, String.class);
         
        //Verify request succeed
        Assert.assertEquals(200, result.getStatusCodeValue());
        Assert.assertEquals(true, result.getBody().contains("employeeList"));
    }
}

3. RestTemplate basic authentication Demo

For demo purpose, we can write a simple REST API given below.

3.1. REST API

EmployeeController.java
@RestController
@RequestMapping(path = "/employees")
public class EmployeeController
{
    @Autowired
    private EmployeeDAO employeeDao;
     
    @GetMapping(path="/", produces = "application/json")
    public Employees getEmployees()
    {
        return employeeDao.getAllEmployees();
    }
}

3.2. REST Security Configuration

And the security is configured globally.
SecurityConfig.java
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
    @Override
    protected void configure(HttpSecurity http) throws Exception
    {
        http
         .csrf().disable()
         .authorizeRequests().anyRequest().authenticated()
         .and()
         .httpBasic();
    }
  
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth)
            throws Exception
    {
        auth
            .inMemoryAuthentication()
            .withUser("admin")
            .password("{noop}password")
            .roles("USER");
    }
}

3.3. Access rest api by executing junit test

When we execute the junit test, it starts the application and deploys the rest api on it’s endpoint. It then invokes the rest api, and perform basic authentication upon getting 401 error.
We can verify the whole authentication process in application logs by setting 'logging.level.org.apache.http=DEBUG' in application.properties file.
Console
o.a.http.impl.auth.HttpAuthenticator     : Authentication required
o.a.http.impl.auth.HttpAuthenticator     : localhost:54770 requested authentication
o.a.h.i.c.TargetAuthenticationStrategy   : Authentication schemes in the order of preference: [Negotiate, Kerberos, NTLM, Digest, Basic]
o.a.h.i.c.TargetAuthenticationStrategy   : Challenge for Negotiate authentication scheme not available
o.a.h.i.c.TargetAuthenticationStrategy   : Challenge for Kerberos authentication scheme not available
o.a.h.i.c.TargetAuthenticationStrategy   : Challenge for NTLM authentication scheme not available
o.a.h.i.c.TargetAuthenticationStrategy   : Challenge for Digest authentication scheme not available
o.a.http.impl.auth.HttpAuthenticator     : Selected authentication options: [BASIC [complete=true]]
org.apache.http.wire                     : http-outgoing-0 << "0[\r][\n]"
org.apache.http.wire                     : http-outgoing-0 << "[\r][\n]"
o.a.http.impl.execchain.MainClientExec   : Executing request GET /employees/ HTTP/1.1
o.a.http.impl.execchain.MainClientExec   : Target auth state: CHALLENGED
o.a.http.impl.auth.HttpAuthenticator     : Generating response to an authentication challenge using basic scheme
o.a.http.impl.execchain.MainClientExec   : Proxy auth state: UNCHALLENGED
org.apache.http.headers                  : http-outgoing-0 >> GET /employees/ HTTP/1.1
org.apache.http.headers                  : http-outgoing-0 >> Accept: text/plain, application/json, application/*+json, */*
org.apache.http.headers                  : http-outgoing-0 >> Host: localhost:54770
org.apache.http.headers                  : http-outgoing-0 >> Connection: Keep-Alive
org.apache.http.headers                  : http-outgoing-0 >> User-Agent: Apache-HttpClient/4.5.3 (Java/1.8.0_171)
org.apache.http.headers                  : http-outgoing-0 >> Accept-Encoding: gzip,deflate
org.apache.http.headers                  : http-outgoing-0 >> Authorization: Basic YWRtaW46cGFzc3dvcmQ=
org.apache.http.wire                     : http-outgoing-0 >> "GET /employees/ HTTP/1.1[\r][\n]"
org.apache.http.wire                     : http-outgoing-0 >> "Accept: text/plain, application/json, application/*+json, */*[\r][\n]"
org.apache.http.wire                     : http-outgoing-0 >> "Host: localhost:54770[\r][\n]"
org.apache.http.wire                     : http-outgoing-0 >> "Connection: Keep-Alive[\r][\n]"
org.apache.http.wire                     : http-outgoing-0 >> "User-Agent: Apache-HttpClient/4.5.3 (Java/1.8.0_171)[\r][\n]"
org.apache.http.wire                     : http-outgoing-0 >> "Accept-Encoding: gzip,deflate[\r][\n]"
org.apache.http.wire                     : http-outgoing-0 >> "Authorization: Basic YWRtaW46cGFzc3dvcmQ=[\r][\n]"
org.apache.http.wire                     : http-outgoing-0 >> "[\r][\n]"
org.apache.http.wire                     : http-outgoing-0 << "HTTP/1.1 200 [\r][\n]"
org.apache.http.wire                     : http-outgoing-0 << "Set-Cookie: JSESSIONID=A54891F86FEF06160BDAFC78CE631C4E; Path=/; HttpOnly[\r][\n]"
org.apache.http.wire                     : http-outgoing-0 << "X-Content-Type-Options: nosniff[\r][\n]"
org.apache.http.wire                     : http-outgoing-0 << "X-XSS-Protection: 1; mode=block[\r][\n]"
org.apache.http.wire                     : http-outgoing-0 << "Cache-Control: no-cache, no-store, max-age=0, must-revalidate[\r][\n]"
org.apache.http.wire                     : http-outgoing-0 << "Pragma: no-cache[\r][\n]"
org.apache.http.wire                     : http-outgoing-0 << "Expires: 0[\r][\n]"
org.apache.http.wire                     : http-outgoing-0 << "X-Frame-Options: DENY[\r][\n]"
org.apache.http.wire                     : http-outgoing-0 << "Content-Type: application/json;charset=UTF-8[\r][\n]"
org.apache.http.wire                     : http-outgoing-0 << "Transfer-Encoding: chunked[\r][\n]"
org.apache.http.wire                     : http-outgoing-0 << "Date: Thu, 18 Oct 2018 17:54:26 GMT[\r][\n]"
org.apache.http.wire                     : http-outgoing-0 << "[\r][\n]"
org.apache.http.wire                     : http-outgoing-0 << "101[\r][\n]"
org.apache.http.wire                     : http-outgoing-0 << "{"employeeList":[{"id":1,"firstName":"Lokesh","lastName":"Gupta","email":"howtodoinjava@gmail.com"},{"id":2,"firstName":"Alex","lastName":"Kolenchiskey","email":"abc@gmail.com"},{"id":3,"firstName":"David","lastName":"Kameron","email":"titanic@gmail.com"}]}[\r][\n]"
org.apache.http.headers                  : http-outgoing-0 << HTTP/1.1 200

4. Conclusion

In this spring resttemplate example, we learned to pass basic authentication via “Authorization” header while accessing rest api. It is done in two steps. First step is to include required dependencies e.g. spring-boot-starter-web and httpclient. Second step is to configure RestTemplate and add auth details.
Share if you face any problem in resttemplate set authorization header example.

No comments:

Post a Comment