具有 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")));
我正在尝试使用 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")));