NonRepeatableRequestException after adding Basic Auth - java

I'm using the most recent apache http:
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient-osgi</artifactId>
<version>4.5.6</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore-osgi</artifactId>
<version>4.4.10</version>
</dependency>
I have the following operation:
public void store(InputStream input) throws IOException {
HttpClientBuilder builder = HttpClientBuilder.create();
if (StringUtils.isNotBlank(username)) {
CredentialsProvider provider = new BasicCredentialsProvider();
UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(username.trim(), StringUtils.trimToEmpty(password));
provider.setCredentials(AuthScope.ANY, credentials);
builder.setDefaultCredentialsProvider(provider);
}
HttpClient client = builder.build();
HttpPost post = new HttpPost(uri);
post.setEntity(new InputStreamEntity(input));
HttpResponse response = client.execute(post);
}
Until basic auth was active, everything was working fine, however, after adding basic auth I get the following error:
Caused by: org.apache.http.client.NonRepeatableRequestException:
Cannot retry request with a non-repeatable request entity. at
org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:226)
at
org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:185)
at
org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:89)
at
org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:111)
at
org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185)
... 6 more
I've found a following bug report: https://github.com/http-builder-ng/http-builder-ng/issues/10, however it is assigned to another problem.
What is causing the error? How to use basic auth with apache httpclient? I have no idea what is 'repeatable HTTP request', from what I know all the client need to set is Authorization header. Is it possible that I've misconfigured something on the server so that it requires 'repeatable' HTTP request?

It seems that basic authentication model is broken in apache httpclient. The library tries to sneak around authentication and sends request without Authorization header, which of course fails. Then the library tries to resend the request, which of course fails, because InputStream can't be rewinded.
The solution is to forget BasicCredentialsProvider and use HttpRequestInterceptor to set headers:
HttpClientBuilder builder = HttpClientBuilder.create();
if (StringUtils.isNotBlank(username)) {
builder.addInterceptorFirst(new HttpRequestInterceptor()
{
#Override
public void process(HttpRequest request, HttpContext context) throws HttpException, IOException
{
String token = Base64.getEncoder().encodeToString((username + ":" + password).getBytes(StandardCharsets.UTF_8));
request.addHeader("Authorization", "Basic "+token);
}
});
}

I do not agree with the OP's own solution, because it is somehow very hackish and circumvents the Credential Mechanism of the library.
There are several entity types for HTTP Entities, as documented here. So now that you know, you cannot use a repeatable entity in your scenario, how about using a self-contained one or a wrapper which uses a buffer.
You can achieve this with a one-liner. Without having tried it out, I think the correct solution is:
post.setEntity(new BufferedHttpEntity(new InputStreamEntity(input)));

Related

How Get is better than Post in Basic Authentication apache http client (Java Programming)

I have heard that Post is most preferred while reaching a link which has password /sensitive information.
Q1.I know that Post is better than Put for Name-value pair parameters as ,the Get exposes it in URL and Post doesn't. But if we are talking about authentication ,How does it matter what method i use as i set the Credentials to the HttpClient not to the HttpGet or HttpPost.So credentials are not getting exposed anyway .The encapsulation of Name-value pairs is benefited by using POST.
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(
new AuthScope(target.getHostName(), target.getPort()),
new UsernamePasswordCredentials("user", "passwd"));
CloseableHttpClient httpClient = HttpClients.custom()
.setDefaultCredentialsProvider(credsProvider)
.build();
Q2. Again i can set the credentials as header to httppost as well to httpget like this..
String encoding = new BASE64Encoder().encode("user:passwd".getBytes());
httpGet.setHeader("Authorization", "Basic " + encoding);
httpPost.setHeader("Authorization", "Basic " + encoding);
Where does the post method takes precedence over get method?
I think whoever said post is better than get when using passwords meant that the passwords would be part of the url in the get request, which would obviously be bad.

Get all the headers for HttpPost with entity

