繁体   English   中英

如何从一个端口为Jetty提供https和http?

[英]How do I serve https and http for Jetty from one port?

(我知道这是一个重复的问题,但原始的海报问错了原因。我并不是说我正在问它是正确的理由,但让我们看看。)

我们有一个运行在非标准端口号上的Web服务。 尽管用户似乎能够记住端口号,但偶尔他们会输入http:而不是https:。 有人问我们是否可以在该端口上提供HTTP,然后在同一端口上将它们重定向到HTTPS。 这听起来很邪恶......我喜欢可用性但感觉浏览器的工作应该是这样做的吗?

我见过的一个解决方案是“在Jetty面前编写自己的代理”。 这个解决方案可行,但我不认为它会运行良好,因为我不相信我能编写一个与Jetty本身一样高效的代理。 此外,即使代理本身有效,所有数据仍然需要额外跳跃,这无论如何都会保证减慢流量。

有比这更好的方法吗? 也许Jetty本身有一些协议检测逻辑可以被楔入的地方,这将允许利用它们的速度,同时还移除代理将引入的额外跳。

更新:有关如何将单个端口重定向到HTTPS和HTTP侦听器的说明,请参阅此答案 如果由于某种原因您不使用该解决方案,请参阅以下内容:

无法在同一端口上管理来自http和https的流量。 Jetty使用两个完全不同的连接器绑定到安全和不安全的端口。 事实上,我遇到的每个Web服务器都将这两个协议绑定到两个完全独立的端口。

我建议可用性的一件事是使用默认端口,它完全隐藏了用户的端口。 默认情况下,http使用端口80,默认情况下,https使用端口443.因此,如果您将连接器配置为分别在端口80和端口443上运行,那么您的用户不必键入端口,并且您的开发团队不会必须处理HTML,CSS,JavaScript和其他资源中的绝对路径中的端口号。

Jetty被设计为独立的Web服务器,不像旧版本的Tomcat ,Apache建议在Apache HTTP服务器后运行。 因此,只要您没有其他HTTP服务器正在运行,并且使用这些端口而您不能,您就应该能够将Jetty配置为在默认端口上运行而不会出现任何问题。 这来自经验。 我们正是以这种方式运行Jetty。

最后,协议可以绑定到多个端口。 因此,如果您当前在端口8080上为http运行Jetty,在8443上为https运行Jetty,则可以保持这些连接器处于活动状态,并为端口80和端口443添加另外两个连接器。这样可以向您的应用程序部分向后兼容使用端口号,让你有时间向前走。

<!-- Legacy HTTP connector -->
<Call name="addConnector">
  <Arg>
      <New class="org.mortbay.jetty.nio.SelectChannelConnector">
        <Set name="host"><SystemProperty name="jetty.host" /></Set>
        <Set name="port"><SystemProperty name="jetty.port" default="8080"/></Set>
        <Set name="maxIdleTime">30000</Set>
        <Set name="Acceptors">2</Set>
        <Set name="statsOn">false</Set>
        <Set name="confidentialPort">8443</Set>
        <Set name="lowResourcesConnections">5000</Set>
        <Set name="lowResourcesMaxIdleTime">5000</Set>
      </New>
  </Arg>
</Call>
<!-- Second connector for http on port 80 -->
<Call name="addConnector">
  <Arg>
      <New class="org.mortbay.jetty.nio.SelectChannelConnector">
        <Set name="host"><SystemProperty name="jetty.host" /></Set>
        <Set name="port"><SystemProperty name="jetty.port" default="80"/></Set>
        <Set name="maxIdleTime">30000</Set>
        <Set name="Acceptors">2</Set>
        <Set name="statsOn">false</Set>
        <Set name="confidentialPort">8443</Set>
        <Set name="lowResourcesConnections">5000</Set>
        <Set name="lowResourcesMaxIdleTime">5000</Set>
      </New>
  </Arg>
</Call>

