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)
This commit is contained in:
Joakim Erdfelt 2014-09-30 12:54:42 -07:00
parent 93520df3f9
commit d0fa66ddf0
4 changed files with 391 additions and 3 deletions

View File

@ -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
* <p>
* 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);
}
}

View File

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

View File

@ -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("<a href=\"/test1\">"));
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("<html>");
out.println("<head><title>Contexts</title></head>");
out.println("<body>");
out.println("<h4>Child Contexts</h4>");
out.println("<ul>");
for (String child : childContexts)
{
out.printf("<li><a href=\"%s\">%s</a></li>%n",child,child);
}
out.println("</ul>");
out.println("</body></html>");
baseRequest.setHandled(true);
}
}
}

View File

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