最近用HttpClient做一个工具做代理访问服务器的WebService,由于服务器是采用NTLM验证的,再加上网上的很多资料大多是关于HttpClient 3的,折磨了好久才搞定。这里记录一下,供以后参考使用。
关于文中内容,我不按照手册上的写法,而是整理成小Tips,希望看起来简单轻松一点。
1, HttpClient 4.x 库可以自己处理Cookie
这一点误导了我很久,HttpClient 4.x提供了CookieStore来帮助程序员管理本地Cookie,并且在Http请求的过程中,如果服务器的返回中带有Set-Cookie这样的字段,当前HttpClient的CookieStore中就会增加一条新的Cookie。
(注意:误导来了)很多网上的资料认为即便当前的HttpClient的CookieStore中有服务器返回的Cookie,HttpClient在下次请求的时候也不会主动加上,需要用户自己手动添加,添加的方法有两种,一种是直接SetHeader,一种是设置CookieStore,比如:
if (!httpclient.getCookieStore().getCookies().isEmpty()) { Cookie getCookie = httpclient.getCookieStore().getCookies().get(0); BasicCookieStore cookieStore =newBasicCookieStore(); cookieStore.addCookie(getCookie); httpclient.setCookieStore(cookieStore); }
HttpClient是否在下次请求中携带从服务器端请求来的Cookie,完全是由设置决定的。
httpclient.getParams.setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.BEST_MATCH)
如果设置为Cookie策略为BEST_MATCH的话,HttpClient会在请求中携带由服务器返回的Cookie。如果按照上面的写法,手动添加了CookieStore,那么就会在下次请求中夹带着两个Cookie,Cookie和Cookie2。
2,NTLM如何使用HttpClient模拟
NTLM分为很多很多版本,HttpClient 3.x是支持NTLMv1,而因为legal的原因,HttpClient 4.0.x不再支持,转而使用jcifs库来支持NTLM,但是估计还是只支持v1,而没有继续扩展支持更高级的验证身份。
如何使用NTLM验证呢?首先NTLM过程至少要3次请求才能完成一个资源的请求,我们不需要手动模拟这三个请求,只需要使用NTLM的功能就可以了。
配置如下:
httpclient = new DefaultHttpClient(); httpclient.getAuthSchemes().register("ntlm",newNTLMSchemeFactory()); httpclient.getCredentialsProvider().setCredentials(AuthScope.ANY, newNTCredentials(asi.getUser(), asi.getPass(), asi.SERVER_NAME, asi.getDomain())); List<String> authpref =newArrayList<String>(); authpref.add(AuthPolicy.NTLM); httpclient.getParams().setParameter(AuthPNames.TARGET_AUTH_PREF, authpref);
其中的NTLMSchemeFactory实现如下:
package ntml;
import jcifs.ntlmssp.NtlmFlags;
import jcifs.ntlmssp.Type1Message;
import jcifs.ntlmssp.Type2Message;
import jcifs.ntlmssp.Type3Message;
import jcifs.util.Base64;
import org.apache.http.impl.auth.NTLMEngine;
import org.apache.http.impl.auth.NTLMEngineException;
import java.io.IOException;
public final class JCIFSEngineimplementsNTLMEngine {
privatestaticfinal int TYPE_1_FLAGS =
NtlmFlags.NTLMSSP_NEGOTIATE_56 |
NtlmFlags.NTLMSSP_NEGOTIATE_128 |
NtlmFlags.NTLMSSP_NEGOTIATE_NTLM2 |
NtlmFlags.NTLMSSP_NEGOTIATE_ALWAYS_SIGN |
NtlmFlags.NTLMSSP_REQUEST_TARGET;
publicString generateType1Msg(finalString domain,finalString workstation)
throwsNTLMEngineException {
finalType1Message type1Message =newType1Message(TYPE_1_FLAGS, domain, workstation);
returnBase64.encode(type1Message.toByteArray());
}
publicString generateType3Msg(finalString username,finalString password,
finalString domain,finalString workstation,finalString challenge)
throwsNTLMEngineException {
Type2Message type2Message;
try{
type2Message =newType2Message(Base64.decode(challenge));
}catch(finalIOException exception) {
thrownewNTLMEngineException("Invalid NTLM type 2 message", exception);
}
finalinttype2Flags = type2Message.getFlags();
finalinttype3Flags = type2Flags
& (0xffffffff^ (NtlmFlags.NTLMSSP_TARGET_TYPE_DOMAIN | NtlmFlags.NTLMSSP_TARGET_TYPE_SERVER));
finalType3Message type3Message =newType3Message(type2Message, password, domain,
username, workstation, type3Flags);
returnBase64.encode(type3Message.toByteArray());
}
}
3, NTLM中的Cookie问题
由于NTLM的几个过程中,我们无法插一脚,所以实际的NTLM认证过程中,对于哪些服务器产生产生Cookie,并要求客户端携Cookie请求以让服务器绑定Session的情况,如果不利用tips 1里面的自动携带Cookie,可能就不能做什么了。这也是前一段时间卡住最主要的内容。
4,下一阶段准备尝试使用HttpClient编写一个库来爬Baidu或者douban的音乐...已经有Python的了,哈哈。。。