Merge branch 'jetty-9' of ssh://git.eclipse.org/gitroot/jetty/org.eclipse.jetty.project into jetty-9

This commit is contained in:
Greg Wilkins 2012-09-13 17:06:46 +10:00
commit e9d7cc3cab
12 changed files with 546 additions and 182 deletions

View File

@ -1,3 +1,21 @@
//
// ========================================================================
// Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.embedded;
import org.eclipse.jetty.xml.XmlConfiguration;
@ -14,6 +32,5 @@ public class TestXml
"../jetty-spdy/spdy-jetty-http-webapp/src/main/config/etc/jetty-spdy.xml"
}
);
}
}

View File

@ -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,102 +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;
for (WWWAuthenticate wwwAuthenticate : wwwAuthenticates)
{
authentication = client.getAuthenticationStore().findAuthentication(wwwAuthenticate.type, uri, wwwAuthenticate.realm);
if (authentication != null)
break;
}
authentication = client.getAuthenticationStore().findAuthentication(wwwAuthn.type, uri, wwwAuthn.realm);
if (authentication != null)
{
final Authentication authn = authentication;
authn.authenticate(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)
{
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;
}
}
}

View File

@ -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();

View File

@ -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

View File

@ -142,12 +142,17 @@ public class HttpSender
header = BufferUtil.EMPTY_BUFFER;
if (chunk == null)
chunk = BufferUtil.EMPTY_BUFFER;
LOG.debug("Writing {} {} {}", header, chunk, content);
endPoint.write(null, callback, header, chunk, content);
if (callback.pending())
{
LOG.debug("Write incomplete {} {} {}", header, chunk, content);
return;
}
if (callback.completed())
{
LOG.debug("Write complete {} {} {}", header, chunk, content);
if (!committed)
committed(request);
@ -234,7 +239,7 @@ public class HttpSender
// Notify after
HttpExchange exchange = connection.getExchange();
Request request = exchange.request();
LOG.debug("Failed {}", request);
LOG.debug("Failed {} {}", request, failure);
boolean exchangeCompleted = exchange.requestComplete(false);
if (!exchangeCompleted && !committed)

View File

@ -18,31 +18,18 @@
package org.eclipse.jetty.client.api;
import org.eclipse.jetty.util.Attributes;
public interface Authentication
{
boolean matches(String type, String uri, String realm);
void authenticate(Request request);
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);
}
}

View File

@ -30,5 +30,5 @@ public interface AuthenticationStore
public void removeAuthenticationResults();
public Authentication findAuthenticationResult(String uri);
public Authentication.Result findAuthenticationResult(String uri);
}

View File

@ -22,8 +22,10 @@ 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;
import org.eclipse.jetty.util.B64Code;
import org.eclipse.jetty.util.StringUtil;
@ -55,17 +57,47 @@ public class BasicAuthentication implements Authentication
}
@Override
public void authenticate(Request request)
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);
}
}
}

View File

@ -0,0 +1,268 @@
//
// ========================================================================
// Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.client.util;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
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;
import org.eclipse.jetty.util.TypeUtil;
public class DigestAuthentication implements Authentication
{
private static final Pattern PARAM_PATTERN = Pattern.compile("([^=]+)=(.*)");
private final String uri;
private final String realm;
private final String user;
private final String password;
public DigestAuthentication(String uri, String realm, String user, String password)
{
this.uri = uri;
this.realm = realm;
this.user = user;
this.password = password;
}
@Override
public boolean matches(String type, String uri, String realm)
{
if (!"digest".equalsIgnoreCase(type))
return false;
if (!uri.startsWith(this.uri))
return false;
return this.realm.equals(realm);
}
@Override
public Result authenticate(Request request, ContentResponse response, String wwwAuthenticate, Attributes context)
{
// 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 null;
String opaque = params.get("opaque");
String algorithm = params.get("algorithm");
if (algorithm == null)
algorithm = "MD5";
MessageDigest digester = getMessageDigest(algorithm);
if (digester == null)
return null;
String serverQOP = params.get("qop");
String clientQOP = null;
if (serverQOP != null)
{
List<String> serverQOPValues = Arrays.asList(serverQOP.split(","));
if (serverQOPValues.contains("auth"))
clientQOP = "auth";
else if (serverQOPValues.contains("auth-int"))
clientQOP = "auth-int";
}
return new DigestResult(request.uri(), response.content(), realm, user, password, algorithm, nonce, clientQOP, opaque);
}
private Map<String, String> parseParams(String wwwAuthenticate)
{
Map<String, String> result = new HashMap<>();
List<String> parts = splitParams(wwwAuthenticate);
for (String part : parts)
{
Matcher matcher = PARAM_PATTERN.matcher(part);
if (matcher.matches())
{
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;
}
private List<String> splitParams(String paramString)
{
List<String> result = new ArrayList<>();
int start = 0;
for (int i = 0; i < paramString.length(); ++i)
{
int quotes = 0;
char ch = paramString.charAt(i);
switch (ch)
{
case '\\':
++i;
break;
case '"':
++quotes;
break;
case ',':
if (quotes % 2 == 0)
{
result.add(paramString.substring(start, i).trim());
start = i + 1;
}
break;
default:
break;
}
}
result.add(paramString.substring(start, paramString.length()).trim());
return result;
}
private MessageDigest getMessageDigest(String algorithm)
{
try
{
return MessageDigest.getInstance(algorithm);
}
catch (NoSuchAlgorithmException x)
{
return null;
}
}
private class DigestResult implements Result
{
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;
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;
}
@Override
public String getURI()
{
return uri;
}
@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();
}
}
}

