3

Is there a way to implement preemptive basic authentication with Apache HttpClient 5.2 using a HttpRequestInterceptor similar to how it's done here (accepted response) for Apache HttpClient 4?

We use Apache HttpClient both directly and as the RestTemplate underlying Http client. Preemptive auth used to work with HttpClient 4 using HttpRequestInterceptor (see the accepted response for the question I linked above), but we can't reuse the same code as the AuthScheme class is now gone.

I tried a couple of things but neither worked (see below). Every first request gets a 401 from the server, the client retries with basic auth and gets a successful response. This is not the behaviour I want though, the first request should add the Authorisation header with Basic auth as it used to.

@Component
public class HttpClientPreemptiveAuthInterceptor implements HttpRequestInterceptor {

    @Override
    public void process(HttpRequest httpRequest, EntityDetails entityDetails, HttpContext httpContext) throws IOException, HttpException {

        // Apparently, we have two options:
        // - Set HttpContext AuthCache with Basic auth for the target.
        // - Copy org.apache.hc.client5.http.ContextBuilder#preemptiveBasicAuth()
        // The above method is available for HttpClientContext creation. Not sure why it doesn't set AuthCache though,
        // but instead adds an entry to the AuthExchange map of the given http context.

        HttpClientContext httpClientContext = (HttpClientContext) httpContext;

        // get the target host from the http context
        RouteInfo routeInfo = httpClientContext.getHttpRoute();
        if (routeInfo != null) {
            HttpHost targetHost = routeInfo.getTargetHost();
            AuthExchange authExchange = httpClientContext.getAuthExchange(targetHost);

            if (authExchange.getAuthScheme() == null) {
                CredentialsProvider credentialsProvider = httpClientContext.getCredentialsProvider();
                Credentials credentials = credentialsProvider.getCredentials(new AuthScope(targetHost.getHostName(), targetHost.getPort()), httpClientContext);
                if (credentials == null) {
                    throw new HttpException("No credentials provided for preemptive authentication");
                }
                BasicScheme authScheme = new BasicScheme();
                authScheme.initPreemptive(credentials);
                //authExchange.select(authScheme);
                //httpClientContext.setAuthExchange(targetHost, authExchange);
                DefaultSchemePortResolver schemePortResolver = DefaultSchemePortResolver.INSTANCE;
                httpClientContext.resetAuthExchange(RoutingSupport.normalize(targetHost, schemePortResolver), authScheme);
            }

        }


        /** Second approach
        // get the target host from the http context
        RouteInfo routeInfo = httpClientContext.getHttpRoute();
        if (routeInfo != null) {
            HttpHost targetHost = routeInfo.getTargetHost();
            // add Basic Auth for the target host (credentials for this host will be selected from the CredentialsProvider)
            httpClientContext.resetAuthExchange(targetHost, new BasicScheme());
        }
         **/

        // Third approach
        // Create AuthCache instance
        // final AuthCache authCache = new BasicAuthCache();
        // Generate BASIC scheme object and add it to the local auth cache
        // authCache.put(targetHost, new BasicScheme());
        // httpClientContext.setAuthCache(new BasicAuthCache());
    }
3
  • What does happen? Does it go into the if branches? Isn't it possible to use the sample on the Apache HttpClient website? Or don't use the HttpClient directly and use the BasicAuthInterceptor for the RestTemplate to set the header. Commented Jul 12, 2023 at 13:42
  • This sample here sets a request-id header before performing the actual request via request interceptors. Replacing request-id with Authorization and the long value with the actual basic-auth authorization string shouldn't be to difficult I guess. Not sure though if there might be any further hurdles using HttpClient 5.2. Commented Jul 12, 2023 at 13:51
  • @M.Deinum yeah it goes into the if branches. If a inspect the httpClientCOntext instance at the end of the process method, it seems to have everything it needs. The authExchange with basicScheme is there, host and credentials seem OK, still doesn't work. The Authorization header is not there for the first request. Commented Jul 12, 2023 at 14:16

1 Answer 1

1

You could also try forcing it using a request interceptor, e.g.:

HttpClients.custom()
  .addRequestInterceptorLast((request, entity, context) -> 
    request.setHeader("Authorization", "Basic Zm9vOmJhcgo="))
  .build();
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.