第四章
4. HTTP身份验证
HttpClient提供了全力支持的HTTP标准规范以及一些广泛使用的非标准的认证计划,如NTLM和SPNEGO定义的身份验证方案。
4.1.用户凭据
任何用户身份验证的过程中,需要一组可以用来建立用户身份的凭据。在最简单的形式的用户认证可能只是一个用户名/密码对。 UsernamePasswordCredentials表示一组安全主体和密码以明文的凭证。这个实现是足够的HTTP标准规范所定义的标准身份验证方案。
用法如下:
UsernamePasswordCredentials creds = new UsernamePasswordCredentials("user", "pwd"); System.out.println(creds.getUserPrincipal().getName()); System.out.println(creds.getPassword());
输出:
user pwd
NTCredentials是一个Microsoft Windows的具体实施,包括除了用户名/密码对一组额外的Windows特定的属性,如用户域的名称。在Microsoft Windows网络中的同一个用户可以属于多个域,每个域有一组不同的授权。
用法如下:
NTCredentials creds = new NTCredentials("user", "pwd", "workstation", "domain"); System.out.println(creds.getUserPrincipal().getName()); System.out.println(creds.getPassword());
输出:
DOMAIN/user pwd
4.2.认证计划
AuthScheme接口代表一个抽象的面向挑战 - 响应认证方案。的身份验证方案预计将支持以下功能:
解析和处理的目标服务器发送回应请求受保护的资源的挑战。
提供处理过的挑战:性能认证计划的类型和参数,这样的境界,此认证方案是适用的,如果有
对于给定的一组的凭据和HTTP请求响应于实际授权挑战生成的授权字符串。
请注意,身份验证方案可能是有状态的,涉及一系列的挑战 - 响应交换。
HttpClient附带有几个AuthScheme实现:
Basic : 基本身份验证方案,如RFC 2617中定义。该验证方案是不安全的,都以明文形式传输的凭据。尽管它不安全,基本身份验证方案是完全足够的,如果结合使用TLS/ SSL加密。
Digest:摘要式身份验证方案,在RFC2617中定义的。比基本,摘要式身份验证方案更安全,并为那些不想通过TLS/ SSL加密的开销完整的运输安全的应用程序,可以是一个不错的选择。
NTLM: NTLM是由微软开发和优化Windows平台专有的身份验证方案。 NTLM被认为是比Digest更安全。
SPNEGO: 简单且受保护的GSSAPI协商机制(SPNEGO)是一个GSSAPI“伪机制”的用于协商了一些可能的机制之一。 SPNEGO的最明显的用途是在微软的HTTP Negotiate身份验证扩展。转让机制,包括NTLM和Kerberos,Active Directory支持。目前HttpClient的支持Kerberos子机构。
Kerberos: Kerberos身份验证的实施。
4.3. HTTP认证参数
这些参数可用于自定义HTTP认证过程和行为的个人认证计划:
ClientPNames.HANDLE_AUTHENTICATION
='http.protocol.handle-authentication': 定义身份验证是否应自动处理。这个参数期望得到一个java.lang.Boolean类型的值。如果该参数没有设置,HttpClient会自动处理身份验证。
AuthPNames.CREDENTIAL_CHARSET
='http.auth.credential-charset': 字符集定义的字符集进行编码时使用的用户凭据。这个参数期望得到一个java.lang.String类型的值。如果这个参数没有被设置,US-ASCII将被使用。
AuthPNames.TARGET_AUTH_PREF
='http.auth.target-scheme-pref': 定义与目标主机进行身份验证时,偏好支持AuthSchemes的顺序。这个参数期望得到一个java.util.Collection类型的值。预计该集合包含java.lang.String的实例代表一个ID的身份验证方案。
AuthPNames.PROXY_AUTH_PREF
='http.auth.proxy-scheme-pref': 定义的顺序偏好支持AuthSchemes的代理主机进行身份验证时。这个参数期望得到一个java.util.Collection类型的值。预计该集合包含java.lang.String的实例代表一个ID的身份验证方案。
例如,一个可以强制HttpClient的认证计划使用一个不同的优先顺序
用法如下:
DefaultHttpClient httpclient = new DefaultHttpClient(ccm, params); // Choose BASIC over DIGEST for proxy authentication List<String> authpref = new ArrayList<String>(); authpref.add(AuthPolicy.BASIC); authpref.add(AuthPolicy.DIGEST); httpclient.getParams().setParameter(AuthPNames.PROXY_AUTH_PREF, authpref);
4.4.认证模式注册表
HttpClient的注册表保持现有的认证计划,使用AuthSchemeRegistry类。默认情况下,以下每个方案都被登记:
AuthPolicy.BASIC:基本身份验证
AuthPolicy.DIGEST:摘要式身份验证
AuthPolicy.NTLM:NTLMv1,NTLMv2和NTLM2会话认证
AuthPolicy.SPNEGO:SPNEGO认证
AuthPolicy.KERBEROS:Kerberos身份验证
4.5.凭据提供
凭据供应商的目的是维护一组用户凭据,并能够产生一个特定的认证范围的用户凭据。认证范围包括主机名,端口号,领域名称和认证方案的名称。注册时提供的凭据凭据,可以提供一个通配符(任何主机的任何端口,任何领域,任何计划),而不是一个具体的属性值。提供的凭据,然后预计将能够找到最匹配的一个特定的范围,如果无法找到直接匹配。
HttpClient可以与任何凭据提供程序,这些程序实现了CredentialsProvider接口。默认的CredentialsProvider一个简单的实现名为BasicCredentialsProvider ,返回一个java.util.HashMap。
用法如下:
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]
4.6. HTTP认证和执行上下文
HttpClient依赖AuthState类来跟踪认证过程的状态的详细信息。 HttpClient的HTTP请求执行的过程中创建两个实例AuthState:一个目标主机认证,另外一个代理身份验证。如果目标服务器或代理服务器要求用户身份验证各自的AuthScope实例,那么将填充AuthScope,AuthScheme和Crednetials来用于身份验证过程中。可以通过检查AuthState来找出需要什么样的认证,是否匹配AuthScheme的执行被发现和提供的凭据是否设法找到了在给定的认证范围的用户凭据。
在HTTP请求执行的过程中,HttpClient的认证相关的对象添加到执行上下文:
ClientContext.AUTHSCHEME_REGISTRY
='http.authscheme-registry': AuthSchemeRegistry实例代表实际的认证模式注册表。此属性的值设置在本地范围内的优先级高于默认的。
ClientContext.CREDS_PROVIDER
='http.auth.credentials-provider': CookieSpec实例代表实际凭据供应商。此属性的值设置在本地范围内的优先级高于默认的。
ClientContext.TARGET_AUTH_STATE
='http.auth.target-scope': AuthState实例代表实际的目标身份验证状态。此属性的值设置在本地范围内的优先级高于默认的。
ClientContext.PROXY_AUTH_STATE
='http.auth.proxy-scope': AuthState实例代表实际的代理身份验证状态。此属性的值设置在本地范围内的优先级高于默认的。
ClientContext.AUTH_CACHE
='http.auth.auth-cache': AuthCache实例代表实际的认证数据缓存。此属性的值设置在本地范围内的优先级高于默认的。
本地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 state: " + proxyAuthState.getState()); 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 state: " + targetAuthState.getState()); System.out.println("Target auth scheme: " + targetAuthState.getAuthScheme()); System.out.println("Target auth credentials: " + targetAuthState.getCredentials());
4.7.高速缓存的认证数据
在4.1版中,HttpClient的自动缓存主机的信息,它已成功验证。请注意,必须使用相同的执行上下文执行逻辑相关的请求,以缓存的身份验证数据从一个请求传播到另一个。尽快的执行上下文超出范围的认证数据将会丢失。
4.8.抢占认证
HttpClient不支持开箱即用先发制人的认证,因为如果使用不当或使用不当的先发制人的认证可能会导致重大的安全问题,如发送用户凭据以明文未授权的第三方。因此,我们希望用户先发制人的认证与安全风险的背景下,其具体的应用环境来评估潜在的好处。
Nonethess可以配置HttpClient的先发制人预先填充的认证数据缓存进行身份验证。
用法如下:
HttpHost targetHost = new HttpHost("localhost", 80, "http"); DefaultHttpClient httpclient = new DefaultHttpClient(); httpclient.getCredentialsProvider().setCredentials( new AuthScope(targetHost.getHostName(), targetHost.getPort()), new UsernamePasswordCredentials("username", "password")); // 创建一个 AuthCache 实例 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 BasicHttpContext localcontext = new BasicHttpContext(); localcontext.setAttribute(ClientContext.AUTH_CACHE, authCache); HttpGet httpget = new HttpGet("/"); for (int i = 0; i < 3; i++) { HttpResponse response = httpclient.execute(targetHost, httpget, localcontext); HttpEntity entity = response.getEntity(); EntityUtils.consume(entity); }
4.9. NTLM身份验证
版本4.1 HttpClient提供了完整的支持NTLMv1,NTLMv2工作,NTLM2会话认证的开箱即用。仍然可以继续使用外部的NTLM引擎,如JCIFS库开发的Samba项目作为Windows的互操作性程序套件的一部分。
4.9.1. NTLM连接持久
NTLM身份验证方案是更昂贵的计算开销和性能的影响比标准基本和摘要方案。这可能是为什么微软选择NTLM身份验证方案有状态的主要原因之一。也就是说,一旦通过认证,用户身份相关联,连接其整个生命周期。状态特性的NTLM的连接,连接的持久性更复杂,原因很明显的,持续的NTLM连接可能不能再使用的用户用不同的用户身份。在HttpClient附带的标准连接管理器是完全有能力管理状态连接。然而,这是极为重要的,逻辑相关的请求在同一会话中使用相同的执行上下文中,以便使他们认识到当前用户的身份。否则,HttpClient会为每个HTTP请求对NTLM受保护的资源的创建一个新的HTTP连接。对于有状态的HTTP连接的详细讨论,请参阅本节。
由于NTLM连接是有状态的,一般建议使用相对廉价的方法,比如GET或HEAD触发NTLM身份验证,并重新使用相同的连接,以执行更昂贵的方法,特别是那些附上请求的实体,如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"); // Make sure the same context is used to execute logically related requests HttpContext localContext = new BasicHttpContext(); // Execute a cheap method first. This will trigger NTLM authentication HttpGet httpget = new HttpGet("/ntlm-protected/info"); HttpResponse response1 = httpclient.execute(target, httpget, localContext); HttpEntity entity1 = response1.getEntity(); EntityUtils.consume(entity1); // 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")); HttpResponse response2 = httpclient.execute(target, httppost, localContext); HttpEntity entity2 = response2.getEntity(); EntityUtils.consume(entity2);
4.10. SPNEGO / Kerberos身份验证
简单且受保护的GSSAPI协商机制(SPNEGO)的设计,以便结束时既不知道什么其他的可以使用/提供认证服务。这是最常用做Kerberos身份验证。它可以换其他的机制,但是目前的HttpClient的版本是专仅使用Kerberos记。
4.10.1. SPNEGO HttpClient的支持
SPNEGO认证方案是使用Sun Java1.5及更高版本兼容。然而,使用Java>=1.6强烈推荐,因为它支持SPNEGO身份验证,更彻底。
Sun的JRE提供的支持类来完成几乎所有的Kerberos和SPNEGO令牌处理。这意味着,有很多的设置是为GSS类。 SPNegoScheme是一个简单的类来处理编组的标记,读,写正确的标题。
最好的办法是例子抢KerberosHttpClient.java的文件中,并设法得到它的工作。有很多可能发生的问题,但如果幸运的话,它会工作,没有太多的问题。它也应该提供一些输出调试。
在Windows中默认使用的登录凭据,这可以被覆盖使用“kinit的”,例如$ JAVA_HOME / BIN / kinit的testuser@AD.EXAMPLE.NET,这是非常有用的测试和调试的问题。删除通过使用kinit创建的缓存文件恢复到Windows Kerberos缓存。
确保,在krb5.conf文件中列出domain_realms。这是问题的主要来源。
tips:
客户端Web浏览器的HTTP GET资源。
Web服务器返回HTTP 401状态和头的WWW-Authenticate:面议
客户端产生一个NegTokenInit,即base64编码,并重新提交Authorization头的GET:授权:协商<base64 encoding>。
服务器解码NegTokenInit,提取的的支持MechTypes(在我们的例子中唯一的Kerberos V5),确保它是预期的,,然后提取MechToken,(Kerberos令牌),并验证它。
如果需要更多的处理是另一个HTTP 401返回给客户端的WWW-Authenticate头提供更多的数据。客户端需要的信息,并生成另一个令牌传递这回在Authorization头,直到完成。
当客户端已通过身份验证的Web服务器返回的HTTP 200状态,最后的WWW-Authenticate头和页面内容。
4.10.2. GSS / Java的Kerberos设置
本文档假定你使用的是Windows,但许多信息,以及适用于UNIX。
org.ietf.jgss类有很多可能的配置参数,主要在krb5.conf/krb5.ini文件。一些更多的信息的格式在http://web.mit.edu/kerberos/krb5-1.4/krb5-1.4.1/doc/krb5-admin/krb5.conf.html。
4.10.3. login.conf文件
下面的配置是在Windows XP中对IIS和JBoss谈判模块的基本设置。
可用于系统属性java.security.auth.login.config的login.conf的文件指向。
login.conf中的内容可能看起来像下面这样:
com.sun.security.jgss.login { com.sun.security.auth.module.Krb5LoginModule required client=TRUE useTicketCache=true; }; com.sun.security.jgss.initiate { com.sun.security.auth.module.Krb5LoginModule required client=TRUE useTicketCache=true; }; com.sun.security.jgss.accept { com.sun.security.auth.module.Krb5LoginModule required client=TRUE useTicketCache=true; };
4.10.4. krb5.conf中/ krb5.ini文件
如果未指定,则系统将使用默认的。覆盖如果需要的话,设置的系统属性java.security.krb5.conf点到自定义的krb5.conf文件。
krb5.conf的内容可能看起来像下面这样:
[libdefaults] default_realm = AD.EXAMPLE.NET udp_preference_limit = 1 [realms] AD.EXAMPLE.NET = { kdc = KDC.AD.EXAMPLE.NET } [domain_realms] .ad.example.net=AD.EXAMPLE.NET ad.example.net=AD.EXAMPLE.NET
4.10.5. Windows的具体配置
为了让Windows使用当前用户的车票,系统属性javax.security.auth.useSubjectCredsOnly必须被设置为false,并且Windows注册表键allowtgtsessionkey应正确设置,以允许发送会话密钥在Kerberos票证授予票。
在Windows Server 2003和Windows2000 SP4,这里是所需的注册表设置:
HKEY_LOCAL_MACHINE/System/CurrentControlSet/Control/Lsa/Kerberos/Parameters Value Name: allowtgtsessionkey Value Type: REG_DWORD Value: 0x01
下面是在Windows XP SP2的注册表设置的位置:
HKEY_LOCAL_MACHINE/System/CurrentControlSet/Control/Lsa/Kerberos/ Value Name: allowtgtsessionkey Value Type: REG_DWORD Value: 0x01