<!-- Legacy SSL Connector for https port 8443 -->
<Call name="addConnector">
 <Arg>
  <New class="org.mortbay.jetty.security.SslSocketConnector">
    <Set name="Port">8443</Set>
    <Set name="maxIdleTime">30000</Set>
    <Set name="handshakeTimeout">2000</Set>
    <Set name="keystore"><SystemProperty name="jetty.home" default="." />/etc/keystore</Set>
    <Set name="password">xxxxxx</Set>
    <Set name="keyPassword">xxxxxx</Set>
    <Set name="truststore"><SystemProperty name="jetty.home" default="." />/etc/keystore</Set>
    <Set name="trustPassword">OBF:xxxxx</Set>
    <Set name="handshakeTimeout">2000</Set>
    <!-- Set name="ThreadPool">
      <New class="org.mortbay.thread.BoundedThreadPool">
        <Set name="minThreads">10</Set>
        <Set name="maxThreads">250</Set>
     </New>
    </Set -->
  </New>
 </Arg>
</Call>



<!-- Default SSL Connector for https port 443 -->
<Call name="addConnector">
 <Arg>
  <New class="org.mortbay.jetty.security.SslSocketConnector">
    <Set name="Port">443</Set>
    <Set name="maxIdleTime">30000</Set>
    <Set name="handshakeTimeout">2000</Set>
    <Set name="keystore"><SystemProperty name="jetty.home" default="." />/etc/keystore</Set>
    <Set name="password">xxxxxx</Set>
    <Set name="keyPassword">xxxxxx</Set>
    <Set name="truststore"><SystemProperty name="jetty.home" default="." />/etc/keystore</Set>
    <Set name="trustPassword">OBF:xxxxx</Set>
    <Set name="handshakeTimeout">2000</Set>
    <!-- Set name="ThreadPool">
      <New class="org.mortbay.thread.BoundedThreadPool">
        <Set name="minThreads">10</Set>
        <Set name="maxThreads">250</Set>
     </New>
    </Set -->
  </New>
 </Arg>
</Call>

对于第2和第4个连接器,唯一真正的区别是端口号。 简而言之,您可以为每个连接器/协议配置多个端口,但不能为同一端口配置多个协议/连接器。

更新 :截至Jetty-9.4.15.v20190215支持端口统一内置于Jetty; 看到这个答案

我们可以

这是可能的,我们已经做到了。 这里的代码适用于Jetty 8; 我没有测试过Jetty 9,但这个答案有类似Jetty 9的代码。

顺便说一下,这被称为端口统一 ,并且使用Grizzly显然在Glassfish中得到了长期支持。

大纲

基本思想是生成org.eclipse.jetty.server.Connector的实现,它可以提前查看客户端请求的第一个字节。 幸运的是,HTTP和HTTPS都让客户端开始通信。 对于HTTPS(通常为TLS / SSL),第一个字节为0x16 (TLS),或>= 0x80 (SSLv2)。 对于HTTP,第一个字节将是旧的可打印的7位ASCII。 现在,根据第一个字节, Connector将生成SSL连接或普通连接。

在这里的代码中,我们利用Jetty的SslSelectChannelConnector本身扩展SelectChannelConnector ,并具有newPlainConnection()方法(调用其超类来生成非SSL连接)以及newConnection()方法(以生成SSL连接newConnection()这一newConnection() )。 所以我们的新Connector可以在观察客户端的第一个字节后扩展SslSelectChannelConnector并委托给其中一个方法。

不幸的是,我们需要第一个字节可用之前创建一个AsyncConnection实例。 甚至可以在第一个字节可用之前调用该实例的某些方法。 因此,我们创建一个LazyConnection implements AsyncConnection ,它可以在以后确定它将委托给哪种连接,或者甚至在它知道之前返回一些方法的合理默认响应。

基于NIO,我们的Connector将使用SocketChannel 幸运的是,我们可以扩展SocketChannel来创建一个ReadAheadSocketChannelWrapper ,它委托给“真正的” SocketChannel但可以检查和存储客户端消息的第一个字节。

一些细节

一个非常hacky位。 我们的Connector必须覆盖的方法之一是customize(Endpoint,Request) 如果我们最终得到一个基于SSL的Endpoint我们就可以传递给我们的超类; 否则超类将抛出ClassCastException ,但只有在传递给它的超类Request上设置方案之后。 所以我们传递给超类,但是当我们看到异常时撤消设置方案。

