Netty ProxyHandler writeAndFlush没有写入对服务器

时间:2017-05-03 05:16:45

标签: proxy netty handler ntlmv2

尝试在Netty中实现NTLMProxyHandler,它可以执行NTLM消息交换并使用Web代理对客户端进行身份验证。

NTLMProxyHandler扩展了Netty的ProxyHandler类。因此,初始HTTP请求由代理处理程序触发,并且到达我创建的模拟代理服务器。代理服务器读取此请求并以407代理身份验证所需响应进行响应。

NTLMProxyHandler在客户端读取此响应并准备新的NTLM Type1Message并再次将响应写回服务器。我面临的问题是,虽然调用了通道future的成功处理程序,但此请求永远不会发送到我的代理服务器。

我在日志记录中启用了Netty软件包,但无法弄清楚为什么只有第二次从ntlm代理处理程序写入的响应丢失。

我尝试使用Netty ProxyHandler的sendToProxyServer(msg)以及使用从channelRead()传递的channelHandlerCtx。在这两种情况下,writeAndFlush都已完成,但响应永远不会到达服务器,服务器也会超时。

有没有人使用channelHandlerCtx写回服务器的响应并执行类似于此的消息交换?

  1. 为什么来自ntlm代理处理程序的初始请求 - >服务器成功但没有从这个ntlm代理处理程序写入的连续响应。
  2. 我也在调试时看到,即使我在编写NTLMMessage1时关闭代理服务器,writeAndFlush的未来仍然是成功的。为什么writeAndFlush会在这种情况下成功?
  3. 任何指针都会非常有用。谢谢!

    NTLMProxyHandler.java

        import io.netty.buffer.ByteBuf;
        import io.netty.buffer.Unpooled;
        import io.netty.channel.ChannelHandlerContext;
        import io.netty.channel.ChannelPipeline;
        import io.netty.handler.codec.http.DefaultFullHttpRequest;
        import io.netty.handler.codec.http.DefaultFullHttpResponse;
        import io.netty.handler.codec.http.FullHttpResponse;
        import io.netty.handler.codec.http.HttpClientCodec;
        import io.netty.handler.codec.http.HttpContent;
        import io.netty.handler.codec.http.HttpHeaderNames;
        import io.netty.handler.codec.http.HttpHeaders;
        import io.netty.handler.codec.http.HttpMethod;
        import io.netty.handler.codec.http.HttpResponse;
        import io.netty.handler.codec.http.HttpResponseStatus;
        import io.netty.handler.codec.http.HttpVersion;
        import io.netty.handler.codec.http.LastHttpContent;
        import io.netty.handler.proxy.ProxyConnectException;
        import jcifs.ntlmssp.Type1Message;
        import jcifs.ntlmssp.Type2Message;
        import jcifs.ntlmssp.Type3Message;
        import jcifs.smb.NtlmContext;
        import jcifs.smb.NtlmPasswordAuthentication;
        import jcifs.util.Base64;
    
        import org.slf4j.Logger;
        import org.slf4j.LoggerFactory;
    
        import java.net.InetSocketAddress;
        import java.net.SocketAddress;
    
    
        public class NTLMProxyHandler extends AbstractProxyHandler {
    
            private String userName;
            private String password;
            private final static String DOMAIN      = "CORP";
            public static final String NTLM_Prefix = "NTLM";
    
            private static final Logger logger = LoggerFactory.getLogger(NTLMProxyHandler.class);
    
            private static int NTLMV2_FLAGS_TYPE3 = 0xa2888205;
            private HttpResponseStatus status;
            private HttpResponse response;
    
            private NtlmPasswordAuthentication ntlmPasswordAuthentication;
            private NtlmContext ntlmContext;
            private final HttpClientCodec codec = new HttpClientCodec();
    
            public NTLMProxyHandler(SocketAddress proxyAddress) {
                super(proxyAddress);
            }
    
            public NTLMProxyHandler(SocketAddress proxyAddress, String domain, String username, String password) {
                super(proxyAddress);
                setConnectTimeoutMillis(50000);
                this.userName = username;
                this.password = password;
                ntlmPasswordAuthentication = new NtlmPasswordAuthentication(DOMAIN, username, password);
                ntlmContext = new NtlmContext(ntlmPasswordAuthentication, true);
            }
    
            @Override
            public String protocol() {
                return "http";
            }
    
            @Override
            public String authScheme() {
                return "ntlm";
            }
    
            protected void addCodec(ChannelHandlerContext ctx) throws Exception {
                ChannelPipeline p = ctx.pipeline();
                String name = ctx.name();
                p.addBefore(name, (String)null, this.codec);
            }
    
            protected void removeEncoder(ChannelHandlerContext ctx) throws Exception {
                this.codec.removeOutboundHandler();
            }
    
            protected void removeDecoder(ChannelHandlerContext ctx) throws Exception {
                this.codec.removeInboundHandler();
            }
    
            @Override
            protected Object newInitialMessage(ChannelHandlerContext channelHandlerContext) throws Exception {
                InetSocketAddress raddr = this.destinationAddress();
                String rhost;
                if(raddr.isUnresolved()) {
                    rhost = raddr.getHostString();
                } else {
                    rhost = raddr.getAddress().getHostAddress();
                }
    
                String host = rhost + ':' + raddr.getPort();
                DefaultFullHttpRequest req = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.CONNECT, host, Unpooled.EMPTY_BUFFER, false);
                req.headers().set(HttpHeaderNames.HOST, host);
                req.headers().set("connection", "keep-alive");
    
    // This initial request successfully reaches the server !
                return req;
            }
    
            @Override
            protected boolean handleResponse(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
    
                if (o instanceof HttpResponse) {
                     response = (HttpResponse) o;
    
                }
                boolean finished = o instanceof LastHttpContent;
    
                if(finished) {
                    status = response.status();
                    logger.info("Status: " + status);
    
                    if (!response.headers().isEmpty()) {
                        for (String name: response.headers().names()) {
                            for (String value: response.headers().getAll(name)) {
                                logger.debug("Header: " + name + " = " + value);
                            }
                        }
                    }
                    if(status.code() == 407) {
                        negotiate(channelHandlerContext, response);
                    }
                    else if(status.code() == 200){
                        logger.info("Client: NTLM exchange complete. Authenticated !");
                    }
                    else {
                        throw new ProxyConnectException(this.exceptionMessage("status: " + this.status));
                    }
                }
    
                return finished;
            }
    
            private void negotiate(ChannelHandlerContext channelHandlerContext, HttpResponse msg) throws Exception{
                String ntlmHeader = msg.headers().get(HttpHeaderNames.PROXY_AUTHENTICATE);
    
                if(ntlmHeader.equalsIgnoreCase("NTLM")){
                    logger.info("Client: Creating NTLM Type1Message");
                    //Send Type1Message
                    byte[] rawType1Message = ntlmContext.initSecContext(new byte[]{}, 0, 0);
                    Type1Message type1Message = new Type1Message(rawType1Message);
    
                    FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
                    String proxyAuthHeader = Base64.encode(type1Message.toByteArray());
                    logger.info("Setting proxyAuthHeader = " + proxyAuthHeader);
                    response.headers().set(HttpHeaders.Names.PROXY_AUTHORIZATION, proxyAuthHeader);
    
                    ByteBuf byteBuf = Unpooled.buffer(rawType1Message.length);
                    byteBuf.writeBytes(response.content());
    
    //This is where the response is lost and never reaches the proxy server
                    sendToProxyServer(byteBuf);
                    // channelHandlerContext.writeAndFlush(response.content));
    
                } else if (ntlmHeader.contains(NTLM_Prefix)) {
                    logger.info("Client: Creating NTLM Type3Message");
                    //Send Type3 Message
    
                }
            }
        }
    

1 个答案:

答案 0 :(得分:-1)

我终于找到了问题所在。 NTLM代理处理程序在响应代理的消息时发送的是FullHTTPResponse而不是FullHTTPRequest。看起来Netty的管道正在丢弃作为响应写入的数据,这在日志中没有显示。

DefaultFullHttpRequest req = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.CONNECT, host, Unpooled.EMPTY_BUFFER, false);
req.headers().set(HttpHeaderNames.HOST, host);
req.headers().set(HttpHeaders.Names.PROXY_AUTHORIZATION, "type3message");

sendToProxyServer(req);