4.1.用戶憑證
任何用戶認證的過程,都需要一系列的憑證來確定用戶的身份。最簡單的用戶憑證可以是用戶名和密碼這種形式。UsernamePasswordCredentials
這個類可以用來表示這種情況,這種憑據包含明文的用戶名和密碼。
這個類對於HTTP標準規範中定義的認證模式來說已經足夠了。
UsernamePasswordCredentials creds = new UsernamePasswordCredentials("user", "pwd");
System.out.println(creds.getUserPrincipal().getName());
System.out.println(creds.getPassword());
上述代碼會在控制檯輸出:
user
pwd
NTCredentials
是微軟的windows系統使用的一種憑據,包含username、password,還包括一系列其他的屬性,比如用戶所在的域名。在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. 認證方案
AutoScheme
接口表示一個抽象的面向挑戰/響應的認證方案。一個認證方案要支持下面的功能:
- 客戶端請求服務器受保護的資源,服務器會發送過來一個chanllenge(挑戰),認證方案(Authentication scheme)需要解析、處理這個挑戰
- 爲processed challenge提供一些屬性值:認證方案的類型,和此方案需要的一些參數,這種方案適用的範圍
- 使用給定的授權信息生成授權字符串;生成http請求,用來響應服務器發送來過的授權challenge
請注意:一個認證方案可能是有狀態的,因爲它可能涉及到一系列的挑戰/響應。
HttpClient實現了下面幾種AutoScheme
:
- Basic: Basic認證方案是在RFC2617號文檔中定義的。這種授權方案用明文來傳輸憑證信息,所以它是不安全的。雖然Basic認證方案本身是不安全的,但是它一旦和TLS/SSL加密技術結合起來使用,就完全足夠了。
- Digest: Digest(摘要)認證方案是在RFC2617號文檔中定義的。Digest認證方案比Basic方案安全多了,對於那些受不了Basic+TLS/SSL傳輸開銷的系統,digest方案是個不錯的選擇。
- NTLM: NTLM認證方案是個專有的認證方案,由微軟開發,並且針對windows平臺做了優化。NTLM被認爲比Digest更安全。
- SPNEGO: SPNEGO(Simple and Protected GSSAPI Negotiation Mechanism)是GSSAPI的一個“僞機制”,它用來協商真正的認證機制。SPNEGO最明顯的用途是在微軟的HTTP協商認證機制拓展上。可協商的子機制包括NTLM、Kerberos。目前,HttpCLient只支持Kerberos機制。(原文:The negotiable sub-mechanisms include NTLM and Kerberos supported by Active Directory. At present HttpClient only supports the Kerberos sub-mechanism.)
4.3. 憑證 provider
憑證providers旨在維護一套用戶的憑證,當需要某種特定的憑證時,providers就應該能產生這種憑證。認證的具體內容包括主機名、端口號、realm name和認證方案名。當使用憑據provider的時候,我們可以很模糊的指定主機名、端口號、realm和認證方案,不用寫的很精確。因爲,憑據provider會根據我們指定的內容,篩選出一個最匹配的方案。
只要我們自定義的憑據provider實現了CredentialsProvider
這個接口,就可以在HttpClient中使用。默認的憑據provider叫做BasicCredentialsProvider
,它使用java.util.HashMap
對CredentialsProvider
進行了簡單的實現。
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.4.HTTP授權和執行上下文
HttpClient依賴AuthState
類去跟蹤認證過程中的狀態的詳細信息。在Http請求過程中,HttpClient創建兩個AuthState
實例:一個用於目標服務器認證,一個用於代理服務器認證。如果服務器或者代理服務器需要用戶的授權信息,AuthScope
、AutoScheme
和認證信息就會被填充到兩個AuthScope
實例中。通過對AutoState
的檢測,我們可以確定請求的授權類型,確定是否有匹配的AuthScheme
,確定憑據provider根據指定的授權類型是否成功生成了用戶的授權信息。
在Http請求執行過程中,HttpClient會向執行上下文中添加下面的授權對象:
Lookup
對象,表示使用的認證方案。這個對象的值可以在本地上下文中進行設置,來覆蓋默認值。CredentialsProvider
對象,表示認證方案provider,這個對象的值可以在本地上下文中進行設置,來覆蓋默認值。AuthState
對象,表示目標服務器的認證狀態,這個對象的值可以在本地上下文中進行設置,來覆蓋默認值。AuthState
對象,表示代理服務器的認證狀態,這個對象的值可以在本地上下文中進行設置,來覆蓋默認值。AuthCache
對象,表示認證數據的緩存,這個對象的值可以在本地上下文中進行設置,來覆蓋默認值。
我們可以在請求執行前,自定義本地HttpContext
對象來設置需要的http認證上下文;也可以在請求執行後,再檢測HttpContext
的狀態,來查看授權是否成功。
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://www.yeetrack.com/");
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"));
// 創建 AuthCache 對象
AuthCache authCache = new BasicAuthCache();
//創建 BasicScheme,並把它添加到 auth cache中
BasicScheme basicAuth = new BasicScheme();
authCache.put(targetHost, basicAuth);
// 把AutoCache添加到上下文中
HttpClientContext context = HttpClientContext.create();
context.setCredentialsProvider(credsProvider);
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就全面支持NTLMv1、NTLMv2和NTLM2認證。當人我們可以仍舊使用外部的NTLM引擎(比如Samba開發的JCIFS庫)作爲與Windows互操作性程序的一部分。
4.7.1. NTLM連接持久性
相比Basic
和Digest
認證,NTLM認證要明顯需要更多的計算開銷,性能影響也比較大。這也可能是微軟把NTLM協議設計成有狀態連接的主要原因之一。也就是說,NTLM連接一旦建立,用戶的身份就會在其整個生命週期和它相關聯。NTLM連接的狀態性使得連接持久性更加複雜,The stateful nature of NTLM connections makes connection persistence more complex, as for the
obvious reason persistent NTLM connections may not be re-used by users with a different user identity. HttpClient中標準的連接管理器就可以管理有狀態的連接。但是,同一會話中邏輯相關的請求,必須使用相同的執行上下文,這樣才能使用用戶的身份信息。否則,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");
//使用相同的上下文來執行邏輯相關的請求
HttpClientContext context = HttpClientContext.create();
context.setCredentialsProvider(credsProvider);
//使用輕量級的請求來觸發NTLM認證
HttpGet httpget = new HttpGet("/ntlm-protected/info");
CloseableHttpResponse response1 = httpclient.execute(target, httpget, context);
try {
HttpEntity entity1 = response1.getEntity();
} finally {
response1.close();
}
//使用相同的上下文,執行重量級的方法
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 Megotiation Mechanism),當雙方均不知道對方能使用/提供什麼協議的情況下,可以使用SP認證協議。這種協議在Kerberos認證方案中經常使用。It can wrap other mechanisms, however the current version in HttpClient is designed solely with Kerberos in mind.
4.8.1. 在HttpCient中使用SPNEGO
SPNEGO認證方案兼容Sun java 1.5及以上版本。但是強烈推薦jdk1.6以上。Sun的JRE提供的類就已經幾乎完全可以處理Kerberos和SPNEGO token。這就意味着,需要設置很多的GSS類。SpnegoScheme
是個很簡單的類,可以用它來handle marshalling the tokens and 讀寫正確的頭消息。
最好的開始方法就是從示例程序中找到KerberosHttpClient.java
這個文件,嘗試讓它運行起來。運行過程有可能會出現很多問題,但是如果人品比較高可能會順利一點。這個文件會提供一些輸出,來幫我們調試。
在Windows系統中,應該默認使用用戶的登陸憑據;當然我們也可以使用kinit
來覆蓋這個憑據,比如$JAVA_HOME\bin\kinit [email protected]
,這在我們測試和調試的時候就顯得很有用了。如果想用回Windows默認的登陸憑據,刪除kinit創建的緩存文件即可。
確保在krb5.conf文件中列出domain_realms
。這能解決很多不必要的問題。
4.8.2. 使用GSS/Java Kerberos
下面的這份文檔是針對Windows系統的,但是很多信息同樣適合Unix。
org.ietf.jgss
這個類有很多的配置參數,這些參數大部分都在krb5.conf/krb5.ini
文件中配置。更多的信息,參考此處。
login.conf文件
下面是一個基本的login.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 文件
如果沒有手動指定,系統會使用默認配置。如果要手動指定,可以在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.8.5. Windows詳細的配置
爲了允許Windows使用當前用戶的tickets,javax.security.auth.useSubjectCredsOnly
這個系統變量應該設置成false
,並且需要在Windows註冊表中添加allowtgtsessionkey
這個項,而且要allow session keys to be sent in the Kerberos Ticket-Granting Ticket.
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