具有POST文件上传功能的Apache HttpClient因神秘原因未能进行基本身份验证

时间:2018-01-23 11:37:17

标签: java http post apache-httpclient-4.x http-authentication

我正在尝试使用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--

正如我们所看到的,一切都没问题,除了遗漏了认证标题之外。

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

1 个答案:

答案 0 :(得分:2)

根据RFC7617,您只需要一个标题"授权"与价值观#34;基本" + login:使用Base64编码的passord成功通过基本授权。

您的代码是正确的,除了一件事 - 当您调用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);
        }
    }
}

但对于使用此API的Basic Auth可能有点冗长,它旨在支持许多不同的授权方案。

如果您知道charset服务器将使用什么来解码授权标头(假设它是UTF-8),您可以编写单行代码:

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