Java 中的代理身份驗證
1. 引言
本教學將介紹如何在 Java 中設定代理身份驗證。許多企業環境要求 HTTP 請求通過代理伺服器,而這通常需要身份驗證憑證。
我們將示範如何使用四種流行的方法來設定代理身份驗證:Java 11+ HttpClient 、Apache HttpClient 、Spring 的RestTemplate和 Spring 的WebClient 。
2. 了解代理身份驗證
代理伺服器負責在客戶端和資源之間傳遞請求。當代理伺服器需要驗證時,用戶端在收到 HTTP 407 回應代碼後,必須在Proxy-Authorization標頭中提供有效的憑證,代理伺服器才會將請求轉送到目標伺服器。
雖然我們不需要直接處理標頭,但 Java 應用程式在透過經過驗證的代理程式發出 HTTP 請求時,需要設定代理伺服器詳細資訊(主機和連接埠)以及憑證。
2.1. 代理配置助手
在檢查特定的 HTTP 用戶端實作之前,讓我們先建立一個ProxyConfig類別來封裝代理設定:
public class ProxyConfig {
private String host;
private int port;
private String username;
private String password;
// default getters and setters and all-args constructor
// ...
}
我們也要加入一個取得 JVM 身份驗證器的方法:
public Authenticator authenticator() {
return new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password.toCharArray());
}
};
}
然後是ProxySelector ,它提供了一個機制來確定請求期間要使用的代理伺服器:
public ProxySelector proxySelector() {
return ProxySelector.of(new InetSocketAddress(host, port));
}
最後, Proxy定義:
public Proxy proxy() {
return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(host, port));
}
我們將利用這一點來減少配置客戶端時的樣板程式碼。
2.2 訪問資源伺服器
在我們的測試中,我們需要存取位於localhost:8080資源伺服器,這需要透過位於localhost:8079代理程式進行驗證。因此,我們將復用以下代理配置:
ProxyConfig authProxyConfig = new ProxyConfig("localhost", 8079, "testuser", "testpass");
3. 使用 Java HttpClient
Java 11 引進了HttpClient API,作為HttpURLConnection API 的現代化替代方案。它提供了對代理配置和身份驗證的支援。
3.1. 配置客戶端
我們將使用HttpClient建構器,並使用我們的ProxyConfig輔助函數進行設定:
public static HttpClient createClient(ProxyConfig config) {
return HttpClient.newBuilder()
.proxy(config.proxySelector())
.authenticator(config.authenticator())
.build();
}
我們使用proxySelector()方法來配置代理位址,並authenticator()方法提供憑證。當代理程式回傳 407 狀態碼時, HttpClient會自動處理驗證握手。
3.2 發送請求
一旦客戶端配置完成,我們就無需進行更多配置即可發出請求:
public static String sendRequest(HttpClient client, String url) {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:8080"))
.GET()
.build();
HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString());
return response.body();
}
客戶端會自動使用配置的代理和身份驗證來處理所有請求。
3.3. 使用MockServer進行測試
我們使用MockServer來測試代理認證是否正常運作。首先,我們模擬一個資源伺服器:
@Test
void givenAuthenticatedProxy_whenSendRequest_thenSuccess() {
ClientAndServer resourceServer =
ClientAndServer.startClientAndServer(8080);
resourceServer.when(request().withMethod("GET")
.withPath("/secure"))
.respond(response().withStatusCode(200)
.withBody("Authenticated Response"));
// ...
}
然後,為了模擬和配置代理伺服器,我們將使用Configuration建構器,並使用proxyAuthentication*()方法來定義使用者名稱和密碼:
Configuration config = Configuration.configuration()
.proxyAuthenticationUsername("testuser")
.proxyAuthenticationPassword("testpass");
proxyServer = ClientAndServer.startClientAndServer(config, 8079);
最後,我們使用ProxyConfig類別定義代理訊息,然後建立客戶端並使用我們先前建立的方法發送請求:
ProxyConfig authProxyConfig = new ProxyConfig("localhost", 8079, "testuser", "testpass");
HttpClient client = createClient(authProxyConfig);
String response = sendRequest(client, "http://localhost:8080/secure");
assertEquals("Authenticated Response", response);
4. 使用 Apache HttpClient 5
Apache HttpClient 5 是一個非常常用的函式庫,具有靈活的代理程式配置。
4.1. 配置客戶端
我們首先使用HttpHost來表示代理伺服器:
public static CloseableHttpClient createClient(ProxyConfig config) {
HttpHost proxy = new HttpHost(config.getHost(), config.getPort());
// ...
}
對於已認證的代理,我們需要配置一個BasicCredentialsProvider ,並將憑證與代理的AuthScope:
BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(
new AuthScope(proxy),
new UsernamePasswordCredentials(
config.getUsername(), config.getPassword().toCharArray()));
請注意,在 Apache HttpClient 5 中, UsernamePasswordCredentials建構子現在接受字元陣列而不是String密碼。這提高了安全性,因為數組在使用後可以明確地從記憶體中清除。
完成所有這些樣板程式碼後,我們可以透過在setProxy()中再次傳入代理程式來建置客戶端。當代理程式請求身份驗證時,客戶端會自動提供憑證:
return HttpClients.custom()
.setProxy(proxy)
.setDefaultCredentialsProvider(credentialsProvider)
.build();
4.2 發送請求
我們使用帶有 lambda 響應處理程序的execute()方法。這會自動處理資源清理,因為該程式庫會確保回應被正確關閉:
public static String sendRequest(CloseableHttpClient client, String url) {
HttpGet request = new HttpGet(url);
return client.execute(
request, response -> EntityUtils.toString(response.getEntity()));
}
這也比上一版本所使用的 try-with-resources 模式更簡單。
5. 使用 Spring RestTemplate
Spring 的RestTemplate是一個同步 HTTP 用戶端,常用於 Spring 應用程式中。
5.1. 配置客戶端
我們可以使用SimpleClientHttpRequestFactory對其進行配置,使其能夠與代理一起使用:
public static RestTemplate createClient(ProxyConfig config) {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
// ...
}
在我們的ProxyConfig類別中,我們使用proxy()方法來取得已配置的Proxy實例,並使用authenticator()方法取得憑證:
requestFactory.setProxy(config.proxy());
Authenticator.setDefault(config.authenticator());
return new RestTemplate(requestFactory);
請注意, Authenticator.setDefault()是 JVM 等級的設定。因此,它不適用於使用多個代理程式的應用程式。
5.2 發送請求
使用RestTemplate發送請求很簡單:
public String sendRequest(RestTemplate restTemplate, String url) {
return restTemplate.getForObject(url, String.class);
}
此外,它還能透明地處理所有代理和身份驗證細節。
6. 使用 Spring WebClient
Spring WebClient是 Spring 5 中引入的RestTemplate的響應式替代方案。最重要的是,它為非同步操作提供了更好的支援。
6.1 配置客戶端
WebClient底層使用了 Reactor Netty HttpClient :
public static WebClient createClient(ProxyConfig config) {
HttpClient httpClient = createHttpClient(config);
// ...
}
Reactor Netty HttpClient提供了一個用於設定的建構器 API。我們使用proxy()方法設定代理類型、主機和連接埠。由於需要身份驗證,我們還需要新增使用者名稱和密碼:
private static HttpClient createHttpClient(ProxyConfig config) {
return HttpClient.create().proxy(proxy -> proxy
.type(ProxyProvider.Proxy.HTTP)
.host(config.getHost())
.port(config.getPort())
.username(config.getUsername())
.password(u -> config.getPassword()));
}
ReactorClientHttpConnector讓這個HttpClient能夠與WebClient一起使用:
return WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
6.2 發送請求
使用WebClient ,我們可以發出同步請求和非同步請求:
public static String sendRequest(WebClient webClient, String url) {
return webClient.get()
.uri(url)
.retrieve()
.bodyToMono(String.class)
.block();
}
retrieve()方法發起請求, bodyToMono()方法提取回應, block()方法等待結果。對於非同步操作,我們省略block() ,而使用響應式運算元。
7. 結論
本文探討了在 Java 中使用四種不同的方法來配置代理身份驗證。 Java HttpClient提供了一個現代化的內建解決方案,具有簡潔的 API。 Apache HttpClient 5 提供了細粒度的控制。 Spring 的RestTemplate可以與 Spring 應用程式無縫整合。 Spring 的WebClient提供了響應式、非阻塞操作。
最終的選擇取決於專案需求:對於現代 Java 應用程序,請使用 Java HttpClient對於最大的靈活性,請使用 Apache HttpClient 5;對於現有的基於 Spring 的項目,請RestTemplate對於響應式 Spring 應用程序, WebClient 。
和往常一樣,完整的程式碼範例可以在 GitHub 上找到。