Я хочу использовать Apache HttpClient 4+ для отправки аутентифицированных запросов на HTTP-сервер (фактически, мне это нужно для разных реализаций сервера) И для аутентификации (или повторной аутентификации) автоматически ТОЛЬКО, когда это требуется, когда токен аутентификации отсутствует или он мертв.
Для аутентификации мне нужно отправить запрос POST с JSON, содержащим учетные данные пользователя.
В случае, если токен аутентификации не указан в файле cookie, один сервер возвращает код состояния 401, еще один 500 с текстом AUTH_REQUIRED в теле ответа.
Я много играл с разными версиями HttpClient
, установив CredentialsProvider
с правильным Credentials
, пытаясь реализовать собственный AuthScheme
и зарегистрировав его и отменив регистрацию остальных стандартных.
Я также попытался установить собственный AuthenticationHandler
. Когда вызывается isAuthenticationRequested
, я анализирую HttpResponse
, который передается как аргумент метода, и решил, что возвращать, анализируя код состояния и тело ответа. Я ожидал, что это (isAuthenticationRequested() == true
) - это то, что заставляет клиент аутентифицироваться, вызывая AuthScheme.authenticate
(моя реализация AuthScheme
, которая возвращается AuthenticationHandler.selectScheme
), но вместо вызова AuthScheme.authenticate
я могу видеть AuthenticationHandler.getChallenges
. Я действительно не знаю, что я должен вернуть этим методом, поэтому я просто возвращаю new HashMap<>()
.
Вот результат отладки, который у меня есть в результате
DEBUG org.apache.http.impl.client.DefaultHttpClient - Authentication required
DEBUG org.apache.http.impl.client.DefaultHttpClient - example.com requested authentication
DEBUG com.test.httpclient.MyAuthenticationHandler - MyAuthenticationHandler.getChallenges()
DEBUG org.apache.http.impl.client.DefaultHttpClient - Response contains no authentication challenges
Что мне делать дальше? Я двигаюсь в правильном направлении?
UPDATE
Я почти достиг того, что мне нужно. К сожалению, я не могу предоставить полностью рабочие источники проекта, потому что я не могу предоставить публичный доступ к моему серверу. Вот мой упрощенный пример кода:
MyAuthScheme.java
public class MyAuthScheme implements ContextAwareAuthScheme {
public static final String NAME = "myscheme";
@Override
public Header authenticate(Credentials credentials,
HttpRequest request,
HttpContext context) throws AuthenticationException {
HttpClientContext clientContext = ((HttpClientContext) context);
String name = clientContext.getTargetAuthState().getState().name();
// Hack #1:
// I've come to this check. I don't like it, but it allows to authenticate
// first request and don't repeat authentication procedure for further
// requests
if(name.equals("CHALLENGED") && clientContext.getResponse() == null) {
//
// auth procedure must be here but is omitted in current example
//
// Hack #2: first request won't be present with auth token cookie set via cookie store
request.setHeader(new BasicHeader("Cookie", "MYAUTHTOKEN=bru99rshi7r5ucstkj1wei4fshsd"));
// this works for second and subsequent requests
BasicClientCookie authTokenCookie = new BasicClientCookie("MYAUTHTOKEN", "bru99rshi7r5ucstkj1wei4fshsd");
authTokenCookie.setDomain("example.com");
authTokenCookie.setPath("/");
BasicCookieStore cookieStore = (BasicCookieStore) clientContext.getCookieStore();
cookieStore.addCookie(authTokenCookie);
}
// I can't return cookie header here, otherwise it will clear
// other cookies, right?
return null;
}
@Override
public void processChallenge(Header header) throws MalformedChallengeException {
}
@Override
public String getSchemeName() {
return NAME;
}
@Override
public String getParameter(String name) {
return null;
}
@Override
public String getRealm() {
return null;
}
@Override
public boolean isConnectionBased() {
return false;
}
@Override
public boolean isComplete() {
return true;
}
@Override
public Header authenticate(Credentials credentials,
HttpRequest request) throws AuthenticationException {
return null;
}
}
MyAuthStrategy.java
public class MyAuthStrategy implements AuthenticationStrategy {
@Override
public boolean isAuthenticationRequested(HttpHost authhost,
HttpResponse response,
HttpContext context) {
return response.getStatusLine().getStatusCode() == 401;
}
@Override
public Map<String, Header> getChallenges(HttpHost authhost,
HttpResponse response,
HttpContext context) throws MalformedChallengeException {
Map<String, Header> challenges = new HashMap<>();
challenges.put(MyAuthScheme.NAME, new BasicHeader(
"WWW-Authentication",
"Myscheme realm=\"My SOAP authentication\""));
return challenges;
}
@Override
public Queue<AuthOption> select(Map<String, Header> challenges,
HttpHost authhost,
HttpResponse response,
HttpContext context) throws MalformedChallengeException {
Credentials credentials = ((HttpClientContext) context)
.getCredentialsProvider()
.getCredentials(new AuthScope(authhost));
Queue<AuthOption> authOptions = new LinkedList<>();
authOptions.add(new AuthOption(new MyAuthScheme(), credentials));
return authOptions;
}
@Override
public void authSucceeded(HttpHost authhost, AuthScheme authScheme, HttpContext context) {}
@Override
public void authFailed(HttpHost authhost, AuthScheme authScheme, HttpContext context) {}
}
MyApp.java
public class MyApp {
public static void main(String[] args) throws IOException {
CredentialsProvider credsProvider = new BasicCredentialsProvider();
Credentials credentials = new UsernamePasswordCredentials("[email protected]", "secret");
credsProvider.setCredentials(AuthScope.ANY, credentials);
HttpClientContext context = HttpClientContext.create();
context.setCookieStore(new BasicCookieStore());
context.setCredentialsProvider(credsProvider);
CloseableHttpClient client = HttpClientBuilder.create()
// my server requires this header otherwise it returns response with code 500
.setDefaultHeaders(Collections.singleton(new BasicHeader("x-requested-with", "XMLHttpRequest")))
.setTargetAuthenticationStrategy(new MyAuthStrategy())
.build();
String url = "https://example.com/some/resource";
String url2 = "https://example.com/another/resource";
// ======= REQUEST 1 =======
HttpGet request = new HttpGet(url);
HttpResponse response = client.execute(request, context);
String responseText = EntityUtils.toString(response.getEntity());
request.reset();
// ======= REQUEST 2 =======
HttpGet request2 = new HttpGet(url);
HttpResponse response2 = client.execute(request2, context);
String responseText2 = EntityUtils.toString(response2.getEntity());
request2.reset();
// ======= REQUEST 3 =======
HttpGet request3 = new HttpGet(url2);
HttpResponse response3 = client.execute(request3, context);
String responseText3 = EntityUtils.toString(response3.getEntity());
request3.reset();
client.close();
}
}
Версия
httpcore: 4.4.6
httpclient: 4.5.3
Возможно, это не лучший код, но по крайней мере он работает.
Пожалуйста, просмотрите мои комментарии в методе MyAuthScheme.authenticate()
.