Preemptive Basic authentication with Apache HttpClient 4
Asked Answered
M

10

55

Is there an easier way to setup the http client for preemptive basic authentication than what described here?
In previous version (3.x) it used to be a simple method call (eg, httpClient.getParams().setAuthenticationPreemptive(true)).
The main thing I want to avoid is adding the BasicHttpContext to each method I execute.

Markitamarkka answered 6/1, 2010 at 17:5 Comment(0)
A
24

It's difficult to do this without passing a context through every time, but you can probably do it by using a request interceptor. Here is some code that we use (found from their JIRA, iirc):

// Pre-emptive authentication to speed things up
BasicHttpContext localContext = new BasicHttpContext();

BasicScheme basicAuth = new BasicScheme();
localContext.setAttribute("preemptive-auth", basicAuth);

httpClient.addRequestInterceptor(new PreemptiveAuthInterceptor(), 0);

(...)

static class PreemptiveAuthInterceptor implements HttpRequestInterceptor {

    public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException {
        AuthState authState = (AuthState) context.getAttribute(ClientContext.TARGET_AUTH_STATE);

        // If no auth scheme avaialble yet, try to initialize it
        // preemptively
        if (authState.getAuthScheme() == null) {
            AuthScheme authScheme = (AuthScheme) context.getAttribute("preemptive-auth");
            CredentialsProvider credsProvider = (CredentialsProvider) context.getAttribute(ClientContext.CREDS_PROVIDER);
            HttpHost targetHost = (HttpHost) context.getAttribute(ExecutionContext.HTTP_TARGET_HOST);
            if (authScheme != null) {
                Credentials creds = credsProvider.getCredentials(new AuthScope(targetHost.getHostName(), targetHost.getPort()));
                if (creds == null) {
                    throw new HttpException("No credentials for preemptive authentication");
                }
                authState.setAuthScheme(authScheme);
                authState.setCredentials(creds);
            }
        }

    }

}
Adai answered 16/8, 2010 at 13:49 Comment(7)
Actually I ended up doing something similar (subversion.jfrog.org/jfrog/build-info/trunk/build-info-client/…). Just forgot to post back. ThanksMarkitamarkka
The correct URL: subversion.jfrog.org/jfrog/build-info/trunk/build-info-client/…Markitamarkka
@Markitamarkka Your site requires authentication; do you have a link usable without authentication?Excrescency
And make sure not to forget the 0 as second parameter for addRequestInterceptor(), as without it, your interceptor will be called after HttpClient's RequestTargetAuthentication, which is itself implemented as an interceptor and which processes the AuthState you create in your interceptor.Excrescency
Sorry, here is the link again: subversion.jfrog.org/jfrog/build-info/trunk/build-info-client/…Markitamarkka
In more recent versions, ClientContext renamed to HttpClientContext, ExecutionContext renamed to HttpCoreContext, and the authState.setXXX methods replaced with authState.update(authScheme, creds).Typographer
an easier solution is described at: www.baeldung.com/httpclient-4-basic-authenticationHenleyonthames
M
91

If you are looking to force HttpClient 4 to authenticate with a single request, the following will work:

String username = ...
String password = ...
UsernamePasswordCredentials creds = new UsernamePasswordCredentials(username, password);

HttpRequest request = ...
request.addHeader(new BasicScheme().authenticate(creds, request));
Marinetti answered 1/12, 2010 at 20:20 Comment(7)
Possibly better: request.addHeader(BasicScheme.authenticate(creds,"US-ASCII",false));Demesne
Wow! this was much easier than following the clumsy documentation Chapter 4. HTTP authentication. Thanks!Tallow
I added the line request.addHeader(BasicScheme.authenticate(creds,"US-ASCII",false)); in an HttpRequestInterceptor and that solved my problem. Plz, give Adam for this solution another medal.Passe
With Robert's addition it did the job for me. Thanks!Osber
BasicScheme.authenticate is deprecated in HttpClient 4.Dynamotor
I was using spring integration with httpclient. I recently upgraded from 3.1 to 4 and this post saved my day !! Thanks for this. They should really have provided a simpler way of doing this.Soddy
If you want to use methods not deprecated in HttpClient 4.3: request.addHeader(new BasicScheme().authenticate(creds, request, null)). The method signature throws AuthenticationException but it never actually happens for BasicScheme, so you can catch the exception and throw new AssertionError(e) or something similar.Demesne
A
24

