Дайджест-аутентификация в Android с использованием HttpURLConnection



Как говорится в вопросе allready, я пытаюсь сделать дайджест-аутентификацию в android.

До сих пор я использовал DefaultHttpClient и это метод аутентификации (используя UsernamePasswordCredentials и так далее), но он устарел с Android 5 и будет удален в Android 6.

Поэтому я собираюсь переключиться с DefaultHttpClient на HttpUrlConnection.

Теперь я пытаюсь добиться дайджест-аутентификации, которая должна работать довольно просто, как описано здесь :



Authenticator.setDefault(new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});


Но getPasswordAuthentication никогда не вызывается для некоторых причина.

Во время поиска этой проблемы я нашел разные сообщения, в которых говорилось, что дайджест-аутентификация не поддерживается HttpUrlConnection в android, но эти сообщения относятся к 2010-2012 годам, поэтому я не уверен, что это все еще верно. Кроме того, мы используем HttpUrlConnection с дайджест-аутентификацией в нашем настольном java-приложении, где она действительно работает.



Я также нашел несколько сообщений, говорящих о OkHttp. OkHttp, по-видимому, используется Android under the hood (если быть более точным, то HttpUrlConnectionImpl). Но это HttpUrlConnectionImpl немного странно, он даже не показан в иерархии типов Eclipse, и я не могу его отладить. Также это должно быть com.squareup.okhttp.internal.huc.HttpUrlConnectionImpl, в то время как в android это com.android.okhttp.internal.http.HttpUrlConnectionImpl.



Так что я просто не в состоянии сделать дайджест-аутентификацию с этим HttpUrlConnection в android.

Может ли кто-нибудь сказать мне, как это сделать без внешних библиотек?



Редактировать:

Сервер запрашивает дайджест-аутентификацию:



WWW-Authenticate: Digest realm="Realm Name",domain="/domain",nonce="nonce",algorithm=MD5,qop="auth"


Таким образом, базовая аутентификация не должна работать, так как сервер запрашивает дайджест.

578   5  

5 ответов:

Ответ таков, что HttpUrlConnection не поддерживает digest.

Поэтому вы должны реализовать RFC2617 самостоятельно.

В качестве базовой реализации можно использовать следующий код: HTTP Digest Auth для Android .

Шаги включают (см. RFC2617 Для справки):

  • Если вы получаете ответ 401, повторите все заголовки WWW-Authenticate и проанализируйте их:
    • проверьте, является ли алгоритм MD5 или неопределенным, (необязательно выберите auth qop option), в противном случае проигнорируйте вызов и перейдите к следующему заголовку.
    • получите учетные данные, используя Authenticator.requestPasswordAuthentication.
    • вычислите H (A1), используя имя пользователя, область и пароль.
    • храните канонический корневой URL, область, HA1, имя пользователя, nonce (+необязательно алгоритм, непрозрачный и выбранный клиентом параметр qop, если он присутствует).
    • повторите запрос.
  • при каждом запросе повторите все области, для которых у вас есть информация о сеансе, сохраненная каноническим корнем URL-АДРЕС:
    • вычислите H (A2), используя метод запроса и путь.
    • вычислите H(A3), используя HA1, nonce (+ необязательно nc, cnonce, qop) и HA2.
    • создайте и добавьте заголовок Authorization в свой HttpUrlConnection.
  • реализуйте своего рода сеансовую обрезку.

Используя Authenticator, Вы можете убедиться, что как только HttpUrlConnection поддерживает дайджест изначально, ваш код больше не используется (потому что вы не получите 401 в первую очередь).

Это всего лишь краткое резюме о том, как его реализовать, чтобы у вас появилась идея.

Если вы хотите пойти дальше, вы, вероятно, хотели бы также реализовать SHA256: RFC7616

Правильно, что HttpUrlConnection не поддерживает дайджест-аутентификацию. Если ваш клиент должен аутентифицироваться с помощью Digest, у вас есть несколько вариантов:

  • напишите свою собственную реализацию http Digest. Это может быть хорошим вариантом, если вы знаете, какие серверы вам нужно аутентифицировать, и можете игнорировать части спецификации The digest, которые вам не нужны. Вот пример, в котором реализовано подмножество дайджеста: https://gist.github.com/slightfoot/5624590 .
  • Используйте внешний libbare-bones-digest , который является дайджестом lib для Android. Вы можете использовать его для анализа проблем дайджеста и генерирования ответов на них. Он поддерживает общие случаи использования дайджеста и некоторые из редко используемых и может использоваться поверх HttpURLConnection.
  • Используйте Ohttp вместе с ohttp-digest, который является плагином, добавляющим поддержку HTTP Digest в Ohttp. Поддерживая дайджест с OkHttp это легко, просто добавьте okhttp-digest в качестве аутентификатора, и у вас будет прозрачная поддержка HTTP digest. Если вы уже используете Ohttp или не возражаете перейти на него, это может быть привлекательным вариантом.
  • Используйте Apache HttpClient, который поддерживает Digest. Вопрос явно указывает, что HttpClient не является вариантом, поэтому я включаю его в основном для завершения. Google не рекомендует использовать HttpClient и устарел.

Вы пытались установить заголовок вручную, как:

