I'm using Apache HttpClient to connect to a web server which is running Jetty and using WAFFLE for authentication. I'm having a problem where the server sends three WWW-Authenticate
headers (because it does support three authentication schemes) but then on the client, CredentialsProvider
is being asked three times for the same credentials.
If I connect to the same server with the built-in Java URL
class, it gets the resource without asking me for credentials.
The HTTP client setup is fairly straight-forward:
RequestConfig requestConfig =
HttpClientUtils.createDefaultRequestConfig();
HttpClientBuilder builder = HttpClients.custom()
.setDefaultCredentialsProvider(credentialsProvider)
.setDefaultCookieStore(cookieStore)
.setDefaultRequestConfig(requestConfig);
return builder.build();
The CredentialsProvider
is a custom one which delegates to our own framework for prompting the user for that information.
public class AdaptedCredentialsProvider
implements CredentialsProvider
{
private final CredentialsProvider internal =
new BasicCredentialsProvider();
//I tried this too:
//new SystemDefaultCredentialsProvider();
private final CredentialsPrompt prompt;
public AdaptedCredentialsProvider(CredentialsPrompt prompt) {
this.prompt = prompt;
}
@Override
public void setCredentials(AuthScope authscope,
Credentials credentials) {
internal.setCredentials(authscope, credentials);
}
@Override
public Credentials getCredentials(@NotNull AuthScope authScope) {
System.err.println("Asked for credentials, scheme: " +
authScope.getScheme());
Credentials localCredentials =
internal.getCredentials(authScope);
if (localCredentials != null) {
return localCredentials;
}
if (authScope.getHost() != null) {
OurCredentials credentials = prompt.prompt(
authScope.getHost(),
authScope.getPort());
if (credentials != null) {
switch (authScope.getScheme()) {
case "NEGOTIATE":
case "NTLM":
return new NTCredentials(
credentials.getUsername(),
new String(credentials.getPassword()),
null, null);
default:
return new UsernamePasswordCredentials(
credentials.getUsername(),
new String(credentials.getPassword()));
}
}
}
return null;
}
@Override
public void clear() {
internal.clear();
}
}
A timeline of the requests and responses with the login requests interspersed is interesting to look at...
=== Initial request
=== Request
GET /api/noop HTTP/1.1
Host: 192.168.1.162:27443
Connection: Keep-Alive
Accept-Encoding: gzip,deflate
=== Response
HTTP/1.1 401 Unauthorized
Date: Mon, 27 Apr 2015 02:44:23 GMT
Set-Cookie: JSESSIONID=1lsrefm3yzmmz15n8md7efdc7f;Path=/;Secure
WWW-Authenticate: Negotiate
WWW-Authenticate: NTLM
WWW-Authenticate: Basic realm="Test"
Cache-Control: must-revalidate,no-cache,no-store
Content-Type: text/html; charset=ISO-8859-1
Content-Length: 289
=== Asked for credentials for host: 192.168.1.162 - scheme: NEGOTIATE
=== GUI checking whether the credentials work...
=== Request
GET /api/noop HTTP/1.1
Host: 192.168.1.162:27443
Connection: Keep-Alive
Accept-Encoding: gzip,deflate
=== Response
HTTP/1.1 401 Unauthorized
Date: Mon, 27 Apr 2015 02:44:30 GMT
Set-Cookie: JSESSIONID=ucsnvsvawp301injh6ijwbywe;Path=/;Secure
WWW-Authenticate: Negotiate
WWW-Authenticate: NTLM
WWW-Authenticate: Basic realm="Test"
Cache-Control: must-revalidate,no-cache,no-store
Content-Type: text/html; charset=ISO-8859-1
Content-Length: 289
=== Request
GET /api/noop HTTP/1.1
Host: 192.168.1.162:27443
Connection: Keep-Alive
Accept-Encoding: gzip,deflate
Authorization: Basic <REDACTED>
=== Response
HTTP/1.1 200 OK
Date: Mon, 27 Apr 2015 02:44:30 GMT
Set-Cookie: JSESSIONID=1la5wb3fje1fy1qxu14weqxaqm;Path=/;Secure
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Content-Length: 0
=== Asked for credentials for host: 192.168.1.162 - scheme: NTLM
=== GUI checking whether the credentials work...
=== Request
GET /api/noop HTTP/1.1
Host: 192.168.1.162:27443
Connection: Keep-Alive
Accept-Encoding: gzip,deflate
=== Response
HTTP/1.1 401 Unauthorized
Date: Mon, 27 Apr 2015 02:44:33 GMT
Set-Cookie: JSESSIONID=5pmtxxhinky91bzoj18yx6zkz;Path=/;Secure
WWW-Authenticate: Negotiate
WWW-Authenticate: NTLM
WWW-Authenticate: Basic realm="Test"
Cache-Control: must-revalidate,no-cache,no-store
Content-Type: text/html; charset=ISO-8859-1
Content-Length: 289
=== Request
GET /api/noop HTTP/1.1
Host: 192.168.1.162:27443
Connection: Keep-Alive
Accept-Encoding: gzip,deflate
Authorization: Basic <REDACTED>
=== Response
HTTP/1.1 200 OK
Date: Mon, 27 Apr 2015 02:44:33 GMT
Set-Cookie: JSESSIONID=16jhmw3fxuhsy1edjtpquiw564;Path=/;Secure
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Content-Length: 0
=== Asked for credentials for host: 192.168.1.162 - scheme: BASIC
=== GUI checking whether the credentials work...
=== Request
GET /api/noop HTTP/1.1
Host: 192.168.1.162:27443
Connection: Keep-Alive
Accept-Encoding: gzip,deflate
=== Response
HTTP/1.1 401 Unauthorized
Date: Mon, 27 Apr 2015 02:44:36 GMT
Set-Cookie: JSESSIONID=2yjw3xcoerq5wdtqu1etxur0;Path=/;Secure
WWW-Authenticate: Negotiate
WWW-Authenticate: NTLM
WWW-Authenticate: Basic realm="Test"
Cache-Control: must-revalidate,no-cache,no-store
Content-Type: text/html; charset=ISO-8859-1
Content-Length: 289
=== Request
GET /api/noop HTTP/1.1
Host: 192.168.1.162:27443
Connection: Keep-Alive
Accept-Encoding: gzip,deflate
Authorization: Basic <REDACTED>
=== Response
HTTP/1.1 200 OK
Date: Mon, 27 Apr 2015 02:44:36 GMT
Set-Cookie: JSESSIONID=cvf6mvdagknk1mb893u7ylozb;Path=/;Secure
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Content-Length: 0
=== Presumably here it has decided it doesn't support NEGOTIATE so
=== it's going with NTLM.
=== Request
GET /api/noop HTTP/1.1
Host: 192.168.1.162:27443
Connection: Keep-Alive
Accept-Encoding: gzip,deflate
Authorization: NTLM <REDACTED>
=== Response
HTTP/1.1 401 Unauthorized
Date: Mon, 27 Apr 2015 02:44:36 GMT
Set-Cookie: JSESSIONID=es21dv6pnktmqoxr9qs52n3e;Path=/;Secure
Expires: Thu, 01 Jan 1970 00:00:00 GMT
WWW-Authenticate: NTLM <REDACTED>
Transfer-Encoding: chunked
0
=== Request
GET /api/noop HTTP/1.1
Host: 192.168.1.162:27443
Connection: Keep-Alive
Accept-Encoding: gzip,deflate
Authorization: NTLM <REDACTED>
=== Response
HTTP/1.1 200 OK
Date: Mon, 27 Apr 2015 02:44:36 GMT
Set-Cookie: JSESSIONID=1nzdiyw90zun218drgkobi3y6g;Path=/;Secure
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Content-Length: 0
It seems like it isn't even using the credentials it's asking for. Eventually it somehow breaks out of this weird cycle and then tries to use them.
It eventually logs in, but how can I stop it asking for the same information three times? (I know I could roll this sort of thing into the CredentialsProvider
, but I didn't have to do that with the other API and it seemed like an awkward way to work around it for this one.)
In fact, how can I stop it asking for this information at all? Largely the point of using Negotiate in the first place is to avoid being prompted for a password, but this thing is always prompting me for a password and I don't think it should be.
Update: Half the problem is solved. I discovered that HttpClient didn't support single sign-on at all (bit misleading, IMO, to claim you support NTLM while not supporting the only purpose of using NTLM!) But version 4.4 has experimental support for it.
The experimental support appears to let me login but I still get prompted for a password for the Basic. So I'm back to the question of how to disable this behaviour of asking the credentials provider for every single auth scheme. Ideally, it should just ask for the first one and then fall back to the next one in the sequence.