我们还重写了isConfidential()isIntegral()以确保我们的servlet可以正确使用HttpServletRequest.isSecure()来确定是否使用了HTTP或HTTPS。

尝试从客户端读取第一个字节可能会抛出IOException ,但我们可能必须在不期望IOException的地方尝试,在这种情况下,我们保留异常并稍后抛出它。

扩展SocketChannel在Java> = 7和Java 6中看起来不同。在后一种情况下,只需注释掉Java 6 SocketChannel没有的方法。

代码

public class PortUnificationSelectChannelConnector extends SslSelectChannelConnector {
    public PortUnificationSelectChannelConnector() {
        super();
    }

    public PortUnificationSelectChannelConnector(SslContextFactory sslContextFactory) {
        super(sslContextFactory);
    }

    @Override
    protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key) throws IOException {
        return super.newEndPoint(new ReadAheadSocketChannelWrapper(channel, 1), selectSet, key);
    }

    @Override
    protected AsyncConnection newConnection(SocketChannel channel, AsyncEndPoint endPoint) {
        return new LazyConnection((ReadAheadSocketChannelWrapper)channel, endPoint);
    }

    @Override
    public void customize(EndPoint endpoint, Request request) throws IOException {
        String scheme = request.getScheme();
        try {
            super.customize(endpoint, request);
        } catch (ClassCastException e) {
            request.setScheme(scheme);
        }
    }

    @Override
    public boolean isConfidential(Request request) {
        if (request.getAttribute("javax.servlet.request.cipher_suite") != null) return true;
        else return isForwarded() && request.getScheme().equalsIgnoreCase(HttpSchemes.HTTPS);
    }

    @Override
    public boolean isIntegral(Request request) {
        return isConfidential(request);
    }

    class LazyConnection implements AsyncConnection {
        private final ReadAheadSocketChannelWrapper channel;
        private final AsyncEndPoint endPoint;
        private final long timestamp;
        private AsyncConnection connection;

        public LazyConnection(ReadAheadSocketChannelWrapper channel, AsyncEndPoint endPoint) {
            this.channel = channel;
            this.endPoint = endPoint;
            this.timestamp = System.currentTimeMillis();
            this.connection = determineNewConnection(channel, endPoint, false);
        }

        public Connection handle() throws IOException {
            if (connection == null) {
                connection = determineNewConnection(channel, endPoint, false);
                channel.throwPendingException();
            }
            if (connection != null) return connection.handle();
            else return this;
        }

        public long getTimeStamp() {
            return timestamp;
        }

        public void onInputShutdown() throws IOException {
            if (connection == null) connection = determineNewConnection(channel, endPoint, true);
            connection.onInputShutdown();
        }

        public boolean isIdle() {
            if (connection == null) connection = determineNewConnection(channel, endPoint, false);
            if (connection != null) return connection.isIdle();
            else return false;
        }

        public boolean isSuspended() {
            if (connection == null) connection = determineNewConnection(channel, endPoint, false);
            if (connection != null) return connection.isSuspended();
            else return false;
        }

        public void onClose() {
            if (connection == null) connection = determineNewConnection(channel, endPoint, true);
            connection.onClose();
        }

        public void onIdleExpired(long l) {
            if (connection == null) connection = determineNewConnection(channel, endPoint, true);
            connection.onIdleExpired(l);
        }

        AsyncConnection determineNewConnection(ReadAheadSocketChannelWrapper channel, AsyncEndPoint endPoint, boolean force) {
            byte[] bytes = channel.getBytes();
            if ((bytes == null || bytes.length == 0) && !force) return null;
            if (looksLikeSsl(bytes)) {
                return PortUnificationSelectChannelConnector.super.newConnection(channel, endPoint);
            } else {
                return PortUnificationSelectChannelConnector.super.newPlainConnection(channel, endPoint);
            }
        }

        // TLS first byte is 0x16
        // SSLv2 first byte is >= 0x80
        // HTTP is guaranteed many bytes of ASCII
        private boolean looksLikeSsl(byte[] bytes) {
            if (bytes == null || bytes.length == 0) return false; // force HTTP
            byte b = bytes[0];
            return b >= 0x7F || (b < 0x20 && b != '\n' && b != '\r' && b != '\t');
        }
    }

    static class ReadAheadSocketChannelWrapper extends SocketChannel {
        private final SocketChannel channel;
        private final ByteBuffer start;
        private byte[] bytes;
        private IOException pendingException;
        private int leftToRead;

        public ReadAheadSocketChannelWrapper(SocketChannel channel, int readAheadLength) throws IOException {
            super(channel.provider());
            this.channel = channel;
            start = ByteBuffer.allocate(readAheadLength);
            leftToRead = readAheadLength;
            readAhead();
        }

        public synchronized void readAhead() throws IOException {
            if (leftToRead > 0) {
                int n = channel.read(start);
                if (n == -1) {
                    leftToRead = -1;
                } else {
                    leftToRead -= n;
                }
                if (leftToRead <= 0) {
                    start.flip();
                    bytes = new byte[start.remaining()];
                    start.get(bytes);
                    start.rewind();
                }
            }
        }

        public byte[] getBytes() {
            if (pendingException == null) {
                try {
                    readAhead();
                } catch (IOException e) {
                    pendingException = e;
                }
            }
            return bytes;
        }

        public void throwPendingException() throws IOException {
            if (pendingException != null) {
                IOException e = pendingException;
                pendingException = null;
                throw e;
            }
        }

        private int readFromStart(ByteBuffer dst) throws IOException {
            int sr = start.remaining();
            int dr = dst.remaining();
            if (dr == 0) return 0;
            int n = Math.min(dr, sr);
            dst.put(bytes, start.position(), n);
            start.position(start.position() + n);
            return n;
        }

        public synchronized int read(ByteBuffer dst) throws IOException {
            throwPendingException();
            readAhead();
            if (leftToRead > 0) return 0;
            int sr = start.remaining();
            if (sr > 0) {
                int n = readFromStart(dst);
                if (n < sr) return n;
            }
            return sr + channel.read(dst);
        }

        public synchronized long read(ByteBuffer[] dsts, int offset, int length) throws IOException {
            throwPendingException();
            if (offset + length > dsts.length || length < 0 || offset < 0) {
                throw new IndexOutOfBoundsException();
            }
            readAhead();
            if (leftToRead > 0) return 0;
            int sr = start.remaining();
            int newOffset = offset;
            if (sr > 0) {
                int accum = 0;
                for (; newOffset < offset + length; newOffset++) {
                    accum += readFromStart(dsts[newOffset]);
                    if (accum == sr) break;
                }
                if (accum < sr) return accum;
            }
            return sr + channel.read(dsts, newOffset, length - newOffset + offset);
        }

        public int hashCode() {
            return channel.hashCode();
        }

        public boolean equals(Object obj) {
            return channel.equals(obj);
        }

        public String toString() {
            return channel.toString();
        }

        public Socket socket() {
            return channel.socket();
        }

        public boolean isConnected() {
            return channel.isConnected();
        }

        public boolean isConnectionPending() {
            return channel.isConnectionPending();
        }

        public boolean connect(SocketAddress remote) throws IOException {
            return channel.connect(remote);
        }

        public boolean finishConnect() throws IOException {
            return channel.finishConnect();
        }

        public int write(ByteBuffer src) throws IOException {
            return channel.write(src);
        }

        public long write(ByteBuffer[] srcs, int offset, int length) throws IOException {
            return channel.write(srcs, offset, length);
        }

        @Override
        protected void implCloseSelectableChannel() throws IOException {
            channel.close();
        }

        @Override
        protected void implConfigureBlocking(boolean block) throws IOException {
            channel.configureBlocking(block);
        }

//        public SocketAddress getLocalAddress() throws IOException {
//            return channel.getLocalAddress();
//        }
//
//        public <T> T getOption(java.net.SocketOption<T> name) throws IOException {
//            return channel.getOption(name);
//        }
//
//        public Set<java.net.SocketOption<?>> supportedOptions() {
//            return channel.supportedOptions();
//        }
//
//        public SocketChannel bind(SocketAddress local) throws IOException {
//            return channel.bind(local);
//        }
//
//        public SocketAddress getRemoteAddress() throws IOException {
//            return channel.getRemoteAddress();
//        }
//
//        public <T> SocketChannel setOption(java.net.SocketOption<T> name, T value) throws IOException {
//            return channel.setOption(name, value);
//        }
//
//        public SocketChannel shutdownInput() throws IOException {
//            return channel.shutdownInput();
//        }
//
//        public SocketChannel shutdownOutput() throws IOException {
//            return channel.shutdownOutput();
//        }
    }
}

