jetty-9 - HTTP client: implemented digest authentication.
This commit is contained in:
parent
52accdf761
commit
f5d68f0caf
|
@ -18,6 +18,7 @@
|
|||
|
||||
package org.eclipse.jetty.client;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
@ -25,21 +26,32 @@ import java.util.regex.Matcher;
|
|||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jetty.client.api.Authentication;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
public class AuthenticationProtocolHandler extends Response.Listener.Adapter implements ProtocolHandler
|
||||
public class AuthenticationProtocolHandler implements ProtocolHandler
|
||||
{
|
||||
private static final Pattern WWW_AUTHENTICATE_PATTERN = Pattern.compile("([^\\s]+)\\s+realm=\"([^\"]+)\"(\\s*,\\s*)?(.*)", Pattern.CASE_INSENSITIVE);
|
||||
public static final Logger LOG = Log.getLogger(AuthenticationProtocolHandler.class);
|
||||
private static final Pattern WWW_AUTHENTICATE_PATTERN = Pattern.compile("([^\\s]+)\\s+realm=\"([^\"]+)\".*", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
private final ResponseNotifier notifier = new ResponseNotifier();
|
||||
private final HttpClient client;
|
||||
private final int maxContentLength;
|
||||
|
||||
public AuthenticationProtocolHandler(HttpClient client)
|
||||
{
|
||||
this(client, 4096);
|
||||
}
|
||||
|
||||
public AuthenticationProtocolHandler(HttpClient client, int maxContentLength)
|
||||
{
|
||||
this.client = client;
|
||||
this.maxContentLength = maxContentLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -51,107 +63,146 @@ public class AuthenticationProtocolHandler extends Response.Listener.Adapter imp
|
|||
@Override
|
||||
public Response.Listener getResponseListener()
|
||||
{
|
||||
return this;
|
||||
return new AuthenticationListener();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(Result result)
|
||||
private class AuthenticationListener extends Response.Listener.Adapter
|
||||
{
|
||||
if (!result.isFailed())
|
||||
private byte[] buffer = new byte[0];
|
||||
|
||||
@Override
|
||||
public void onContent(Response response, ByteBuffer content)
|
||||
{
|
||||
List<WWWAuthenticate> wwwAuthenticates = parseWWWAuthenticate(result.getResponse());
|
||||
if (buffer.length == maxContentLength)
|
||||
return;
|
||||
|
||||
long newLength = buffer.length + content.remaining();
|
||||
if (newLength > maxContentLength)
|
||||
newLength = maxContentLength;
|
||||
|
||||
byte[] newBuffer = new byte[(int)newLength];
|
||||
System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
|
||||
content.get(newBuffer, buffer.length, content.remaining());
|
||||
buffer = newBuffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(Result result)
|
||||
{
|
||||
Request request = result.getRequest();
|
||||
ContentResponse response = new HttpContentResponse(result.getResponse(), buffer);
|
||||
if (result.isFailed())
|
||||
{
|
||||
Throwable failure = result.getFailure();
|
||||
LOG.debug("Authentication challenge failed {}", failure);
|
||||
forwardFailure(request, response, failure);
|
||||
return;
|
||||
}
|
||||
|
||||
List<WWWAuthenticate> wwwAuthenticates = parseWWWAuthenticate(response);
|
||||
if (wwwAuthenticates.isEmpty())
|
||||
{
|
||||
// TODO
|
||||
LOG.debug("Authentication challenge without WWW-Authenticate header");
|
||||
forwardFailure(request, response, new HttpResponseException("HTTP protocol violation: 401 without WWW-Authenticate header", response));
|
||||
return;
|
||||
}
|
||||
else
|
||||
|
||||
final String uri = request.uri();
|
||||
Authentication authentication = null;
|
||||
WWWAuthenticate wwwAuthenticate = null;
|
||||
for (WWWAuthenticate wwwAuthn : wwwAuthenticates)
|
||||
{
|
||||
Request request = result.getRequest();
|
||||
final String uri = request.uri();
|
||||
Authentication authentication = null;
|
||||
String params = null;
|
||||
for (WWWAuthenticate wwwAuthenticate : wwwAuthenticates)
|
||||
{
|
||||
authentication = client.getAuthenticationStore().findAuthentication(wwwAuthenticate.type, uri, wwwAuthenticate.realm);
|
||||
if (authentication != null)
|
||||
{
|
||||
params = wwwAuthenticate.params;
|
||||
break;
|
||||
}
|
||||
}
|
||||
authentication = client.getAuthenticationStore().findAuthentication(wwwAuthn.type, uri, wwwAuthn.realm);
|
||||
if (authentication != null)
|
||||
{
|
||||
final Authentication authn = authentication;
|
||||
authn.authenticate(request, params, client.getConversation(request));
|
||||
request.send(new Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onComplete(Result result)
|
||||
{
|
||||
if (!result.isFailed())
|
||||
{
|
||||
Authentication.Result authnResult = new Authentication.Result(uri, authn);
|
||||
client.getAuthenticationStore().addAuthenticationResult(authnResult);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
noAuthentication(request, result.getResponse());
|
||||
wwwAuthenticate = wwwAuthn;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<WWWAuthenticate> parseWWWAuthenticate(Response response)
|
||||
{
|
||||
// TODO: these should be ordered by strength
|
||||
List<WWWAuthenticate> result = new ArrayList<>();
|
||||
List<String> values = Collections.list(response.headers().getValues(HttpHeader.WWW_AUTHENTICATE.asString()));
|
||||
for (String value : values)
|
||||
{
|
||||
Matcher matcher = WWW_AUTHENTICATE_PATTERN.matcher(value);
|
||||
if (matcher.matches())
|
||||
if (authentication == null)
|
||||
{
|
||||
String type = matcher.group(1);
|
||||
String realm = matcher.group(2);
|
||||
String params = matcher.group(4);
|
||||
WWWAuthenticate wwwAuthenticate = new WWWAuthenticate(type, realm, params);
|
||||
result.add(wwwAuthenticate);
|
||||
LOG.debug("No authentication available for {}", request);
|
||||
forwardSuccess(request, response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void noAuthentication(Request request, Response response)
|
||||
{
|
||||
HttpConversation conversation = client.getConversation(request);
|
||||
Response.Listener listener = conversation.exchanges().peekFirst().listener();
|
||||
notifier.notifyBegin(listener, response);
|
||||
notifier.notifyHeaders(listener, response);
|
||||
notifier.notifySuccess(listener, response);
|
||||
// TODO: this call here is horrid, but needed... but here it is too late for the exchange
|
||||
// TODO: to figure out that the conversation is finished, so we need to manually do it here, no matter what.
|
||||
// TODO: However, we also need to make sure that requests are not resent with the same ID
|
||||
// TODO: because here the connection has already been returned to the pool, so the "new" request may see
|
||||
// TODO: the same conversation but it's not really the case.
|
||||
// TODO: perhaps the factory for requests should be the conversation ?
|
||||
conversation.complete();
|
||||
notifier.notifyComplete(listener, new Result(request, response));
|
||||
HttpConversation conversation = client.getConversation(request);
|
||||
final Authentication.Result authnResult = authentication.authenticate(request, response, wwwAuthenticate.value, conversation);
|
||||
LOG.debug("Authentication result {}", authnResult);
|
||||
if (authnResult == null)
|
||||
{
|
||||
forwardSuccess(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
authnResult.apply(request);
|
||||
request.send(new Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onSuccess(Response response)
|
||||
{
|
||||
client.getAuthenticationStore().addAuthenticationResult(authnResult);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void forwardFailure(Request request, Response response, Throwable failure)
|
||||
{
|
||||
HttpConversation conversation = client.getConversation(request);
|
||||
Response.Listener listener = conversation.exchanges().peekFirst().listener();
|
||||
notifier.notifyBegin(listener, response);
|
||||
notifier.notifyHeaders(listener, response);
|
||||
if (response instanceof ContentResponse)
|
||||
notifier.notifyContent(listener, response, ByteBuffer.wrap(((ContentResponse)response).content()));
|
||||
notifier.notifyFailure(listener, response, failure);
|
||||
conversation.complete();
|
||||
notifier.notifyComplete(listener, new Result(request, response, failure));
|
||||
}
|
||||
|
||||
private void forwardSuccess(Request request, Response response)
|
||||
{
|
||||
HttpConversation conversation = client.getConversation(request);
|
||||
Response.Listener listener = conversation.exchanges().peekFirst().listener();
|
||||
notifier.notifyBegin(listener, response);
|
||||
notifier.notifyHeaders(listener, response);
|
||||
if (response instanceof ContentResponse)
|
||||
notifier.notifyContent(listener, response, ByteBuffer.wrap(((ContentResponse)response).content()));
|
||||
notifier.notifySuccess(listener, response);
|
||||
conversation.complete();
|
||||
notifier.notifyComplete(listener, new Result(request, response));
|
||||
}
|
||||
|
||||
private List<WWWAuthenticate> parseWWWAuthenticate(Response response)
|
||||
{
|
||||
// TODO: these should be ordered by strength
|
||||
List<WWWAuthenticate> result = new ArrayList<>();
|
||||
List<String> values = Collections.list(response.headers().getValues(HttpHeader.WWW_AUTHENTICATE.asString()));
|
||||
for (String value : values)
|
||||
{
|
||||
Matcher matcher = WWW_AUTHENTICATE_PATTERN.matcher(value);
|
||||
if (matcher.matches())
|
||||
{
|
||||
String type = matcher.group(1);
|
||||
String realm = matcher.group(2);
|
||||
WWWAuthenticate wwwAuthenticate = new WWWAuthenticate(value, type, realm);
|
||||
result.add(wwwAuthenticate);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private class WWWAuthenticate
|
||||
{
|
||||
private final String value;
|
||||
private final String type;
|
||||
private final String realm;
|
||||
private final String params;
|
||||
|
||||
public WWWAuthenticate(String type, String realm, String params)
|
||||
public WWWAuthenticate(String value, String type, String realm)
|
||||
{
|
||||
this.value = value;
|
||||
this.type = type;
|
||||
this.realm = realm;
|
||||
this.params = params;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ import org.eclipse.jetty.client.api.AuthenticationStore;
|
|||
public class HttpAuthenticationStore implements AuthenticationStore
|
||||
{
|
||||
private final List<Authentication> authentications = new CopyOnWriteArrayList<>();
|
||||
private final Map<String, Authentication> results = new ConcurrentHashMap<>();
|
||||
private final Map<String, Authentication.Result> results = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void addAuthentication(Authentication authentication)
|
||||
|
@ -57,7 +57,7 @@ public class HttpAuthenticationStore implements AuthenticationStore
|
|||
@Override
|
||||
public void addAuthenticationResult(Authentication.Result result)
|
||||
{
|
||||
results.put(result.getURI(), result.getAuthentication());
|
||||
results.put(result.getURI(), result);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -67,10 +67,10 @@ public class HttpAuthenticationStore implements AuthenticationStore
|
|||
}
|
||||
|
||||
@Override
|
||||
public Authentication findAuthenticationResult(String uri)
|
||||
public Authentication.Result findAuthenticationResult(String uri)
|
||||
{
|
||||
// TODO: I should match the longest URI
|
||||
for (Map.Entry<String, Authentication> entry : results.entrySet())
|
||||
for (Map.Entry<String, Authentication.Result> entry : results.entrySet())
|
||||
{
|
||||
if (uri.startsWith(entry.getKey()))
|
||||
return entry.getValue();
|
||||
|
|
|
@ -161,9 +161,9 @@ public class HttpConnection extends AbstractConnection implements Connection
|
|||
request.header(HttpHeader.COOKIE.asString(), cookieString.toString());
|
||||
|
||||
// Authorization
|
||||
Authentication authentication = client.getAuthenticationStore().findAuthenticationResult(request.uri());
|
||||
if (authentication != null)
|
||||
authentication.authenticate(request);
|
||||
Authentication.Result authnResult = client.getAuthenticationStore().findAuthenticationResult(request.uri());
|
||||
if (authnResult != null)
|
||||
authnResult.apply(request);
|
||||
|
||||
// TODO: decoder headers
|
||||
|
||||
|
|
|
@ -24,27 +24,12 @@ public interface Authentication
|
|||
{
|
||||
boolean matches(String type, String uri, String realm);
|
||||
|
||||
boolean authenticate(Request request, String params, Attributes context);
|
||||
Result authenticate(Request request, ContentResponse response, String wwwAuthenticate, Attributes context);
|
||||
|
||||
public static class Result
|
||||
public static interface Result
|
||||
{
|
||||
private final String uri;
|
||||
private final Authentication authentication;
|
||||
String getURI();
|
||||
|
||||
public Result(String uri, Authentication authentication)
|
||||
{
|
||||
this.uri = uri;
|
||||
this.authentication = authentication;
|
||||
}
|
||||
|
||||
public String getURI()
|
||||
{
|
||||
return uri;
|
||||
}
|
||||
|
||||
public Authentication getAuthentication()
|
||||
{
|
||||
return authentication;
|
||||
}
|
||||
void apply(Request request);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,5 +30,5 @@ public interface AuthenticationStore
|
|||
|
||||
public void removeAuthenticationResults();
|
||||
|
||||
public Authentication findAuthenticationResult(String uri);
|
||||
public Authentication.Result findAuthenticationResult(String uri);
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import java.io.UnsupportedEncodingException;
|
|||
import java.nio.charset.UnsupportedCharsetException;
|
||||
|
||||
import org.eclipse.jetty.client.api.Authentication;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.util.Attributes;
|
||||
|
@ -56,17 +57,47 @@ public class BasicAuthentication implements Authentication
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean authenticate(Request request, String params, Attributes context)
|
||||
public Result authenticate(Request request, ContentResponse response, String wwwAuthenticate, Attributes context)
|
||||
{
|
||||
String encoding = StringUtil.__ISO_8859_1;
|
||||
try
|
||||
{
|
||||
String value = "Basic " + B64Code.encode(user + ":" + password, encoding);
|
||||
request.header(HttpHeader.AUTHORIZATION.asString(), value);
|
||||
return new BasicResult(request.uri(), value);
|
||||
}
|
||||
catch (UnsupportedEncodingException x)
|
||||
{
|
||||
throw new UnsupportedCharsetException(encoding);
|
||||
}
|
||||
}
|
||||
|
||||
private static class BasicResult implements Result
|
||||
{
|
||||
private final String uri;
|
||||
private final String value;
|
||||
|
||||
public BasicResult(String uri, String value)
|
||||
{
|
||||
this.uri = uri;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getURI()
|
||||
{
|
||||
return uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(Request request)
|
||||
{
|
||||
request.header(HttpHeader.AUTHORIZATION.asString(), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("Basic authentication result for %s", uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,10 +26,13 @@ import java.util.Arrays;
|
|||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jetty.client.api.Authentication;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.util.Attributes;
|
||||
|
@ -65,19 +68,23 @@ public class DigestAuthentication implements Authentication
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean authenticate(Request request, String paramString, Attributes context)
|
||||
public Result authenticate(Request request, ContentResponse response, String wwwAuthenticate, Attributes context)
|
||||
{
|
||||
Map<String, String> params = parseParams(paramString);
|
||||
// Avoid case sensitivity problems on the 'D' character
|
||||
String type = "igest";
|
||||
wwwAuthenticate = wwwAuthenticate.substring(wwwAuthenticate.indexOf(type) + type.length());
|
||||
|
||||
Map<String, String> params = parseParams(wwwAuthenticate);
|
||||
String nonce = params.get("nonce");
|
||||
if (nonce == null || nonce.length() == 0)
|
||||
return false;
|
||||
return null;
|
||||
String opaque = params.get("opaque");
|
||||
String algorithm = params.get("algorithm");
|
||||
if (algorithm == null)
|
||||
algorithm = "MD5";
|
||||
MessageDigest digester = getMessageDigest(algorithm);
|
||||
if (digester == null)
|
||||
return false;
|
||||
return null;
|
||||
String serverQOP = params.get("qop");
|
||||
String clientQOP = null;
|
||||
if (serverQOP != null)
|
||||
|
@ -89,32 +96,24 @@ public class DigestAuthentication implements Authentication
|
|||
clientQOP = "auth-int";
|
||||
}
|
||||
|
||||
String hash = compute(digester, clientQOP, content, nonce);
|
||||
|
||||
StringBuilder value = new StringBuilder("Digest");
|
||||
value.append(" username=\"").append(user).append("\"");
|
||||
value.append(", realm=\"").append(realm).append("\"");
|
||||
value.append(", nonce=\"").append(nonce).append("\"");
|
||||
if (opaque != null)
|
||||
value.append(", opaque=\"").append(opaque).append("\"");
|
||||
value.append(", algorithm=\"").append(algorithm).append("\"");
|
||||
value.append(", uri=\"").append(request.uri()).append("\"");
|
||||
if (clientQOP != null)
|
||||
value.append(", qop=\"").append(clientQOP).append("\"");
|
||||
value.append(", response=\"").append(hash).append("\"");
|
||||
|
||||
request.header(HttpHeader.AUTHORIZATION.asString(), value.toString());
|
||||
return new DigestResult(request.uri(), response.content(), realm, user, password, algorithm, nonce, clientQOP, opaque);
|
||||
}
|
||||
|
||||
private Map<String, String> parseParams(String paramString)
|
||||
private Map<String, String> parseParams(String wwwAuthenticate)
|
||||
{
|
||||
Map<String, String> result = new HashMap<>();
|
||||
List<String> parts = splitParams(paramString);
|
||||
List<String> parts = splitParams(wwwAuthenticate);
|
||||
for (String part : parts)
|
||||
{
|
||||
Matcher matcher = PARAM_PATTERN.matcher(part);
|
||||
if (matcher.matches())
|
||||
result.put(matcher.group(1).trim().toLowerCase(), matcher.group(2).trim());
|
||||
{
|
||||
String name = matcher.group(1).trim().toLowerCase();
|
||||
String value = matcher.group(2).trim();
|
||||
if (value.startsWith("\"") && value.endsWith("\""))
|
||||
value = value.substring(1, value.length() - 1);
|
||||
result.put(name, value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -129,6 +128,9 @@ public class DigestAuthentication implements Authentication
|
|||
char ch = paramString.charAt(i);
|
||||
switch (ch)
|
||||
{
|
||||
case '\\':
|
||||
++i;
|
||||
break;
|
||||
case '"':
|
||||
++quotes;
|
||||
break;
|
||||
|
@ -159,23 +161,108 @@ public class DigestAuthentication implements Authentication
|
|||
}
|
||||
}
|
||||
|
||||
private String compute(Request request, MessageDigest digester, String qop, byte[] content, String serverNonce)
|
||||
private class DigestResult implements Result
|
||||
{
|
||||
Charset charset = Charset.forName("ISO-8859-1");
|
||||
String A1 = user + ":" + realm + ":" + password;
|
||||
String hashA1 = TypeUtil.toHexString(digester.digest(A1.getBytes(charset)));
|
||||
private final AtomicInteger nonceCount = new AtomicInteger();
|
||||
private final String uri;
|
||||
private final byte[] content;
|
||||
private final String realm;
|
||||
private final String user;
|
||||
private final String password;
|
||||
private final String algorithm;
|
||||
private final String nonce;
|
||||
private final String qop;
|
||||
private final String opaque;
|
||||
|
||||
String A2 = request.method().asString() + ":" + request.uri();
|
||||
if ("auth-int".equals(qop))
|
||||
A2 += ":" + TypeUtil.toHexString(digester.digest(content));
|
||||
String hashA2 = TypeUtil.toHexString(digester.digest(A2.getBytes(charset)));
|
||||
public DigestResult(String uri, byte[] content, String realm, String user, String password, String algorithm, String nonce, String qop, String opaque)
|
||||
{
|
||||
this.uri = uri;
|
||||
this.content = content;
|
||||
this.realm = realm;
|
||||
this.user = user;
|
||||
this.password = password;
|
||||
this.algorithm = algorithm;
|
||||
this.nonce = nonce;
|
||||
this.qop = qop;
|
||||
this.opaque = opaque;
|
||||
}
|
||||
|
||||
String A3;
|
||||
if (qop != null)
|
||||
A3 = hashA1 + ":" + serverNonce + ":" + nonceCount + ":" + clientNonce + ":" + qop + ":" + hashA2;
|
||||
else
|
||||
A3 = hashA1 + ":" + serverNonce + ":" + hashA2;
|
||||
@Override
|
||||
public String getURI()
|
||||
{
|
||||
return uri;
|
||||
}
|
||||
|
||||
return TypeUtil.toHexString(digester.digest(A3.getBytes(charset)));
|
||||
@Override
|
||||
public void apply(Request request)
|
||||
{
|
||||
MessageDigest digester = getMessageDigest(algorithm);
|
||||
if (digester == null)
|
||||
return;
|
||||
|
||||
Charset charset = Charset.forName("ISO-8859-1");
|
||||
String A1 = user + ":" + realm + ":" + password;
|
||||
String hashA1 = toHexString(digester.digest(A1.getBytes(charset)));
|
||||
|
||||
String A2 = request.method().asString() + ":" + request.uri();
|
||||
if ("auth-int".equals(qop))
|
||||
A2 += ":" + toHexString(digester.digest(content));
|
||||
String hashA2 = toHexString(digester.digest(A2.getBytes(charset)));
|
||||
|
||||
String nonceCount;
|
||||
String clientNonce;
|
||||
String A3;
|
||||
if (qop != null)
|
||||
{
|
||||
nonceCount = nextNonceCount();
|
||||
clientNonce = newClientNonce();
|
||||
A3 = hashA1 + ":" + nonce + ":" + nonceCount + ":" + clientNonce + ":" + qop + ":" + hashA2;
|
||||
}
|
||||
else
|
||||
{
|
||||
nonceCount = null;
|
||||
clientNonce = null;
|
||||
A3 = hashA1 + ":" + nonce + ":" + hashA2;
|
||||
}
|
||||
String hashA3 = toHexString(digester.digest(A3.getBytes(charset)));
|
||||
|
||||
StringBuilder value = new StringBuilder("Digest");
|
||||
value.append(" username=\"").append(user).append("\"");
|
||||
value.append(", realm=\"").append(realm).append("\"");
|
||||
value.append(", nonce=\"").append(nonce).append("\"");
|
||||
if (opaque != null)
|
||||
value.append(", opaque=\"").append(opaque).append("\"");
|
||||
value.append(", algorithm=\"").append(algorithm).append("\"");
|
||||
value.append(", uri=\"").append(request.uri()).append("\"");
|
||||
if (qop != null)
|
||||
{
|
||||
value.append(", qop=\"").append(qop).append("\"");
|
||||
value.append(", nc=\"").append(nonceCount).append("\"");
|
||||
value.append(", cnonce=\"").append(clientNonce).append("\"");
|
||||
}
|
||||
value.append(", response=\"").append(hashA3).append("\"");
|
||||
|
||||
request.header(HttpHeader.AUTHORIZATION.asString(), value.toString());
|
||||
}
|
||||
|
||||
private String nextNonceCount()
|
||||
{
|
||||
String padding = "00000000";
|
||||
String next = Integer.toHexString(nonceCount.incrementAndGet()).toLowerCase();
|
||||
return padding.substring(0, padding.length() - next.length()) + next;
|
||||
}
|
||||
|
||||
private String newClientNonce()
|
||||
{
|
||||
Random random = new Random();
|
||||
byte[] bytes = new byte[8];
|
||||
random.nextBytes(bytes);
|
||||
return toHexString(bytes);
|
||||
}
|
||||
|
||||
private String toHexString(byte[] bytes)
|
||||
{
|
||||
return TypeUtil.toHexString(bytes).toLowerCase();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -93,7 +93,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
|
|||
public void test_DigestAuthentication() throws Exception
|
||||
{
|
||||
startDigest(new EmptyHandler());
|
||||
test_Authentication(new DigestAuthentication("http://localhost:" + connector.getLocalPort(), realm));
|
||||
test_Authentication(new DigestAuthentication("http://localhost:" + connector.getLocalPort(), realm, "digest", "digest"));
|
||||
}
|
||||
|
||||
private void test_Authentication(Authentication authentication) throws Exception
|
||||
|
@ -113,7 +113,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
|
|||
|
||||
// Request without Authentication causes a 401
|
||||
Request request = client.newRequest("localhost", connector.getLocalPort()).path("/test");
|
||||
ContentResponse response = request.send().get(5, TimeUnit.SECONDS);
|
||||
ContentResponse response = request.send().get(555, TimeUnit.SECONDS);
|
||||
Assert.assertNotNull(response);
|
||||
Assert.assertEquals(401, response.status());
|
||||
Assert.assertEquals(1, requests.get());
|
||||
|
@ -133,7 +133,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
|
|||
client.getRequestListeners().add(requestListener);
|
||||
|
||||
// Request with authentication causes a 401 (no previous successful authentication) + 200
|
||||
response = request.send().get(5, TimeUnit.SECONDS);
|
||||
response = request.send().get(555, TimeUnit.SECONDS);
|
||||
Assert.assertNotNull(response);
|
||||
Assert.assertEquals(200, response.status());
|
||||
Assert.assertEquals(2, requests.get());
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
# Format is <user>:<password>,<roles>
|
||||
basic:basic
|
||||
digest:digest
|
||||
|
|
Loading…
Reference in New Issue