尝试在Netty中实现NTLMProxyHandler,它可以执行NTLM消息交换并使用Web代理对客户端进行身份验证。
NTLMProxyHandler扩展了Netty的ProxyHandler类。因此,初始HTTP请求由代理处理程序触发,并且到达我创建的模拟代理服务器。代理服务器读取此请求并以407代理身份验证所需响应进行响应。
NTLMProxyHandler在客户端读取此响应并准备新的NTLM Type1Message并再次将响应写回服务器。我面临的问题是,虽然调用了通道future的成功处理程序,但此请求永远不会发送到我的代理服务器。
我在日志记录中启用了Netty软件包,但无法弄清楚为什么只有第二次从ntlm代理处理程序写入的响应丢失。
我尝试使用Netty ProxyHandler的sendToProxyServer(msg)以及使用从channelRead()传递的channelHandlerCtx。在这两种情况下,writeAndFlush都已完成,但响应永远不会到达服务器,服务器也会超时。
有没有人使用channelHandlerCtx写回服务器的响应并执行类似于此的消息交换?
任何指针都会非常有用。谢谢!
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 :(得分:-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);