I'm trying to implement digest auth with an HTTP client, but this does not work at the moment.
Can someone check if this code is correct? For testing purpose I use http://httpbin.org/, but all I get is HTTP/1.1 401 Unauthorized.
Here is the example code:
private static void doDigestAuth() throws ClientProtocolException,
IOException,
AuthenticationException,
MalformedChallengeException
{
HttpHost target = new HttpHost("httpbin.org", 80, "http");
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(new AuthScope(target.getHostName(), target.getPort()), new UsernamePasswordCredentials(
"user", "passwd"));
CloseableHttpClient httpclient = HttpClients.custom().setDefaultCredentialsProvider(credsProvider).build();
try {
HttpGet httpget = new HttpGet("http://httpbin.org/digest-auth/auth/user/passwd");
// Create AuthCache instance
AuthCache authCache = new BasicAuthCache();
// Generate DIGEST scheme object, initialize it and add it to the local
// auth cache
DigestScheme digestAuth = new DigestScheme();
// Suppose we already know the realm name
digestAuth.overrideParamter("realm", "me@kennethreitz.com");
// // Suppose we already know the expected nonce value
// digestAuth.overrideParamter("nonce", Long.toString(new SecureRandom().nextLong(), 36));
// qop-value = "auth" | "auth-int" | token
digestAuth.overrideParamter("qop", "auth");
authCache.put(target, digestAuth);
// Add AuthCache to the execution context
HttpClientContext context = HttpClientContext.create();
context.setCredentialsProvider(credsProvider);
// context.setAuthSchemeRegistry(authRegistry);
context.setAuthCache(authCache);
System.out.println("Executing request " + httpget.getRequestLine() + " to target " + target);
for (int i = 0; i < 3; i++) {
CloseableHttpResponse response = httpclient.execute(httpget, context);
try {
System.out.println("----------------------------------------");
System.out.println(response.getStatusLine());
HttpEntity entity = response.getEntity();
InputStream instream = entity.getContent();
// Header contentCncoding = entity .getContentEncoding();
String contentString = IOUtils.toString(instream, null);
System.out.println("ContentString:" + contentString);
AuthState proxyAuthState = context.getProxyAuthState();
System.out.println("Proxy auth state: " + proxyAuthState.getState());
System.out.println("Proxy auth scheme: " + proxyAuthState.getAuthScheme());
System.out.println("Proxy auth credentials: " + proxyAuthState.getCredentials());
AuthState targetAuthState = context.getTargetAuthState();
System.out.println("Target auth state: " + targetAuthState.getState());
System.out.println("Target auth scheme: " + targetAuthState.getAuthScheme());
System.out.println("Target auth credentials: " + targetAuthState.getCredentials());
EntityUtils.consume(response.getEntity());
}
finally {
response.close();
}
}
}
finally {
httpclient.close();
}
}
解决方案
It was a cookie problem as @heaphach suggested. The wire-log (shown with log category org.apache.http.wire set to debug) shows:
<< "Set-Cookie: fake=fake_value[\r][\n]"
but the HttpClient never picks this up
and does not use it in the second GET request containing the full "Authorization" header with the digest-response.
As a consequence, the server just ignores the digest response.
After I updated the example code (also known as the Preemptive DIGEST authentication example)
with the code shown below (copied from the HTTP state management tutorial), the server responded "200 OK".
CookieStore cookieStore = new BasicCookieStore();
BasicClientCookie cookie = new BasicClientCookie("fake", "fake_value");
cookie.setDomain("httpbin.org");
cookie.setPath("/");
cookieStore.addCookie(cookie);
CloseableHttpClient httpclient = HttpClients.custom()
.setDefaultCookieStore(cookieStore)
.setDefaultCredentialsProvider(credsProvider)
.build();
I also came across a gist containing some code to calculate a "nonce"
so you can use
digestAuth.overrideParamter("nonce", calculateNonce());
and org.apache.http.impl.auth.HttpAuthenticator no longer shows the error message "missing nonce in challenge".
public static synchronized String calculateNonce() {
Date d = new Date();
SimpleDateFormat f = new SimpleDateFormat("yyyy:MM:dd:hh:mm:ss");
String fmtDate = f.format(d);
Random rand = new Random(100000);
Integer randomInt = rand.nextInt();
return org.apache.commons.codec.digest.DigestUtils.md5Hex(fmtDate + randomInt.toString());
}