From d0fa66ddf0b13f754c6219120156fb66ab90268a Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Tue, 30 Sep 2014 12:54:42 -0700 Subject: [PATCH] 445542 - Add SecuredRedirectHandler for embedded jetty use to redirect to secure port/scheme + Adding SecuredRedirectHandler as option for those jetty embedded folks to have a simple http -> https solution (can even be setup and bound to specific connectors via the named virtualhosts concepts) --- .../handler/SecuredRedirectHandler.java | 66 ++++ .../server/handler/AllowAllVerifier.java | 31 ++ .../handler/SecuredRedirectHandlerTest.java | 288 ++++++++++++++++++ .../jetty/server/ssl/SslUploadTest.java | 9 +- 4 files changed, 391 insertions(+), 3 deletions(-) create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/handler/SecuredRedirectHandler.java create mode 100644 jetty-server/src/test/java/org/eclipse/jetty/server/handler/AllowAllVerifier.java create mode 100644 jetty-server/src/test/java/org/eclipse/jetty/server/handler/SecuredRedirectHandlerTest.java diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/SecuredRedirectHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/SecuredRedirectHandler.java new file mode 100644 index 00000000000..756e15a6db7 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/SecuredRedirectHandler.java @@ -0,0 +1,66 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.server.handler; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.HttpChannel; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.util.URIUtil; + +/** + * Secured Redirect Handler + *

+ * Using information present in the {@link HttpConfiguration}, will attempt to redirect to the {@link HttpConfiguration#getSecureScheme()} and + * {@link HttpConfiguration#getSecurePort()} for any request that {@link HttpServletRequest#isSecure()} == false. + */ +public class SecuredRedirectHandler extends AbstractHandler +{ + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + HttpConfiguration httpConfig = HttpChannel.getCurrentHttpChannel().getHttpConfiguration(); + + if (baseRequest.isSecure()) + { + return; // all done + } + + if (httpConfig.getSecurePort() > 0) + { + String scheme = httpConfig.getSecureScheme(); + int port = httpConfig.getSecurePort(); + + String url = URIUtil.newURI(scheme,baseRequest.getServerName(),port,baseRequest.getRequestURI(),baseRequest.getQueryString()); + response.setContentLength(0); + response.sendRedirect(url); + } + else + { + response.sendError(HttpStatus.FORBIDDEN_403,"Not Secure"); + } + baseRequest.setHandled(true); + } +} \ No newline at end of file diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/AllowAllVerifier.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/AllowAllVerifier.java new file mode 100644 index 00000000000..0c203076f3e --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/AllowAllVerifier.java @@ -0,0 +1,31 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.server.handler; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLSession; + +public class AllowAllVerifier implements HostnameVerifier +{ + @Override + public boolean verify(String hostname, SSLSession session) + { + return true; + } +} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/SecuredRedirectHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/SecuredRedirectHandlerTest.java new file mode 100644 index 00000000000..ebcf20a1e5b --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/SecuredRedirectHandlerTest.java @@ -0,0 +1,288 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.server.handler; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URL; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLSocketFactory; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.SecureRequestCustomizer; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.SslConnectionFactory; +import org.eclipse.jetty.toolchain.test.IO; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +public class SecuredRedirectHandlerTest +{ + private static Server server; + private static HostnameVerifier origVerifier; + private static SSLSocketFactory origSsf; + private static URI serverHttpUri; + private static URI serverHttpsUri; + + @BeforeClass + public static void startServer() throws Exception + { + // Setup SSL + File keystore = MavenTestingUtils.getTestResourceFile("keystore"); + SslContextFactory sslContextFactory = new SslContextFactory(); + sslContextFactory.setKeyStorePath(keystore.getAbsolutePath()); + sslContextFactory.setKeyStorePassword("storepwd"); + sslContextFactory.setKeyManagerPassword("keypwd"); + sslContextFactory.setTrustStorePath(keystore.getAbsolutePath()); + sslContextFactory.setTrustStorePassword("storepwd"); + + server = new Server(); + + int port = 12080; + int securePort = 12443; + + // Setup HTTP Configuration + HttpConfiguration httpConf = new HttpConfiguration(); + httpConf.setSecurePort(securePort); + httpConf.setSecureScheme("https"); + + ServerConnector httpConnector = new ServerConnector(server,new HttpConnectionFactory(httpConf)); + httpConnector.setName("unsecured"); + httpConnector.setPort(port); + + // Setup HTTPS Configuration + HttpConfiguration httpsConf = new HttpConfiguration(httpConf); + httpsConf.addCustomizer(new SecureRequestCustomizer()); + + ServerConnector httpsConnector = new ServerConnector(server,new SslConnectionFactory(sslContextFactory,"http/1.1"),new HttpConnectionFactory(httpsConf)); + httpsConnector.setName("secured"); + httpsConnector.setPort(securePort); + + // Add connectors + server.setConnectors(new Connector[] { httpConnector, httpsConnector }); + + // Wire up contexts + String secureHosts[] = new String[] { "@secured" }; + + ContextHandler test1Context = new ContextHandler(); + test1Context.setContextPath("/test1"); + test1Context.setHandler(new HelloHandler("Hello1")); + test1Context.setVirtualHosts(secureHosts); + + ContextHandler test2Context = new ContextHandler(); + test2Context.setContextPath("/test2"); + test2Context.setHandler(new HelloHandler("Hello2")); + test2Context.setVirtualHosts(secureHosts); + + ContextHandler rootContext = new ContextHandler(); + rootContext.setContextPath("/"); + rootContext.setHandler(new RootHandler("/test1","/test2")); + rootContext.setVirtualHosts(secureHosts); + + // Wire up context for unsecure handling to only + // the named 'unsecured' connector + ContextHandler redirectHandler = new ContextHandler(); + redirectHandler.setContextPath("/"); + redirectHandler.setHandler(new SecuredRedirectHandler()); + redirectHandler.setVirtualHosts(new String[] { "@unsecured" }); + + // Establish all handlers that have a context + ContextHandlerCollection contextHandlers = new ContextHandlerCollection(); + contextHandlers.setHandlers(new Handler[] { redirectHandler, rootContext, test1Context, test2Context }); + + // Create server level handler tree + HandlerList handlers = new HandlerList(); + handlers.addHandler(contextHandlers); + handlers.addHandler(new DefaultHandler()); // round things out + + server.setHandler(handlers); + + server.start(); + + // calculate serverUri + String host = httpConnector.getHost(); + if (host == null) + { + host = "localhost"; + } + serverHttpUri = new URI(String.format("http://%s:%d/",host,httpConnector.getLocalPort())); + serverHttpsUri = new URI(String.format("https://%s:%d/",host,httpsConnector.getLocalPort())); + + origVerifier = HttpsURLConnection.getDefaultHostnameVerifier(); + origSsf = HttpsURLConnection.getDefaultSSLSocketFactory(); + + HttpsURLConnection.setDefaultHostnameVerifier(new AllowAllVerifier()); + HttpsURLConnection.setDefaultSSLSocketFactory(sslContextFactory.getSslContext().getSocketFactory()); + } + + @AfterClass + public static void stopServer() throws Exception + { + HttpsURLConnection.setDefaultSSLSocketFactory(origSsf); + HttpsURLConnection.setDefaultHostnameVerifier(origVerifier); + + server.stop(); + server.join(); + } + + @Test + public void testRedirectUnsecuredRoot() throws Exception + { + URL url = serverHttpUri.resolve("/").toURL(); + HttpURLConnection connection = (HttpURLConnection)url.openConnection(); + connection.setInstanceFollowRedirects(false); + connection.setAllowUserInteraction(false); + assertThat("response code",connection.getResponseCode(),is(302)); + assertThat("location header",connection.getHeaderField("Location"),is(serverHttpsUri.resolve("/").toASCIIString())); + connection.disconnect(); + } + + @Test + public void testRedirectSecuredRoot() throws Exception + { + URL url = serverHttpsUri.resolve("/").toURL(); + HttpURLConnection connection = (HttpURLConnection)url.openConnection(); + connection.setInstanceFollowRedirects(false); + connection.setAllowUserInteraction(false); + assertThat("response code",connection.getResponseCode(),is(200)); + String content = getContent(connection); + assertThat("response content",content,containsString("")); + connection.disconnect(); + } + + @Test + public void testAccessUnsecuredHandler() throws Exception + { + URL url = serverHttpUri.resolve("/test1").toURL(); + HttpURLConnection connection = (HttpURLConnection)url.openConnection(); + connection.setInstanceFollowRedirects(false); + connection.setAllowUserInteraction(false); + assertThat("response code",connection.getResponseCode(),is(302)); + assertThat("location header",connection.getHeaderField("Location"),is(serverHttpsUri.resolve("/test1").toASCIIString())); + connection.disconnect(); + } + + @Test + public void testAccessUnsecured404() throws Exception + { + URL url = serverHttpUri.resolve("/nothing/here").toURL(); + HttpURLConnection connection = (HttpURLConnection)url.openConnection(); + connection.setInstanceFollowRedirects(false); + connection.setAllowUserInteraction(false); + assertThat("response code",connection.getResponseCode(),is(302)); + assertThat("location header",connection.getHeaderField("Location"),is(serverHttpsUri.resolve("/nothing/here").toASCIIString())); + connection.disconnect(); + } + + @Test + public void testAccessSecured404() throws Exception + { + URL url = serverHttpsUri.resolve("/nothing/here").toURL(); + HttpURLConnection connection = (HttpURLConnection)url.openConnection(); + connection.setInstanceFollowRedirects(false); + connection.setAllowUserInteraction(false); + assertThat("response code",connection.getResponseCode(),is(404)); + connection.disconnect(); + } + + private String getContent(HttpURLConnection connection) throws IOException + { + try (InputStream in = connection.getInputStream(); InputStreamReader reader = new InputStreamReader(in)) + { + StringWriter writer = new StringWriter(); + IO.copy(reader,writer); + return writer.toString(); + } + } + + public static class HelloHandler extends AbstractHandler + { + private final String msg; + + public HelloHandler(String msg) + { + this.msg = msg; + } + + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + response.setContentType("text/plain"); + response.getWriter().printf("%s%n",msg); + baseRequest.setHandled(true); + } + } + + public static class RootHandler extends AbstractHandler + { + private final String[] childContexts; + + public RootHandler(String... children) + { + this.childContexts = children; + } + + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + if (!"/".equals(target)) + { + response.sendError(404); + return; + } + + response.setContentType("text/html"); + PrintWriter out = response.getWriter(); + out.println(""); + out.println("Contexts"); + out.println(""); + out.println("

Child Contexts

"); + out.println(""); + out.println(""); + baseRequest.setHandled(true); + } + } +} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslUploadTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslUploadTest.java index 0d39ab52a90..1942270e62b 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslUploadTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslUploadTest.java @@ -21,6 +21,7 @@ package org.eclipse.jetty.server.ssl; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; @@ -40,6 +41,7 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.SslConnectionFactory; import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.junit.AfterClass; @@ -58,12 +60,13 @@ public class SslUploadTest @BeforeClass public static void startServer() throws Exception { - String keystorePath = System.getProperty("basedir",".") + "/src/test/resources/keystore"; + File keystore = MavenTestingUtils.getTestResourceFile("keystore"); + SslContextFactory sslContextFactory = new SslContextFactory(); - sslContextFactory.setKeyStorePath(keystorePath); + sslContextFactory.setKeyStorePath(keystore.getAbsolutePath()); sslContextFactory.setKeyStorePassword("storepwd"); sslContextFactory.setKeyManagerPassword("keypwd"); - sslContextFactory.setTrustStorePath(keystorePath); + sslContextFactory.setTrustStorePath(keystore.getAbsolutePath()); sslContextFactory.setTrustStorePassword("storepwd"); server = new Server();