It's difficult to do this without passing a context through every time, but you can probably do it by using a request interceptor. Here is some code that we use (found from their JIRA, iirc):

// Pre-emptive authentication to speed things up
BasicHttpContext localContext = new BasicHttpContext();

BasicScheme basicAuth = new BasicScheme();
localContext.setAttribute("preemptive-auth", basicAuth);

httpClient.addRequestInterceptor(new PreemptiveAuthInterceptor(), 0);

(...)

static class PreemptiveAuthInterceptor implements HttpRequestInterceptor {

    public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException {
        AuthState authState = (AuthState) context.getAttribute(ClientContext.TARGET_AUTH_STATE);

        // If no auth scheme avaialble yet, try to initialize it
        // preemptively
        if (authState.getAuthScheme() == null) {
            AuthScheme authScheme = (AuthScheme) context.getAttribute("preemptive-auth");
            CredentialsProvider credsProvider = (CredentialsProvider) context.getAttribute(ClientContext.CREDS_PROVIDER);
            HttpHost targetHost = (HttpHost) context.getAttribute(ExecutionContext.HTTP_TARGET_HOST);
            if (authScheme != null) {
                Credentials creds = credsProvider.getCredentials(new AuthScope(targetHost.getHostName(), targetHost.getPort()));
                if (creds == null) {
                    throw new HttpException("No credentials for preemptive authentication");
                }
                authState.setAuthScheme(authScheme);
                authState.setCredentials(creds);
            }
        }

    }

}
Adai answered 16/8, 2010 at 13:49 Comment(7)
Actually I ended up doing something similar (subversion.jfrog.org/jfrog/build-info/trunk/build-info-client/…). Just forgot to post back. ThanksMarkitamarkka
The correct URL: subversion.jfrog.org/jfrog/build-info/trunk/build-info-client/…Markitamarkka
@Markitamarkka Your site requires authentication; do you have a link usable without authentication?Excrescency
And make sure not to forget the 0 as second parameter for addRequestInterceptor(), as without it, your interceptor will be called after HttpClient's RequestTargetAuthentication, which is itself implemented as an interceptor and which processes the AuthState you create in your interceptor.Excrescency
Sorry, here is the link again: subversion.jfrog.org/jfrog/build-info/trunk/build-info-client/…Markitamarkka
In more recent versions, ClientContext renamed to HttpClientContext, ExecutionContext renamed to HttpCoreContext, and the authState.setXXX methods replaced with authState.update(authScheme, creds).Typographer
an easier solution is described at: www.baeldung.com/httpclient-4-basic-authenticationHenleyonthames
W
18

This is the same solution as Mat's Mannion's, but you don't have to put localContext to each request. It's simpler, but it adds authentication to ALL requests. Useful, if you don't have control over individual requests, as in my case when using Apache Solr, which uses HttpClient internally.

import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.AuthState;
import org.apache.http.auth.Credentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpCoreContext;

httpClient.addRequestInterceptor(new PreemptiveAuthInterceptor(), 0);

(...)

static class PreemptiveAuthInterceptor implements HttpRequestInterceptor {

    public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException {
         AuthState authState = (AuthState) context.getAttribute(HttpClientContext.TARGET_AUTH_STATE);

         // If no auth scheme available yet, try to initialize it
         // preemptively
         if (authState.getAuthScheme() == null) {
             CredentialsProvider credsProvider = (CredentialsProvider) context.getAttribute(HttpClientContext.CREDS_PROVIDER);
             HttpHost targetHost = (HttpHost) context.getAttribute(HttpCoreContext.HTTP_TARGET_HOST);
             Credentials creds = credsProvider.getCredentials(new AuthScope(targetHost.getHostName(), targetHost.getPort()));
             if (creds == null) {
                 throw new HttpException("No credentials for preemptive authentication");
             }
             authState.update(new BasicScheme(), creds);
         }
     }
}

