blob: 6122acb182a9da147992ea4efb1a37005a7e4849 [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2017 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;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
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.api.Response;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.util.BasicAuthentication;
import org.eclipse.jetty.client.util.DigestAuthentication;
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.server.handler.AbstractHandler;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.Attributes;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.security.Constraint;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.Assert;
import org.junit.Test;
public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
{
private String realm = "TestRealm";
public HttpClientAuthenticationTest(SslContextFactory sslContextFactory)
{
super(sslContextFactory);
}
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[]{"**"}); //allow any authenticated user
ConstraintMapping mapping = new ConstraintMapping();
mapping.setPathSpec("/secure");
mapping.setConstraint(constraint);
securityHandler.addConstraintMapping(mapping);
securityHandler.setAuthenticator(authenticator);
securityHandler.setLoginService(loginService);
securityHandler.setHandler(handler);
start(securityHandler);
}
@Test
public void test_BasicAuthentication() throws Exception
{
startBasic(new EmptyServerHandler());
URI uri = URI.create(scheme + "://localhost:" + connector.getLocalPort());
test_Authentication(new BasicAuthentication(uri, realm, "basic", "basic"));
}
@Test
public void test_DigestAuthentication() throws Exception
{
startDigest(new EmptyServerHandler());
URI uri = URI.create(scheme + "://localhost:" + connector.getLocalPort());
test_Authentication(new DigestAuthentication(uri, realm, "digest", "digest"));
}
private void test_Authentication(Authentication authentication) throws Exception
{
AuthenticationStore authenticationStore = client.getAuthenticationStore();
final AtomicReference<CountDownLatch> requests = new AtomicReference<>(new CountDownLatch(1));
Request.Listener.Adapter requestListener = new Request.Listener.Adapter()
{
@Override
public void onSuccess(Request request)
{
requests.get().countDown();
}
};
client.getRequestListeners().add(requestListener);
// Request without Authentication causes a 401
Request request = client.newRequest("localhost", connector.getLocalPort()).scheme(scheme).path("/secure");
ContentResponse response = request.timeout(5, TimeUnit.SECONDS).send();
Assert.assertNotNull(response);
Assert.assertEquals(401, response.getStatus());
Assert.assertTrue(requests.get().await(5, TimeUnit.SECONDS));
client.getRequestListeners().remove(requestListener);
authenticationStore.addAuthentication(authentication);
requests.set(new CountDownLatch(2));
requestListener = new Request.Listener.Adapter()
{
@Override
public void onSuccess(Request request)
{
requests.get().countDown();
}
};
client.getRequestListeners().add(requestListener);
// Request with authentication causes a 401 (no previous successful authentication) + 200
request = client.newRequest("localhost", connector.getLocalPort()).scheme(scheme).path("/secure");
response = request.timeout(5, TimeUnit.SECONDS).send();
Assert.assertNotNull(response);
Assert.assertEquals(200, response.getStatus());
Assert.assertTrue(requests.get().await(5, TimeUnit.SECONDS));
client.getRequestListeners().remove(requestListener);
requests.set(new CountDownLatch(1));
requestListener = new Request.Listener.Adapter()
{
@Override
public void onSuccess(Request request)
{
requests.get().countDown();
}
};
client.getRequestListeners().add(requestListener);
// Further requests do not trigger 401 because there is a previous successful authentication
// Remove existing header to be sure it's added by the implementation
request = client.newRequest("localhost", connector.getLocalPort()).scheme(scheme).path("/secure");
response = request.timeout(5, TimeUnit.SECONDS).send();
Assert.assertNotNull(response);
Assert.assertEquals(200, response.getStatus());
Assert.assertTrue(requests.get().await(5, TimeUnit.SECONDS));
client.getRequestListeners().remove(requestListener);
}
@Test
public void test_BasicAuthentication_ThenRedirect() throws Exception
{
startBasic(new AbstractHandler()
{
private final AtomicInteger requests = new AtomicInteger();
@Override
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
if (requests.incrementAndGet() == 1)
response.sendRedirect(URIUtil.newURI(scheme,request.getServerName(),request.getServerPort(),request.getRequestURI(),null));
}
});
URI uri = URI.create(scheme + "://localhost:" + connector.getLocalPort());
client.getAuthenticationStore().addAuthentication(new BasicAuthentication(uri, realm, "basic", "basic"));
final CountDownLatch requests = new CountDownLatch(3);
Request.Listener.Adapter requestListener = new Request.Listener.Adapter()
{
@Override
public void onSuccess(Request request)
{
requests.countDown();
}
};
client.getRequestListeners().add(requestListener);
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.path("/secure")
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertNotNull(response);
Assert.assertEquals(200, response.getStatus());
Assert.assertTrue(requests.await(5, TimeUnit.SECONDS));
client.getRequestListeners().remove(requestListener);
}
@Test
public void test_Redirect_ThenBasicAuthentication() throws Exception
{
startBasic(new AbstractHandler()
{
@Override
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
if (request.getRequestURI().endsWith("/redirect"))
response.sendRedirect(URIUtil.newURI(scheme,request.getServerName(),request.getServerPort(),"/secure",null));
}
});
URI uri = URI.create(scheme + "://localhost:" + connector.getLocalPort());
client.getAuthenticationStore().addAuthentication(new BasicAuthentication(uri, realm, "basic", "basic"));
final CountDownLatch requests = new CountDownLatch(3);
Request.Listener.Adapter requestListener = new Request.Listener.Adapter()
{
@Override
public void onSuccess(Request request)
{
requests.countDown();
}
};
client.getRequestListeners().add(requestListener);
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.path("/redirect")
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertNotNull(response);
Assert.assertEquals(200, response.getStatus());
Assert.assertTrue(requests.await(5, TimeUnit.SECONDS));
client.getRequestListeners().remove(requestListener);
}
@Test
public void test_BasicAuthentication_WithAuthenticationRemoved() throws Exception
{
startBasic(new EmptyServerHandler());
final AtomicReference<CountDownLatch> requests = new AtomicReference<>(new CountDownLatch(2));
Request.Listener.Adapter requestListener = new Request.Listener.Adapter()
{
@Override
public void onSuccess(Request request)
{
requests.get().countDown();
}
};
client.getRequestListeners().add(requestListener);
AuthenticationStore authenticationStore = client.getAuthenticationStore();
URI uri = URI.create(scheme + "://localhost:" + connector.getLocalPort());
BasicAuthentication authentication = new BasicAuthentication(uri, realm, "basic", "basic");
authenticationStore.addAuthentication(authentication);
Request request = client.newRequest("localhost", connector.getLocalPort()).scheme(scheme).path("/secure");
ContentResponse response = request.timeout(5, TimeUnit.SECONDS).send();
Assert.assertNotNull(response);
Assert.assertEquals(200, response.getStatus());
Assert.assertTrue(requests.get().await(5, TimeUnit.SECONDS));
authenticationStore.removeAuthentication(authentication);
requests.set(new CountDownLatch(1));
request = client.newRequest("localhost", connector.getLocalPort()).scheme(scheme).path("/secure");
response = request.timeout(5, TimeUnit.SECONDS).send();
Assert.assertNotNull(response);
Assert.assertEquals(200, response.getStatus());
Assert.assertTrue(requests.get().await(5, TimeUnit.SECONDS));
Authentication.Result result = authenticationStore.findAuthenticationResult(request.getURI());
Assert.assertNotNull(result);
authenticationStore.removeAuthenticationResult(result);
requests.set(new CountDownLatch(1));
request = client.newRequest("localhost", connector.getLocalPort()).scheme(scheme).path("/secure");
response = request.timeout(5, TimeUnit.SECONDS).send();
Assert.assertNotNull(response);
Assert.assertEquals(401, response.getStatus());
Assert.assertTrue(requests.get().await(5, TimeUnit.SECONDS));
}
@Test
public void test_BasicAuthentication_WithWrongPassword() throws Exception
{
startBasic(new EmptyServerHandler());
AuthenticationStore authenticationStore = client.getAuthenticationStore();
URI uri = URI.create(scheme + "://localhost:" + connector.getLocalPort());
BasicAuthentication authentication = new BasicAuthentication(uri, realm, "basic", "wrong");
authenticationStore.addAuthentication(authentication);
Request request = client.newRequest("localhost", connector.getLocalPort()).scheme(scheme).path("/secure");
ContentResponse response = request.timeout(5, TimeUnit.SECONDS).send();
Assert.assertNotNull(response);
Assert.assertEquals(401, response.getStatus());
}
@Test
public void test_Authentication_ThrowsException() throws Exception
{
startBasic(new EmptyServerHandler());
// Request without Authentication would cause a 401,
// but the client will throw an exception trying to
// send the credentials to the server.
final String cause = "thrown_explicitly_by_test";
client.getAuthenticationStore().addAuthentication(new Authentication()
{
@Override
public boolean matches(String type, URI uri, String realm)
{
return true;
}
@Override
public Result authenticate(Request request, ContentResponse response, HeaderInfo headerInfo, Attributes context)
{
throw new RuntimeException(cause);
}
});
final CountDownLatch latch = new CountDownLatch(1);
client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.path("/secure")
.timeout(5, TimeUnit.SECONDS)
.send(new Response.CompleteListener()
{
@Override
public void onComplete(Result result)
{
Assert.assertTrue(result.isFailed());
Assert.assertEquals(cause, result.getFailure().getMessage());
latch.countDown();
}
});
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
}
}