Статья

Базовая аутентификация HttpClient в Java

Давайте начнем со стандартного способа настройки базовой аутентификации HttpClient – через CredentialsProvider:
CredentialsProvider provider = new BasicCredentialsProvider();
UsernamePasswordCredentials credentials
 = new UsernamePasswordCredentials("user3", "password");
provider.setCredentials(AuthScope.ANY, credentials);
 
HttpClient client = HttpClientBuilder.create()
  .setDefaultCredentialsProvider(provider)
  .build();

HttpResponse response = client.execute(
  new HttpGet(URL_SECURED_BY_BASIC_AUTHENTICATION));
int statusCode = response.getStatusLine()
  .getStatusCode();
 
assertThat(statusCode, equalTo(HttpStatus.SC_OK));
Как мы можем видеть – несложно создать HttpClient с поставщиком учетных данных для его настройки с помощью базовой аутентификации.

Чтобы понять, что HttpClient на самом деле будет делать за кулисами, нам нужно посмотреть на логи:
# request is sent with no credentials

[main] DEBUG ... - Authentication required
[main] DEBUG ... - localhost:80 requested authentication
[main] DEBUG ... - Authentication schemes in the order of preference: 
  [Kerberos, Digest, Basic]
[main] DEBUG ... - Challenge for negotiate authentication scheme not available
[main] DEBUG ... - Challenge for Kerberos authentication scheme not available
[main] DEBUG ... - Challenge for NTLM authentication scheme not available
[main] DEBUG ... - Challenge for Digest authentication scheme not available
[main] DEBUG ... - Selected authentication options: [BASIC]
# the request is sent again - with credentials
Вся связь между клиентом и сервером стала понятной:

  • Клиент отправляет HTTP-запрос без учетных данных
  • Сервер отправляет ответный вызов
  • Клиент согласовывает и определяет правильную схему аутентификации
  • Клиент отправляет второй запрос, на этот раз с учетными данными

Предварительная базовая аутентификация

Из коробки HttpClient не выполняет упреждающую аутентификацию.
Во–первых, нам нужно создать HttpContext – предварительно заполнив его кэшем аутентификации с предварительно выбранным правильным типом схемы аутентификации. Согласование из предыдущего примера больше не требуется – базовая аутентификация уже выбрана:
HttpHost targetHost = new HttpHost("localhost", 8081, "http");
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(AuthScope.ANY, 
  new UsernamePasswordCredentials(DEF_USER, DEF_PASS));

AuthCache authCache = new BasicAuthCache();
authCache.put(targetHost, new BasicScheme());

// добавляем AuthCache в контекст

HttpClientContext context = HttpClientContext.create();
context.setCredentialsProvider(credsProvider);
context.setAuthCache(authCache);
Теперь мы можем использовать клиент с новым контекстом и отправить запрос предварительной аутентификации:
HttpClient client = HttpClientBuilder.create().build();
response = client.execute(
  new HttpGet(URL_SECURED_BY_BASIC_AUTHENTICATION), context);

int statusCode = response.getStatusLine().getStatusCode();
assertThat(statusCode, equalTo(HttpStatus.SC_OK));
Давайте посмотрим на логи:
[main] DEBUG ... - Re-using cached 'basic' auth scheme for http://localhost:8081
[main] DEBUG ... - Executing request 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:8081
[main] DEBUG ... >> Authorization: Basic d5Nl6jE6dXNlc31RYXvz
[main] DEBUG ... << HTTP/1.1 200 OK
[main] DEBUG ... - Authentication succeeded
Все выглядит нормально:
  • предварительно выбрана схема “Базовой аутентификации”
  • запрос отправляется с заголовком авторизации
  • сервер отвечает 200 OK
  • Аутентификация прошла успешно

Базовая аутентификация с необработанными HTTP-заголовками

Упреждающая базовая аутентификация влечет за собой предварительную отправку заголовка авторизации.
Вместо того, чтобы проходить через довольно сложный предыдущий пример, чтобы настроить заголовок, мы можем создать его по другому:
HttpGet request = new HttpGet(URL_SECURED_BY_BASIC_AUTHENTICATION);
String auth = DEF_USER + ":" + DEF_PASS;
byte[] encodedAuth = Base64.encodeBase64(
  auth.getBytes(StandardCharsets.ISO_8859_1));
String authHeader = "Basic " + new String(encodedAuth);
request.setHeader(HttpHeaders.AUTHORIZATION, authHeader);

HttpClient client = HttpClientBuilder.create().build();
HttpResponse response = client.execute(request);

int statusCode = response.getStatusLine().getStatusCode();
assertThat(statusCode, equalTo(HttpStatus.SC_OK));
Убедимся, что это работает правильно:
[main] DEBUG ... - Auth cache not set in the context
[main] DEBUG ... - Opening connection {}->http://localhost:80
[main] DEBUG ... - Connecting to localhost/127.0.0.1:80
[main] DEBUG ... - Executing request GET /spring-security-rest-basic-auth/api/1 HTTP/1.1
[main] DEBUG ... - Proxy auth state: 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 dQNlcjE6dX36AFx51YXNS
[main] DEBUG ... - http-outgoing-0 << HTTP/1.1 200 OK
Итак, несмотря на отсутствие кэша аутентификации, базовая аутентификация по-прежнему работает корректно, и мы получаем ответ 200 OK.
java