HttpClient对HTTP标准规范中定义的认证机制和非标准的认证机制如NTLM和SPNEGO提供了全面的支持。
4.1 用户凭证
大部分的用户认证过程都需要一组凭证用于鉴定用户的身份,用户凭证最简单的方式就是一组 用户名/密码 对,UsernamePasswordCredentials用明文形式表示一组安全主体和密码凭证,该类一般可满足HTTP标准规范中的认证机制的要求。
//安全主体user 密码凭证pwd 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网络中同一个用户可以属于不同的域,每一个域都有不同的认证机制。
//wordstation指发出请求的机器名称,一般是电脑名,domain是域的名称 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 request生成认证字符串用于认证挑战的应答。
请注意认证机制可能是有状态的,应该避免一系列的挑战-应答交换。
HttpClient附带多个AuthScheme的实现:
Basic: 如RFC2617所定义的基本认证机制,由于凭证通过明文传递,该认证机制是不安全的,尽管其安全性比较低,但是如果与TLS/SSL加密协同工作,其认证机制仍然是可以接受的。
Digest: 如RFC2617所定义的Digest认证机制,Digest认证机制明显比Basic要更安全,该机制对于那些顾虑TLS/SSL加密所带来的开销的应用会是一个比较好的选择。
NTLM: NTLM是一个微软开发的并且针对Windows平台进行了优化的专有认证机制,NTML被认为比Digest更安全。
SPNEGO: SPNEGO(Simple and Protected GSSAPI Negotiation Mechanism)是一个GSSAPI伪机制,用于协商那些潜在的真正机制,SPNEGO最明显的用途是在微软的HTTP Negotiate验证扩展,可协商的子机制包含活动目录中支持的NTML和Kerberos,现在HttpClient只支持Kerberos子机制。
Kerberos: Kerberos认证的实现。
4.3 凭证提供器
凭证提供器旨在维护一组用户凭证并且能够为特定的认证域生产用户凭证,认证领域包含一个主机名,一个端口名,一个域名称和一个认证机制名,当使用凭证提供器(能够提供任意的主机,任意端口,任意域名和任意机制)来替代具体属性值注册凭证时,其期望能够为某个特定认证域找到一个最贴近的匹配值(在直接匹配找不到的情况下)。
HttpClient可以与任意实现了CredentialsProvider接口的实际凭证提供器协同工作,默认的CredentialsProvider实现叫做BasicCredentialsProvider,其是一个依赖于java.util.HashMap的简单实现。
//凭证注册器 CredentialsProvider credsProvider = new BasicCredentialsProvider(); //注册1 somehost主机名 任意PORT 指定用户U1 密码P1 credsProvider.setCredentials( new AuthScope("somehost", AuthScope.ANY_PORT), new UsernamePasswordCredentials("u1", "p1")); //注册2 somehost主机名 8080端口 用户U2 密码P2 credsProvider.setCredentials( new AuthScope("somehost", 8080), new UsernamePasswordCredentials("u2", "p2")); //注册3 otherhost主机名 8080端口 任意域 认证机制ntlm 用户U3 密码P3 credsProvider.setCredentials( new AuthScope("otherhost", 8080, AuthScope.ANY_REALM, "ntlm"), new UsernamePasswordCredentials("u3", "p3")); //与somehost 端口80 realm域 basic认证机制匹配的凭证 System.out.println(credsProvider.getCredentials( new AuthScope("somehost", 80, "realm", "basic"))); //与somehost 端口8080 realm域 basic认证机制匹配的凭证 System.out.println(credsProvider.getCredentials( new AuthScope("somehost", 8080, "realm", "basic"))); //与otherhost 端口8080 realm域 basic认证机制匹配的凭证 System.out.println(credsProvider.getCredentials( new AuthScope("otherhost", 8080, "realm", "basic"))); //与otherhost 端口8080 realm域 basic认证机制匹配的凭证 System.out.println(credsProvider.getCredentials( new AuthScope("otherhost", 8080, null, "ntlm")));
输出
//与注册1最类似 [principal: u1] //与注册2最类似 [principal: u2] //都不类似 因为没有对应的主机名和认证机制 null //与注册3最类似 [principal: u3]
4.4 HTTP身份认证和执行环境
HttpClient依靠AuthState类来对认证处理状态的详细信息进行跟踪,HttpClient在HTTP请求执行过程中创建了两个AuthState实例:一个用于目标主机认证另一个用于代理认证,由于目标服务器或者代理服务器都可能要求用户身份认证,所以各自的AuthScope实例将会被认证过程中所使用的AuthScope,AuthScheme和Credentials所填充,AuthState可以用于检查请求是属于何种认证类型,无论是否找到匹配的AuthScheme实现,也不管是否有凭证提供器用于查找特定的用户凭证。
在HTTP请求执行的过程中,HttpClient添加如下的认证相关对象到执行环境中:
Lookup实例代表实际注册的认证机制,该属性值优先级为局部环境中的值大于默认值。
CredentialsProvider实例代表了实际的凭证提供器,该属性值优先级为局部环境中的值大于默认值。
AuthState实例代表了实际的目标认证状态,该属性值优先级为局部环境中的值大于默认值。
AuthState实例代表了实际的代理认证状态,该属性值优先级为局部环境中的值大于默认值。
AuthCache实例代表了实际的认证数据缓存,该属性值优先级为局部环境中的值大于默认值。
局部HttpContext对象可以先于request执行环来定制化Http认证环境,或者在请求执行之后用于检查其状态。
CloseableHttpClient httpclient = <...> CredentialsProvider credsProvider = <...> Lookup<AuthSchemeProvider> authRegistry = <...> AuthCache authCache = <...> //HttpClient上下文 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会自动缓存其认证成功的主机的相关信息,请注意你必须使用相同的执行环境去执行逻辑相关的请求,以此保证缓存的认证数据能够在不同的request之间传输,当执行环境超出范围时认证数据会立即被丢弃。
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")); // 创建认证缓存 AuthCache authCache = new BasicAuthCache(); // 创建基础认证机制 添加到缓存 BasicScheme basicAuth = new BasicScheme(); authCache.put(targetHost, basicAuth); // 将认证缓存添加到执行环境中 即预填充 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.1版本后,HttpClient对NTMLv1,NTLMv2,和NTML2绘画认证提供了现成的支持,你可以继续使用外部的NTLM引擎比如JCIFS库(Samba工程作为他们的windows协同编程套件研发)。
4.7.1 NTLM连接持久化
NTLM认证机制在计算开销和性能影响上明显比Basic和Digest机制更昂贵,这很可能是微软选择将NTLM认证机制做成有状态化的主要原因之一,换句话说,一旦已经认证过了,用户的身份就跟整个生命周期的连接关联起来了,NTML连接这种有状态的性质使得连接持久化更加复杂,一个明显的情形是持久化的NTLM连接不会被不同身份的另一个用户重用。而HttpClient附带的标准连接管理器完全有能力管理有状态的连接,但是,有一点非常重要的就是逻辑相关request要使用同一个执行上下文和会话,以便于他们共享同一个当前用户。否则,对于每个请求NTML保护资源的request,HttpClient最终会为其创建一个新的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"); //确保相同逻辑的request使用同一个context HttpClientContext context = HttpClientContext.create(); context.setCredentialsProvider(credsProvider); //使用GET来触发NTLM认证 HttpGet httpget = new HttpGet("/ntlm-protected/info"); CloseableHttpResponse response1 = httpclient.execute(target, httpget, context); try { HttpEntity entity1 = response1.getEntity(); } finally { response1.close(); } //使用POST来提交请求 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(); }
4.8 SPNEGO/Kerberos 认证
SPNEGO(Simple and Protected GSSAPI Negotiation Mechanism) 设计成在不知道服务可以提供或者使用什么时为其提供认证,其最普遍的用法是扩展Kerberos认证,它可以包装其他机制,但是在HttpClient中,SPNEGO的当前版本唯一关心的只有Kerberos。
4.8.1 HttpClient中的SPNEGO支持
SPNEGO认证机制能够工作在Sun Java的1.5版本及以上,但是建议使用1.6及以上的版本,对SPNEGO认证的支持会更加完整一些。
SUN JRE提供了支持类几乎能够处理所有的Kerberos和SPNEGO的token,这意味着为了使用GSS类你需要做很多的设置,SPNegoScheme是一个简单的用于处理token编组和正确读写headers的类。
开始的最好方式是用KerberosHttpClient.java类,尝试编写一个示例并跑起来,中途可能会发生很多异常情况,但是幸运的话还是能够正常工作的,为了调试你应该打印出足够多的调试信息。
在Windows里面,它应该默认使用已存在的登录凭证,该方式可以通过使用'kinit'重写,如$JAVA_HOME\bin\kinit testuser@AD.EXAMPLE.NET ,kinit对于调试异常和进行测试非常有用,你可以通过删除kinit的缓存文件来回滚到windows的Kerberos。
确信在krb5.conf文件中列出了domain_realms,这通常是发生问题的主要原因之一。
4.8.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.8.3 log.conf文件
以下是在windows中配置IIS和JBoss Negotiation模块的基本配置
系统属性 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.8.4 krb5.conf/krb5.ini文件
如果没有指定的话,系统默认使用该文件,你可以设置krb5.conf文件中的java.security.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.NET4.8.5 Windows特定配置
要允许Windows使用当前用户的身份票据,必须设置系统属性javax.security.auth.useSubjectCredsOnly为false并且Windows注册表项"allowtgtsessionkey"应该被正确的添加和设置,以允许session的key能够被发送到Kerberos Ticket-Granting Tikect(Kerberos授权票据)
在Windows Server 2003和Windows 2000 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