HttpClientBuilder базовая аутентификация
Начиная с HttpClient 4.3, я использую HttpClientBuilder. Я подключаюсь к REST-сервису с базовой аутентификацией. Я устанавливаю учетные данные следующим образом:
HttpClientBuilder builder = HttpClientBuilder.create();
// Get the client credentials
String username = Config.get(Constants.CONFIG_USERNAME);
String password = Config.get(Constants.CONFIG_PASSWORD);
// If username and password was found, inject the credentials
if (username != null && password != null)
{
CredentialsProvider provider = new BasicCredentialsProvider();
// Create the authentication scope
AuthScope scope = new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT, AuthScope.ANY_REALM);
// Create credential pair
UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(username, password);
// Inject the credentials
provider.setCredentials(scope, credentials);
// Set the default credentials provider
builder.setDefaultCredentialsProvider(provider);
}
Однако это не работает (служба REST, которую я использую, возвращает 401). Что не так?
5 ответов
Из документации по приоритетной аутентификации здесь:
http://hc.apache.org/httpcomponents-client-ga/tutorial/html/authentication.html
По умолчанию httpclient не будет предоставлять учетные данные с преимуществом, он сначала создаст HTTP-запрос без параметров аутентификации. Это сделано специально, в качестве меры безопасности и как часть спецификации. Но это вызывает проблемы, если вы не повторяете соединение или куда бы вы ни подключались, вы ожидаете, что вы отправите данные аутентификации при первом соединении. Это также приводит к дополнительной задержке запроса, так как вам нужно сделать несколько звонков, и заставляет 401 появляться в журналах.
Обходной путь - использовать кеш аутентификации, чтобы сделать вид, что вы уже подключились к серверу один раз. Это означает, что вы сделаете только один HTTP-вызов и не увидите 401 в журналах:
CloseableHttpClient httpclient = HttpClientBuilder.create().build();
HttpHost targetHost = new HttpHost("localhost", 80, "http");
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(
new AuthScope(targetHost.getHostName(), targetHost.getPort()),
new UsernamePasswordCredentials("username", "password"));
// Create AuthCache instance
AuthCache authCache = new BasicAuthCache();
// Generate BASIC scheme object and add it to the local auth cache
BasicScheme basicAuth = new BasicScheme();
authCache.put(targetHost, basicAuth);
// Add AuthCache to the execution context
HttpClientContext context = HttpClientContext.create();
context.setCredentialsProvider(credsProvider);
context.setAuthCache(authCache);
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();
}
}
Обратите внимание: вам нужно доверять хосту, к которому вы подключаетесь, и если вы используете HTTP, ваше имя пользователя и пароль будут отправлены в виде открытого текста (ну, base64, но это не считается).
Вы также должны использовать гораздо более конкретный Authscope, а не полагаться на AuthScope .ANY_HOST
а также AuthScope.ANY_PORT
как в вашем примере.
На самом деле, поскольку вы уже доверяете серверу, вероятно, проще всего создать заголовок авторизации самостоятельно.
byte[] credentials = Base64.encodeBase64((username + ":" + password).getBytes(StandardCharsets.UTF_8));
request.setHeader("Authorization", "Basic " + new String(credentials, StandardCharsets.UTF_8));
httpClient.execute(request);
Это только один из тех случаев, когда легче прочитать спецификацию и выполнить ее самостоятельно.
Я только что попробовал ваш пример кода (с простым URL-адресом с базовой аутентификацией), и он работает нормально - это журнал из HttpClient
- немного упрощённо для краткости:
web - 2014-01-04 12:43:19,700 [main] DEBUG o.a.h.c.protocol.RequestAddCookies - CookieSpec selected: best-match
web - 2014-01-04 12:43:19,710 [main] DEBUG o.a.h.c.protocol.RequestAuthCache - Auth cache not set in the context
web - 2014-01-04 12:43:19,728 [main] DEBUG o.a.h.impl.execchain.MainClientExec - Opening connection {}->http://localhost:8080
web - 2014-01-04 12:43:19,730 [main] DEBUG o.a.h.c.HttpClientConnectionManager - Connecting to localhost/127.0.0.1:8080
web - 2014-01-04 12:43:19,731 [main] DEBUG o.a.h.impl.execchain.MainClientExec - Executing request GET /spring-security-mvc-basic-auth/homepage.html HTTP/1.1
web - 2014-01-04 12:43:19,731 [main] DEBUG o.a.h.impl.execchain.MainClientExec - Target auth state: UNCHALLENGED
web - 2014-01-04 12:43:19,731 [main] DEBUG o.a.h.impl.execchain.MainClientExec - Proxy auth state: UNCHALLENGED
web - 2014-01-04 12:43:19,732 [main] DEBUG org.apache.http.headers - http-outgoing-0 >> GET /spring-security-mvc-basic-auth/homepage.html HTTP/1.1
web - 2014-01-04 12:43:19,732 [main] DEBUG org.apache.http.headers - http-outgoing-0 >> Host: localhost:8080
web - 2014-01-04 12:43:19,732 [main] DEBUG org.apache.http.headers - http-outgoing-0 >> User-Agent: Apache-HttpClient/4.3.1 (java 1.5)
web - 2014-01-04 12:43:19,735 [main] DEBUG org.apache.http.headers - http-outgoing-0 << HTTP/1.1 401 Unauthorized
web - 2014-01-04 12:43:19,735 [main] DEBUG org.apache.http.headers - http-outgoing-0 << Server: Apache-Coyote/1.1
web - 2014-01-04 12:43:19,735 [main] DEBUG org.apache.http.headers - http-outgoing-0 << Set-Cookie: JSESSIONID=B8E6D0D7DE0C99991A74E9B2E4EA68AE; Path=/spring-security-mvc-basic-auth/; HttpOnly
web - 2014-01-04 12:43:19,735 [main] DEBUG org.apache.http.headers - http-outgoing-0 << WWW-Authenticate: Basic realm="Baeldung"
web - 2014-01-04 12:43:19,735 [main] DEBUG org.apache.http.headers - http-outgoing-0 << Content-Length: 75
web - 2014-01-04 12:43:19,735 [main] DEBUG org.apache.http.headers - http-outgoing-0 << Date: Sat, 04 Jan 2014 10:43:19 GMT
web - 2014-01-04 12:43:19,738 [main] DEBUG o.a.http.impl.auth.HttpAuthenticator - Authentication required
web - 2014-01-04 12:43:19,738 [main] DEBUG o.a.http.impl.auth.HttpAuthenticator - localhost:8080 requested authentication
web - 2014-01-04 12:43:19,738 [main] DEBUG o.a.h.i.c.TargetAuthenticationStrategy - Authentication schemes in the order of preference: [negotiate, Kerberos, NTLM, Digest, Basic]
web - 2014-01-04 12:43:19,738 [main] DEBUG o.a.h.i.c.TargetAuthenticationStrategy - Challenge for negotiate authentication scheme not available
web - 2014-01-04 12:43:19,738 [main] DEBUG o.a.h.i.c.TargetAuthenticationStrategy - Challenge for Kerberos authentication scheme not available
web - 2014-01-04 12:43:19,738 [main] DEBUG o.a.h.i.c.TargetAuthenticationStrategy - Challenge for NTLM authentication scheme not available
web - 2014-01-04 12:43:19,738 [main] DEBUG o.a.h.i.c.TargetAuthenticationStrategy - Challenge for Digest authentication scheme not available
web - 2014-01-04 12:43:19,745 [main] DEBUG o.a.http.impl.auth.HttpAuthenticator - Selected authentication options: [BASIC]
web - 2014-01-04 12:43:19,746 [main] DEBUG o.a.h.impl.execchain.MainClientExec - Executing request GET /spring-security-mvc-basic-auth/homepage.html HTTP/1.1
web - 2014-01-04 12:43:19,746 [main] DEBUG o.a.h.impl.execchain.MainClientExec - Target auth state: CHALLENGED
web - 2014-01-04 12:43:19,746 [main] DEBUG o.a.http.impl.auth.HttpAuthenticator - Generating response to an authentication challenge using basic scheme
web - 2014-01-04 12:43:19,747 [main] DEBUG o.a.h.impl.execchain.MainClientExec - Proxy auth state: UNCHALLENGED
web - 2014-01-04 12:43:19,747 [main] DEBUG org.apache.http.headers - http-outgoing-0 >> GET /spring-security-mvc-basic-auth/homepage.html HTTP/1.1
web - 2014-01-04 12:43:19,747 [main] DEBUG org.apache.http.headers - http-outgoing-0 >> Host: localhost:8080
web - 2014-01-04 12:43:19,747 [main] DEBUG org.apache.http.headers - http-outgoing-0 >> User-Agent: Apache-HttpClient/4.3.1 (java 1.5)
web - 2014-01-04 12:43:19,747 [main] DEBUG org.apache.http.headers - http-outgoing-0 >> Authorization: Basic dXNlcjE6dXNlcjFQYXNz
web - 2014-01-04 12:43:19,750 [main] DEBUG org.apache.http.headers - http-outgoing-0 << HTTP/1.1 200 OK
web - 2014-01-04 12:43:19,750 [main] DEBUG org.apache.http.headers - http-outgoing-0 << Server: Apache-Coyote/1.1
web - 2014-01-04 12:43:19,750 [main] DEBUG org.apache.http.headers - http-outgoing-0 << Set-Cookie: JSESSIONID=C03FD4EB1421A4C3A003ADC895D49599; Path=/spring-security-mvc-basic-auth/; HttpOnly
web - 2014-01-04 12:43:19,750 [main] DEBUG org.apache.http.headers - http-outgoing-0 << Content-Type: text/html;charset=ISO-8859-1
web - 2014-01-04 12:43:19,750 [main] DEBUG org.apache.http.headers - http-outgoing-0 << Content-Language: en-US
web - 2014-01-04 12:43:19,751 [main] DEBUG org.apache.http.headers - http-outgoing-0 << Content-Length: 96
web - 2014-01-04 12:43:19,751 [main] DEBUG org.apache.http.headers - http-outgoing-0 << Date: Sat, 04 Jan 2014 10:43:19 GMT
web - 2014-01-04 12:43:19,751 [main] DEBUG o.a.http.impl.auth.HttpAuthenticator - Authentication succeeded
web - 2014-01-04 12:43:19,751 [main] DEBUG o.a.h.i.c.TargetAuthenticationStrategy - Caching 'basic' auth scheme for http://localhost:8080
web - 2014-01-04 12:43:19,760 [main] DEBUG o.a.h.c.p.ResponseProcessCookies - Cookie accepted: "[version: 0][name: JSESSIONID][value: C03FD4EB1421A4C3A003ADC895D49599][domain: localhost][path: /spring-security-mvc-basic-auth/][expiry: null]".
Итак - проще говоря:
- Сервер оспаривает первоначальный запрос
- HttpClient
распознает схему базовой аутентификации и правильно отвечает на вызов
- в этот момент серверы сервера ожидаемого 200 OK
Может случиться, что служба REST, которую вы используете, на самом деле не использует обычную аутентификацию. Вы можете попробовать вставить полный HttpClient
журналы, чтобы лучше диагностировать проблему.
Надеюсь, это поможет.
Я думаю, что HttpClient похож на другое решение на основе curl, оно соответствует спецификации.
И спецификация "Не отправляйте учетные данные, кроме случаев, когда сервер говорит вам сделать это". Таким образом, вы получаете 401 ("Я хочу, чтобы вы отправили учетные данные")...
Это обычная проблема с мыльным интерфейсом: когда вы не знаете, это не очевидно
У людей, использующих Apache http client5, такой же вопрос, как и у меня.
Эти коды вдохновлены @Cetra, и я добавляю initPreemptive() для решения проблемы 401.
HttpHost targetHost = new HttpHost("https", "test.com", 443);
BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
AuthScope authScope = new AuthScope(targetHost);
Credentials credentials = new UsernamePasswordCredentials("username", "password".toCharArray());
credentialsProvider.setCredentials(authScope, credentials);
AuthCache authCache = new BasicAuthCache();
BasicScheme basicScheme = new BasicScheme();
// Call initPreemptive() to use Preemptive credentials
basicScheme.initPreemptive(credentials);
authCache.put(targetHost, basicScheme);
HttpClientContext context = HttpClientContext.create();
context.setCredentialsProvider(credentialsProvider);
context.setAuthCache(authCache);
HttpGet request = new HttpGet("https://test.com/need/basic/auth");
try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
client.execute(request, context, response -> {
final HttpEntity responseEntity = response.getEntity();
try {
System.out.println("---------");
System.out.printf("%s %s\r\n%s%n", new StatusLine(response), response.getReasonPhrase(), EntityUtils.toString(responseEntity));
System.out.println("---------");
} finally {
EntityUtils.consume(responseEntity);
}
return null;
});
} catch (Exception e) {
System.out.println(e);
}