2. 通过 API 实现基本认证
让我们从 HttpClient 配置基本认证的标准方式 开始——通过 CredentialsProvider
:
final HttpHost targetHost = new HttpHost("http", "localhost", 8082);
final BasicCredentialsProvider provider = new BasicCredentialsProvider();
AuthScope authScope = new AuthScope(targetHost);
provider.setCredentials(authScope, new UsernamePasswordCredentials(DEFAULT_USER, DEFAULT_PASS_ARRAY));
final HttpGet request = new HttpGet(URL_SECURED_BY_BASIC_AUTHENTICATION);
try (CloseableHttpClient client = HttpClientBuilder.create()
.setDefaultCredentialsProvider(provider())
.build();
CloseableHttpResponse response = (CloseableHttpResponse) client
.execute(request, new CustomHttpClientResponseHandler())) {
final int statusCode = response.getCode();
assertThat(statusCode, equalTo(HttpStatus.SC_OK));
}
如你所见,通过凭据提供者创建客户端并配置基本认证并不复杂。
要理解 HttpClient 幕后的实际行为,我们得看看日志:
# ... 初始请求未携带凭据
[main] DEBUG ... - Authentication required
[main] DEBUG ... - localhost:8080 要求认证
[main] DEBUG ... - 按优先级排序的认证方案:
[negotiate, Kerberos, NTLM, Digest, Basic]
[main] DEBUG ... - negotiate 认证方案不可用
[main] DEBUG ... - Kerberos 认证方案不可用
[main] DEBUG ... - NTLM 认证方案不可用
[main] DEBUG ... - Digest 认证方案不可用
[main] DEBUG ... - 选定认证方案: [BASIC]
# ... 第二次请求携带凭据
整个 客户端-服务器通信流程 现在很清晰了:
- 客户端发送不带凭据的 HTTP 请求
- 服务器返回认证挑战
- 客户端协商并确定正确的认证方案
- 客户端发送 第二次请求,这次携带凭据
3. 预先式基本认证
HttpClient 默认不启用预先式认证。这需要客户端主动开启。
首先,创建 HttpContext
——预填充认证缓存并指定认证方案。这样就能跳过上例中的协商步骤——直接选定 Basic 认证:
final HttpHost targetHost = new HttpHost("http", "localhost", 8082);
final BasicCredentialsProvider credsProvider = new BasicCredentialsProvider();
AuthScope authScope = new AuthScope(targetHost);
credsProvider.setCredentials(authScope, new UsernamePasswordCredentials(DEFAULT_USER, DEFAULT_PASS_ARRAY));
// 创建 AuthCache 实例
final AuthCache authCache = new BasicAuthCache();
// 生成 Basic 认证方案对象并加入本地缓存
authCache.put(targetHost, new BasicScheme());
// 将 AuthCache 添加到执行上下文
final HttpClientContext context = HttpClientContext.create();
context.setCredentialsProvider(credsProvider);
context.setAuthCache(authCache);
现在使用新上下文发送 预先认证请求:
final HttpGet request = new HttpGet(URL_SECURED_BY_BASIC_AUTHENTICATION);
try (CloseableHttpClient client = HttpClientBuilder.create()
.build();
CloseableHttpResponse response = (CloseableHttpResponse) client
.execute(request, context(), new CustomHttpClientResponseHandler())) {
final int statusCode = response.getCode();
assertThat(statusCode, equalTo(200));
}
检查日志输出:
[main] DEBUG ... - 重用缓存的 'basic' 认证方案 http://localhost:8082
[main] DEBUG ... - 执行请求 GET /spring-security-rest-basic-auth/api/foos/1 HTTP/1.1
[main] DEBUG ... >> GET /spring-security-rest-basic-auth/api/foos/1 HTTP/1.1
[main] DEBUG ... >> Host: localhost:8082
[main] DEBUG ... >> Authorization: Basic dXNlcjE6dXNlcjFQYXNz
[main] DEBUG ... << HTTP/1.1 200 OK
[main] DEBUG ... - 认证成功
一切正常:
- ✅ "Basic 认证"方案被预先选定
- ✅ 请求携带
Authorization
头发送 - ✅ 服务器返回
200 OK
- ✅ 认证成功
4. 通过原始 HTTP 头实现基本认证
预先式基本认证本质就是预发送 Authorization
头。
与其使用前例的复杂配置,不如直接手动构造这个头:
final HttpGet request = new HttpGet(URL_SECURED_BY_BASIC_AUTHENTICATION);
final String auth = DEFAULT_USER + ":" + DEFAULT_PASS;
final byte[] encodedAuth = Base64.encodeBase64(auth.getBytes(StandardCharsets.ISO_8859_1));
final String authHeader = "Basic " + new String(encodedAuth);
request.setHeader(HttpHeaders.AUTHORIZATION, authHeader);
try (CloseableHttpClient client = HttpClientBuilder.create()
.build();
CloseableHttpResponse response = (CloseableHttpResponse) client
.execute(request, new CustomHttpClientResponseHandler())) {
final int statusCode = response.getCode();
assertThat(statusCode, equalTo(HttpStatus.SC_OK));
}
验证是否生效:
[main] DEBUG ... - 上下文中未设置认证缓存
[main] DEBUG ... - 建立连接 {}->http://localhost:8080
[main] DEBUG ... - 连接到 localhost/127.0.0.1:8080
[main] DEBUG ... - 执行请求 GET /spring-security-rest-basic-auth/api/foos/1 HTTP/1.1
[main] DEBUG ... - 代理认证状态: UNCHALLENGED
[main] DEBUG ... - http-outgoing-0 >> GET /spring-security-rest-basic-auth/api/foos/1 HTTP/1.1
[main] DEBUG ... - http-outgoing-0 >> Authorization: Basic dXNlcjE6dXNlcjFQYXNz
[main] DEBUG ... - http-outgoing-0 << HTTP/1.1 200 OK
即使没有认证缓存,基本认证依然正常工作并返回 200 OK。
5. 总结
本文展示了在 Apache HttpClient 中配置和使用基本认证的多种方式。
⚠️ 关键点对比: | 方案 | 优势 | 劣势 | |------|------|------| | API 方式 | 标准实现,自动协商 | 需要两次请求 | | 预先式认证 | 单次请求,高效 | 需要额外配置上下文 | | 原始 HTTP 头 | 简单粗暴,无依赖 | 需手动处理编码 |
本文的完整代码可在 GitHub 获取。这是一个 Maven 项目,可直接导入运行。