基于答案“是的我们可以”我构建了适用于当前码头9.3.11的代码,我想有些人会感兴趣。

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ReadPendingException;
import java.nio.channels.WritePendingException;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;

public class MyReadAheadEndpoint implements EndPoint {
/** real endpoint we are wrapping    */ private final EndPoint endPoint;
/** buffer used to read start bytes  */ private final ByteBuffer start     ;
/** how many N start bytes to read   */ private       int        leftToRead;
/** first  N bytes                   */ private final byte[]     bytes     ;
/** buffered exception to throw next */ private IOException pendingException = null;
@Override public InetSocketAddress getLocalAddress            () { return endPoint.getLocalAddress(); }
@Override public InetSocketAddress getRemoteAddress           () { return endPoint.getRemoteAddress(); }
@Override public boolean           isOpen                     () { return endPoint.isOpen(); }
@Override public long              getCreatedTimeStamp        () { return endPoint.getCreatedTimeStamp(); }
@Override public boolean           isOutputShutdown           () { return endPoint.isOutputShutdown(); }
@Override public boolean           isInputShutdown            () { return endPoint.isInputShutdown(); }
@Override public void              shutdownOutput             () { endPoint.shutdownOutput(); }
@Override public void              close                      () { endPoint.close(); }
@Override public Object            getTransport               () { return endPoint.getTransport(); }
@Override public long              getIdleTimeout             () { return endPoint.getIdleTimeout(); }
@Override public Connection        getConnection              () { return endPoint.getConnection(); }
@Override public void              onOpen                     () { endPoint.onOpen(); }
@Override public void              onClose                    () { endPoint.onClose(); }
@Override public boolean           isOptimizedForDirectBuffers() { return endPoint.isOptimizedForDirectBuffers(); }
@Override public boolean           isFillInterested           () { return endPoint.isFillInterested(); }
@Override public boolean           flush                      (final ByteBuffer... v) throws IOException { return endPoint.flush(v); }
@Override public void              setIdleTimeout             (final long          v) { endPoint.setIdleTimeout(v); }
@Override public void              write                      (final Callback      v, final ByteBuffer... b) throws WritePendingException { endPoint.write(v, b); }
@Override public void              setConnection              (final Connection    v) { endPoint.setConnection(v); }
@Override public void              upgrade                    (final Connection    v) { endPoint.upgrade(v); }
@Override public void              fillInterested  (final Callback   v) throws ReadPendingException { endPoint.fillInterested(v); }
@Override public int               hashCode() { return endPoint.hashCode(); }
@Override public boolean           equals(final Object obj) { return endPoint.equals(obj); }
@Override public String            toString() { return endPoint.toString(); }
public byte[] getBytes() { if (pendingException == null) { try { readAhead(); } catch (final IOException e) { pendingException = e; } } return bytes; }
private void throwPendingException() throws IOException { if (pendingException != null) { final IOException e = pendingException; pendingException = null; throw e; } }

public MyReadAheadEndpoint(final EndPoint channel, final int readAheadLength){
    this.endPoint = channel;
    start = ByteBuffer.wrap(bytes = new byte[readAheadLength]);
    start.flip();
    leftToRead = readAheadLength;
}

private synchronized void readAhead() throws IOException {
    if (leftToRead > 0) {
        final int n = endPoint.fill(start);
        if (n == -1) { leftToRead = -1; }
        else         {  leftToRead -= n; }
        if (leftToRead <= 0) start.rewind();
    }
}

private int readFromStart(final ByteBuffer dst) throws IOException {
    final int n = Math.min(dst.remaining(), start.remaining());
    if (n > 0)  {
        dst.put(bytes, start.position(), n);
        start.position(start.position() + n);
        dst.flip();
    }
    return n;
}

@Override public synchronized int fill(final ByteBuffer dst) throws IOException {
    throwPendingException();
    if (leftToRead > 0) readAhead();
    if (leftToRead > 0) return 0;
    final int sr = start.remaining();
    if (sr > 0) {
        dst.compact();
        final int n = readFromStart(dst);
        if (n < sr) return n;
    }
    return sr + endPoint.fill(dst);
}

}