I'm using HttpClient to send a PUT request with a body to Azure.
The body is represented by a StringEntity.
I need to add the Azure authentication signature to the request, and in order to compute it correctly I need the values of the Content-Type and Content-Length headers.
When call the setEntity() method on the HttpPost request, no headers are added to the request, but using a HTTP debugging proxy I can see that they are correctly sent with the request.
From the Apache documentation (here) I saw I could use entity.getContentLength() and entity.getContentType() to compute those, but I would prefer to extract the data directly from the HttpPost if possible.
Anyone knows a way to force the entity to add the headers to the request, before the request is executed?
This is the code I'm using
HttpPut createBlob = new HttpPut(createBlobUrl);
createBlob.addHeader("x-ms-blob-type", "BlockBlob");
createBlob.addHeader("x-ms-date", utcTime);
createBlob.addHeader("x-ms-version", "2015-04-05");
HttpEntity body = new StringEntity("test blob", "UTF-8");
createBlob.setEntity(body);
// h is missing Content-Length and Content-Type
Header[] h = createBlob.getAllHeaders();
resp = httpclient.execute(createBlob);
I found a solution for it.
Adding an interceptor to the client, I can get the complete request with all the headers just before it's executed, so I don't need to deal with special cases where the headers are added by the entity.
HttpClientBuilder builder = HttpClientBuilder.create();
builder = builder.disableContentCompression().disableConnectionState();
builder.addInterceptorLast((HttpRequestInterceptor) (request, context) -> {
try {
SignRequest(request, account, secret);
}
catch (Exception e) {
}
});
HttpClient httpclient = builder.build();
Official tutorial about interceptor can be found at this link

HttpClient 4.3.6 returning "WARNING: NEGOTIATE authentication error"

I'm running HttpClient 4.3.6 in Java 6. When I run the following code, the authentication appears to succeed. The Status Code returned is 200. However, I'm getting the following error message in the console:
WARNING: NEGOTIATE authentication error: Invalid name provided (Mechanism level: Could not load configuration file C:\Windows\krb5.ini (the system cannot find the file specified))
How do I eliminate this warning?
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpContext localContext = new BasicHttpContext();
HttpGet method = new HttpGet(url);
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(
new AuthScope(host, 80),
new NTCredentials(userid, password, host, login_domain));
localContext.setAttribute(HttpClientContext.CREDS_PROVIDER, credsProvider);
String filePath = null;
// Execute the method.
CloseableHttpResponse clientResponse = httpclient.execute(method, localContext);
HttpEntity entity = clientResponse.getEntity();
int statusCode = clientResponse.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK) {
System.err.println("Method failed: " + method.getRequestLine());
}
You need to pass in a set of target preferred auth schemes:
Create your httpClient like this:
PoolingHttpClientConnectionManager connPool = new PoolingHttpClientConnectionManager();
connPool.setMaxTotal(200);
connPool.setDefaultMaxPerRoute(200);
// Authentication
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(AuthScope.ANY, new NTCredentials(username, password, workstation, domain));
RequestConfig config = RequestConfig.custom().setTargetPreferredAuthSchemes(Arrays.asList(AuthSchemes.NTLM)).build();
CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(connPool).setDefaultRequestConfig(config).build();
HttpClientContext context = HttpClientContext.create();
context.setCredentialsProvider(credsProvider);
Yes I believe that, in fact, your authentication is successful and is probably just falling back to NTLM from Kerberos. My code looks similar to yours and in my application I'm connecting to SharePoint using HttpClient 4.3.5 in Java 7. When SharePoint is configured to "Negotiate" (Attempt Kerberos and then failover to NTLM), I will see a similar error to what you reported in the HttpClient generated logging, specifically:
Selected authentication options: [NEGOTIATE, NTLM]
Executing request GET /my/personal/user2/_api/web?$select=ServerRelativeUrl HTTP/1.1
Target auth state: CHALLENGED
Generating response to an authentication challenge using Negotiate scheme
init XXX.XXX.XXX.XXX:80
NEGOTIATE authentication error: org.ietf.jgss.GSSException, major code: 11, minor code: 0
major string: General failure, unspecified at GSSAPI level
minor string: Desired initLifetime zero or less
Generating response to an authentication challenge using ntlm scheme
Following that, it will successfully authenticate via NTLM. So, I read that error message as saying "Kerberos didn't work, now we'll use NTLM". As long as you're getting a 200 response, you should be good to go.
Are you sure authentication is happening successfully, if the website is set to Negotiate (Attempt Kerbero, then failover to NTLM) BASIC authentication would probably not be successful.