Of course, you have to set the credentials provider:

httpClient.getCredentialsProvider().setCredentials(
                new AuthScope(url.getHost(), url.getPort()),
                new UsernamePasswordCredentials(username, password))

The AuthScope must not contain realm, as it is not known in advance.

Whorton answered 8/8, 2012 at 15:31 Comment(3)
Since authState.setAuthScheme() and authState.setCredentials() is deprecated, authState.update(new BasicScheme(), creds); should be used instead.Evaleen
Also in more recent versions, ClientContext renamed to HttpClientContext, and ExecutionContext renamed to HttpCoreContext.Typographer
Updated the code due to deprecations (see above comments).Gangrel
O
8

A lot of the answers above use deprecated code. I am using Apache SOLRJ version 5.0.0. My code consists of

private HttpSolrClient solrClient; 

private void initialiseSOLRClient() {
            URL solrURL = null;
            try {
                solrURL = new URL(urlString);
            } catch (MalformedURLException e) {
                LOG.error("Cannot parse the SOLR URL!!" + urlString);
                throw new SystemException("Cannot parse the SOLR URL!! " + urlString, e);
            }
            String host = solrURL.getHost();
            int port = solrURL.getPort();
            AuthScope authScope = new AuthScope(host, port);

    BasicTextEncryptor textEncryptor = new BasicTextEncryptor();
    textEncryptor.setPassword("red bananas in the spring");
    String decryptPass = textEncryptor.decrypt(pass);
    UsernamePasswordCredentials creds = new UsernamePasswordCredentials(userName, decryptPass);

    CredentialsProvider credsProvider = new BasicCredentialsProvider();
    credsProvider.setCredentials(
            authScope,
            creds);

    HttpClientBuilder builder = HttpClientBuilder.create();
    builder.addInterceptorFirst(new PreemptiveAuthInterceptor());
    builder.setDefaultCredentialsProvider(credsProvider);
    CloseableHttpClient httpClient = builder.build();

    solrClient = new HttpSolrClient(urlString, httpClient);
}

The PreemptiveAuthInterceptor is now as follows:-

static class PreemptiveAuthInterceptor implements HttpRequestInterceptor {

    public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException {
        AuthState authState = (AuthState) context.getAttribute(HttpClientContext.TARGET_AUTH_STATE);
        // If no auth scheme available yet, try to initialize it
        // preemptively
        if (authState.getAuthScheme() == null) {
            CredentialsProvider credsProvider = (CredentialsProvider) 
                        context.getAttribute(HttpClientContext.CREDS_PROVIDER);
            HttpHost targetHost = (HttpHost) context.getAttribute(HttpCoreContext.HTTP_TARGET_HOST);
            AuthScope authScope = new AuthScope(targetHost.getHostName(), targetHost.getPort());
            Credentials creds = credsProvider.getCredentials(authScope);
            if(creds == null){

            }
            authState.update(new BasicScheme(), creds);
        }

    }
}
Osmious answered 12/3, 2015 at 14:27 Comment(1)
This was exactly the issue I was having with Solr 5.x. Thanks!Bevus
B
6

A little late to the party but I came accross the thread trying to solve this for proxy pre-authorization of a post request. To add to Adam's response, I found the following worked for me:

HttpPost httppost = new HttpPost(url);
UsernamePasswordCredentials creds = new UsernamePasswordCredentials(username, password);
Header bs = new BasicScheme().authenticate(creds, httppost);
httppost.addHeader("Proxy-Authorization", bs.getValue());

Thought that might be helpful for anyone else who runs into this.

Belita answered 7/7, 2011 at 14:56 Comment(0)
E
6

I think the best way may be to just do it manually. I added the following function

Classic Java:

