Search code examples
javahttppostapache-httpclient-4.xhttp-authentication

Apache HttpClient with POST file-uploading won't do a basic auth on mysterious reasons


I am trying to upload a file by the POST method with the Apache HttpClient library.

I used the example code for the preemptive basic authentification here:

package ahcs;

// many imports, press ctrl-o in eclipse

public class App {
    static final String url = "http://127.0.0.1:64738/test/";
    static final String content = "test\nfile\ndata";
    static final String httpUser = "testuser";
    static final String httpPasswd = "testPassword";
    static final String fileUploadFieldName = "uploadData";
    static final String fileName = "upload.dat";

    public static void main(String[] args) {
        System.err.println("Uploading to URL " + url);
        CloseableHttpClient httpclient = HttpClientBuilder.create().build();

        HttpPost httpPost = new HttpPost(url);
        httpPost.setProtocolVersion(HttpVersion.HTTP_1_1);

        MultipartEntityBuilder mpEntityBuilder =
            MultipartEntityBuilder.create();
        mpEntityBuilder.setMode(HttpMultipartMode.RFC6532);
        mpEntityBuilder.addBinaryBody(fileUploadFieldName,
            content.getBytes(), ContentType.DEFAULT_BINARY, fileName);

        httpPost.setEntity(mpEntityBuilder.build());
        System.err.println("executing request " + httpPost.getRequestLine());

        HttpEntity resEntity = null;

        try {
            // Really simple HTTP Authentification, grat Apache
            HttpHost httpHost = URIUtils.extractHost(new URI(url));
            CredentialsProvider credsProvider = new BasicCredentialsProvider();
            credsProvider.setCredentials(AuthScope.ANY,
                new UsernamePasswordCredentials(httpUser, httpPasswd));
            AuthCache authCache = new BasicAuthCache();
            authCache.put(httpHost, new BasicScheme());
            HttpClientContext context = HttpClientContext.create();
            context.setCredentialsProvider(credsProvider);
            context.setAuthCache(authCache);

            HttpResponse response = httpclient.execute(httpPost);
            resEntity = response.getEntity();

            System.err.println(response.getStatusLine().toString());
            if (resEntity != null) {
                System.err.println(EntityUtils.toString(resEntity));
            }
            int status = response.getStatusLine().getStatusCode();
            if (status != HttpStatus.SC_OK) {
                throw new HttpResponseException(status,
                    "Upload error! (" + status + ")");
            }
            EntityUtils.consume(resEntity);
            httpclient.close();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

Unfortunately, it doesn't do what I want. The request what the apache httpclient gives, is this (I got this by listening from the command line with an nc -p 64738 -l command):

POST /test/ HTTP/1.1
Content-Length: 249
Content-Type: multipart/form-data; boundary=PIrvSJ07MLxTV2rC4d-5ZfoL3CvJFJdJqO4i
Host: 127.0.0.1:64738
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.5.4 (Java/1.8.0_151)
Accept-Encoding: gzip,deflate

--PIrvSJ07MLxTV2rC4d-5ZfoL3CvJFJdJqO4i
Content-Disposition: form-data; name="uploadData"; filename="upload.dat"
Content-Type: application/octet-stream
Content-Transfer-Encoding: binary

test
file
data
--PIrvSJ07MLxTV2rC4d-5ZfoL3CvJFJdJqO4i--

As we can see, everything is okay, except that the authentification header is simply missing.

Why is it so? What is the bug?


Solution

  • According to RFC7617 you need only one header "Authorization" with values "Basic " + login:passord in Base64 encoding to successefuly pass Basic authorization.

    Your code is correct, except one thing - when you call httpPost.execute you are not pass execution context, and AuthCache and CredentialsProvider wasn't used at all.

    package ahcs;
    
    // many imports, press ctrl-o in eclipse
    
    public class App {
        static final String url = "http://127.0.0.1:64738/test/";
        static final String content = "test\nfile\ndata";
        static final String httpUser = "testuser";
        static final String httpPasswd = "testPassword";
        static final String fileUploadFieldName = "uploadData";
        static final String fileName = "upload.dat";
    
        public static void main(String[] args) {
            System.err.println("Uploading to URL " + url);
            CloseableHttpClient httpclient = HttpClientBuilder.create().build();
    
            HttpPost httpPost = new HttpPost(url);
            httpPost.setProtocolVersion(HttpVersion.HTTP_1_1);
    
            MultipartEntityBuilder mpEntityBuilder =
                MultipartEntityBuilder.create();
            mpEntityBuilder.setMode(HttpMultipartMode.RFC6532);
            mpEntityBuilder.addBinaryBody(fileUploadFieldName,
                content.getBytes(), ContentType.DEFAULT_BINARY, fileName);
    
            httpPost.setEntity(mpEntityBuilder.build());
            System.err.println("executing request " + httpPost.getRequestLine());
    
            HttpEntity resEntity = null;
    
            try {
                // Really simple HTTP Authentification, grat Apache
                HttpHost httpHost = URIUtils.extractHost(new URI(url));
                CredentialsProvider credsProvider = new BasicCredentialsProvider();
                credsProvider.setCredentials(AuthScope.ANY,
                    new UsernamePasswordCredentials(httpUser, httpPasswd));
                AuthCache authCache = new BasicAuthCache();
                authCache.put(httpHost, new BasicScheme());
                HttpClientContext context = HttpClientContext.create();
                context.setCredentialsProvider(credsProvider);
                context.setAuthCache(authCache);
                // context was missed
                HttpResponse response = httpclient.execute(httpPost, context);  
                resEntity = response.getEntity();
    
                System.err.println(response.getStatusLine().toString());
                if (resEntity != null) {
                    System.err.println(EntityUtils.toString(resEntity));
                }
                int status = response.getStatusLine().getStatusCode();
                if (status != HttpStatus.SC_OK) {
                    throw new HttpResponseException(status,
                        "Upload error! (" + status + ")");
                }
                EntityUtils.consume(resEntity);
                httpclient.close();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
    

    But for Basic Auth using this API may be a bit verbose, it was designed to support many different authorization schemes.

    If you know what charset server will use to decode Authorization header (suppose it UTF-8), you can write one-liner:

    httpPost.setHeader("Authorization", "Basic " + Base64.getEncoder().encodeToString((httpUser + ':' + httpPasswd).getBytes‌​("UTF-8")));