4.4 HTTP验证和执行上下文
HttpClient依赖于AuthState类来追踪验证进程的状态的详细信息。HttpClient在执行HTTP请求执行时,创建AuthState的两个实例:一个对目标主机认证,另外一个用于代理认证。一旦目标主机或者代理要求用户验证,对应的AuthState实例将会在验证过程中被AuthScope,AuthScheme和Crednetials填充。这个AuthState可以被检查用于找出哪种类型要求验证,是否对应的AuthScheme被找到,以及凭证提供者可以找到在给定的验证范围内找到用户凭证。
在HTTP请求过程中HttpClient添加了以下的验证关系对象到执行上下文中:
- Lookup 实例代表实际的验证方案注册。这个属性值在本地上下文中优先与默认值被设置。
- CredentialsProvider实例代表实际的凭证提供者。这个属性值本地上下文中优先与默认值被设置。
- AuthState实例代表实际的目标验证状态。这个属性值本地上下文中优先与默认值被设置。
- AuthState实例代表实际的代理验证状态。这个属性值本地上下文中优先与默认值被设置。
- AuthCache实例代表实际的验证数据缓存。这个属性值本地上下文中优先与默认值被设置。
这个本地的HttpContext对象可以在请求执行之前定制HTTP验证上下文或者在请求被执行后检查它的状态:
CloseableHttpClient httpclient = <...>
CredentialsProvider credsProvider = <...>
Lookup<AuthSchemeProvider> authRegistry = <...>
AuthCache authCache = <...>
HttpClientContext context = HttpClientContext.create();
context.setCredentialsProvider(credsProvider);
context.setAuthSchemeRegistry(authRegistry);
context.setAuthCache(authCache);
HttpGet httpget = new HttpGet("http://somehost/");
CloseableHttpResponse response1 = httpclient.execute(httpget, context);
<...>
AuthState proxyAuthState = context.getProxyAuthState();
System.out.println("Proxy auth state: " + proxyAuthState.getState());
System.out.println("Proxy auth scheme: " + proxyAuthState.getAuthScheme());
System.out.println("Proxy auth credentials: " + proxyAuthState.getCredentials());
AuthState targetAuthState = context.getTargetAuthState();
System.out.println("Target auth state: " + targetAuthState.getState());
System.out.println("Target auth scheme: " + targetAuthState.getAuthScheme());
System.out.println("Target auth credentials: " + targetAuthState.getCredentials());
4.5 验证数据缓存
从4.1版本开始HttpClient自动缓存验证成功的主机的信息。请注意为了使缓存的验证数据从一个请求传播到另外一个请求上,必须使用相同的上下文来执行相关的逻辑请求。如果执行上下文超出了范围,验证数据将会很快丢失。
4.6 抢占式验证
HttpClient 不支持抢占试验证因为如果错误使用抢占式验证会导致明显的安全问题,比如明文发送用户凭证到第三方没有验证的组织。除此之外用户被期望在他们自己的应用环境中自己来评估抢占式验证的潜在好处和安全风险。
尽管如此可以通过配置HttpClient预填充验证数据到缓冲中来实现抢占式验证。
CloseableHttpClient httpclient = <…>
HttpHost targetHost = new HttpHost("localhost", 80, "http");
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(
new AuthScope(targetHost.getHostName(), targetHost.getPort()),
new UsernamePasswordCredentials("username", "password"));
// Create AuthCache instance
AuthCache authCache = new BasicAuthCache();
// Generate BASIC scheme object and add it to the local auth cache
BasicScheme basicAuth = new BasicScheme();
authCache.put(targetHost, basicAuth);
// Add AuthCache to the execution context
HttpClientContext context = HttpClientContext.create();
context.setCredentialsProvider(credsProvider);
context.setAuthCache(authCache);
HttpGet httpget = new HttpGet("/");
for (int i = 0; i < 3; i++) {
CloseableHttpResponse response = httpclient.execute(
targetHost, httpget, context);
try {
HttpEntity entity = response.getEntity();
} finally {
response.close();
}
}
4.7 NTLM验证
自从4.1HttpClient版本对NTLMv1, NTLMv2, and NTLM2会话验证提供完全的支持。外部的NTML引擎比如由Samba项目开发的JCIFS库作为其Windows互操作性程序套件的一部分仍然可以被使用。
4.7.1 NTLM连接持久化
NTLM验证方案比标准的Basic和Digest方案要明显消耗更多的计算性能。这也许是微软选择使NTLM验证方案状态话的主要原因。只要验证成功了,这个用户的标志会和它整个连接的寿命相关。NTLM有状态的特性使连接持久化更加复杂,一个明显的原因:连接持久化的NTLM不能被不同的用户标识进行重用。无论如何在同一个会话中逻辑相关的请求使用相同的执行上下文来保证它们意识到当前用户的身份。否则HttpClient将会针对每一个NTLM保护请求的每一个Http请求创建一个新的HTTP连接。对于状态话的HTTP请求的详细讨论请参考这个页面。
由于NTLM是状态话的,它一般推荐使用相对便宜的方式来触发NTLM验证,比如GET或者HEAD,以及复用相同的连接来执行更加昂贵的操作,特别是包含了请求实体的比如POST或者PUT。
CloseableHttpClient httpclient = <...>
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(AuthScope.ANY,
new NTCredentials("user", "pwd", "myworkstation", "microsoft.com"));
HttpHost target = new HttpHost("www.microsoft.com", 80, "http");
// Make sure the same context is used to execute logically related requests
HttpClientContext context = HttpClientContext.create();
context.setCredentialsProvider(credsProvider);
// Execute a cheap method first. This will trigger NTLM authentication
HttpGet httpget = new HttpGet("/ntlm-protected/info");
CloseableHttpResponse response1 = httpclient.execute(target, httpget, context);
try {
HttpEntity entity1 = response1.getEntity();
} finally {
response1.close();
}
// Execute an expensive method next reusing the same context (and connection)
HttpPost httppost = new HttpPost("/ntlm-protected/form");
httppost.setEntity(new StringEntity("lots and lots of data"));
CloseableHttpResponse response2 = httpclient.execute(target, httppost, context);
try {
HttpEntity entity2 = response2.getEntity();
} finally {
response2.close();
}