最近一个项目拿到客户那运行不了。原来我的这个项目要和另一个系统通过http的接口进行通讯。但在客户的生产环境中,那套系统将web应用的登录和Windows Domain的登录结合,做了一个sso单点登录(jcifs实现)。那么我必须要修改我的程序,好自动登录Windows Domain。
通过抓包分析,局域网使用的是NTLM 协议。
当通过浏览器访问被NTLM协议保护的资源的时候,NTLM的认证方式和流程如下:
1: C --> S GET ... 2: C <-- S 401 Unauthorized WWW-Authenticate: NTLM 3: C --> S GET ... Authorization: NTLM <base64-encoded type-1-message> 4: C <-- S 401 Unauthorized WWW-Authenticate: NTLM <base64-encoded type-2-message> 5: C --> S GET ... Authorization: NTLM <base64-encoded type-3-message> 6: C <-- S 200 Ok
Type-1消息包括机器名、Domain等
Type-2消息包括server发出的NTLM challenge
Type-3消息包括用户名、机器名、Domain、以及两个根据server发出的challenge计算出的response,这里response是基于challenge和当前用户的登录密码计算而得
PS:在第二步时,当浏览器接收到一个401 Unauthorized 的response ,会弹出该对话框让用户输入用户名、密码。(ie有可能会自动登录)我的程序(client)要和另个程序走http接口通讯(server),server再去ad验证域登录
httpclient 实现NTLM验证(当然你也可以自己实现协议)
HttpClient从version 4.1 开始完全支持NTLM authentication protocol(NTLMv1, NTLMv2, and NTLM2 ),文档的原话是“The NTLM authentication scheme is significantly more expensive in terms of computational overhead
and performance impact than the standard Basic and Digest schemes.”
但是使用起来还是非常的方便的。因为 NTLM 连接是有状态的,通常建议使用相对简单的方法触发NTLM 认证,比如GET或 HEAD, 而重用相同的连接来执行代价更大的方法,特别是它们包含请求实体,比如 POST或 PUT。
DefaultHttpClient httpclient = new DefaultHttpClient(); NTCredentials creds = new NTCredentials("user", "pwd", "myworkstation", "microsoft.com"); httpclient.getCredentialsProvider().setCredentials(AuthScop .ANY, creds); HttpHost target = new HttpHost("www.microsoft.com", 80, "http"); // 保证相同的内容来用于执行逻辑相关的请求 HttpContext localContext = new BasicHttpContext(); // 首先执行简便的方法。这会触发NTLM认证 HttpGet httpget = new HttpGet("/ntlm-protected/info"); HttpResponse response1 = httpclient.execute(target, httpget localContext); HttpEntity entity1 = response1.getEntity(); if (entity1 != null) { entity1.consumeContent(); } //之后使用相同的内容(和连接)执行开销大的方法。 HttpPost httppost = new HttpPost("/ntlm-protected/form"); httppost.setEntity(new StringEntity("lots and lots of data")) HttpResponse response2 = httpclient.execute(target, httppost, localContext); HttpEntity entity2 = response2.getEntity(); if (entity2 != null) { entity2.consumeContent(); }