import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.ssl.SslConnection;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.AbstractConnectionFactory;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.annotation.Name;
public class MySslConnectionFactory extends AbstractConnectionFactory {
private final SslContextFactory _sslContextFactory;
private final String _nextProtocol;

public MySslConnectionFactory() { this(HttpVersion.HTTP_1_1.asString()); }

public MySslConnectionFactory(@Name("next") final String nextProtocol) { this((SslContextFactory)null, nextProtocol); }

public MySslConnectionFactory(@Name("sslContextFactory") final SslContextFactory factory, @Name("next") final String nextProtocol) {
    super("SSL");
    this._sslContextFactory = factory == null?new SslContextFactory():factory;
    this._nextProtocol = nextProtocol;
    this.addBean(this._sslContextFactory);
}

public SslContextFactory getSslContextFactory() { return this._sslContextFactory; }

@Override protected void doStart() throws Exception {
    super.doStart();
    final SSLEngine engine = this._sslContextFactory.newSSLEngine();
    engine.setUseClientMode(false);
    final SSLSession session = engine.getSession();
    if(session.getPacketBufferSize() > this.getInputBufferSize()) this.setInputBufferSize(session.getPacketBufferSize());
}

@Override public Connection newConnection(final Connector connector, final EndPoint realEndPoint) {
    final MyReadAheadEndpoint aheadEndpoint = new MyReadAheadEndpoint(realEndPoint, 1);
    final byte[] bytes = aheadEndpoint.getBytes();
    final boolean isSSL;
    if (bytes == null || bytes.length == 0) {
        System.out.println("NO-Data in newConnection : "+aheadEndpoint.getRemoteAddress());
        isSSL = true;
    } else {
        final byte b = bytes[0];    // TLS first byte is 0x16 , SSLv2 first byte is >= 0x80 , HTTP is guaranteed many bytes of ASCII
        isSSL = b >= 0x7F || (b < 0x20 && b != '\n' && b != '\r' && b != '\t');
        if(!isSSL) System.out.println("newConnection["+isSSL+"] : "+aheadEndpoint.getRemoteAddress());
    }
    final EndPoint      plainEndpoint;
    final SslConnection sslConnection;
    if (isSSL) {
        final SSLEngine engine = this._sslContextFactory.newSSLEngine(aheadEndpoint.getRemoteAddress());
        engine.setUseClientMode(false);
        sslConnection = this.newSslConnection(connector, aheadEndpoint, engine);
        sslConnection.setRenegotiationAllowed(this._sslContextFactory.isRenegotiationAllowed());
        this.configure(sslConnection, connector, aheadEndpoint);
        plainEndpoint = sslConnection.getDecryptedEndPoint();
    } else {
        sslConnection = null;
        plainEndpoint = aheadEndpoint;
    }
    final ConnectionFactory next = connector.getConnectionFactory(_nextProtocol);
    final Connection connection = next.newConnection(connector, plainEndpoint);
    plainEndpoint.setConnection(connection);
    return sslConnection == null ? connection : sslConnection;
}

protected SslConnection newSslConnection(final Connector connector, final EndPoint endPoint, final SSLEngine engine) {
    return new SslConnection(connector.getByteBufferPool(), connector.getExecutor(), endPoint, engine);
}

@Override public String toString() {
    return String.format("%s@%x{%s->%s}", new Object[]{this.getClass().getSimpleName(), Integer.valueOf(this.hashCode()), this.getProtocol(), this._nextProtocol});
}

}