HttpClient 4.1.1 returns 401 when authenticating with NTLM, browsers work fine

I'm trying to use the Apache/Jakarta HttpClient 4.1.1 to connect to an arbitrary web page using the given credentials. To test this, I have a minimal install of IIS 7.5 on my dev machine running where only one authentication mode is active at a time. Basic authentication works fine, but Digest and NTLM return 401 error messages whenever I try to log in. Here is my code:
DefaultHttpClient httpclient = new DefaultHttpClient();
HttpContext localContext = new BasicHttpContext();
HttpGet httpget = new HttpGet("http://localhost/");
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(AuthScope.ANY,
new NTCredentials("user", "password", "", "localhost"));
if (!new File(System.getenv("windir") + "\\krb5.ini").exists()) {
List<String> authtypes = new ArrayList<String>();
authtypes.add(AuthPolicy.NTLM);
authtypes.add(AuthPolicy.DIGEST);
authtypes.add(AuthPolicy.BASIC);
httpclient.getParams().setParameter(AuthPNames.PROXY_AUTH_PREF,
authtypes);
httpclient.getParams().setParameter(AuthPNames.TARGET_AUTH_PREF,
authtypes);
}
localContext.setAttribute(ClientContext.CREDS_PROVIDER, credsProvider);
HttpResponse response = httpclient.execute(httpget, localContext);
System.out.println("Response code: " + response.getStatusLine());
The one thing I've noticed in Fiddler is that the hashes sent by Firefox versus by HttpClient are different, making me think that maybe IIS 7.5 is expecting stronger hashing than HttpClient provides? Any ideas? It'd be great if I could verify that this would work with NTLM. Digest would be nice too, but I can live without that if necessary.
I am not an expert on the subject but during the NTLM authentication using http components I have seen that the client needs 3 attempts in order to connect to an NTML endpoint in my case. It is kinda described here for Spnego but it is a bit different for the NTLM authentication.
For NTLM in the first attempt client will make a request with Target auth state: UNCHALLENGED and Web server returns HTTP 401 status and a header: WWW-Authenticate: NTLM
Client will check for the configured Authentication schemes, NTLM should be configured in client code.
Second attempt, client will make a request with Target auth state: CHALLENGED, and will send an authorization header with a token encoded in base64 format: Authorization: NTLM TlRMTVNTUAABAAAAAYIIogAAAAAoAAAAAAAAACgAAAAFASgKAAAADw==
Server again returns HTTP 401 status but the header: WWW-Authenticate: NTLM now is populated with encoded information.
3rd Attempt Client will use the information from WWW-Authenticate: NTLM header and will make the final request with Target auth state: HANDSHAKE and an authorisation header Authorization: NTLM which contains more information for the server.
In my case I receive an HTTP/1.1 200 OK after that.
In order to avoid all this in every request documentation at chapter 4.7.1 states that the same execution token must be used for logically related requests. For me it did not worked.
My code:
I initialize the client once in a #PostConstruct method of an EJB
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
cm.setMaxTotal(18);
cm.setDefaultMaxPerRoute(6);
RequestConfig requestConfig = RequestConfig.custom()
.setSocketTimeout(30000)
.setConnectTimeout(30000)
.setTargetPreferredAuthSchemes(Arrays.asList(AuthSchemes.NTLM))
.setProxyPreferredAuthSchemes(Arrays.asList(AuthSchemes.BASIC))
.build();
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY,
new NTCredentials(userName, password, hostName, domainName));
// Finally we instantiate the client. Client is a thread safe object and can be used by several threads at the same time.
// Client can be used for several request. The life span of the client must be equal to the life span of this EJB.
this.httpclient = HttpClients.custom()
.setConnectionManager(cm)
.setDefaultCredentialsProvider(credentialsProvider)
.setDefaultRequestConfig(requestConfig)
.build();
Use the same client instance in every request:
HttpPost httppost = new HttpPost(endPoint.trim());
// HttpClientContext is not thread safe, one per request must be created.
HttpClientContext context = HttpClientContext.create();
response = this.httpclient.execute(httppost, context);
Deallocate the resources and return the connection back to connection manager, at the #PreDestroy method of my EJB:
this.httpclient.close();
I had the same problem with HttpClient4.1.X After upgrading it to
HttpClient 4.2.6 it woked like charm. Below is my code
DefaultHttpClient httpclient = new DefaultHttpClient();
HttpContext localContext = new BasicHttpContext();
HttpGet httpget = new HttpGet("url");
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(AuthScope.ANY,
new NTCredentials("username", "pwd", "", "domain"));
List<String> authtypes = new ArrayList<String>();
authtypes.add(AuthPolicy.NTLM);
httpclient.getParams().setParameter(AuthPNames.TARGET_AUTH_PREF,authtypes);
localContext.setAttribute(ClientContext.CREDS_PROVIDER, credsProvider);
HttpResponse response = httpclient.execute(httpget, localContext);
HttpEntity entity=response.getEntity();
The easiest way troubleshoot such situations I found is Wireshark. It is a very big hammer, but it really will show you everything. Install it, make sure your server is on another machine (does not work with Localhost) and start logging.
Run your request that fails, run one that works. Then, filter by http (just put http in the filter field), find the first GET request, find the other GET request and compare. Identify meaningful difference, you now have specific keywords or issues to search code/net for. If not enough, narrow down to first TCP conversation and look at full request/response. Same with the other one.
I solved an unbelievable number of problems with that approach. And Wireshark is very useful tool to know. Lots of super-advanced functions to make your network debugging easier.
You can also run it on either client or server end. Whatever will show you both requests to allow you to compare.
I had a similar problem with HttpClient 4.1.2. For me, it was resolved by reverting to HttpClient 4.0.3. I could never get NTLM working with 4.1.2 using either the built-in implementation or using JCIFS.
Updating our application to use the jars in the httpcomponents-client-4.5.1 resolved this issue for me.
I finally figured it out. Digest authentication requires that if you use a full URL in the request, the proxy also needs to use the full URL. I did not leave the proxy code in the sample, but it was directed to "localhost", which caused it to fail. Changing this to 127.0.0.1 made it work.

