HttpClient 4.0.x Tips

最近用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 = new BasicCookieStore();	
                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", new NTLMSchemeFactory());
        httpclient.getCredentialsProvider().setCredentials(AuthScope.ANY, 
                new NTCredentials(asi.getUser(), asi.getPass(), asi.SERVER_NAME, asi.getDomain()));
        List<String> authpref = new ArrayList<String>();
        authpref.add(AuthPolicy.NTLM);  
        httpclient.getParams().setParameter(AuthPNames.TARGET_AUTH_PREF, authpref);

其中的NTLMSchemeFactory实现如下:

package ntml;
import org.apache.http.auth.AuthScheme;
import org.apache.http.auth.AuthSchemeFactory;
import org.apache.http.impl.auth.NTLMScheme;
import org.apache.http.params.HttpParams;
public class NTLMSchemeFactory implements AuthSchemeFactory {
    public AuthScheme newInstance(final HttpParams params) {
        return new NTLMScheme(new JCIFSEngine());
    }
}
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 JCIFSEngine implements NTLMEngine {
    private static final 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;
	
    public String generateType1Msg(final String domain, final String workstation)	
            throws NTLMEngineException {	
        final Type1Message type1Message = new Type1Message(TYPE_1_FLAGS, domain, workstation);
        return Base64.encode(type1Message.toByteArray());
    }	
    public String generateType3Msg(final String username, final String password,	
            final String domain, final String workstation, final String challenge)	
            throws NTLMEngineException {
        Type2Message type2Message;	
        try {	
            type2Message = new Type2Message(Base64.decode(challenge));	
        } catch (final IOException exception) {
            throw new NTLMEngineException("Invalid NTLM type 2 message", exception);
        }
        final int type2Flags = type2Message.getFlags();
        final int type3Flags = type2Flags	
                & (0xffffffff ^ (NtlmFlags.NTLMSSP_TARGET_TYPE_DOMAIN | NtlmFlags.NTLMSSP_TARGET_TYPE_SERVER));
        final Type3Message type3Message = new Type3Message(type2Message, password, domain,
                username, workstation, type3Flags);	
        return Base64.encode(type3Message.toByteArray());	
    }
}

  

3, NTLM中的Cookie问题

由于NTLM的几个过程中,我们无法插一脚,所以实际的NTLM认证过程中,对于哪些服务器产生产生Cookie,并要求客户端携Cookie请求以让服务器绑定Session的情况,如果不利用tips 1里面的自动携带Cookie,可能就不能做什么了。这也是前一段时间卡住最主要的内容。

 

4,下一阶段准备尝试使用HttpClient编写一个库来爬Baidu或者douban的音乐...已经有Python的了,哈哈。。。

posted @ 2011-12-06 16:24  MMJX  阅读(1817)  评论(0编辑  收藏  举报