您可以通过编写自定义Jetty ConnectionFactory来实现此目的。 我建议首先复制和修改SslConnectionFactory和SslConnection的代码。 您需要检查连接的前几个字节(根据需要进行缓冲)以查找SSL客户端Hello。 使用SSLv2 Hello,您可以通过两个长度字节识别,然后是0x01,后跟版本字节。 SSLv3 Hello以0x16开头,后跟版本字节。 版本字节序列对于SSL 3.0为0x03 0x00,对于SSL 2.0为0x02 0x00,对于TLS 1.0为0x03 0x01,对于TLS 1.1为0x03 0x02,对于TLS 1.2为0x03 0x03。 有效的HTTP流量不应该以这些字节序列开头。 这个答案有更多细节。)如果是SSL,则将其传递给SSLEngine; 如果没有,请将其直接传递给下一个协议连接器。

截至Jetty-9.4.15.v20190215,端口统一的支持通过类OptionalSslConnectionFactory构建到Jetty中。

这是一个示例类,在运行时,将启动一个侦听单个端口8000的服务器,并将响应HTTP或HTTPS。 (这是基于此处单独的HTTP和HTTPS连接器的Jetty示例代码。)

import java.io.*;
import javax.servlet.http.*;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.server.*;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.ssl.SslContextFactory;

