In our previous tutorial, we implemented a thin REST API client application to consume secured CRUD REST APIs. However, the client does not support proxy authentication. Therefore, we are going to configure the client with proxy authentication.
Introduction
Often, corporate networks provide internet access via proxy servers, and at times they require authentication as well. Hence, applications must do proxy authentication to connect to the internet.
What You’ll Build
A REST client that supports proxy authentication to consume REST APIs with JWT authentication.
What You’ll Need
- Spring Tool Suite or any IDE of your choice
- JDK 11
- MySQL Server
- Apache Maven
Configuring HTTP Client with Proxy Authentication
Let’s see how we can create an HTTP client v2 with proxy authentication in step-by-step.
- Firstly, create an HTTP Client v2 builder with 10 seconds of connection timeout.
- Secondly, If the proxy
host
andport
are provided, then- Create a
ProxySelector
usingProxySelector::of
method. It provides aProxySelector
which uses a single proxy for all requests. The system-wide proxy selector can be retrieved byProxySelector.getDefault()
. - Invoke the
builder.proxy()
method to use thisProxySelector
. Note: If this method is not invoked prior to building, then newly built clients will use the default proxy selector, which is usually adequate for client applications. The default proxy selector supports a set of system properties related to proxy settings. This default behavior can be disabled by supplying an explicit proxy selector, such asNO_PROXY
, or one returned byProxySelector::of
, before building. - Get the default
Authenticator
which will be used for connection without authentication. If the proxy username and password are provided, then it means the proxy server requires authentication. Hence, In this case, we have to create a newAuthenticator
and override thegetPasswordAuthentication()
method to create a newPasswordAuthentication
object using these credentials. - Invoke the
builder.authenticator()
method to use this authenticator.
- Create a
- Finally, invoke the
builder.build()
method to create the instance of HTTP Client.
public ProductManagerImpl(ProductManagerConfig config) {
this.productManagerConfig = config;
HttpClient.Builder builder = HttpClient.newBuilder().version(HttpClient.Version.HTTP_2)
.connectTimeout(Duration.ofSeconds(10));
if (config.proxyHost() != null && config.proxyPort() != null) {
builder.proxy(ProxySelector.of(new InetSocketAddress(config.proxyHost(), config.proxyPort())));
Authenticator authenticator = Authenticator.getDefault();
if (config.proxyUser() != null && config.proxyPass() != null) {
authenticator = new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(config.proxyUser(), config.proxyPass().toCharArray());
}
};
}
builder.authenticator(authenticator);
}
this.requestHelper = new HttpRequestHelper(builder.build());
}
Modifying REST Client API
Now, let’s see the complete changes required to support proxy authentication in our REST client that we developed earlier.
ProductManagerConfig.java
ProductManagerConfig
class holds the REST API base URL and credentials required for authentication.
package com.javachinna.rest.client.config;
public record ProductManagerConfig(String baseUrl, String username, String password, String proxyHost,
Integer proxyPort, String proxyUser, String proxyPass) {
}
Note: We have changed this class to a record class since it is intended to serve as a simple “data carrier”. A record class declares a sequence of fields, and then the appropriate accessors, constructors,
equals
,hashCode
, andtoString
methods are created automatically. All the fields are final.
ProductManagerImpl.java
We have modified this implementation to use the new HttpRequestHelper
class with the dynamically created HTTP Client instance.
package com.javachinna.rest.client;
import com.javachinna.rest.client.config.ProductManagerConfig;
import com.javachinna.rest.client.helper.HttpRequestHelper;
import com.javachinna.rest.client.model.*;
import lombok.RequiredArgsConstructor;
import java.net.Authenticator;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
import java.net.ProxySelector;
import java.net.http.HttpClient;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RequiredArgsConstructor
public class ProductManagerImpl implements ProductManager {
private static final String AUTH_API = "auth/signin";
private static final String PRODUCT_API = "products";
private final ProductManagerConfig productManagerConfig;
private final HttpRequestHelper requestHelper;
public ProductManagerImpl(ProductManagerConfig config) {
this.productManagerConfig = config;
HttpClient.Builder builder = HttpClient.newBuilder().version(HttpClient.Version.HTTP_2)
.connectTimeout(Duration.ofSeconds(10));
if (config.proxyHost() != null && config.proxyPort() != null) {
builder.proxy(ProxySelector.of(new InetSocketAddress(config.proxyHost(), config.proxyPort())));
Authenticator authenticator = Authenticator.getDefault();
if (config.proxyUser() != null && config.proxyPass() != null) {
authenticator = new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(config.proxyUser(), config.proxyPass().toCharArray());
}
};
}
builder.authenticator(authenticator);
}
this.requestHelper = new HttpRequestHelper(builder.build());
}
public Product getProduct(Integer productId) throws Exception {
String url = productManagerConfig.baseUrl() + PRODUCT_API + "/" + productId;
return requestHelper.get(url, getAccessToken(), Product.class);
}
@SuppressWarnings("unchecked")
public List<HashMap<String, Object>> getAllProducts() throws Exception {
String url = productManagerConfig.baseUrl() + PRODUCT_API;
Map<String, Object> response = requestHelper.get(url, getAccessToken(), Map.class);
return (List<HashMap<String, Object>>) response.get("content");
}
public ApiResponse createProduct(ProductRequest request) throws Exception {
return requestHelper.post(productManagerConfig.baseUrl() + PRODUCT_API, request, ApiResponse.class,
getAccessToken());
}
public ApiResponse updateProduct(Integer productId, ProductRequest request) throws Exception {
return requestHelper.put(productManagerConfig.baseUrl() + PRODUCT_API + "/" + productId, request,
ApiResponse.class, getAccessToken());
}
public ApiResponse deleteProduct(Integer productId) throws Exception {
return requestHelper.delete(productManagerConfig.baseUrl() + PRODUCT_API + "/" + productId,
ApiResponse.class, getAccessToken());
}
public String getAccessToken() throws Exception {
LoginRequest loginRequest = new LoginRequest(productManagerConfig.username(),
productManagerConfig.password());
JwtAuthenticationResponse jwtResponse = requestHelper.post(productManagerConfig.baseUrl() + AUTH_API,
loginRequest, JwtAuthenticationResponse.class);
return jwtResponse.getAccessToken();
}
}
HttpRequestHelper.java
HttpRequestHelper
contains generic methods used to make HTTP GET, POST, PUT, and DELETE requests.
package com.javachinna.rest.client.helper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.BodyPublisher;
import java.net.http.HttpRequest.Builder;
import java.net.http.HttpResponse;
/**
*
* @author Chinna [javachinna.com]
*
*/
public class HttpRequestHelper {
private final String APPLICATION_JSON = "application/json";
private final String CONTENT_TYPE = "Content-Type";
private final String AUTHORIZATION = "Authorization";
private final String BEARER = "Bearer ";
private final ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules();
private final HttpClient httpClient;
public HttpRequestHelper(final HttpClient httpClient) {
this.httpClient = httpClient;
}
public <T> T get(String url, String token, Class<T> valueType)
throws IOException, InterruptedException {
Builder builder = HttpRequest.newBuilder().GET().uri(URI.create(url));
return send(valueType, token, builder);
}
public <T> T post(String uri, Object request, Class<T> valueType)
throws IOException, InterruptedException {
return post(uri, request, valueType, null);
}
public <T> T post(String uri, Object request, Class<T> valueType, String token)
throws IOException, InterruptedException {
Builder builder = HttpRequest.newBuilder().uri(URI.create(uri)).POST(getBodyPublisher(request)).header(CONTENT_TYPE, APPLICATION_JSON);
return send(valueType, token, builder);
}
public <T> T put(String uri, Object request, Class<T> valueType, String token)
throws IOException, InterruptedException {
Builder builder = HttpRequest.newBuilder().uri(URI.create(uri)).PUT(getBodyPublisher(request)).header(CONTENT_TYPE, APPLICATION_JSON);
return send(valueType, token, builder);
}
public <T> T patch(String uri, Class<T> valueType, String token)
throws IOException, InterruptedException {
Builder builder = HttpRequest.newBuilder().uri(URI.create(uri)).method("PATCH", HttpRequest.BodyPublishers.noBody()).header(CONTENT_TYPE, APPLICATION_JSON);
return send(valueType, token, builder);
}
public <T> T delete(String uri, Class<T> valueType, String token)
throws IOException, InterruptedException {
Builder builder = HttpRequest.newBuilder().uri(URI.create(uri)).DELETE();
return send(valueType, token, builder);
}
private BodyPublisher getBodyPublisher(Object request) throws JsonProcessingException {
return HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(request));
}
private <T> T send(Class<T> valueType, String token, Builder builder)
throws IOException, InterruptedException {
if (token != null) {
builder.header(AUTHORIZATION, BEARER + token);
}
HttpResponse<String> response = httpClient.send(builder.build(), HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new RuntimeException(response.body());
}
return objectMapper.readValue(response.body(), valueType);
}
}
Note: We have removed the
HttpRequestUtils
static class and created this helper class instead to create the HttpClient dynamically based on the configured proxy properties.
Source Code
https://github.com/JavaChinna/java-rest-api-client
Conclusion
That’s all folks. In this article, we have added support for proxy authentication to our lightweight Java HttpClient.