2017-05-03 6 views
1

NTLMメッセージ交換を実行してWebプロキシでクライアントを認証できるNTLMProxyHandlerをNettyに実装しようとしていますか?Netty ProxyHandler writeAndFlushがサーバーへの応答を書いていません

NTLMProxyHandlerは、NettyのProxyHandlerクラスを拡張しています。これにより、初期HTTP要求がプロキシハンドラによってトリガされ、これが私が作成したモックプロキシサーバに到達します。プロキシサーバーはこの要求を読み取り、407のプロキシ認証要求応答で応答します。

NTLMProxyHandlerはこの応答をクライアント側で読み取り、新しいNTLM Type1Messageを準備し、応答をサーバーに再度書き戻します。私が直面している問題は、チャネル未来の成功ハンドラが呼び出されても、このリクエストは私のプロキシサーバに送信されないということです。

ロギングでNettyパッケージを有効にしましたが、ntlmプロキシハンドラから2回目に書き込まれた応答のみが失われた理由がわかりません。

私は、channelRead()から渡されたchannelHandlerCtxを使用するだけでなく、Netty ProxyHandlerのsendToProxyServer(msg)を使用しようとしました。どちらの場合も、writeAndFlushは実行されますが、応答はサーバーに到達せず、サーバーはタイムアウトします。

誰かがchannelHandlerCtxを使用してサーバーに応答を書き戻し、これと同様のメッセージ交換を実行しましたか?

  1. ntlmプロキシハンドラからサーバへの最初の要求は成功しますが、このntlmプロキシハンドラからの連続応答ではありません。
  2. NTLMMessage1を書き込んでいるときにプロキシサーバーをシャットダウンしても、writeAndFlushの将来はまだ成功しています。この場合、writeAndFlushはなぜ成功しますか?

すべてのポインタは本当に役に立ちます。ありがとう!

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 

      } 
     } 
    } 
+0

writeAndFlush(...)呼び出しによって返されたChannelFutureにChannelFutureListenerを添付しましたか?これにより、何らかの理由で書き込みが失敗したかどうかを確認できます。 –

+0

@ NormanMaurerはい、リスナーがoperationComplete()で通知されると、io.netty.util.concurrent.DefaultPromise#SUCCESSが表示されます。実際には、私が拡張するNettyのProxyHandlerもsendtoProxyServer中にwriteListenerを添付し、成功します。 – ram

答えて

0

私は最終的に問題を考え出しました。プロキシのメッセージに応答するNTLMプロキシハンドラは、FullHTTPRequestの代わりにFullHTTPResponseを送信していました。 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); 
+0

これは失敗を引き起こしたはずです。奇妙な –

関連する問題