View File

@ -49,7 +49,8 @@ public class AbstractHttpClientServerTest
public void start(Handler handler) throws Exception
{
server = new Server();
if (server == null)
server = new Server();
connector = new SelectChannelConnector(server);
server.addConnector(connector);
server.setHandler(handler);
@ -62,11 +63,12 @@ public class AbstractHttpClientServerTest
}
@After
public void destroy() throws Exception
public void dispose() throws Exception
{
if (client != null)
client.stop();
if (server != null)
server.stop();
server = null;
}
}

View File

@ -18,33 +18,87 @@
package org.eclipse.jetty.client;
import java.io.IOException;
import java.io.File;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.api.Authentication;
import org.eclipse.jetty.client.api.AuthenticationStore;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.util.BasicAuthentication;
import org.eclipse.jetty.client.util.DigestAuthentication;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.B64Code;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.security.Authenticator;
import org.eclipse.jetty.security.ConstraintMapping;
import org.eclipse.jetty.security.ConstraintSecurityHandler;
import org.eclipse.jetty.security.HashLoginService;
import org.eclipse.jetty.security.LoginService;
import org.eclipse.jetty.security.authentication.BasicAuthenticator;
import org.eclipse.jetty.security.authentication.DigestAuthenticator;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.security.Constraint;
import org.junit.Assert;
import org.junit.Test;
public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
{
@Test
public void test_BasicAuthentication_WithChallenge() throws Exception
{
start(new BasicAuthenticationHandler());
private String realm = "TestRealm";
public void startBasic(Handler handler) throws Exception
{
start(new BasicAuthenticator(), handler);
}
public void startDigest(Handler handler) throws Exception
{
start(new DigestAuthenticator(), handler);
}
private void start(Authenticator authenticator, Handler handler) throws Exception
{
server = new Server();
File realmFile = MavenTestingUtils.getTestResourceFile("realm.properties");
LoginService loginService = new HashLoginService(realm, realmFile.getAbsolutePath());
server.addBean(loginService);
ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
Constraint constraint = new Constraint();
constraint.setAuthenticate(true);
constraint.setRoles(new String[]{"*"});
ConstraintMapping mapping = new ConstraintMapping();
mapping.setPathSpec("/*");
mapping.setConstraint(constraint);
securityHandler.addConstraintMapping(mapping);
securityHandler.setAuthenticator(authenticator);
securityHandler.setLoginService(loginService);
securityHandler.setStrict(false);
securityHandler.setHandler(handler);
start(securityHandler);
}
@Test
public void test_BasicAuthentication() throws Exception
{
startBasic(new EmptyHandler());
test_Authentication(new BasicAuthentication("http://localhost:" + connector.getLocalPort(), realm, "basic", "basic"));
}
@Test
public void test_DigestAuthentication() throws Exception
{
startDigest(new EmptyHandler());
test_Authentication(new DigestAuthentication("http://localhost:" + connector.getLocalPort(), realm, "digest", "digest"));
}
private void test_Authentication(Authentication authentication) throws Exception
{
AuthenticationStore authenticationStore = client.getAuthenticationStore();
String realm = "test";
final AtomicInteger requests = new AtomicInteger();
Request.Listener.Adapter requestListener = new Request.Listener.Adapter()
@ -58,20 +112,15 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
client.getRequestListeners().add(requestListener);
// Request without Authentication causes a 401
Request request = client.newRequest("localhost", connector.getLocalPort())
.path("/test")
.param("type", "Basic")
.param("realm", realm);
ContentResponse response = request.send().get(5, TimeUnit.SECONDS);
Request request = client.newRequest("localhost", connector.getLocalPort()).path("/test");
ContentResponse response = request.send().get(555, TimeUnit.SECONDS);
Assert.assertNotNull(response);
Assert.assertEquals(401, response.status());
Assert.assertEquals(1, requests.get());
client.getRequestListeners().remove(requestListener);
requests.set(0);
String user = "jetty";
String password = "rocks";
authenticationStore.addAuthentication(new BasicAuthentication("http://localhost:" + connector.getLocalPort(), realm, user, password));
authenticationStore.addAuthentication(authentication);
requestListener = new Request.Listener.Adapter()
{
@ -84,8 +133,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
client.getRequestListeners().add(requestListener);
// Request with authentication causes a 401 (no previous successful authentication) + 200
request.param("user", user).param("password", password);
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());
@ -112,58 +160,4 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
client.getRequestListeners().remove(requestListener);
requests.set(0);
}
private class BasicAuthenticationHandler extends AbstractHandler
{
@Override
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
try
{
String type = request.getParameter("type");
String authorization = request.getHeader(HttpHeader.AUTHORIZATION.asString());
if (authorization == null)
{
String realm = request.getParameter("realm");
response.setStatus(401);
switch (type)
{
case "Basic":
{
response.setHeader("WWW-Authenticate", "Basic realm=\"" + realm + "\"");
break;
}
default:
{
throw new IllegalStateException();
}
}
}
else
{
switch (type)
{
case "Basic":
{
String user = request.getParameter("user");
String password = request.getParameter("password");
String expected = "Basic " + B64Code.encode(user + ":" + password);
if (!expected.equals(authorization))
throw new IOException(expected + " != " + authorization);
IO.copy(request.getInputStream(), response.getOutputStream());
break;
}
default:
{
throw new IllegalStateException();
}
}
}
}
finally
{
baseRequest.setHandled(true);
}
}
}
}

View File

@ -0,0 +1,3 @@
# Format is <user>:<password>,<roles>
basic:basic
digest:digest