任何用户身份验证的过程都需要一组可以用于建立用户身份的凭据。用户凭证的最简单的形式可以仅仅是用户名/密码对。UsernamePasswordCredentials代表了一组包含安全规则和明文密码的凭据。这个实现对由HTTP标准规范中定义的标准认证模式是足够的
UsernamePasswordCredentials creds = new UsernamePasswordCredentials("user", "pwd");System.out.println(creds.getUserPrincipal().getName());System.out.println(creds.getPassword());
输出内容为:
userpwd
NTCredentials是微软Windows指定的实现,它包含了除了用户名/密码对外,一组额外的Windows指定的属性,比如用户域名的名字,比如在微软的Windows网络中,相同的用户使用不同设置的认证可以属于不同的域。
NTCredentials creds = new NTCredentials("user", "pwd", "workstation", "domain");System.out.println(creds.getUserPrincipal().getName());System.out.println(creds.getPassword());
输出内容为:
DOMAIN/userpwd
凭据提供器意来维护一组用户凭据,还有能够对特定认证范围生产用户凭据。认证范围包括主机名,端口号,领域名称和认证模式名称。当使用凭据提供器来注册凭据时,我们可以提供一个通配符(任意主机,任意端口,任意领域,任意模式)来替代确定的属性值。如果直接匹配没有发现,凭据提供器期望被用来发现最匹配的特定范围。
CredentialsProvider credsProvider = new BasicCredentialsProvider();credsProvider.setCredentials(new AuthScope("somehost", AuthScope.ANY_PORT),new UsernamePasswordCredentials("u1", "p1"));credsProvider.setCredentials(new AuthScope("somehost", 8080),new UsernamePasswordCredentials("u2", "p2"));credsProvider.setCredentials(new AuthScope("otherhost", 8080, AuthScope.ANY_REALM, "ntlm"),new UsernamePasswordCredentials("u3", "p3"));System.out.println(credsProvider.getCredentials(new AuthScope("somehost", 80, "realm", "basic")));System.out.println(credsProvider.getCredentials(new AuthScope("somehost", 8080, "realm", "basic")));System.out.println(credsProvider.getCredentials(new AuthScope("otherhost", 8080, "realm", "basic")));System.out.println(credsProvider.getCredentials(new AuthScope("otherhost", 8080, null, "ntlm")));
输出内容为:
[principal: u1][principal: u2]null[principal: u3]
HttpClient依赖于AuthState类来跟踪关于认证过程状态的详细信息。在HTTP请求执行过程中,HttpClient创建2个AuthState的实例:一个对于目标主机认证,另外一个对于代理认证。如果目标服务器或代理需要用户认证,那么各自的AuthState实例将会被在认证处理过程中使用的AuthScope,AuthScheme和Crednetials来填充。AuthState可以被检查来找出请求的认证是什么类型的,是否匹配AuthScheme的实现,是否凭据提供器对给定的认证范围去找用户凭据。
在HTTP请求执行的过程中,HttpClient添加了下列和认证相关的对象到执行上下文中:
本地的HttpContext对象可以用于定制HTTP认证内容,并先于请求执行或在请求被执行之后检查它的状态:
HttpClient httpclient = new DefaultHttpClient();HttpContext localContext = new BasicHttpContext();HttpGet httpget = new HttpGet("http://localhost:8080/");HttpResponse response = httpclient.execute(httpget, localContext);AuthState proxyAuthState = (AuthState) localContext.getAttribute(ClientContext.PROXY_AUTH_STATE);System.out.println("Proxy auth scope: " + proxyAuthState.getAuthScope());System.out.println("Proxy auth scheme: " + proxyAuthState.getAuthScheme());System.out.println("Proxy auth credentials: " + proxyAuthState.getCredentials());AuthState targetAuthState = (AuthState) localContext.getAttribute(ClientContext.TARGET_AUTH_STATE);System.out.println("Target auth scope: " + targetAuthState.getAuthScope());System.out.println("Target auth scheme: " + targetAuthState.getAuthScheme());System.out.println("Target auth credentials: " + targetAuthState.getCredentials());
HttpClient不支持开箱的抢占认证,因为滥用或重用不正确的抢占认证可能会导致严重的安全问题,比如将用户凭据以明文形式发送给未认证的第三方。因此,用户期望评估抢占认证和在它们只能应用程序环境内容安全风险潜在的好处,而且要求使用如协议拦截器的标准HttpClient扩展机制添加对抢占认证的支持。
这是一个简单的协议拦截器,如果没有企图认证,来抢先引入BasicScheme的实例到执行上下文中。请注意拦截器必须在标准认证拦截器之前加入到协议处理链中。
HttpRequestInterceptor preemptiveAuth = new HttpRequestInterceptor() {public void process(final HttpRequest request,final HttpContext context) throws HttpException, IOException {AuthState authState = (AuthState) context.getAttribute(ClientContext.TARGET_AUTH_STATE);CredentialsProvider credsProvider = (CredentialsProvider) context.getAttribute(ClientContext.CREDS_PROVIDER);HttpHost targetHost = (HttpHost) context.getAttribute(ExecutionContext.HTTP_TARGET_HOST);// 如果没有初始化auth模式if (authState.getAuthScheme() == null) {AuthScope authScope = new AuthScope(targetHost.getHostName(),targetHost.getPort());// 获得匹配目标主机的凭据Credentials creds = credsProvider.getCredentials(authScope);// 如果发现了,抢先生成BasicSchemeif (creds != null) {authState.setAuthScheme(new BasicScheme());authState.setCredentials(creds);}}}};DefaultHttpClient httpclient = new DefaultHttpClient();// 作为第一个拦截器加入到协议链中httpclient.addRequestInterceptor(preemptiveAuth, 0);
当前HttpClient没有提对开箱的NTLM认证模式的支持也可能永远也不会。这个原因是法律上的而不是技术上的。然而,NTLM认证可以使用外部的NTLM引擎比如JCIFS[http://jcifs.samba.org/]来开启,类库由Samba[http://www.samba.org/]项目开发,作为它们Windows的交互操作程序套装的一部分。要获取详细内容请参考HttpClient发行包中包含的NTLM_SUPPORT.txt文档。
NTLM认证模式是在计算开销方面昂贵的多的,而且对标准的Basic和Digest模式的性能影响也很大。这很可能是为什么微软选择NTLM认证模式为有状态的主要原因之一。也就是说,一旦认证通过,用户标识是和连接的整个生命周期相关联的。NTLM连接的状态特性使得连接持久化非常复杂,对于明显的原因,持久化NTLM连接不能被使用不同用户标识的用户重用。标准的连接管理器附带HttpClient是完全能够管理状态连接的。而逻辑相关的,使用同一session和执行上下文为了让它们了解到当前的用户标识的请求也是极为重要的。否则,HttpClient将会终止对每个基于NTLM保护资源的HTTP请求创建新的HTTP连接。要获取关于有状态的HTTP连接的详细讨论,请参考这个部分。
因为NTLM连接是有状态的,通常建议使用相对简单的方法触发NTLM认证,比如GET或HEAD,而重用相同的连接来执行代价更大的方法,特别是它们包含请求实体,比如POST或PUT。
DefaultHttpClient httpclient = new DefaultHttpClient();NTCredentials creds = new NTCredentials("user", "pwd", "myworkstation", "microsoft.com");httpclient.getCredentialsProvider().setCredentials(AuthScope.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();}