import javax.xml.bind.DatatypeConverter;

...

private static void addAuthHeader(HttpRequestBase http, String username, String password) throws UnsupportedEncodingException {
        String encoded = DatatypeConverter.printBase64Binary((username + ":" + password).getBytes("UTF-8"));
        http.addHeader("AUTHORIZATION", "Basic " + encoded);
    }

HTTPRequestBase can be an instance of HttpGet or HttpPost

Android:

import android.util.Base64;

...

private static void addAuthHeader(HttpRequestBase http, String username, String password) throws UnsupportedEncodingException {
    String encoded = Base64.encodeToString((username + ":" + password).getBytes("UTF-8"), Base64.NO_WRAP);
    http.addHeader("AUTHORIZATION", "Basic " + encoded);
}
Ecdysiast answered 19/1, 2014 at 22:46 Comment(0)
M
2

I'm using this code, based on my reading of the HTTPClient 4.5 docs:

HttpClientContext ctx = HttpClientContext.create()
ctx.setCredentialsProvider(new BasicCredentialsProvider())
ctx.setAuthCache(new BasicAuthCache())
UsernamePasswordCredentials creds = new UsernamePasswordCredentials(user, pass)
AuthScope authScope = new AuthScope(host, port)
ctx.getCredentialsProvider.setCredentials(authScope, credentials)

// This part makes authentication preemptive:
HttpHost targetHost = new HttpHost(host, port, scheme)
ctx.getAuthCache.put(targetHost, new BasicScheme())

...and make sure you always pass that context to HTTPClient.execute().

Megara answered 16/3, 2017 at 19:12 Comment(0)
S
1

I don't quite get your closing comment. It's the HttpClient that has all of that machinery for doing preemptive auth, and you only have to do that once (when you construct and configure your HttpClient). Once you've done that, you construct your method instances the same way as always. You don't "add the BasicHttpContext" to the method.

Your best bet, I'd think, is to have your own object that sets up all of the junk required for preemptive auth, and has a simple method or methods for executing requests on given HTTPMethod objects.

Sinuation answered 6/1, 2010 at 17:11 Comment(2)
According to the example (and some testing I did) the context must pass whenever I call the execute method: httpclient.execute(targetHost, httpget, localcontext);Markitamarkka
Sure, that's a good idea, I'll wait a little bit to see if someone has an out-of-the-box solution.Markitamarkka
P
1

SolrConfig:

@Configuration
public class SolrConfig {

    @Value("${solr.http.url}")
    private String solrUrl;

    @Value("${solr.http.username}")
    private String solrUser;

    @Value("${solr.http.password}")
    private String solrPassword;

    @Value("${solr.http.pool.maxTotal}")
    private int poolMaxTotal;

    @Value("${solr.http.pool.maxPerRoute}")
    private int pollMaxPerRoute;

    @Bean
    public SolrClient solrClient() {
        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
        connectionManager.setMaxTotal(poolMaxTotal);
        connectionManager.setDefaultMaxPerRoute(pollMaxPerRoute);

        CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(solrUser, solrPassword));

        CloseableHttpClient httpClient = HttpClientBuilder.create()
                .addInterceptorFirst(new PreemptiveAuthInterceptor())
                .setConnectionManager(connectionManager)
                .setDefaultCredentialsProvider(credentialsProvider)
                .build();

        return new HttpSolrClient.Builder(solrUrl).withHttpClient(httpClient).build();
    }


}

PreemptiveAuthInterceptor:

public class PreemptiveAuthInterceptor implements HttpRequestInterceptor {

