具有 POST file-uploading 的 Apache HttpClient 不会出于神秘原因进行基本身份验证

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

我正在尝试使用 Apache HttpClient 库通过 POST 方法上传文件。

我使用了抢占式基本身份验证的示例代码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);
        }
    }
}

不幸的是,它不符合我的要求。 apache httpclient 给出的请求是这样的(我通过使用 nc -p 64738 -l 命令从命令行监听得到这个):

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--

正如我们所见,一切正常,只是缺少身份验证 header。

为什么会这样?错误是什么?

根据RFC7617,您只需要一个 header 值为“基本”的“授权”+ login:passord Base64 编码即可成功通过基本授权。

您的代码是正确的,除了一件事 - 当您调用 httpPost.execute 时,您没有传递执行上下文,并且根本没有使用 AuthCache 和 CredentialsProvider。

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);
        }
    }
}

但是对于 Basic Auth 使用这个 API 可能有点冗长,它被设计为支持许多不同的授权方案。

如果您知道服务器将使用什么字符集解码授权 header(假设它是 UTF-8),您可以编写 one-liner:

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