Proper use of HttpRequestInterceptor and CredentialsProvider in doing preemptive authentication with HttpClient

I'm writing an application in Android that consumes some REST services I've created. These web services aren't issuing a standard Apache Basic challenge / response. Instead in the server-side code I'm wanting to interrogate the username and password from the HTTP(S) request and compare it against a database user to make sure they can run that service.
I'm using HttpClient to do this and I have the credentials stored on the client after the initial login (at least that's how I see this working). So here is where I'm stuck. Preemptive authenticate under HttpClient requires you to setup an interceptor as a static member. This is the example Apache Components uses.
HttpRequestInterceptor preemptiveAuth = new HttpRequestInterceptor() {
#Override
public void process( final HttpRequest request, final HttpContext context) throws HttpException, IOException {
AuthState authState = (AuthState) context.getAttribute(ClientContext.TARGET_AUTH_STATE);
CredentialsProvider credsProvider = (CredentialsProvider) context.getAttribute(
ClientContext.CREDS_PROVIDER);
HttpHost targetHost = (HttpHost) context.getAttribute(ExecutionContext.HTTP_TARGET_HOST);
if (authState.getAuthScheme() == null) {
AuthScope authScope = new AuthScope(targetHost.getHostName(), targetHost.getPort());
Credentials creds = credsProvider.getCredentials(authScope);
if (creds != null) {
authState.setAuthScheme(new BasicScheme());
authState.setCredentials(creds);
}
}
}
};
So the question would be this. What would the proper use of this be? Would I spin this up as part of the application when the application starts? Pulling the username and password out of memory and then using them to create this CredentialsProvider which is then utilized by the HttpRequestInterceptor? Or is there a way to do this more dynamically?
HttpClient does not like pre-emptive authentication very much.
If your REST API supports BASIC authentication, then it is probably simpler to just put in the proper header yourself. Here is a sample Twitter client, using the Twitter API, that uses this technique.
Nevermind, I found out what was wrong. I took another look at the Apache examples and realized that I wasn't passing along the "http" when creating the HttpHost object I was using. It was completely unrelated. Ugh.

Categories