    public void process(final HttpRequest request, final HttpContext context)
            throws HttpException {
        AuthState authState = (AuthState) context
                .getAttribute(HttpClientContext.TARGET_AUTH_STATE);

        // If no auth scheme available yet, try to initialize it
        // preemptively
        if (authState.getAuthScheme() == null) {
            CredentialsProvider credentialsProvider = (CredentialsProvider) context
                    .getAttribute(HttpClientContext.CREDS_PROVIDER);
            HttpHost targetHost = (HttpHost) context
                    .getAttribute(HttpCoreContext.HTTP_TARGET_HOST);
            Credentials credentials = credentialsProvider.getCredentials(new AuthScope(
                    targetHost.getHostName(), targetHost.getPort()));
            if (credentials == null) {
                throw new HttpException(
                        "No credentials for preemptive authentication");
            }
            authState.update(new BasicScheme(), credentials);
        }

    }

}
Phosphorescent answered 3/8, 2021 at 11:18 Comment(1)
Thanks @Yersin. This looks more cleaner.Menorca
S
0

in android,Mat Mannion's answer can't resolve https,still send two requests,you can do like below,the trick is append authHeader with user-agent:

    public static DefaultHttpClient createProxyHttpClient() {
        try {
            final DefaultHttpClient client = createPlaintHttpClient();
            client.setRoutePlanner(new HttpRoutePlanner() {
                @Override
                public HttpRoute determineRoute(HttpHost target, HttpRequest request, HttpContext context) throws HttpException {
                    boolean isSecure = "https".equalsIgnoreCase(target.getSchemeName());
                    if (needProxy) {
                        Header header = isSecure ? ProxyUtils.createHttpsAuthHeader() : ProxyUtils.createAuthHeader();
                        if (isSecure) {
                            client.getParams().setParameter(CoreProtocolPNames.USER_AGENT, com.netease.cloudmusic.utils.HttpRequest.USER_AGENT + "\r\n" + header.getName() + ":" + header.getValue());
                        } else {
                            client.getParams().setParameter(CoreProtocolPNames.USER_AGENT, com.netease.cloudmusic.utils.HttpRequest.USER_AGENT);
                            if (request instanceof RequestWrapper) {
                                request = ((RequestWrapper) request).getOriginal();
                            }
                            request.setHeader(header);
                        }
                        String host = isSecure ? ProxyUtils.SECURE_HOST : ProxyUtils.HOST;
                        int port = isSecure ? ProxyUtils.SECURE_PORT : ProxyUtils.PORT;
                        return new HttpRoute(target, null,  new HttpHost(host, port), isSecure);
                    } else {
                        client.getParams().setParameter(CoreProtocolPNames.USER_AGENT, com.netease.cloudmusic.utils.HttpRequest.USER_AGENT);
                        return new HttpRoute(target, null, isSecure);
                    }
                }
            });
            return client;
        } catch (Exception e) {
            e.printStackTrace();
            return new DefaultHttpClient();
        }
    }

public static DefaultHttpClient createPlaintHttpClient() {
       try {
            KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
            trustStore.load(null, null);
            PlainSSLSocketFactory socketFactory = new PlainSSLSocketFactory(trustStore);
            socketFactory.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
            BasicHttpParams params = new BasicHttpParams();
            HttpConnectionParams.setConnectionTimeout(params, 30000);
            HttpConnectionParams.setSoTimeout(params, 30000);
            HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
            HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);
            SchemeRegistry registry = new SchemeRegistry();
            registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
            registry.register(new Scheme("https", socketFactory, 443));
            ThreadSafeClientConnManager ccm = new ThreadSafeClientConnManager(params, registry);
            HttpClientParams.setCookiePolicy(params, CookiePolicy.BROWSER_COMPATIBILITY);
            final DefaultHttpClient client = new DefaultHttpClient(ccm, params);
            client.setRoutePlanner(new HttpRoutePlanner() {
        @Override
        public HttpRoute determineRoute(HttpHost target, HttpRequest arg1, HttpContext arg2) throws HttpException {
               client.getParams().setParameter(CoreProtocolPNames.USER_AGENT, com.netease.cloudmusic.utils.HttpRequest.USER_AGENT);
            return new HttpRoute(target, null, "https".equalsIgnoreCase(target.getSchemeName()));
        }
        });
            return client;
        } catch (Exception e) {
            e.printStackTrace();
            return new DefaultHttpClient();
        }
}
Squadron answered 29/1, 2014 at 2:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.