String basic = "Basic " + new String(Base64.encode("username:password".getBytes(),Base64.NO_WRAP ));
connection.setRequestProperty ("Authorization", basic);

Также имейте в виду некоторые проблемы в Jellybeans и ошибку при попытке выполнить post-запрос: проблема с базовой аутентификацией HTTP на Android Jelly Bean 4.1 с помощью HttpURLConnection

EDIT: для дайджест-аутентификации

Взгляните сюда https://code.google.com/p/android/issues/detail?id=9579

Особенно это может сработать:

try {   
        HttpClient client = new HttpClient(
                new MultiThreadedHttpConnectionManager());

        client.getParams().setAuthenticationPreemptive(true);
        Credentials credentials = new UsernamePasswordCredentials("username", "password");
        client.getState().setCredentials(AuthScope.ANY, credentials);
        List<String> authPrefs = new ArrayList<String>(2);
        authPrefs.add(AuthPolicy.DIGEST);
        authPrefs.add(AuthPolicy.BASIC);
        client.getParams().setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY,
                authPrefs);
        GetMethod getMethod = new GetMethod("your_url");
        getMethod.setRequestHeader("Accept", "application/xml");
        client.executeMethod(getMethod);
        int status = getMethod.getStatusCode();
        getMethod.setDoAuthentication(true);
        System.out.println("status: " + status);
        if (status == HttpStatus.SC_OK) {
            String responseBody = getMethod.getResponseBodyAsString();
            String resp = responseBody.replaceAll("\n", " ");
            System.out.println("RESPONSE \n" + resp);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }

Я, наконец, заменил устаревший DefaultHttpClient своей собственной реализацией HttpUrlConnection и реализовал digest atuhentication сам, используяЭтот в качестве шаблона.
Окончательный код выглядит примерно так:

// requestMethod: "GET", "POST", "PUT" etc.
// Headers: A map with the HTTP-Headers for the request
// Data: Body-Data for Post/Put
int statusCode = this.requestImpl(requestMethod, headers, data);
if (statusCode == HttpURLConnection.HTTP_UNAUTHORIZED && hasUserNameAndPassword) {
    String auth = getResponseHeaderField("WWW-Authenticate");
    // Server needs Digest authetication
    if(auth.startsWith("Digest")){
          // Parse the auth Header
          HashMap<String, String> authFields = parseWWWAuthenticateHeader(auth);
          // Generate Auth-Value for request
          String requestAuth = generateDigestAuth(authFields);
          headers.put("Authorization", authStr);
          statusCode = this.requestImpl(requestMethod, headers, data);
    }
}

Поэтому в основном я делаю запрос, и если он возвращает 401, я смотрю, если сервер хочет digest authentication и если у меня есть имя пользователя и пароль. Если это так, я разбираю заголовок auth ответа, который содержит всю необходимую информацию о проверке подлинности.
Чтобы разобрать заголовок auth я использую какой-то StateMachine, который описан здесь.
После разбора заголовка auth ответа я генерирую заголовок auth запроса, используя информацию из ответа:

    String digestAuthStr = null;

    String uri = getURL().getPath();
    String nonce = authFields.get("nonce");
    String realm = authFields.get("realm");
    String qop = authFields.get("qop");
    String algorithm = authFields.get("algorithm");
    String cnonce = generateCNonce();
    String nc = "1";
    String ha1 = toMD5DigestString(concatWithSeparator(":", username, realm, password));
    String ha2 = toMD5DigestString(concatWithSeparator(":", requestMethod, uri));
    String response = null;
    if (!TextUtils.isEmpty(ha1) && !TextUtils.isEmpty(ha2))
        response = toMD5DigestString(concatWithSeparator(":", ha1, nonce, nc, cnonce, qop, ha2));

    if (response != null) {
        StringBuilder sb = new StringBuilder(128);
        sb.append("Digest ");
        sb.append("username").append("=\"").append(username).append("\", ");
        sb.append("realm").append("=\"").append(realm).append("\", ");
        sb.append("nonce").append("=\"").append(nonce).append("\", ");
        sb.append("uri").append("=\"").append(uri).append("\", ");
        sb.append("qop").append("=\"").append(qop).append("\", ");
        sb.append("nc").append("=\"").append(nc).append("\", ");
        sb.append("cnonce").append("=\"").append(cnonce).append("\"");
        sb.append("response").append("=\"").append(response).append("\"");
        sb.append("algorithm").append("=\"").append(algorithm).append("\"");
        digestAuthStr = sb.toString();
    }

Для генерации клиент-Nonce я использую следующий код:

private static String generateCNonce() {
    String s = "";
    for (int i = 0; i < 8; i++)
        s += Integer.toHexString(new Random().nextInt(16));
    return s;
}
Я надеюсь, что это кому-то поможет. Если код содержит какие-либо ошибки, пожалуйста, дайте мне знать, чтобы я мог исправить это. Но прямо сейчас это, кажется, работает.

Для Android я обнаружил, что библиотека bare-bones-digest работает хорошо: https://github.com/al-broco/bare-bones-digest

  1. Добавьте одну строку для построения.gradle
  2. Используйте пример кода по указанному выше url

Работает!

Comments

    Ничего не найдено.