public class Jetty9PortUnification {

    public static void main(String[] args) throws Exception {
        // Use example keystore and keys from Jetty distribution
        String keystorePath = "jetty-distribution/demo-base/etc/keystore";
        File keystoreFile = new File(keystorePath);
        if (!keystoreFile.exists()) {
            throw new FileNotFoundException(keystoreFile.getAbsolutePath());
        }

        Server server = new Server();

        HttpConfiguration httpConfig = new HttpConfiguration();
        httpConfig.setSecureScheme("https");
        httpConfig.setSecurePort(8000);

        SecureRequestCustomizer src = new SecureRequestCustomizer();
        httpConfig.addCustomizer(src);

        HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactory(httpConfig);

        SslContextFactory sslContextFactory = new SslContextFactory();
        sslContextFactory.setKeyStorePath(keystoreFile.getAbsolutePath());
        sslContextFactory.setKeyStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4");
        sslContextFactory.setKeyManagerPassword("OBF:1u2u1wml1z7s1z7a1wnl1u2g");

        SslConnectionFactory sslConnectionFactory = new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString());

        ServerConnector portUnified = new ServerConnector(server,
            new OptionalSslConnectionFactory(sslConnectionFactory, HttpVersion.HTTP_1_1.asString()),
            sslConnectionFactory,
            httpConnectionFactory);
        portUnified.setPort(8000);

        server.addConnector(portUnified);

        server.setHandler(new AbstractHandler() {
            @Override
            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException {
                response.setContentType("text/plain");
                response.getWriter().println("Hello");
                baseRequest.setHandled(true);
            }
        });

        server.start();
        server.join();
    }
}

要运行它,你需要javax.servlet-api-3.1.0.jarjetty-server-9.4.15.v20190215.jarjetty-util-9.4.15.v20190215.jarjetty-http-9.4.15.v20190215.jarjetty-io-9.4.15.v20190215.jar

即使将Jetty排除在外,这实际上也是不可能的,因为服务器必须检测传入连接是HTTP还是SSL / TLS。 TLS协议不是为支持这种用法而设计的,因此任何实现都是黑客攻击(我找不到任何实现)。

确实存在一个SSL-SSH多路复用器 ,它可以区分传入连接是TLS还是SSH,而OpenVPN具有“端口共享”功能,它可以将非OpenVPN连接代理到另一个端口。

一种可能的方法是使用匹配数据包内的字符串的iptables规则。 HTTP请求的第一个数据包应包含“HTTP /”,而TLS ClientHello数据包则不包含。 然后可以将连接重定向到不使用TLS的其他端口。 请注意,由于在整个数据包中进行字符串搜索,这会产生额外的开销,这是一个非常糟糕的解决方案。

iptables --table nat --append PREROUTING --protocol tcp --dport 10433 --match string --string "HTTP/" --REDIRECT 1080

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM