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 项目,可直接导入运行。


原始标题:Apache HttpClient Basic Authentication | Baeldung

« 上一篇: Baeldung 周报4
» 下一篇: Baeldung周报5